Entity View Component
Introduction
View components bind the simulation state of a Quantum entity to view-side code, similar to MonoBehaviours but for Quantum entity views.
They are updated by the QuantumEntityViewUpdater (EVU) and work well with the QuantumEntityViewPool.
Here is a video that elaborates on this topic within an example project:
Entity View Components
Add a script derived from QuantumEntityViewComponent to any game object under a QuantumEntityView.
The script immediately has access to the entity and the game and can override the lifetime callbacks.
| Property | Description |
|---|---|
EntityRef |
The Quantum entity the view belongs to. |
EntityView |
The QuantumEntityView the component belongs to. |
Game |
The QuantumGame the entity view is observing. |
PredictedFrame |
The latest predicted frame, use it for visuals. |
VerifiedFrame |
The latest verified frame, use it for anything that must not roll back. |
PredictedPreviousFrame |
The frame before the predicted frame. In Quantum 3.1 it returns the Verified frame unless the legacy frame copies are enabled, see Frames. |
IsGameValid |
true while Game is not null (Quantum 3.1). |
Helper methods read Quantum components of the entity from the predicted or verified frame:
HasPredictedQuantumComponent<T>(),HasVerifiedQuantumComponent<T>()GetPredictedQuantumComponent<T>(),GetVerifiedQuantumComponent<T>()TryGetPredictedQuantumComponent<T>(out T value),TryGetVerifiedQuantumComponent<T>(out T value)
C#
namespace Quantum {
public class HealthView : QuantumEntityViewComponent {
public override void OnUpdateView() {
if (TryGetPredictedQuantumComponent<Health>(out var health)) {
// update the health bar with health.Current
}
}
}
}
Accessing Frames Outside Of The Callbacks
In Quantum 3.1 Game and the frame properties are null and EntityRef is EntityRef.None after the view component has been deactivated or the game has been destroyed.
Code that runs outside of the view component callbacks, for example in Update(), coroutines or UI events, has to handle this.
Use TryGetPredictedFrame() and TryGetVerifiedFrame() (Quantum 3.1) to access the frames safely.
C#
public void OnButtonClicked() {
if (TryGetPredictedFrame(out var frame) && frame.Exists(EntityRef)) {
// read the entity state
}
}
Lifetime
The following callbacks define the life-cycle of (pooled) entity view and its QuantumViewComponents.
| Callback | Description |
|---|---|
OnInitialize() |
Is called when the entity view component is created for the first time. Its view context is already set, but access to Game, VerifiedFrame, PredictedFrame and PredictedPreviousFrame is not available yet. |
OnActivate(Frame frame) |
Is called when the entity view has been created or enabled (also from pool) for example when a new Quantum entity was created and the EVU created the associated EntityView. |
OnDeactivate() |
Is called before the entity view is deactivated, either destroyed or returned to the pool. |
OnUpdateView() |
Is called on Unity updates originating from the EVUs OnObservedGameUpdated(). |
OnLateUpdateView() |
Is called on the Unity LateUpdate() method inside EVU. |
OnGameChanged() |
Is called after the observed game in the EVU changed. |
After OnDeactivate() the Game and frame properties are null and EntityRef is EntityRef.None (Quantum 3.1).
Contexts
A QuantumEntityViewComponent can be defined to have one optional generic type for a context object. A context object is a simple class (can be a MonoBehaviour, singleton, etc) that derives from IQuantumViewContext. It's an option to share data between different parts of the game.
The context has to be in the same game object as the QuantumEntityViewUpdater or in a child game object and it must be added during Awake() to be automatically loaded and made available for view components.
C#
namespace Quantum {
using UnityEngine;
public class MyGameContext : QuantumMonoBehaviour, IQuantumViewContext {
public GameObject Template;
}
}
The context is then accessible by the ViewContext property.
C#
namespace Quantum {
using UnityEngine;
public class MyViewScript : QuantumEntityViewComponent<MyGameContext> {
GameObject _go;
public override void OnInitialize() {
_go = Instantiate(ViewContext.Template);
}
}
}
A dictionary of each loaded context type can be accessed on the EVU: Dictionary<Type, IQuantumViewContext> Context
In Quantum 3.1 contexts can also be registered from code, for example when the context lives outside of the EVU hierarchy:
C#
// register the context before view components that use it are created
updater.AddViewContext<MyGameContext>(myGameContext);
// remove it again
updater.RemoveViewContext<MyGameContext>();
Only one context per type is supported, adding a second one logs an error.
View components that already received a context keep it after RemoveViewContext().
Scene View Components
A QuantumSceneViewComponent is a view component that does not have an associated entity. It can be added to any object in the scene to get access to the view component properties. But it has to be attached to a QuantumEntityViewUpdater.
Either make it a child game object of the EVU.
Or use the Updater field to reference the EVU directly.
Or toggle on UseFindUpdater which causes a FindFirstObjectByType() during OnEnable() which may be considered to be slow.
The ViewUpdater also allows to dynamically add and remove scene view components. Although OnInitialize() and OnDeactivate() are called right away, OnActivate() is deferred to the next Update call.
QuantumEntityViewUpdater.AddViewComponent(IQuantumViewComponent viewComponent)
QuantumEntityViewUpdater.RemoveViewComponent(IQuantumViewComponent viewComponent)
When the EVU is reused for a new game, its scene view components are activated again and receive OnActivate() for the new game.
Example
Setting character animation based on character controller state.
C#
namespace Quantum {
using UnityEngine;
public class CharacterViewAnimations : QuantumEntityViewComponent {
private Animator _animator;
public override void OnInitialize() {
_animator = GetComponentInChildren<Animator>();
}
public override void OnUpdateView() {
// KinematicVelocity is used as the speed
var kcc = GetPredictedQuantumComponent<KCC>();
var kinematicSpeed = kcc.Data.KinematicVelocity.Magnitude;
_animator.SetFloat("Speed", kinematicSpeed.AsFloat * 10);
_animator.SetBool("Jump", kcc.Data.HasJumped);
_animator.SetBool("FreeFall", !kcc.Data.IsGrounded);
_animator.SetBool("Grounded", kcc.Data.IsGrounded);
}
}
}
Camera follow behaviour example:
C#
// Context, added to QuantumEntityViewUpdater game object
namespace Quantum {
using UnityEngine;
public class CustomViewContext : MonoBehaviour, IQuantumViewContext {
public Camera MyCamera;
}
}
// View component, added to entity prefab (QuantumEntityView)
namespace Quantum {
using UnityEngine;
public class QuantumCameraFollow : QuantumEntityViewComponent<CustomViewContext> {
public Vector3 Offset;
public float LerpSpeed = 4;
private bool _isPlayerLocal;
public override void OnActivate(Frame frame) {
var playerLink = frame.Get<PlayerLink>(EntityRef);
_isPlayerLocal = Game.PlayerIsLocal(playerLink.Player);
}
public override void OnUpdateView() {
if (_isPlayerLocal == false) {
return;
}
var myPosition = transform.position;
var desiredPos = myPosition + Offset;
var currentCameraPos = ViewContext.MyCamera.transform.position;
ViewContext.MyCamera.transform.position = Vector3.Lerp(currentCameraPos, desiredPos, Time.deltaTime * LerpSpeed);
ViewContext.MyCamera.transform.LookAt(transform);
}
}
}
Last updated on
Back to top