Quantum Intro
Overview
Photon Quantum is a high-performance deterministic ECS (Entity Component System) framework for online multiplayer games made with Unity.
It is based on the predict/rollback approach, which is ideal for latency-sensitive online games such as action RPGs, sports games, fighting games and more.
Watch the deep-dive video to understand the general concepts around Quantum:
Quantum implements a state-of-the-art tech stack composed of the following pieces:
- Server-managed predict/rollback simulation core.
- DSL-based code-generation of game state data as memory aligned C# partial structs (high performance API gives direct pointer access to data).
- Complete set of stateless determinsitic libraries (math, 2D and 3D physics, navigation, etc).
- Rich Unity editor integration and tooling.
All built on top of mature and industry-proven existing Photon products and infrastructure (photon realtime transport layer, photon server plugin to host server logic, etc);
Quantum also helps the developer to write clean code, fully decoupling simulation logic (quantum ECS) from view/presentation (Unity), while also taking care of the network implementations specifics (internal predict/rollback + transport layer + game agnostic server logic):
Determinism without Lockstep
In deterministic systems, game clients only exchange player input with the simulation running locally on all clients. In the past, this has used a lockstep approach, in which game clients would wait for the input from all other players before updating each tick/frame of the simulation.
In Quantum, however, game clients are free to advance the simulation locally using input prediction, and an advanced rollback system takes care of restoring game state and re-simulating any mispredictions.
Because Quantum also relies on a game-agnostic authoritative server component (photon server plugin) to manage input latency and clock synchronization, clients never need to wait for the slowest one to rollback/confirm the simulation forward:
These are the basic building blocks of a Quantum game:- Quantum Server Plugin: manages input timing and delivery between game clients, acts as clock synchronization source.
Can be extended to integrate with customer-hosted back-end systems (matchmaking, player services, etc). - Game Client Simulator: communicates with quantum server, runs local simulation, performing all input prediction and rollbacks.
- Custom Gameplay Code: developed by the customer as an isolated pure C# simulation (decoupled from Unity) using the Quantum ECS.
Besides providing a framework for how to organize high-performance code, Quantum's API offers a great range of pre-built components (data) and systems (logic) that can be reused in any game such as deterministic 3D vector math, 2D and 3D physics engines, navmesh pathfinder, etc.
Old School Coding
Starting from the assumption that all simulation code must be high-performance out of the box, Quantum internal systems are all designed with that in mind from the ground up.
The key to Quantum's high performance is the use of direct pointers combined with an ECS data model (all based in memory aligned data-structs and direct allocation/no garbage collection from simulation code at runtime).
The goal is to leave most of of the CPU budget for view/rendering code (Unity), including here the re-simulations induced by input mispredictions, inherent to the predict/rollback approach:
Although the use of pointer-based C# is exposed (for performance), most of the complexity is hidden away from the developer by the clever use of a custom DSL and automatic code generation.
Code Generation
In Quantum (up to SDK 1.2.4, SDK 2.0 changes the memory model), all gameplay data (game state) is structured into a single block as a memory-aligned C# struct.
To define all data structures that go into that, the developer uses a custom DSL (domain specific language) that lets him concentrate on the game concepts instead of performance-oriented restrictions:
C#
// components define reusable game state data groups
component Resources
{
Int32 mana;
FP health;
}
// entities are component containers (custom and pre-built ones)
entity character[32]
{
use Resources;
use Transform2D;
}
The code-snippet above would generate a game state data structure for up to 32 characters, each one containing both transform and resources components.
The auto-generated API lets you both query and modify the game state with comprehensive functions to read, modify, create or destroy entities:
C#
// Auto-generated function to create a new character, returning a pointer (Frame f is the game state container passed at every init or update function)
var c = f.CreateCharacter();
var position = c->Transform2D.Position;
// if you want to update all characters...
var all = f.GetAllCharacters();
while (all.Next())
{
Character* c = all.Current;
}
// destroying an entity is also straightforward...
f.DestroyCaracter(c);
Stateless Systems
While Quantum's DSL covers game state data definition with concepts such as entities, components and auxiliary structures (structs, enums, unions, bitsets, collections, etc), there needs to be a way to organize the custom game logic that will update this game state.
You write custom logic by implementing Systems, which are stateless pieces of logic that will be executed every tick update by Quantum's client simulation loop:
C#
public unsafe class LogicSystem : SystemBase
{
public override void Update(Frame f)
{
// your game logic here (f is a reference for the generated game state container).
}
}
The Systems API game loop call order, signals for system inter-communication (both custom and pre-built, such as the physics engine collision callbacks), events and several other extension hooks.
Events
While the simulation is implemented in pure C#, without referencing Unity's API directly, there are two important features to let gameplay code communicate with the rendering engine: events and the asset linking system.
Events are a way for the game code to inform the rendering engine that important things happened during the simulation.
One good example is when something results in damage to a character.
Using the state from the previous section as a basis, imagine that damage reduces the health value from the resources component of a character entity.
From the Unity rendering scripts, the only noticeable data will be the new health value itself, without any way to know what caused the damage, and also what was the previous health value, etc.
Event definition in a file DSL:
C#
event Damage
{
entity_ref<Character> Character;
FP Amount;
}
Gameplay code raises events as a simple API call (generated):
C#
public void ApplyDamage(Frame f, character* c, FP amount)
{
f.Events.Damage(amount, c->EntityRef);
}
Quantum's even processor will handle all generated events after the tick update is done, taking care of events that require server-confirmed input, event repetitions, etc.
Events raised from the simulation code can then be consumed in runtime from callbacks created in Unity scripts:
C#
public void OnDamage(DamageEvent dmg)
{
var target = QuantumGame.Frame.GetCharacter(dmg.Character);
// instantiate and show floating damage number above the target character, etc
}
Asset Linking
Unity is known for its flexible editor and smooth asset pipeline.
The Asset Linking system allows game and level designers to create and edit data-driven simulation objects from the Unity Editor, which are then fed into the simulation.
This is essential both to prototype to add final balancing touches to the gameplay.
From the C# simulation project, the developer creates a data-driven class exposing the desired attributes:
C#
public partial class CharacterClass
{
public Int32 MaxMana;
public FP MaxHealth;
}
Then from Unity level designers can create as many instances of this asset as needed, each one being automatically assigned with a Unique GUID:
Then, programmers can use data from these assets directly from inside the simulation:
C#
var data = DB.FindAsset<CharacterClass>("character_class_id");
var mana = data.MaxMana;
It's also possible to assign these assets directly to entities and components from the state definition DSL:
C#
entity Character[32]
{
use Resources;
use Transform2D;
fields
{
asset_ref<CharacterClass> CharacterData;
}
}
Deterministic Library
In Quantum, the simulation needs to compute the same results on all clients, given they use the same input values.
This means it has to be deterministic, which implies neither using any float or doubles variables, nor anything from the Unity API such as their vectors, physics engine, etc.
To help game developers to implement this style of gameplay, Quantum comes bundled with a flexible and extensible deterministic library, both with a set of classes/struct/functions and also some components that can be used directly when defining entities with the DSL.
The following modules are available:
- Deterministic math library: FP (Fixed Point) type (Q48.16) to replace floats/doubles, FPVector2, FPVector3, FPMatrix, FPQuaternion, FPRandom, FPBounds2, and all extra math utils including safe casts, and parsers from native types.
The math library is implemented with performance as a primary goal, so we make intense use of inlining, lookup tables and fast operators whenever possible. - 2D and 3D Physics Engines: high performance stateless 2D/3D physics engines with support for static and dynamic objects, callbacks, joints, etc.
- NavMesh/PathFinder/Agents: includes both an exporter from an existing Unity navmesh or an editor to manipulate the mesh directly; also includes industry standard HRVO collision avoidance, funneled paths, and many more features.
- Deterministic Animation Controller: exports data from a mecanim asset into a deterministic format, runs the animation state machine from the deterministic simulation, and also controls the mecanim animator to keep it in sync. Includes support for root motion, etc.
There is a lot more to learn, so we recommend taking a look at the manual section of this documentation.
Back to top