Quick Start Guide
Introduction
Quantum SDK for Unreal Engine 5 is a complete re-imagining (and C++ reimplementation) of our deterministic predict-rollback engine Quantum C#.
Deterministic predict/rollback netcode relies on two fundamental principles:
- Cross-platform deterministic engine building-blocks (physics, navigation, math libraries) that produce the same result when the same input is used, specially when used with confirmed inputs from the server (that controls the timing and input stream).
- Ability to predict ahead the local game state, and to quickly rollback to a previous verified state that can be safely moved forward with confirmed inputs from the server.
The fundamentals above are not specific to any programming language or host-engine, so they remain the same in Quantum for Unreal, but there are very important changes (compared to Quantum C#/Unity) to better fit the Unreal conventions and developer expectations. We also decided to add unique features not present in the C# version, like partial tick prediction (no more need to use 1-tick interpolation for views).
This document is a quick reference to start developing using Quantum on Unreal 5 projects.
Setup
To setup Quantum Unreal in a new project:
- Create a C++ Project through the Unreal Project Browser using Unreal 5
- Create a
Plugins
folder inside your Unreal project folder - Download the Quantum Unreal Plugin
- Unzip the Quantum Unreal Plugin and copy the
PhotonQuantum
folder into the newly createdPlugins
folder - Open the .uproject file for your project in a text editor
- Under Modules add an additional dependency to Quantum:
"Modules": [
{
"Name": "MyTestProject",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"PhotonQuantum
]
}
]
- In your project settings set
UQuantumGameInstance
as your game instance.
The following video shows in detail how Quantum Unreal is setup starting from a new project. Some things might be out of date, the list above will be kept up to date.
Basic Actor or ActorComponent
The entry point for adding predict-rollback game-state and logic on Quantum for Unreal are deterministic actors. A deterministic actor can be either a custom class inherinting from AActor or a BP class inheriting either from one of these custom actors or from our pre-defined class ADeterministicActor
. All Quantum actors will have code generated via the Unreal Header Tool (UHT) to implement the interface defined in QuantumObjectInterface.h
, which provides the necessary APIs and building blocks.
Besides actors, it is also possible to implement custom components implementing the same interface. The rollback-able game-state is comprised of all actors that implement QuantumObjectInterface
and their children components that also implement the same interface.
Here is a C++ example of a minimum quantum actor-component, still without any custom game-state or deterministic callback (will be added later):
C++
#pragma once
#include "CoreMinimal.h"
#include "QuantumObjectInterface.h"
#include "TestComponent.quantum.h"
#include "TestComponent.generated.h"
UCLASS()
class QUANTUMPLUGIN_API UTestComponent : public UActorComponent, public IQuantumObjectInterface
{
GENERATED_BODY()
QGENERATED_BODY(UTestComponent)
};
Important things to notice from the example above:
- Mandatory
#include "FILENAME.quantum.h"
right before#include "FILENAME.generated.h"
. - Mandatory
QGENERATED_BODY(TYPE)
for each type declared in the .h file, right below Unreal's ownGENERATED_BODY()
.
This assures the Quantum UHT add-on will correctly generate the necessary parts for the types included in each file.
Predict-rollback Game state
The core of fast-paced determinism is the ability to both seamlessly predict changes based on local player input, and later efficiently rollback to a verified game state. Then, re-using the same simulation code, confirmed input is used to advance forward the verified state, re-simulate predictions again (perhaps adding a few new ticks of prediction, polling new local player input in this case).
To allow for this to happen, Quantum uses its own fast-copiable heap, and all mutable gameplay-relevant state must be declared in a special way to seamlessly support rollbacks. By convention, for each actor or component, Quantum will code-generate a USTRUCT containing the specified game state members, and will declare it as a pointer named QState
.
There are 3 ways to declare members of an actor or component to be made part of the Quantum QState
, all with macros: QREPLICATE
, QPROPERTY
(a wrapper that accepts all variations of a regular UPROPERTY) or a complete USTRUCT to be used as a "root" QPROPERTY
.
Lets explore these variations with a more concrete example: if the developer needs both a 3D vector and an integer properties in a certain actor type to be part of the Quantum game state, they will both need to be added to the specific QState
property pointer of this actor class declaration.
Option 1: simply declare with QREPLICATE
The simplest way to add a member to the QState
struct is to wrap it with the QREPLICATE
macro:
C++
// ommited for clarity
UCLASS()
class QUANTUMPLUGIN_API UTestComponent : public UActorComponent, public IQuantumObjectInterface
{
GENERATED_BODY()
QGENERATED_BODY(UTestComponent)
QREPLICATE(FCVector3, MyVector)
QREPLICATE(int, MyNumber)
};
The example above will automatically generate a struct (declared as part of UTestComponent in the code generated TestComponent.quantum.h
) with the following declaration:
C++
// code-generated by Quantum's UHT add-on, do not edit
struct FQuantumState
{
FCVector3 MyVector;
int MyNumber;
};
...and add the following to the UTestComponent public declarations:
C++
// code-generated by Quantum's UHT add-on, do not edit
FQuantumState* QState = nullptr;
The actual data for this state is kept by Quantum in its own heap, and when developer-code is executed the right copy of the data (verified or working/prediction copy) will be available for reading or writing.
Option 2: prototypes with the QPROPERTY
macro:
The example above is programmer-centric and does not let a designer edit initial values via the details editor on Blueprints. To allow for that, another approach is used, so prototype UPROPERTIES can be used.
The next example declares the same two members to the generated QState
struct, but will also be exposed as regular UPROPERTIES so a level-designer can type in the initial values (both directly on a Map
-placed instance or on a Blueprint class).
Important: notice two important things:
- use of QPROPERTY instead of Unreal's regular UPROPERTY. This is what triggers Quantum's code generation in this case. You can otherwise use the same parameters as the regular UPROPERTY macro.
- The editable member must have the
Prototype
sufix added to its name (code-generator will complain and not compile otherwise).
C++
// ommited for clarity
UCLASS()
class QUANTUMPLUGIN_API UTestComponent : public UActorComponent, public IQuantumObjectInterface
{
GENERATED_BODY()
QGENERATED_BODY(UTestComponent)
QPROPERTY(EditAnywhere, Category = "Quantum State")
FCVector3 MyVectorPrototype;
QPROPERTY(EditAnywhere, Category = "Quantum State")
int MyNumberPrototype;
};
When this component is added to a Quantum actor, the initial values for the QState
members declared above will be available for editing:

Option 3: full control of QState
as a custom USTRUCT:
If the developer wants to retain full control of the whole struct that holds the rollback-able game state, this is possible by declaring a regular USTRUCT (assuming it only uses the allowed types - more on this later), and telling quantum it will be used as the "root" struct for the state. When using this option, no other QPROPERTY can be declared in the same actor/component.
A few important things to notice:
- root state struct is a regular Unreal USTRUCT.
- members are declared with regular UPROPERTY (not QPROPERTY), and they do not need the
Prototype
sufix. - In the class, there is a single QPROPERTY, pointing to the struct type as the full state prototype.
- All expected parameters of UPROPERTY are respected.
This is the declaration of an example of such struct:
C++
USTRUCT(BlueprintType)
struct QUANTUMPLUGIN_API FMyQuantumState {
GENERATED_BODY()
QGENERATED_BODY(FMyQuantumState)
public:
UPROPERTY(EditAnywhere)
FCVector3 MyVector;
UPROPERTY(EditAnywhere)
int MyNumber;
};
And this is how the root QPROPERTY can be declared in the class:
C++
QPROPERTY(EditAnywhere, Category = "Quantum Root State", Meta = (QStateRoot))
FMyQuantumState StatePrototype;
Relation to regular UPROPERTIES
Regular properties can be used at the same time in these actors or components. It's just important to be aware that these are neither rolled-back, nor considered for late-joiner snapshot serialization, so they can be used for the following use cases:
- read-only configuration settings that can be different on each Blueprint instance.
- auxiliary properties used for view/UI purposes only (like VFX/SFX references, etc).
- any other use case that does not affect gameplay (that does not represent or change game state directly).
To summarize:
- mutable (readwrite) game state must be declared to be part of the
QState
container, which is made available as a pointer to a struct. - there are three variations (syntax sugar) for declaring state for Quantum: QREPLICATE, which is pure code, no editor support, QPROPERTIES (which use the Property sufix convention to have initial values editable), and regular USTRUCT declared as a root QPROPERTY.
- regular UPROPERTIES can also exist in these actors and components, as long as they do not compromise determinism (are either read-only settings, or used for exclusively for view).
Deterministic Logic (callbacks)
The second main entry-point for seamless predict-rollback gameplay logic (besides state) is the DeterministicTick
callback, which must be used to replace what a developer would use Unreal's standard Tick
callback on a typical single-player game. Using DeterministicTick
assures the right copy of the game state data is present on all QState
pointers (verified data when moving forward the simulation with server-confirmed inputs vs. working/prediction data when predicting with local input, including partial tick prediction for snappy rendering).
This is the signature for DeterministicTick
:
C++
virtual void DeterministicTick(const FDeterministicTickInfo& TickInfo) override;
The TickInfo
struct contains important information that may be required depending on the type of logic being implemented. These are the most important ones:
FCReal DeltaTime
: delta time expressed as a fixed-point number. Value will be 1/FixedUpdateRate for regular ticks, and a value between 0 (exclusive) and that for partial ticks (for snappy rendering).int Tick
: the exact tick number being computed (first one simulated is same value the rollback-window setting).bool Verified
: whereas this tick is being simulated forward with server-confirmed inputs (meaning this is the final time this is called for this tick number).bool Partial
: whereas this tick is being simulated as the last before render, using an incomplete delta-time so to align the rendering updates even in the presence of tick-aliasing between fixed-rate simulation and rendering. Notice alias always happens even it rendering rate matches (in number) the quantum simulation rate. This happens because the fixed simulaiton rate is adjusted to the server clock to avoid the risk of hardward drifting.TickInputSet* InputSet
: the complete input set (with input for all players) for this tick, including local player prediction (polled), remote player prediction (copied forward from verified previous inputs), or fully server-confirmed inputs (in case of verified ticks).
In case the developer needs to update UI, or animation and VFX/SFX in general, there is one specific callback that is called only once per Unreal Tick, and always after all instances of Quantum actors and components have had their DeterministicTick
, including both verified and predicted ticks:
C++
virtual void UpdateView() override;
The QState
available during the callback above will be the last data as updated by the code in actors and components last call to DeterministicTick
with the partial tick update to match the rendering rate.
Adding Quantum Actors to the Game
There are two ways to add actors (that implement IQuantumObjectInterface
) to the Quantum simulation: adding them directly to the Unreal gameplay Map
that is loaded before Quantum is started, or spawning them dynamically on verified ticks via code (using the Quantum-provided API).
Map Actors
All AActor instances that implement IQuantumObjectInterface
that are loaded in the Map
before Quantum starts will be automatically registered to it on the even of its start (when join
is called on the subsystem - more on this later). Here's an example of an ADeterministicActor
(base pre-built Quantum Actor class) included in a Map
with some Quantum actor components added to it:

If a map contains meshes with UFixedMeshCollider3dComponent
, the mesh data must be baked into a data asset. In order to do that, a UMapBaker
component has to be added to an actor in the map, which will automatically start baking all fixed mesh colliders and landscapes. With the MapBaker component selected, the "Bake Collision Data" button can be pressed in the details panel to manually bake the data again.
All Quantum mesh colliders hold a reference to the MapBaker which is needed in runtime. Therefore, the MapBaker component should not be removed after baking.
Spawning Quantum Actors Dynamically
Actor classes implementing IQuantumObjectInterface
can also be spawned dynamically and still be part of the predict-rollback simulation as long the following rules are followed:
- spawning is called directly from
DeterministicTick
(or somewhere down the stack from it) on another, already part of Quantum's simulation, actor or component. Example: you can always have a few "spawner" actors pre-added to the gameplayMap
. - spawning is done using the Quantum-provided "SpawnActor" (API snippet below).
- spawned actor implements
IQuantumObjectInterface
.
Signature (from CoriumGame.h):
C++
AActor* = SpawnActor(UClass* type);
Example snippet from any DeterministicTick or another Quantum valid gameplay callback like OnPlayerAdded, OnInit, etc:
C++
// somewhere in the .h
UPROPERTY(EditAnywhere, Category = "PlayerAvatar")
TSubclassOf<ADeterministicActor> MyAvatarType;
// spawn snippet in the .cpp
auto avatar = GetQuantumGame()->SpawnActor(MyAvatarType.Get());
When this is called, Quantum will check if the current state of the simulation is a verified frame or not, and then proceed as follows:
- in a predicted tick,
nullptr
will be immediately returned (spawning can only happen on verified ticks). - in a verified tick: (1) spawn an instance of the desired actor class (
BeginTick
will be called here), (2) register it with Quantum (initial prototype values are applied here), then (3) return a pointer to the spawned actor.
Spawning when a player connects
Whenever a new game client connects and joins a Quantum session, AddLocalPlayer
can be locally called on its Quantum subsystem. If there is still a global player index available, the server will fullfil that request, and OnPlayerAdded
will be called on all instances of the game (on all connected clients, including spectating ones) in a verified frame.
This is a common place to spawn a player character, since the callback also passes along an optional data-buffer (that allows custom player-choices to be sent - function is called AddLocalPlayerData
in this case). Also, because the resulting OnPlayerAdded
is only called on verified frames on all clients, spawning here does not necessarily need a nullptr
check.
Here's a more complete example of spawning a vehicle, using a read-only (specified in the editor) reference blueprint class.
On header file (QuantumPlayerSpawner.h
):
C++
UPROPERTY(EditAnywhere)
TSubclassOf<AQuantumVehicle> PlayerVehicleClass;
virtual void OnPlayerAdded(ExitGames::Common::JArray<nByte>* data, int playerIndex) override;
Implementation (QuantumPlayerSpawner.cpp
):
C++
void UQuantumPlayerSpawner::OnPlayerAdded(ExitGames::Common::JArray<nByte>* data, int playerIndex) {
auto vehicle = Cast<AQuantumVehicle>(GetQuantumGame()->SpawnActor(PlayerClass.Get()));
vehicle->_transform->QState->Value.position = FCVector3(0, playerIndex * 400, 200);
// assigning control to this specific player
vehicle->QState->PlayerIndex = playerIndex;
vehicle->InitUI();
}
Despawning Quantum Actors
WIP: Documentation will be added after feature is fully tested.
Input and Commands
TODO
For now it's fine to ask on Discord or watch previous streams.
Events
TODO
For now it's fine to ask on Discord or watch previous streams.
Prebuilt Quantum Actors and Components
ADeterministicActor
: can be used to compose map colliders and/or actors with logic exclusively on components.UFixedTransform3D
: "bakes" the unreal root actor transform data in FCReal cross-platform deterministic format for initialization, and in runtime (fromOnUpdateView
) updates the Unreal transform to the FCReal data changed from the simulation.UFixedCollider3D
(concrete variationsUFixedLandscapeCollider3D
,UFixedBoxCollider3D
,UFixedSphereCollider3D
,UFixedCompoundCollider3D
): used to bake collider data to be used by Quantum's custom fast predict-rollback physics engine. Any data stored in the correspondingQState
of each of these can be modified in runtime (except for the one inUFixedLandscapeCollider3D
).UFixedPhysicsBody3D
: dynamic physics body data (mass, inertia, drag, gravity multiplier, flags, etc). Also fully modifiableQState
, besides APIs forAddForce
,AddTorque
and expected variations.AQuantumVehicle
: specialized Quantum actor that implements 3D vehicle physics (spring/damper suspension, anti-rollback, Ackermann steering, per-tire friction model, etc). Includes default components to help assemble a vehicle (UFixedTransform3D
,UFixedCollider3D
,UFixedPhysicsBody3D
and a hierarchy of containers for suspension and wheel view hubs).- Prototype actors: basic actors with pre-composed examples for static colliders (box, sphere) and dynamic bodies (box, sphere).
Other physics features:
- Raycast and ShapeOverlap queries.
- Hit/Overlap callbacks (including begin/end variations).
- PhysicsMaterial DataAsset (very similar to the one used by Chaos physics).
- Layers/collision types, using same standards as Chaos.
- Fully configurable solver and other settings.
Quantum Game Instance
Quantum runs as an Unreal Game Instance, which starts to tick in runtime once it is first connected to the Photon cloud.
The Game Instance in the Project Settings has to be set to UQuantumGameInstance
.
Game Instance API
A few useful API functions are available (including from Blueprints):
C++
UFUNCTION(BlueprintPure, Category = "QuantumGameInstance", meta = (WorldContext = "WorldContextObject") )
static const UQuantumGameInstance* GetQuantumGameInstance(const UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "QuantumGameInstance")
bool IsLocalPlayer(int playerIndex);
UFUNCTION(BlueprintCallable, Category = "QuantumGameInstance")
void AddLocalPlayer(int localSlot);
UFUNCTION(BlueprintCallable, Category = "QuantumGameInstance")
void AddLocalPlayerData(int localSlot, ExitGames::Common::JArray<nByte>& data);
UFUNCTION(BlueprintCallable, Category = "QuantumGameInstance")
void Join(FString roomName);
UFUNCTION(BlueprintCallable, Category = "QuantumGameInstance")
void Connect(bool local, FString userID, FString appID);
GetQuantumGameInstance
returns a pointer to the current Quantum Game Instance.
Connect
and Join
are prototype entry-points to allow quick access to running/testing the game online. Connect lets a developer specific the Photon Quantum App ID (must be one for Quantum's Version 3) and "userID" must be unique for each client otherwise quantum will not allow a player index to the next one. A "local" connection can be used if developing with a local photon server (equipped with a Quantum version 3 server plugin).
The call to Join
is used to "JoinOrCreate" the photon server room with the name/identifier passed as parameter, and also immediately starts the local Quantum Session (this will trigger the server session to also start if not yet done).
AddLocalPlayer
can be called after the runner is started (join is called, and game start confirmation is received) to ask for the server to assign a new player index to be controlled by the local instance/session of Quantum. If succeeded, OnPlayerAdded
will be called on implementing objects already in the simulation, like explained in a previous section.
IsLocalPlayer
can be used to check whereas a certain (global) player index is controlled by the local instance or not. This commonly required to setup UI/Cameras to look at an actor that is controlled by the local player (see example on next section).
Extra Examples
The Blueprint code below (spawning comes from the OnPlayerAdded
example) safely setups the vehicle-attached spring-arm camera only if the vehicle is controlled by the local player (reads the player index initialized for the actor, checks whereas this is a local player via the subsystem):

Allowed Deterministic Types
The following types, and compositions (USTRUCTs) made of them correctly get serialization and predict/rollback code generated when used to define QState
in actors or components:
- FCReal: fixed-point (Q48.16) type that must be used instead of float or double.
- FCVector2, FCVector3: equivalent to FVector2 and FVector3, respectively.
- FCQuat: equivalent to FQuat.
- uint32_t, int32_t (and all variations for 8, 16 and 64 bits). Also valid to just use int, etc, but the verbose aliases are available.
- bool
- Any USTRUCT composed of the types above.
- Pointer (TObjectPtr
) to other Quantum actors, components. - Pointer (TDataAssetPtr<assetType) to DataAssets.
Roadmap Summary
missing features (planned for short-medium term)
physics:
- CCD and Joints (medium term)
misc:
- FCReal math library functions fully exposed to BP