Under Construction
Unity25 min178 views

Photon Quantum

Minh Khoa

Minh Khoa

Author

1. What is Photon Quantum?

image.pngPhoton Quantum is a deterministic multiplayer engine for Unity, built with an architecture ECS and predict/rollback networking. In short:

  • Unity handles rendering, animation view, UIaudio, input device.
  • Quantum handles simulation: game state, deterministic physics, rules, damage, spawn, win/lose.
  • Networking does not sync each object's transform in the traditional way. Clients mainly exchange input; because the simulation is deterministic, the same input produces the same result.

If Fusion/PUN usually makes you think aboutNetworkObject, RPCstate replication, interpolation, authority, then Quantum makes you think like this: "The entire game is a tick-based simulation machine. All clients run the same machine. The server helps validate input and coordinate time."

Quantum is a good fit for games that need fast response and high fairness:

  • Fighting games, sports, racing, arena action.
  • Top-down shooters, brawlers, small MOBAs, realtime tactical games.
  • Games physics/network that are complex and where you do not want to write rollback netcode yourself.

Quantum is not the first choice for idle/offline pure UI-heavy social apps, or simple multiplayer that only needs to sync a few variables.

2. Core idea: deterministic + rollback

In traditional multiplayer, client A says "I'm at position X," client B receives it and interpolates. Easy to understand, but easy to drift, easy to cheat, and physics sync is often painful.

In Quantum:

  1. Each client sends input per tick.
  2. Each client runs its own local simulation.
  3. If the actual input from the server differs from the predicted input, Quantum rolls back to an old frame and re-simulate.
  4. Because the simulation is deterministic, the result after re-simulate will be the same across clients.

This creates a feeling of less lag than classic lockstep because the client does not have to wait for all players before simulating the next frame. The tradeoff is that gameplay code must be absolutely deterministic.

3. Mental model: Quantum is a small game engine inside Unity

Split your brain into 2 areas:

Quantum Simulation

This is the "truth" of gameplay:

  • Entity, Component, System.
  • Player input, command, event.
  • Transform2D/3D deterministic.
  • Physics 2D/3D deterministic, KCC, navigation.
  • Fixed-point math:FPFPVector2FPVector3.
  • Not dependent onMonoBehaviourGameObjectTime.timeUnityEngine.RandomUnity's float physics.

Unity View

This is the presentation layer:

  • Model, sprite, animator, particle, sound.
  • UI health bar, floating text, camera.
  • Input polling from keyboard/gamepad/mobile.
  • Subscribe to events from Quantum to trigger VFX/SFX.
  • Read state from Quantum to display it.

Golden rule: Quantum decides what happens; Unity only makes it look better.

4. Basic project structure

After installing Quantum 3, the project usually has:

Assets/
  Photon/          # SDK Photon/Quantum, thường không sửa trực tiếp
  QuantumUser/     # code của game
    Simulation/    # logic deterministic, .qtn, systems
    View/          # Unity-side scripts: input, UI, view glue
    Resources/     # config, assets runtime

When upgrading SDK, Assets/Photonmay be replaced. Game code should live inAssets/QuantumUser.

5. ECS inside Quantum

Quantum uses Entity Component System:

  • Entity: the ID of an object in the simulation.
  • Component: plain data, for exampleHealthPlayerLinkWeaponInventory.
  • System: logic that runs every tick, for exampleMovementSystemCombatSystem.
  • Frame: a snapshot of the current game state. Every simulation read/write goes throughFrame.

Example component:

component Health {
  FP Current;
  FP Max;
}

component PlayerLink {
  player_ref PlayerRef;
}

Example system:

namespace Quantum {
  using Photon.Deterministic;
  using UnityEngine.Scripting;

  [Preserve]
  public unsafe class RegenSystem : SystemMainThreadFilter<RegenSystem.Filter> {
    public struct Filter {
      public EntityRef Entity;
      public Health* Health;
    }

    public override void Update(Frame frame, ref Filter filter) {
      filter.Health->Current = FPMath.Min(
        filter.Health->Max,
        filter.Health->Current + FP._1 * frame.DeltaTime
      );
    }
  }
}

[Preserve]important so Unity stripping does not remove the system.

6. DSL .qtn: where the game state is declared

Quantum uses its own DSL, usually stored in the file.qtnto declare deterministic data. From there, Quantum generates C#.

You can declare:

  • component
  • struct
  • input
  • signal
  • event
  • asset
  • enumflagsunion
  • dynamic collections such aslist<T>dictionary<K,V>hash_set<T>

Example of a filePlayer.qtn:

input {
  FPVector2 Move;
  button Dash;
  button Attack;
}

component PlayerLink {
  player_ref PlayerRef;
}

component Fighter {
  FP MoveSpeed;
  FP DashCooldown;
  FP AttackCooldown;
}

event PlayerAttacked {
  entity_ref Attacker;
  FPVector2 Position;
}

signal OnDamage(FP amount, entity_ref target);

The important point: the game state in Quantum needs blittable/deterministic. Do not put C#objects, Unity references,GameObjectTransformDateTimefloatinto the simulation.

7. Input: the thing sent every tick

Input is small, repeatedly sent data. Used for realtime actions:

  • Move direction.
  • Jump/dash/fire button.
  • Aim direction.
  • The skill slot being held.

Input definition:

input {
  FPVector2 Move;
  button Fire;
}

Unity polls the input and sends it to Quantum:

using Photon.Deterministic;
using Quantum;
using UnityEngine;

public class LocalQuantumInput : MonoBehaviour {
  private void OnEnable() {
    QuantumCallback.Subscribe(this, (CallbackPollInput callback) => PollInput(callback));
  }

  private void PollInput(CallbackPollInput callback) {
    var input = new Quantum.Input();

    var move = new Vector2(
      UnityEngine.Input.GetAxisRaw("Horizontal"),
      UnityEngine.Input.GetAxisRaw("Vertical")
    );

    input.Move = move.normalized.ToFPVector2();
    input.Fire = UnityEngine.Input.GetKey(KeyCode.Space);

    callback.SetInput(input, DeterministicInputFlags.Repeatable);
  }
}

With “, poll the current state using ”buttonFor , poll the current state with , do not use “,GetKey.” Quantum computes 自 in the simulation according to the tick.GetKeyDown/GetKeyUpIn the simulation:WasPressedIsDownWasReleasedOptimize input: keep the input as small as possible. For example, many games encode direction from ”

” to ”

namespace Quantum {
  using Photon.Deterministic;
  using UnityEngine.Scripting;

  [Preserve]
  public unsafe class PlayerMoveSystem : SystemMainThreadFilter<PlayerMoveSystem.Filter> {
    public struct Filter {
      public EntityRef Entity;
      public Transform2D* Transform;
      public PlayerLink* PlayerLink;
      public Fighter* Fighter;
    }

    public override void Update(Frame frame, ref Filter filter) {
      Input* input = frame.GetPlayerInput(filter.PlayerLink->PlayerRef);
      if (input == null) {
        return;
      }

      filter.Transform->Position +=
        input->Move * filter.Fighter->MoveSpeed * frame.DeltaTime;

      if (input->Fire.WasPressed) {
        frame.Events.PlayerAttacked(filter.Entity, filter.Transform->Position);
      }
    }
  }
}

” to reduce bandwidth.FPVector28. Command: actions that do not need to be sent every tickByteCommand is similar to input, but used for actions that happen only occasionally:

Buy an item.

Choose a hero.

  • Vote to surrender.
  • Spawn enemy from debug
  • Teleport
  • Example command: UI.
  • Send from Unity: admin/debug.

Read in the simulation:

namespace Quantum {
  using Photon.Deterministic;

  public class CommandBuyItem : DeterministicCommand {
    public int ItemId;

    public override void Serialize(BitStream stream) {
      stream.Serialize(ref ItemId);
    }
  }
}

Rule: input is for realtime control; command is for infrequent decisions, larger data, and does not need to be sent continuously.

QuantumRunner.Default.Game.SendCommand(new Quantum.CommandBuyItem {
  ItemId = 1001
});
  1. Events and Callbacks: talking from the simulation to Unity
namespace Quantum {
  using UnityEngine.Scripting;

  [Preserve]
  public unsafe class PlayerCommandSystem : SystemMainThread {
    public override void Update(Frame frame) {
      for (int i = 0; i < frame.PlayerCount; i++) {
        var command = frame.GetPlayerCommand(i) as CommandBuyItem;
        if (command == null) {
          continue;
        }

        // Validate trong simulation: tiền đủ không, item hợp lệ không, cooldown không...
        // Sau đó mới mutate game state.
      }
    }
  }
}

Events are used to notify the view that "something just happened":

Player was hit

> enable

  • ", camera shake. -Pickup coin VFX> enable sound.
  • Match ended -> open result
  • Bullet fired -> spawn muzzle flash. UI.
  • Event definition: -Trigger in the simulation:

Subscribe in Unity:

event DamageTaken {
  entity_ref Target;
  FP Amount;
  FPVector2 Position;
}

Events should not be used to change game state. If you want simulation systems to talk to each other, use signals.

frame.Events.DamageTaken(target, damage, hitPosition);

Because of rollback, events have a few nuances:

using Quantum;
using UnityEngine;

public class DamageView : MonoBehaviour {
  private void OnEnable() {
    QuantumEvent.Subscribe<EventDamageTaken>(this, OnDamageTaken);
  }

  private void OnDamageTaken(EventDamageTaken e) {
    Debug.Log($"Damage {e.Amount} at tick {e.Tick}");
    // Spawn VFX/SFX/UI here.
  }
}

Events can usually be emitted in a predicted frame.

Quantum has a mechanism to avoid duplicate event calls in the view.

  • can only be dispatched when the frame has been confirmed by the server, which is less error-prone but has a delay.
  • Event data should be self-sufficient; do not depend too much on resolving it later again
  • synced eventbecause the original frame may no longer exist.
    1. Signals: communication between systems in the simulation entity/component A signal is a deterministic callback inside Quantum. It is used to decouple systems:

System emits signal:

System listens to signal:

signal OnDamage(FP amount, entity_ref target);

Event is simulation

frame.Signals.OnDamage(FP._10, enemyEntity);

> Unity view. Signal is simulation

namespace Quantum {
  using Photon.Deterministic;
  using UnityEngine.Scripting;

  [Preserve]
  public unsafe class DamageSystem : SystemSignalsOnly, ISignalOnDamage {
    public void OnDamage(Frame frame, FP amount, EntityRef target) {
      if (frame.Unsafe.TryGetPointer(target, out Health* health)) {
        health->Current = FPMath.Max(FP._0, health->Current - amount);
      }
    }
  }
}

> simulation. -11. Entity Prototype and Asset -Quantum encourages

workflow:

data is authored in Unity. data-driven ": an data asset used in the simulation, very similar

  • QuantumEntityPrototype: prefab/entity but with “
  • AssetObject": player data sent into the simulation, for example avatar, nickname, loadout. ScriptableObject Example of spawning an avatar when the player enters the game:AssetRef.
  • RuntimePlayerThe right mindset: the designer edits

in Unity; the programmer writes systems that read that data deterministically.

namespace Quantum {
  using UnityEngine.Scripting;

  [Preserve]
  public unsafe class PlayerSpawnSystem : SystemSignalsOnly, ISignalOnPlayerAdded {
    public void OnPlayerAdded(Frame frame, PlayerRef player, bool firstTime) {
      RuntimePlayer data = frame.GetPlayerData(player);
      var prototype = frame.FindAsset<EntityPrototype>(data.PlayerAvatar);

      EntityRef entity = frame.Create(prototype);
      frame.Add(entity, new PlayerLink { PlayerRef = player });
    }
  }
}
  1. Frame: where the entire game state is contained prototype/asset is the most important object in Quantum simulation. It gives you:

Current tick.

Framedeterministic.

  • Player input.
  • DeltaTime Asset database.
  • Entity/component API.
  • Deterministic random if using
  • from Quantum.
  • Events/signals.
  • State API Quantum distinguishes between:
  • Predicted frame: the frame the client predicts on its own to reduce latency. predicted/verified.

Verified frame: the frame that has input confirmed by the server, reliable across clients.

  • You usually write gameplay without having to roll back yourself. Quantum handles it. But you must write deterministic code so that
  • the same result is produced.
  1. Physics, KCC, navigation rollback/re-simulate Quantum has its own deterministic library set:

math.

physics.

  • Fixed-point Kinematic Character Controller
  • 2D/3D Do not use Unity Physics to decide deterministic multiplayer gameplay. Unity 
  • Collider/body/callback/joint.
  • ]} final 】!【We need ensure no weird inserted quotes. But must preserve meaning. However some segments have placeholders and missing terms; maybe source omitted entities. Need not invent. But I accidentally inserted weird unicode line. Also some segments I translated too freely. Need ensure exact one-to-one and preserve punctuation. The JSON schema expects arrays only. Let's craft better consistent translation, keeping placeholders with same strange spacing. Need maybe not translate literal &gt; as is. Since it's source text maybe preserve. Let’s final with clean outputs. Need maybe segment 84/85 etc. translate. For blank placeholders, keep as in source with surrounding text. Let's produce. only required fields.json to avoid invalid with quotes? Need ensure segment 79 translated. Let's do carefully.】񐄁{ 2D/3D.
  • NavMesh/pathfinding/steering/avoidance.

title_segments Physics/Transform can be used for the view, but the actual gameplay should live in Quantum physics/state.

For example:

  • Hitbox of the attack: Quantum physics query.
  • Actual player position:Transform2D/Transform3Din Quantum.
  • Animation-running model: Unity view reads according to the entity view.

14. Real-world example: mini arena "Sushi Duel"

Suppose you make a mode PvP small in a sushi game:

  • 2 players compete for ingredients falling on the map.
  • Pick up fish, dash, push opponents, cook combos.
  • After 90 seconds, whoever has more points wins.

Quantum design:

input {
  FPVector2 Move;
  button Dash;
  button Interact;
}

component PlayerLink {
  player_ref PlayerRef;
}

component Chef {
  FP Speed;
  FP DashCooldown;
  Int32 Score;
}

component Ingredient {
  Int32 TypeId;
  Int32 Point;
}

event IngredientPicked {
  entity_ref Player;
  Int32 TypeId;
  FPVector2 Position;
}

Gameplay flow:

  1. Unity poll move/dash/interact.
  2. ChefMoveSystemmove chef with Quantum transform/physics.
  3. PickupSystemcheck overlap between chef and ingredient.
  4. When pickup is valid, increaseChef.Score, destroy ingredient entity.
  5. TriggerIngredientPickedevent.
  6. Unity receives the event, plays sound, particle, floating score.
  7. MatchTimerSystemend the match when the timer reaches 0, trigger result event.

Thanks to determinism, both machines receive input on the same tick and produce the same result: who picked up first, how many points, when the ingredient disappeared.

15. Survival checklist when writing Quantum

  • Do not usefloatfor gameplay simulation. UseFP.
  • Do not useUnityEngine.Random; use deterministic random from Quantum/frame.
  • Do not readTime.timeDateTime.Nowframerate, local clock in the simulation.
  • Do not mutateGameObject/Transformin the simulation system.
  • Do not send everything through event/command. Keep the game state in components.
  • Input must be small and sent every tick; command only for rare actions.
  • Poll buttons by current state, do not useGetKeyDown/GetKeyUp.
  • Event for VFX/UI/SFX; signal for simulation logic.
  • Data events should be self-describing enough.
  • Dynamic collections must allocate/free correctly, then setdefaultafter free.
  • System order matters. Register systems inSystemConfigpurposefully.
  • Test with local multiplayer, high latency simulation, replay/checksum.
  • When there is desync, the first suspects are: non-deterministic code, wrong random, float, collection lifecycle, different data assets between clients.

16. When should you use Quantum, and when not?

Use Quantum if:

  • The gameplay needs real-time competition and fast response.
  • Physics/game state is complex, hard to sync with transform replication.
  • You want rollback netcode but do not want to build the engine yourself.
  • The team accepts learning ECS, fixed-pointcodegen, deterministic discipline.

Consider Fusion/PUN or another solution if:

  • The game is only co-op lightweight, few objects, no rollback needed.
  • The game is more about room/chat/social than simulation.
  • You need server-authoritative traditional-style state sync.
  • The team is not ready to separate gameplay from Unity MonoBehaviour.

17. The fastest way to learn

7-day roadmap:

  1. Run Quantum's Asteroids sample.
  2. Read and modifyInput.qtn, add a new button.
  3. Write a componentHealth.
  4. Write a system that subtracts health when hit by a shot.
  5. Trigger an event to let Unity enable VFX.
  6. Add commandBuyItemorSpawnEnemy.
  7. Run 2 local players, enable simulated latency, watch rollback work.

Then only later learn physics, KCC, asset pipeline, replay, checksum, custom server plugin in depth.

18. One closing sentence

Photon Quantum is not "Photon but with smoother sync". It is a way to write multiplayer games in deterministic simulation style: you build a pure data world, run it by tick, same input gives the same result; Unity is only the outer layer for seeing/hearing/touching.

When you understand that boundary, Quantum becomes very clear: gameplay lives inFrame, data lives in.qtn, logic is insideSystem, input goes in each tick, events come out for the view. That’s the skeleton of a multiplayer rollback game standing up.