This document is about: QUANTUM 1
SWITCH TO

Frequently Asked Questions

Most of the FAQ content is derived from questions asked by developers in the #quantum-sdk Discord chat. Also use the search functionality there to find more help.

Quantum Unity Framework

Why is the Game scene not loading when starting the game via the demo menu?

When the AppId is rejected by the server there is no proper error message, yet. Make sure you have created your AppId correctly:

Find detailed instructions how to create a Quantum App here: Create A Photon Quantum App

  1. Create a Quantum App Id on your Photon dashboard
  2. Click CREATE A NEW APP
  3. Set Photon Type to Photon Quantum
  4. Fill out the name field and press CREATE
  5. Find the new Quantum app and click MANAGE
  6. Scroll down and press CREATE A NEW PLUGIN
  7. Press SAVE

Why is there a 5 second delay before the Quantum game starts?

Check your Deterministic Config and set the Room Wait Time (seconds) to something less.

The Room Wait Time is used to cope with fluctuating ping times.

It will always be used in full to help you sync all client sessions to start: if you set it to 1 second it will always wait 1 second.

This should not be used to synchronize scene loading times: if you want to coordinate this, load the scene before starting the Quantum session and coordinate with Photon directly. You can safely set the value to 0 then.

Why is the connection timing out while trying to connect to ns.exitgames.com?

You can increase the connection log level Network Logging in your PhotonServerSettings to get more details.

Often the timeout is due to your UDP traffic being blocked.

See if you can open the ports that Photon uses: Photon TCP and UDP Port Numbers

Please approach us if can't open the ports in you company network.

Why are player entities not instantiated when running the game in multiplayer mode while it works in local mode?

Most likely QuantumGame.SendPlayerData() was not executed for each player.

If you are using the demo menus to start the game add the script CustomCallbacks.cs anywhere to the menu scene.

After starting, joining the Quantum Game and after the OnGameStart signal has been fired each player needs to call the SendPlayerData method to be added as a player in every ones simulation. The reason this needs to be called explicitly is that it greatly simplifies late-joining players.

Why does getting a Quantum entity have to be so verbose?

QuantumRunner.Default.Game.Frames.Current.GetEntity(entityRef)

Potentially you can run multiple simulation at the same time (for example for a kill cam feature). To be flexible enough and not have developers not have to re-organize their codebase when integrating a feature like that we chose to keep this way as the default way to access the game and its frames.

Here are a few suggestions how to cope with it:

  • QuantumCallbacks.cs passes the Quantum game object it is called from as an argument, use that one instead of the global one.
  • Quantum events created with the DSL (EventBase) also pass an IDeterministicGame object. Sadly, most of the time you need to cast it to QuantumGame.
  • Create an extension method for each of your concrete EntityRefs that grabs the pointer using the default game and current frame:

C#

namespace Quantum {
    public unsafe static class EntityExtensions {
        public static Ball* GetPtr(this EntityRefBall entityRef) {
            return QuantumRunner.Default.Game.Frames.Current.GetBall(entityRef);
        }
    }
}
  • Create your own global shortcuts:

C#

namespace Quantum {
    public static class MyGame {
        public static Frame CurrentFrame {
            get {
                return QuantumRunner.Default.Game.Frames.Current;
            }
        }
    }
}

Why am I receiving unhandled event warnings from the NetworkPeer class?

Event 102 and 103.

These are clock synchronization and input messages received before Session.Join() is called. The client will receive the relevant ones again after joining the simulation.

Essentially, you're already in the room, so the running plugin session sends messages around even before you start your local session.

Why is the Quantum event order wrong?

Quantum emits events in Unity in reversed order unless you set QUANTUM_FLIP_EVENT_ORDER scripting define symbol in the quantum_system project.

Also see here: Community Wiki/Quantum-Gotchas

Can I move my Quantum dll source code inside the Unity asset folder?

We recommend and guarantee the support for the workflow with the separated dlls but it can be achieved to speed up your development process.

Your final game app should always be build with the release dlls! Otherwise you are missing vital IL performance patches.

  • Requires Assembly Definitions feature from Unity 2017.3
  • All code files from the quantum state project need to be bundled under one assembly definitions called quantum.state.
  • The assembly definitions must be set to allow unsafe code.
  • If you have an extra asm def for the quantum system project link the quantum state asm def to it.
  • Set Scripting Runtime Version in our Unity project to .NET 4.x Equivalent
  • You need a workaround to run the code generation for the Quantum ECS and the code generation of the Unity asset scripts (see pre-build event of quantum.state and post-build event of quantum.systems) somewhere in your pipeline.

Call UnityDB.Init() before following the AssetLinks.

It can safely be called multiple times and also when the game is not running.

When using the Instance getter on the link it tries to retrieve the Quantum asset from the Quantum DB using the GUID stored on the link.

Is there an effective way to simulate network latency when running the game in Unity Editor?

Attach the quantum_unity/Assets/Photon Unity Networking/Plugins/PhotonNetwork/PhotonLagSimulationGui.cs script to a game object while being connected.

Toggle simulate to activate lag, jitter and loss while playing the game.

Why is the game simulation running faster after pausing or debugging a breakpoint?

By default the time is measured internally and does not compensate for halting the simulation. When DeltaTimeType on the SimulationConfig is changed to EngineDeltaTime the gameplay will resume in regular speed after a pause. Caveat: Changing it will make every client use the setting which might be undesirable when only used for debugging. Although some games with very tight camera controls (e.g. flight simulation) will profit from setting it to EngineDeltaTime.

C#

public enum SimulationUpdateTime {
    Default = 0,                        // internal clock
    EngineDeltaTime = 1,                // Time.deltaTime (Unity)
    EngineUnscaledDeltaTime = 2         // Time.unscaledDeltaTime
}

Why is Json serialization breaking when using IL2CPP?

Unused code is stripped automatically. Read here https://docs.unity3d.com/Manual/IL2CPP-BytecodeStripping.html.

Inside the PUN folder there already is a link.xml. You can add you own link.xml to the project and enable your custom libraries this way.

Does Quantum work with HTML5 exports?

No. Memcopy is not fast enough in WebGL.

Do we have plans to support that platform?

Yes, we are constantly checking for ways to overcome that issue.

How do I get the local player?

QuantumGame.GetLocalPlayers() returns an array that is unique for every client and represents the indexes for players that your local machine controls in the Quantum simulation.

  • Usually it returns one index but if a local machine controls more than one player the array will have the length of the local player count.
  • These are exactly the same indexes that are passed into QuantumInput.Instance.PollInput(int player).
  • The indexes are defined by the server (unless it is a local game).
  • The indexes are always within [0, PlayerCount-1]. PlayerCount represents the total player count in the match. It is passed into QuantumGame.StartGame() and then set on DeterministicConfig.
  • The index values are arbitrary (inside their range of course) and depend on the order of multiple players connecting and disconnecting and when their messages reach the server.
  • If a local machine has more than one player the values are not necessarily consecutive.
  • When rejoining the game you can be assigned the same player index as long as you call Session.Join() with the same GUID and the room has not been filled with new players already.

Use the local player index from the function above to send the runtime player data: QuantumGame.SendPlayerData(int player, RuntimePlayer data). Do this for every player on one machine.

Session.IsLocalPlayer(int player) can help to decide if an entity (that has a player_ref member) is associated with the local player.

Use Frame.PlayerToActorId(PlayerRef player) and Frame.ActorIdToAllPlayers(Int32 actorId) to find corresponding Photon Player ids to show player names via PhotonPlayer.Nickname for example.

The PlayerRef, in contrast to the player index, is 1-based. The reason is that default(PlayerRef) will return a "null/invalid" player ref struct for convenience. There are automatic cast operators that can cast an int into a PlayerRef.

  • default(PlayerRef), internally a 0, means NOBODY
  • PlayerRef, internally 1, is the same as player index 0
  • PlayerRef, internally 2, is the same as player index 1

How to fix exceptions when deserializing the DB from JSON when build with IL2CPP?

Json.Net creates a temporary List collection when deserializing serialized arrays and unused generics are stripped out of the IL2CPP build causing this exception:

ExecutionEngineException: Attempting to call method 'System.Collections.Generic.List`1[[Photon.Deterministic.DeterministicTickInputSet, PhotonDeterministic, Version=1.2.4.0, Culture=neutral, PublicKeyToken=null]]::.cctor' for which no ahead of time (AOT) code was generated.
  (...)
  at Newtonsoft.Json.Serialization.JsonArrayContract.CreateTemporaryCollection()

Unitys own link.xml and PreserveAttribute do not help rather the only solution is to explicitly list them inside a Unity script in the following manner:

C#

using System.Collections.Generic;

public class QuantumAOT {
  public List<Photon.Deterministic.DeterministicTickInputSet> DeterministicTickInputSet;
  public List<Photon.Deterministic.FP> FP;
  public List<Photon.Deterministic.FPVector2> FPVector2;
  public List<Photon.Deterministic.FPVector3> FPVector3;
  public List<Quantum.AssetLink> AssetLink;
  public List<Quantum.BuffDataLink> BuffDataLink;
  public List<Quantum.Core.CCWTri> CCWTri;
  public List<Quantum.FPAnimationCurve.Keyframe> Keyframe;
  public List<Quantum.MapStaticCollider> MapStaticCollider;
  public List<Quantum.MapStaticCollider3D> MapStaticCollider3D;
  public List<Quantum.NavMeshLink> NavMeshLink;
  public List<System.Byte> Byte;
  public List<System.Int32> Int32;
}

Quantum 2.0 will automatically generate this file for you, but in older Quantum versions you need to handcraft it: All value types used inside the DB (see below: AssetObjects, nested and fields) that are saved in an array, need to go into the file above.

C#

namespace Quantum {
  public struct Foo1 {
    public int Bar;
  }

  public class Foo2 {
    public int Bar;
  }

  partial class SomeAssetData {
    // Needs List<Foo1> to be known
    public Foo1[] Foo1;
    // Does not need special attention for Quantum apart from Unity having problems serializing nestes value and reference types under IL2CPP :)
    public Foo2[] Foo2;
  }
}

Why are Navmesh islands being generated inside my geometry?

This will at some point be fixed by Unity (see forum post: forum.unity.com/threads/nav-generating-inside-non-walkable-objects)

The workaround with NavmeshModifierVolume will mitigate this (requires NavMeshComponents).

Triangle culling during the import step is another alternative which we could provide in the future.

Developing A Simulation In Quantum

Why is the simulation starting with fame 60?

This is the number of rollback-able frames that are allocated at the start of the simulation. The number will match the value set up in DeterministicConfig->Rollback Window.

Why is Update() called multiple times with the same frame number?

Update() on systems are called multiple times for the same frame number in case of a rollback. This happens when the prediction of a remote players input was detected to be incorrect and the simulation has to re-run a frame with the correct input data to get back into a deterministic state.

Currently this also happens when running in local mode due to an internal architectural decision, this will be resolved in a future version of Quantum.

How do I debug pointers in Visual Studio? I can only see the address.

  • Requires Unity 2018 and Visual Studio 2017
  • Set Scripting Runtime Version in our Unity project to .NET 4.x Equivalent
  • Make sure you build your Quantum dlls as Debug dlls.
  • Attach Visual Studio to Unity via the VS menu Debug->Attach Unity Debugger and select the correct Unity Editor instance
  • Close all instances of VS and Unity Editor, launch VS and rebuild the Quantum dlls, then start Unity and let it reload, then attach the Debugger

Why are there allocations when iterating over DynamicHits or component buffers?

For every Quantum component you create in the Quantum DSL (code generation) there is a getter in the Frame class that looks like this: public Buffer<CollisionsFilter> GetAllCollisions(Boolean excludeCulled = false).

The Buffer object is cached, reused and only created and grown when needed. For the pooling to work Buffer.Disposed() needs to be called when the list is not used anymore. This is automatically done with the using syntax:

C#

using (var collisionComponents = f.GetAllCollisions()) {
    for (Int32 x = 0; x < collisionComponents.Count; x++) {
        // ...
    }
}

The same is true for the DynamicHits objects you get from collision checks:

C#

using (var hits = f.Scene.OverlapCircle(i->Data.ClickToMove.Target, 1)) {
    foreach (var hit in hits)  {
        // ...
    }
}

What's the difference between FP.MaxValue and FP.UseableMax?

The fixed point math only uses 16+16 bits of its 64-bit value. This makes part of the math faster because we don't have to check for overflows. That said: FP.MinValue and FP.MaxValue are using all 64 bits and should never be used for calculations. Use FP.UseableMax and FP.UseableMin instead (to initialize a distance variable with the min FP value for example).

Btw: the FP can represent values from -32,768 to 32,768 (-2¹⁵ to 2¹⁵).

Why don't I receive any OnCollisionStatic callbacks?

For performance reasons the activation is opt-in: Check your SimulationConfig and toggle Physics->Raise Collision Event For Statics.

How does Quantum decide which Id a player gets?

The Quantum player index (Quantum Player Id, PlayerRef) is based on the order in which the Session.Join messages arrive on server. In other words arbitrary.

The Photon Id (Photon Player Id, Actor Id) is also sequence based but on the Photon room join order.

It is not possible to set the "Desired Quantum Id" on a Photon Player, yet.

If the client reconnects, although he will get a new Photon ID, we can guarantee he gets the same Quantum index when connecting with the same ClientId: public static QuantumRunner StartGame(String clientId, Int32 playerCount, StartParameters param).

Send specific player relevant settings with QuantumGame.SendPlayerData(RuntimePlayer features).

How can I add more players to the simulation?

The max player count is essential to know in advance, because it defines how much space needs to be allocated inside the memory chunk for each frame.

Add or configure the following lines in any one of your qtn-files and set your desired count:

#define PLAYER_COUNT 8
#pragma max_players PLAYER_COUNT
  • The define acts like a define you can use inside the DSL (for example for allocating arrays with the player count).
  • The pragma actually defines how many player the simulation can handle.

Why does the pointer to a new struct point to stale data?

Inside the loop the pointer to the struct gets the same stack pointer and will contain stale data if not new or default are used.

C#

struct Bar {
    public bool Foo;
}

static unsafe void Main(string[] args) {
    for (int i = 0; i < 2; i++) {

        Bar bar;
        //Bar bar = default(Bar); // <---- Fixes the stale data
       
        Bar* barPt = &bar;
        if (barPt->Foo)
            Console.WriteLine("Wtf");
  
        barPt->Foo = true;
    }

    Console.ReadKey();
}

Why is my simulation desync-ing?

When DeterministicConfig.ChecksumInterval is > 0 a checksum of a verified frame is computed, send to the server and compared with checksums that other clients have send.

Check out the Desync Check Code in our Community Wiki to find out what values are causing the desync.

Most common causes are:

Writing to Quantum data assets

C#

c->CharacterSpec = DB.FindAsset<CharacterSpec>("WhiteFacedBarghast");
c->CharacterSpec.RemainigLifetime = 21;

Never write anything to the Quantum assets. They contain read-only data.

Writing to Quantum from Unity thread

All scripts in Unity have read-only access to everything that is exposed through the Quantum Frame. Only influence the simulation by Input and/or Commands.

Caching data

C#

public class CleaningSystem : SystemBase {
    public Boolean hasShoweredToday;    // <----- Error
    public override void Update(Frame f) {
        if (!hasShoweredToday && f.Global->ElapsedTime > 100) {
            Shower();
            hasShoweredToday = true;
        }
    }
}

The code above will desync eventually when the simulation is rolled-back. Instead save non-transient data on the Frame or on Entity Components.

Not copying custom Frame UserData

C#

unsafe partial class Frame {
    public Boolean HasShoweredToday;
    partial void CopyFromUser(Frame frame) {
        // <----- Error
    }
}

Read the Example of How to Use Frame.User.cs.

Floating point math

Refrain from using floats inside the simulation and exclusively use FP math.

Also handle FP.FromFloat_UNSAFE() with care. Used "offline" for balancing asset generation on one machine is fine. But be aware that this can return different results on different platforms. If you need to import floats during run-time and can't use integers or FPs (e.g. downloading balancing data): convert from String to FP.

Cross Platform Checksums

When running the game on two different platforms (PC vs iOS) checksums only match if PhotonDeterministic.ChecksumCrossplatformDeterminism is enabled.

Data Created During Asset.Loaded()

Assets.Loaded() is called once per asset during loading and it's totally fine to store calculate and store new data inside the asset members at this time (note that if you are running the simulation on the server all games will share this one asset).

If your assets are loaded from Resources and you are either restarting Unity Editor or resetting the UnityDB during runtime be aware that Unity does not unload the Unity asset.

C#

public partial class FooAsset : AssetBase {
  public Foo Settings;
  public int RawData;
  
  [NonSerialized]
  public List<int> Bar = new List<int>();

  public override AssetObject AssetObject => Settings;

  public override void Loaded() {
    // This will break on the second run (see above) because Bar needs to be reset by either Bar.Clear() or Bar = new List<int>()
    Bar.Add(RawData);
  }
}

Can I reuse the Photon Room for new Quantum sessions with the same clients?

A) No, the recommended and clean solution is switching the room

  • Share the id of the new room between the clients using Photon directly (e.g. room properties).
  • Stop the quantum session. LeaveRoom() but don't disconnect.
  • Use JoinOrCreate() to connect to the new room on the clients to avoid race-conditions when every client enters at the same time.

PUN Matchmaking Guide

B) Yes, soft-restart your game

  • Keep the Quantum session running, though your game round has ended.
  • (optional) You can determinstically disable Quantum systems in the mean-time.
  • Add code to your gameplay systems (e.g. game state machines) that handle the re-starting of a game round.

Caveat (for solution B): Late joiners will get input from all of the rounds if you don't run the simulation on the server (custom Quantum plugin snapshot reconnect) or lighten the (re)connects by buddy snapshots (valid state of the game send by other clients).

Why are there no Collision.OnEnter, OnStay and OnExit callbacks?

We reduce the amount of data we save between the frames for performance reasons.

We have a small addon that enables these features, though. Please approach us directly for the ComplexCollisions snippets.

Back to top