Simple Brawler Tutorial Part 2: Camera Follow, Animation and Data Assets
This page accompanies the Camera Follow, Animation & Data Assets - Photon Quantum 3.1 Simple Brawler Tutorial (Part 2) video and explains how to add a Cinemachine camera follow, simulation-driven character animation, and Quantum Data Assets to the Simple Brawler sample.
Use this page as a reference for the second entry in the Simple Brawler series. It continues from the Part 1 project; complete Part 1 (or use the equivalent starting project) first.
Overview
Part 1 got a character spawning, moving, and jumping. Part 2 makes it feel like a game: a camera that follows the local player, an animated character, and a proper way to expose tunable gameplay values through Quantum Data Assets.
Quantum's deterministic simulation and Unity's view layer are intentionally separate. A Cinemachine camera therefore cannot simply be parented to the player transform as in a single-player project. The video covers the pattern Quantum uses to bridge that gap.
The video covers:
- installing Cinemachine and configuring Orbital Follow for a Quantum scene;
- why Quantum keeps simulation and view separate;
QuantumEntityViewUpdaterandQuantumEntityViewPool;- building an Entity View Context (
IQuantumViewContext) to share references such as the camera; - a
FollowLocalPlayerEntity View Component that sets the Cinemachine follow target; - driving Animator parameters from simulation velocity (
PhysicsBody3D); - rotating the character toward its movement direction (
FPQuaternion.LookRotation+Slerp); - what a Quantum Data Asset is compared to a plain ScriptableObject, and its determinism rules;
- creating a
PlayerConfigData Asset (MoveForce,JumpForce,TurningSpeed); - refactoring the Part 1 movement system to read from it.
Prerequisites: completed Part 1 (or equivalent starting project), the Cinemachine package, Unity 6.3 LTS with URP, and Photon Quantum 3.1.
Video Timeline
| Time | Section |
|---|---|
| 00:00 | Introduction & Recap of Part 1 |
| 00:30 | Installing Cinemachine |
| 00:40 | Configuring the CinemachineCamera & Orbital Follow |
| 01:10 | QuantumEntityViewUpdater & QuantumEntityViewPool |
| 02:30 | Creating the CameraViewContext (IQuantumViewContext) |
| 03:00 | Wiring Up the Camera Reference |
| 03:30 | Building FollowLocalPlayer |
| 04:25 | Testing the Camera Follow |
| 04:30 | Animating the Character From Simulation Velocity |
| 05:45 | Testing the Animation |
| 06:00 | Rotating Toward Movement Direction (FaceMoveDirection) |
| 07:00 | Testing Player Rotation |
| 07:15 | What Quantum Data Assets Are & Determinism Rules |
| 07:45 | Creating the PlayerConfig Data Asset |
| 08:00 | Linking PlayerConfig to the Entity Prototype |
| 08:15 | Refactoring the Movement System to Use the Data Asset |
| 09:20 | Final Test & Wrap-Up |
| 09:55 | Outro |
Installing Cinemachine
- Open
Window > Package Management > Package Manager. - Select
Unity Registry. - Find
Cinemachineand install it.
Configuring the CinemachineCamera & Orbital Follow
The scene already contains a CinemachineCamera game object with a Cinemachine Orbital Follow component. Change the following values:
| Component | Setting | Value |
|---|---|---|
| CinemachineCamera Lens | Orthographic Size | 12 |
| CinemachineCamera Lens | Near Clip Plane | 0.3 |
| CinemachineCamera Lens | Far Clip Plane | 1000 |
| Orbital Follow | Target Offset Y | 25 |
| Orbital Follow | Target Offset Z | -21.22 |
QuantumEntityViewUpdater & QuantumEntityViewPool
Find the QuantumEntityViewUpdater game object in the scene. Its script updates the view part of all entities that exist in the simulation.
Add the QuantumEntityViewPool MonoBehaviour to the same object. It provides an object pool for Entity View GameObjects, which saves performance when many entities are created and destroyed, and requires no additional setup.
Creating the CameraViewContext (IQuantumViewContext)
A view context is a Quantum class that caches references so other Quantum view scripts can use them.
- In
QuantumUser/View, create aScriptsfolder. - Right-click and select
Create > Quantum > Entity ViewContext. - Name the script
CameraViewContextand open it.
The generated script is a QuantumMonoBehaviour (not a classic MonoBehaviour) and implements IQuantumViewContext. Add a serialized reference to the camera:
C#
using Quantum;
using Unity.Cinemachine;
using UnityEngine;
public class CameraViewContext : QuantumMonoBehaviour, IQuantumViewContext
{
public CinemachineCamera Camera;
}
Wiring Up the Camera Reference
Because the script implements IQuantumViewContext, it must be placed on the same GameObject as QuantumEntityViewUpdater, or on one of its children. Otherwise the context cannot be found.
Add CameraViewContext there and assign the CinemachineCamera to its Camera field.
Building FollowLocalPlayer
- Right-click and select
Create > Quantum > Entity View Component With Context. - Name it
FollowLocalPlayer. - Change the generic parameter from
IQuantumViewContexttoCameraViewContext. - Override
OnActivate. - Use the
PlayerLinkcomponent to check whether the activated entity belongs to the local player. If not, return early. - Set the camera's follow target to this object's transform through
ViewContext.
C#
public class FollowLocalPlayer : QuantumEntityViewComponent<CameraViewContext>
{
public override void OnActivate(Frame frame)
{
var link = frame.Get<PlayerLink>(EntityRef);
if (!Game.PlayerIsLocal(link.Player))
{
return;
}
ViewContext.Camera.Follow = transform;
}
}
Add the script to the character prefab. No other references need to be assigned. When tested, the camera follows the local player.
Animating the Character From Simulation Velocity
Create a new Entity View Component for animation via Create > Quantum > Entity View Component. Like the context variant, it must be attached to a GameObject that has an Entity Prototype script.
- Reference the
Animatorin a private field and retrieve it in the overriddenOnActivate. - Override
OnUpdateView. It behaves likeUpdateon a MonoBehaviour, but is called byQuantumEntityViewUpdater. - Check that the entity this view belongs to still exists. A destroyed entity's view can remain in Unity for a brief moment, and the script would otherwise try to read data from an entity that no longer exists in the simulation. Return early if it does not exist.
- Read the velocity from
PhysicsBody3D. - Take the magnitude of the
FPVector3velocity, convert it tofloat, and set it on the Animator'sSpeedparameter. - Hash the parameter name with
Animator.StringToHash. - Set the Animator's
isGroundedparameter totrueso the movement animation displays correctly. - Add the script to the player prefab under
QuantumUser/Resources.
Rotating Toward Movement Direction (FaceMoveDirection)
In the player movement system, add a FaceMoveDirection method taking the frame, ref Filter filter, and the moveDirection vector.
- Add
Transform3D* Transformto the filter struct. - Compute the target rotation with
FPQuaternion.LookRotation(moveDirection). Slerpthe transform's rotation toward the target. For now, useFP._0_1as thetparameter. It is replaced with a configured value later in the video.
C#
private void FaceMoveDirection(Frame frame, ref Filter filter, FPVector3 moveDirection)
{
var targetRotation = FPQuaternion.LookRotation(moveDirection);
filter.Transform->Rotation = FPQuaternion.Slerp(filter.Transform->Rotation, targetRotation, FP._0_1);
}
Only call the method when moveDirection is different from FPVector3.Zero. Otherwise the character would reset its rotation when no input is pressed. Skipping the call keeps the character facing the last valid movement direction.
What Quantum Data Assets Are & Determinism Rules
The movement system from Part 1 has several fields and a magic number inside it. To make values easy to modify in edit and runtime mode, including for game designers, Quantum uses Data Assets.
A Data Asset is a ScriptableObject in the Unity sense, with additional Quantum features such as a deterministic Quantum GUID and helper methods.
Two determinism rules apply:
- Do not put non-deterministic types, such as
floator a regularVector3, in a Data Asset field that is read by the simulation. - Never write to the asset file while the simulation is running.
Creating the PlayerConfig Data Asset
- In the
Simulationfolder, create a folder calledDataAsset. - Right-click and select
Quantum > AssetScript, and name itPlayerConfig. - Add a
Headerfor movement and theMoveForceandJumpForcefields, set to40and10. - Add a
Headerfor turning and theTurningSpeedfield, set to12.
C#
using Photon.Deterministic;
using UnityEngine;
namespace Quantum
{
public partial class PlayerConfig : AssetObject
{
[Header("Movement")]
public FP MoveForce = 40;
public FP JumpForce = 10;
[Header("Turning")]
public FP TurningSpeed = 12;
}
}
Then create an instance in Unity:
- In the
Resourcesfolder, right-click and selectCreate > Quantum > Asset.... - Search by type, select
PlayerConfig, and create it.
The created asset has a path, a Quantum deterministic GUID, and the default values populated.
Linking PlayerConfig to the Entity Prototype
The asset lives in the Unity project, so the simulation needs a reference to it. Add a new component to Player.qtn named PlayerState with an asset reference to the config:
component PlayerState
{
asset_ref<PlayerConfig> PlayerConfig;
}
In Unity, add the PlayerState component to the character's Entity Prototype and assign the PlayerConfig asset.
Refactoring the Movement System to Use the Data Asset
- Remove the
MoveForceandJumpImpulsefields. - Add
PlayerStateto the filter. - Retrieve the asset from the
PlayerState:
C#
PlayerConfig playerConfig = frame.FindAsset(filter.PlayerState->PlayerConfig);
- Multiply the force and the linear impulse by
playerConfig.MoveForceandplayerConfig.JumpForce. - Pass
playerConfigtoFaceMoveDirectionas an additional parameter and multiply the turning speed byframe.DeltaTime, so rotation is accurate per simulation tick.
C#
filter.PhysicsBody->AddForce(moveDirection * playerConfig.MoveForce);
if (input->Jump.WasPressed)
{
filter.PhysicsBody->AddLinearImpulse(new FPVector3(FP._0, playerConfig.JumpForce, FP._0));
}
Gameplay behaves the same as before, but values are now organized in one asset that is easy to maintain as the project grows.
Key Concepts
| Concept | Description |
|---|---|
| Simulation / view separation | Quantum's simulation and Unity's view layer are independent; view code reads simulation state. |
QuantumEntityViewUpdater |
Updates the view part of all entities that exist in the simulation. |
QuantumEntityViewPool |
Object pool for Entity View GameObjects. |
IQuantumViewContext |
Interface for a context class that caches shared references for view scripts. |
CameraViewContext |
Context script holding the CinemachineCamera reference. |
| Entity View Component | View script attached to an Entity Prototype object, with OnActivate, OnUpdateView and other helpful API. |
FollowLocalPlayer |
Entity View Component that sets the camera's follow target for the local player. |
OnUpdateView |
Equivalent of Update, called by QuantumEntityViewUpdater. |
FPQuaternion.LookRotation |
Deterministic rotation facing a given direction. |
| Data Asset | ScriptableObject with a Quantum GUID, read by the simulation as immutable data. |
PlayerConfig |
Data Asset holding MoveForce, JumpForce, and TurningSpeed. |
PlayerState |
Component with an asset reference to the player's PlayerConfig. |
frame.FindAsset |
Resolves an asset reference into the asset object in simulation code. |
Best Practices
Use this checklist when adding view features and data assets to a Quantum project:
- Place a context script on the same GameObject as
QuantumEntityViewUpdater, or on a child of it. - Add
QuantumEntityViewPoolto reuse Entity View GameObjects when many entities are created and destroyed. - Check that the entity still exists at the start of
OnUpdateView. - Drive Animator parameters from simulation data such as
PhysicsBody3Dvelocity, and hash parameter names. - Scale rotation speed by
frame.DeltaTimeso it is simulation-tick accurate. - Move tunable values out of systems and into Data Assets.
- Use only deterministic types (
FP,FPVector3) in Data Asset fields read by the simulation. - Never write to a Data Asset while the simulation is running.
Common Pitfalls
| Pitfall | Why It Is a Problem |
|---|---|
Placing the view context away from QuantumEntityViewUpdater |
The context cannot be found by view components. |
Skipping the entity-exists check in OnUpdateView |
A destroyed entity's view can briefly remain, causing reads from a missing entity. |
Using float or Vector3 fields in a Data Asset |
Non-deterministic values break determinism when read in the simulation. |
| Modifying a Data Asset at runtime | Writing to the asset while the simulation runs can break determinism. |
Summary
Part 2 of the Simple Brawler series adds a camera follow, character animation, and a Data Asset to the Part 1 project. Because Quantum's simulation and view are separate, the camera follow is built in the view layer: a CameraViewContext placed beside QuantumEntityViewUpdater holds the CinemachineCamera, and a FollowLocalPlayer Entity View Component on the player prefab assigns the follow target when the local player's entity is activated.
An animation Entity View Component reads PhysicsBody3D velocity in OnUpdateView and drives the Animator, while FaceMoveDirection rotates the character toward its movement direction using FPQuaternion.LookRotation and Slerp.
Finally, the hard-coded values in the movement system move into a PlayerConfig Data Asset referenced through a PlayerState component and resolved with frame.FindAsset. Gameplay is unchanged, but values are now organized in one place and easy to tune, as long as Data Assets stay deterministic and are never written to during simulation.
Last updated on
Back to top- Overview
- Video Timeline
- Installing Cinemachine
- Configuring the CinemachineCamera & Orbital Follow
- QuantumEntityViewUpdater & QuantumEntityViewPool
- Creating the CameraViewContext (IQuantumViewContext)
- Wiring Up the Camera Reference
- Building FollowLocalPlayer
- Animating the Character From Simulation Velocity
- Rotating Toward Movement Direction (FaceMoveDirection)
- What Quantum Data Assets Are & Determinism Rules
- Creating the PlayerConfig Data Asset
- Linking PlayerConfig to the Entity Prototype
- Refactoring the Movement System to Use the Data Asset
- Key Concepts
- Best Practices
- Common Pitfalls
- Summary