This document is about: QUANTUM 3
SWITCH TO

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.

Camera Follow, Animation & Data Assets - Photon Quantum 3.1 Simple Brawler Tutorial (Part 2)

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;
  • QuantumEntityViewUpdater and QuantumEntityViewPool;
  • building an Entity View Context (IQuantumViewContext) to share references such as the camera;
  • a FollowLocalPlayer Entity 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 PlayerConfig Data 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

  1. Open Window > Package Management > Package Manager.
  2. Select Unity Registry.
  3. Find Cinemachine and 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.

  1. In QuantumUser/View, create a Scripts folder.
  2. Right-click and select Create > Quantum > Entity ViewContext.
  3. Name the script CameraViewContext and 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

  1. Right-click and select Create > Quantum > Entity View Component With Context.
  2. Name it FollowLocalPlayer.
  3. Change the generic parameter from IQuantumViewContext to CameraViewContext.
  4. Override OnActivate.
  5. Use the PlayerLink component to check whether the activated entity belongs to the local player. If not, return early.
  6. 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.

  1. Reference the Animator in a private field and retrieve it in the overridden OnActivate.
  2. Override OnUpdateView. It behaves like Update on a MonoBehaviour, but is called by QuantumEntityViewUpdater.
  3. 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.
  4. Read the velocity from PhysicsBody3D.
  5. Take the magnitude of the FPVector3 velocity, convert it to float, and set it on the Animator's Speed parameter.
  6. Hash the parameter name with Animator.StringToHash.
  7. Set the Animator's isGrounded parameter to true so the movement animation displays correctly.
  8. 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.

  1. Add Transform3D* Transform to the filter struct.
  2. Compute the target rotation with FPQuaternion.LookRotation(moveDirection).
  3. Slerp the transform's rotation toward the target. For now, use FP._0_1 as the t parameter. 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 float or a regular Vector3, 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

  1. In the Simulation folder, create a folder called DataAsset.
  2. Right-click and select Quantum > AssetScript, and name it PlayerConfig.
  3. Add a Header for movement and the MoveForce and JumpForce fields, set to 40 and 10.
  4. Add a Header for turning and the TurningSpeed field, set to 12.

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:

  1. In the Resources folder, right-click and select Create > Quantum > Asset....
  2. 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

  1. Remove the MoveForce and JumpImpulse fields.
  2. Add PlayerState to the filter.
  3. Retrieve the asset from the PlayerState:

C#

PlayerConfig playerConfig = frame.FindAsset(filter.PlayerState->PlayerConfig);
  1. Multiply the force and the linear impulse by playerConfig.MoveForce and playerConfig.JumpForce.
  2. Pass playerConfig to FaceMoveDirection as an additional parameter and multiply the turning speed by frame.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 QuantumEntityViewPool to 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 PhysicsBody3D velocity, and hash parameter names.
  • Scale rotation speed by frame.DeltaTime so 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