Migration Notes
Always read the release history first then follow the upgrade instructions included in every SDK package and use this resource as a reference to get detailed information on the changes we introduced.
Always migrate only one version at the time and make sure the result is working before heading to the next one.
Contents
Migrate from 1.2.3 to 1.2.4
Filters
There are three composition keywords available in the DSL: has, not and any. Here's a simple example and a quick explanation of the resulting iterators:
filter MyEntities {
has Transform3D;
has DynamicBody3D;
not Prefab;
any {
PoisonArea;
EnemyTrigger;
}
}
- "has" filters in entities that contain all of the chosen components;
- "not" filters out entities containing any of the chosen components;
- "any" filters in only entities that have only one of the chosen components.
All filter iterator accessors are grouped in the Frame.Filters object (in the case above as Frame.Filters.MyEntities), and the iterator item (Current) contains cached pointers to all components defined by the "has" and "any" keywords (some of the later will be null, following the expected semantics).
Navmesh (Breaking Change)
Reimport all your navmeshes from Unity and bake all navmeshes again.
Any old Quantum navmesh assets lying inside the DB folder will cause the game to crash with a serialization error.
The import tab and algorithms have been updated and got faster and more robust. The default configuration for the new settings on the MapNavMeshDefinition inspector should be good to start with. Read more about the options here: Navigation - Creating A Quantum Navmesh
Method signatures of the NavMesh class have a new parameter: INavMeshRegionMask. It is required for togglable navmesh regions. The Frame
class implements it:
C#
// For example the usage of the LineOfSight method
public bool LineOfSight(FPVector2 p0, FPVector2 p1, INavMeshRegionMask regionMask);
public override void Update(Frame frame) {
var navmesh = frame.Map.NavMeshes["NavMeshUnity"];
navmesh.LineOfSight(FPVector2.Zero, FPVector2.One, frame);
}
NavMeshAgent (Breaking Change)
SimulationConfig.ProximityFactor
has been replaced with SimulationConfig.AvoidanceRange
. The factor was multiplied with the agent range to measure the distance between two agents. The new range value represents the distance between two agents minus their radius.
new AvoidanceRange ~= AgentConfig.Range * (old ProximityFactor - 2)
The navmesh agent API has been upgraded. Notable changes are:
NavMeshAgent.SetTarget()
sets the input position as the Target and a potentially corrected position as InternalTarget.- Use
NavMeshAgent.Init(NavMeshAgentConfig agentConfig)
instead ofNavMeshAgent.AgentConfig = config
to initialize an agent. We require the extra effort to initialize internals. - The agent radius now works in conjunction with the
MinAgentRadius
saved with each navmesh. Agents waypoints are setagent radius - MinAgentRadius
units away from borders. In other words the MinAgentRadius used to generate the Unity navmesh should be the radius of your smallest unit. Epsilon
can be reduced and is only used to reach the final target. Try setting it to 0.01.- Set the
PathQuality
toGood
. Tweaking this will change the heuristics of the funneling. The trade is between more calculation for a nicer final path. BreakingOnWaypoint
is off by default now.
Find information about all options here: Navigation - NavMeshAgent API
NavMesh Callbacks (Breaking Change)
Are deactivated by default now. Activate them by toggling on SimulationConfig.NavMeshAgent.EnabledCallbacks
.
Be mindful when using this in conjunction with the (navigation) multithreading optimization. Only access to the entity is allowed during the callbacks!!!
Map Baking (Breaking Change)
Bake all static colliders for all you maps again.
We changed the baking for 3D physics and by re-baking you are making sure everything is correctly initialized even if you don’t use 3D colliders at all.
Entity Prefab View Updater
If you have a modified version of EntityPrefabViewUpdater.cs or have derived from it you may want to consider starting over with the new version and remove your modifications. Read this section for more information about why this is a good idea: What’s New In 1.2.4 - New Entity Interpolator
EntityPrefabRoot.InterpolatePositionSpeed
and EntityPrefabRoot.InterpolateRotationSpeed
have been removed. If you use them in your code and want to save the variables on the assets add them back to the EntityPrefabRoot script when upgrading the Unity project.
C#
public unsafe class EntityPrefabRoot : MonoBehaviour {
public float InterpolatePositionSpeed;
public float InterpolateRotationSpeed;
// ..
}
Other
- Changed the signature of
QuantumGameCallbacks.InputConfirmedDelegate
and removed the parameter class to reduce allocation when recording input:
C#
QuantumGameCallbacks.InputConfirmedDelegate(QuantumGame game, Int32 frame, Int32 player, Byte[] rpc, Byte[] data, DeterministicInputFlags flags)
- The Quantum plugin in the cloud has been named “QuantumPlugin” and the name needs to be added to the Photon RoomOptions like in the example below:
charp
RoomOptions roomOptions = new RoomOptions();
// This line is new:
roomOptions.Plugins = new string[] { "QuantumPlugin" };
PhotonNetwork.CreateRoom(null, roomOptions, null);
Migrate from 1.2.2 to 1.2.3
DSL: Changed Primitive Type Array Generation (Breaking Change)
You define an array in a qtn files like this:
component Foo {
array<Int32>[26] Parameters;
}
// before: access via method
public Int32* Parameters(Int32 index)
// now: access via indices
public fixed Int32 Parameters[26];
Fix the places in your code that use that array in this manner:
C#
// old way
*entity->Foo.Parameters(i) = 0;
// new way
entity->Foo.Parameters[i] = 0;
If you can't convert the syntax due to an error like indexing movable fixed buffers' is not available in C# 7.0. Please use language version 7.3 or greater
.
Create a new partial struct of the generated component and add the following get
method to it:
C#
// replace `<NAME>` with the array field name and `<TYPE>` with the generic type of the array.
public unsafe partial struct Foo {
public <TYPE>* <NAME>Getter(Int32 index) {
if (index < 0 || index >= <NAME>Size) { Quantum.Core.ILHelper.ArrayOutOfRange(); }
fixed (<TYPE>* p = <NAME>) { return &p[index]; }
}
}
2.5D or 2D Verticality Feature Needs To Be Activated (Breaking Change)
If you are using 2.5D, e.g. height on physics objects, you need to enable SimulationConfig.UseVerticalTransform
.
DynamicScene.OverlapShape() Now Generates Contact Normals
The returned hit objects now has the DynamicHit.Normal
field correctly filled out.
This is active by default. To save computation time you can disable it by passing Core.DynamicScene.OverlapOptions.SkipNormal
.
C#
using (var hits = f.Scene.OverlapShape(position, rotation, shape), -1, Core.DynamicScene.OverlapOptions.SkipNormal)) { }
PUN Version 1.92
With this update we updated the files inside the Photon Unity Networking (PUN) folder of the Unity project. The files are included in the Unity package of the Quantum SDK upgrade. The PUN upgrade will include Unity 2018 compilation fixes, that likely have been added by you already if you are using the newest Unity version.
The PUN version Quantum uses now is nearly the same as Photon Unity Networking Classic from the AssetStore.
Migrate from 1.2.1 to 1.2.2
SimulationConfig Asset (Breaking Change)
Background Information
The SimulationConfigAsset alongside the DefaultPhysicsMaterial and DefaultNavMeshAgentConfig required a lot of boilerplate code to be passed into the QuantumGame and DB. Now all three of them are part of the DB.
This simplifies 1) passing it from Unity resources into the simulation, 2) fixes bugs with broken physics materials after reloading the scene, 3) it is processed automatically for replays no more need to save them independently and 4) the replay file is not needed anymore to run the simulation in a custom server plugin.
We should have refactored this before adding the replay, though, but it should be easy to migrate to:
Migration
- The data of the current DefaultPhysicsMaterial and DefaultNavMeshAgent on your SimulationConfig will be lost. Make a screenshot of them before the migration (don't forget to foldout the details about the NavMeshAgent and PhysicsMaterial).
- Use the UnityEditor to move your SimulationConfig.asset from
Assets/Quantum/Resources
to the DB folderAssets/Resources/DB/Configs
. The /Configs part is optional, it's just important that the file now resides inside the DB folders. - Create two new assets via the Unity Asset Factory (right-click): Create a Quantum/Assets/Physics/PhysicsMaterial and a Quantum/Assets/Physics/NavMeshAgentConfig and name them accordingly (e.g. DefaultPhysicsMaterial, DefaultNavMeshAgentConfig).

- Set the PhysicsMaterial guid to $DEFAULT_PHYSICS_MATERIAL

- Set the NavMeshAgentConfig guid to $DEFAULT_NAVMESH_AGENT
- Fill in the data that you saved in step 1.
- Drag and drop the PhysicsMaterial asset and NavMeshAgentConfig asset onto the appropriate slots in your SimulationConfig.

- Make sure the SimulationConfig asset has a guid.
UnityDB.Init()
andQuantumRunner.StartParameter
will not require the SimulationConfig as an argument anymore.
The RuntimeConfig now has a drag and drop field SimulationConfig asset. This way you can have different SimulationConfigs for different game sessions. If you don't fill out the field, the first one found in the DB will be used.

Access your simulation config inside Quantum simulation code like this:
C#
public unsafe class MySystem : SystemBase {
public override void OnInit(Frame f) {
f.RuntimeConfig.SimulationConfig.Instance...
}
}
QuantumGame Refactoring (Breaking Change)
Migration
- Breaking Change: moved the QuantumGame class to quantum.systems. Read What's New In 1.2.2 for more details and follow the
upgrade/upgrade_instructions.txt
file from the SDK and add and delete files. - Breaking Change: removed obsolete properties from
QuantumGame
:- For example QuantumGame.Frame, QuantumGame.Running, QuantumGame.IsLocalPlayer, QuantumGame.RuntimeConfig, ..
- Breaking Change: if you are using the QUANTUM_FLIP_EVENT_ORDER define you need to copy this define to your quantum.systems.csproj.
- QuantumGame.Instance is marked obsolete, but will still work for now.
- Use the QuantumGame/IDeterministicGame arguments in callbacks and events instead of the singleton.
- Register to
OnGameStart
instead of checkingQuantumGame.Instance != null
inStart()
orUpdate()
methods. Caveat: This works well on Unity scripts but only if the game object is not destroyed by a scene reload (AutoLoadSceneFromMap) afterOnGameStart
has been called. - Or replace
QuantumGame.Instance
withQuantumRunner.Default.Game
. - Caveat: Do not change/replace the
QuantumGame.Instance = _liveGame;
line inside QuantumInstantReplay.cs. It's required to keep backwards compatibility.
- QuantumRunner.Current is also marked as obsolete.
- If you used it to get the Session you can also get the Session from the QuantumGame.
- Or replace QuantumRunner.Current with QuantumRunner.Default.
- The
UILeaveGame.OnLeaveRoutine()
was changed. If you are reusing this in you game make sure your leave code is also up to date.QuantumRunner.ShutdownAll()
returns true if at least one active runner has been closed. It also takes care of closing simultaneous sessions like the instant replay.- We do not destroy the MapAsset automatically anymore. It should remove itself during a scene unload/reload or tend to it yourself.
QuantumRunner.Shutdown
willDestroy()
its game object, which will triggerOnDisable()
, which will cause the session to be destroyed which will then trigger theOnGameDestroyed()
callbacks. Before the runner would only destroy the session during the next Unity frame duringOnDestroy()
all scripts run one more time with a game that is shutting down.
C#
IEnumerator OnLeaveRoutine() {
if (QuantumRunner.ShutdownAll()) {
// leave room
PhotonNetwork.LeaveRoom();
// wait one second (or wait for LeaveRoom)
var startWait = Time.realtimeSinceStartup;
while (PhotonNetwork.room != null && (Time.realtimeSinceStartup - startWait) < 1f) {
yield return null;
}
// ..
}
SimulationConfig.Gravity
The Gravity parameter is now a 3D vector. For 2D games the XY values will be used for the gravity information. No changes required, except when you use the gravity in your custom code and access it as a vector and not the x,y values directly. For example like this:
C#
// old
_settings->Gravity.SqrMagnitude
// new
_settings->Gravity.XY.SqrMagnitude
We are marking FPVector3.AsFPVector2 and FPVector2.AsFPVector3 obsolete, because it created confusion about the conversion convention we chose. You should now use explicit properties to convert from FPVector2
to FPVector3
and back.
C#
struct FPVector3 {
FPVector2 XY;
FPVector2 XZ;
FPVector2 YZ;
}
struct FPVector2 {
FPVector3 XOY;
FPVector3 XYO;
FPVector3 OXY;
}
QuantumInputRecorderStarter.cs
The logic on QuantumInputRecorderStarter has been moved to the QuantumGame class. Setup RuntimeConfig.RecordingFlags
to start the recording and use the QuantumGame API for all further recording functionality.
Console.Runner Project
We decided to move quantum.console.runner to the quantum_code solution. We want to have this close by and be able to easily "F5" and add breakpoints instantly when debugging with a replay. The upgrade file upgrade/quantum_code.zip
will include the complete project. Just add the csproj
to your quantum_code solution. The runner has a few modifications to old one in your quantum_custom solution (run in loop, checksums).
The runner needs to locate the math LUT files the serialized DB file, a replay file and optionally a checksum file. Out of convenience they point to a folder in the Unity project (quantum_unity/Assets/Resources/replay
). For more information see the upgrade/replay_notes.md or the Quantum online documentation: Replay Notes
Migrate from 1.2.0 to 1.2.1
Navigation Mesh
Re-bake all of the Quantum NavMeshes.
They are now saved, like other Quantum assets, inside a ScriptableObject
. Although most of the navmash data will still be saved in a binary files alongside the navmesh asset it makes the handling much easier. We still need the additional binary file because of the ScriptableObject
size limitation.
Assets/Resources/DB/MapName_NavMeshName.asset (new file)
Assets/Resources/DB/MapName_NavMeshName.bytes (similar to the file before, but some data is now saved on the asset file)
We also changed the way how the supplementary file is loaded: You will need to initialize the static FileLoader
class to make the internal Quantum code know how and where to load the file from. This is already done in QuantumRunner.cs
. Only if you customized the game simulation start routine you need to call this manually.
Quantum.FileLoader.Init(new Quantum.UnityFileLoader());
Additionally we changed the internal navmesh structure to use 3D data in order to prepare three dimensional navigation maps (f.e. bridges).
DSL: Generated Entity Fields
We need to encapsulate all fields under the fields tag in the .qtn
files into a separate nested struct, because the sequential struct packing we rely on can be reordered when running the simulation outside of Unity using the .NET framework (e.g. for replays) under certain prerequisites (a nested struct uses an explicit layout for example).
The access to the fields in the new nested struct is now guarded by getter and setter on the entity itself and by this is backwards compatible except when you are doing one of the following:
C#
// Creating a pointer to the entity field
var isDead = &mage->IsDead; // Compilation error
var isDead = &mage->Fields.IsDead; // New way
// Passing an entity field as reference
foo.bar(ref mage->IsDead) // Compilation error
foo.bar(ref mage->Fields.IsDead) // New way
DSL: Frame Serialization
The frame object can now be serialized into binary form which is required for example to send snapshots on snapshot-based reconnects or to generate platform independent checksums. The DSL automatically creates serialization code, but all custom structs imported and used inside your .qtn
files need to have a static Serialize(StructType* ptr, BitStream stream)
method on them.
C#
[StructLayout(LayoutKind.Sequential, Pack = CodeGenConstants.STRUCT_PACK)]
public unsafe struct Foo {
public Int32 Bar;
public static void Serialize(Foo* ptr, BitStream stream) {
stream.Serialize(ref ptr->Bar);
}
}
Newtonsoft.Json
To enable replay saving and playback we are using JSON serialization by adding the default package from the Asset Store called JSON .NET For Unity to the plugins folder.
quantum_unity/Assets/Plugins/JsonDotNet (new folder)
There is already a version of this dll inside the PUN folders which has to be removed manually:
quantum_unity/Assets/Photon Unity Networking/Editor/PhotonNetwork/Newtonsoft.Json.dll (delete this file)
If you already have your own JSON .NET plugin laying in your project remove either ours or yours.
FYI: the console runner project quantum_custom.sln
will work with a Newtonsoft.Json dependency via nuget.
Physic Layers And Matrix
The physics layers are now saved on the simulation config asset and you need to manually import them:

In the previous version they were imported on application start, which is bad for performance and does not run anywhere else then on Unity.
QuantumRunner.StartGame()
In order to make the replay run with custom configurations we needed to change the signature of StartGame()
again. It's combining most of the parameters in ths struct: QuantumRunner.StartParameters
.
We keep the old method around where you can see how to setup the struct to behave as before.
MapDataBakerCallback.OnBeforeBake
Another callback method was entered that is run before the baking process starts in order to be able to chain for example navmesh generation into the workflow.
App Id
On some occasions app ids are hard linked to a specific plugin version. In case you have trouble starting the online game (e.g. scene is not loading): try to create a new Quantum app id by following the instructions below.
Migrate from 1.1.8 to 1.2.0
These instructions are intended to help porting an existing quantum project that uses SDK 1.1.8 to the new 1.2.0 SDK. Please make sure the steps found in upgrade_instructions.txt were already completed.
Create A New App Id
The app id used to connect to the Photon Cloud needs to be upgraded to be compatible 1.2.0.
Alternatively you can create a new one that is automatically compatible yourself:
- Go to your dashboard
- Create a new AppId
- Click the manage button
- Click Create a new plugin
- Click save
Unity Code Generation
We moved the code generation of Quantum assets to Unity ScriptableObjects out of the UnityEditor to make it run-able when there are script compilation errors. It now is an executable residing in the tools folder tools/codegen_unity/quantum.codegen.unity.host.exe
which is called by a post-build event in the quantum.system project. To migrate you need to add the post-build event manually:
"$(SolutionDir)..\tools\codegen_unity\quantum.codegen.unity.host.exe" "$(TargetDir)\quantum.state.dll" "$(SolutionDir)..\quantum_unity\Assets"
- If you have a custom name or path to the Unity project change the last part of the above line accordingly.
- Caveat: All script files (*.cs) in the Generated folders will be deleted during this process. If you have custom files in that folders, move them out.
- If you are working on a Mac don't forget to add the mono command.
Finally delete the following file from your project repository:
quantum_unity\Assets\Quantum\Editor\GenerateAssetScripts.cs
Code Changes (RuntimePlayers)
The only breaking change in the Quantum API relates to the way the developer adds custom properties to the RuntimeConfig and RuntimePlayer classes, and how this data is injected into the deterministic session (and received on the game clients).
Prior to 1.2.0, any custom data had to be know prior to match start, so this led to all client machines having to locally inject the same data for both RuntimeConfig and every instance of RuntimePlayer (this made late joins particularly cumbersome to design and implement).
We'll first review how the old code worked, and then explain the new approach with some code snippets.
Old Way: SDK up to 1.1.8
Sample code for initializing RuntimeConfig and RuntimePlayer data (lines 66-77 extracted from UIRoom.cs
found in quantum.unity/Assets/Quantum/Lobby
):
C#
RuntimeConfig config;
config = new RuntimeConfig();
config.Players = new RuntimePlayer[PhotonNetwork.room.MaxPlayers];
for (Int32 i = 0; i < config.Players.Length; ++i) {
config.Players[i] = new RuntimePlayer();
}
config.Map.Guid = UnityDB.AllOf<MapAsset>().First(x => x.Settings.Scene == map).Settings.Guid;
config.GameMode = Photon.Deterministic.DeterministicGameMode.Multiplayer;
QuantumRunner.StartGame(config);
The code above would have to run on all game clients, and if the game required custom data per RuntimePlayer instance (player character choice, for example), this had to be distributed via photon player properties, etc.
And this is how you would normally go through all RuntimePlayers instance on a OnInit callback in a System class included into quantum.systems:
C#
public override void OnInit(Frame f)
{
for (int i = 0; i < f.PlayerCount; i++)
{
var playerData = f.RuntimeConfig.Players[i];
// use player data only here when the game starts
}
}
New Way: SDK 1.2.0 and above
RuntimeConfig
Now setting RuntimeConfig is injected similarly (although the actual values used will be the ones from the first game client to connect, or the overhauled values from server), but RuntimePlayer data is NOT required at this point.
Sample code for initilizing RuntimeConfig (lines 66-70 extracted from UIRoom.cs found in quantum.unity/Assets/Quantum/Lobby)
C#
RuntimeConfig config;
config = new RuntimeConfig();
config.Map.Guid = UnityDB.AllOf<MapAsset>().First(x => x.Settings.Scene == map).Settings.Guid;
QuantumRunner.StartGame(Guid.NewGuid().ToString(), PhotonNetwork.room.MaxPlayers, Photon.Deterministic.DeterministicGameMode.Multiplayer, config);
Notice that now there's no need to inject RuntimePlayer instances anymore (not even for the local player). We also changed the signature of QuantumRunner.StartGame().
RuntimePlayer
Setting RuntimePlayer data is now an asynchronous process. Each game client is responsible for injecting a RuntimePlayer instance for its local player(s) into Quantum, which will take care of distributing the serialized data deterministically.
Instantiating and setting RuntimePlayer into Quantum (which will reliably send it to the server to be distributed) can be done either from the OnGameStart callback (creating a menu script that extends QuantumCallbacks) or from any UI code after the session/QuantumGame has been started:
C#
foreach (var lp in QuantumGame.Instance.GetLocalPlayers())
{
QuantumGame.Instance.SetPlayerData(lp, new Quantum.RuntimePlayer { });
}
Notice we are adding support for multiple players per client. This will be exposed in the next iterations of the SDK.
All game clients will now receive the RuntimePlayer instance from the server exactly on the same tick in a new signal (ISignalOnPlayerDataSet):
C#
public unsafe class SampleSystem : SystemBase, ISignalOnPlayerDataSet
{
public void OnPlayerDataSet(Frame f, PlayerRef player)
{
RuntimePlayer playerData = f.GetPlayerData(player);
}
public override void Update(Frame f)
{
}
}
Any code that would create entities or initialize any game state code for the players need to be ported from OnInit callbacks to this signal.
This should make late joins much easier, as a newly connected player can send this data at any moment during gameplay (not only at start anymore). It is also possible for a player to send custom RuntimePlayer instances many times, which can be used in creative ways by the developers.
Serializing custom data (both RuntimeConfig and RuntimePlayer)
Because Quantum is now responsible for distributing any custom data added to either RuntimeConfig or RuntimePlayer classes, the developer is now responsible for answering a new serialization callback added to both classes (using a sample RuntimePlayer as an example):
C#
partial class RuntimePlayer {
public string myCustomData;
partial void SerializeUserData(BitStream stream)
{
stream.Serialize(ref myCustomData);
}
}
Following these steps, it should be safe and straightforward to port an existing game from Quantum SDKs 1.1.x to 1.2.0. All other API changes are new additions, optimizations or new features that should not require changing existing code.
Back to top