### Quantum Engine Reconnection GUI Example
Source: https://doc.photonengine.com/quantum/current/manual/game-session/reconnecting
This C# script demonstrates how to connect to a Quantum cloud, start a game session, and handle disconnections and reconnections. Ensure RuntimeConfig, RuntimePlayers, and QuantumStats are set up correctly. Press 'Connect' to start, 'Disconnect' to stop, and 'Reconnect' to resume an ongoing session.
```csharp
namespace Quantum.Demo {
using System;
using System.Collections.Generic;
using Photon.Deterministic;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.SceneManagement;
///
/// A Unity script that demonstrates how to connect to a Quantum cloud and start a Quantum game session.
///
public class QuantumSimpleReconnectionGUI : QuantumMonoBehaviour {
///
/// The RuntimeConfig to use for the Quantum game session. The RuntimeConfig describes custom game properties.
///
public RuntimeConfig RuntimeConfig;
///
/// The RuntimePlayers to add to the Quantum game session. The RuntimePlayers describe individual custom player properties.
///
public List RuntimePlayers;
///
/// Room keep alive time.
///
public int EmptyRoomTtlInSeconds = 20;
RealtimeClient _client;
string _loadedScene;
QuantumReconnectInformation _reconnectInformation;
int _disconnectedTick;
byte[] _disconnectedFrame;
bool CanReconnect => _reconnectInformation != null && _reconnectInformation.HasTimedOut == false;
async void OnGUI() {
if (_client != null && _client.IsConnectedAndReady) {
if (GUI.Button(new Rect(10, 60, 160, 40), "Disconnect")) {
await Stop();
}
} else {
if (GUI.Button(new Rect(10, 60, 160, 40), CanReconnect ? "Reconnect" : "Connect")) {
await Run();
}
}
}
async System.Threading.Tasks.Task Run() {
var connectionArguments = new MatchmakingArguments {
PhotonSettings = PhotonServerSettings.Global.AppSettings,
PluginName = "QuantumPlugin",
MaxPlayers = Quantum.Input.MAX_COUNT,
// Keep the client connection object, it has cached authentication information
NetworkClient = _client,
// Keep an empty room open for a time
EmptyRoomTtlInSeconds = EmptyRoomTtlInSeconds,
// Set the stored reconnection information
ReconnectInformation = _reconnectInformation,
// Don't let random matchmaking get into this room
IsRoomVisible = false
};
if (CanReconnect) {
// Switch to reconnecting mode
_client = await MatchmakingExtensions.ReconnectToRoomAsync(connectionArguments);
} else {
_client = await MatchmakingExtensions.ConnectToRoomAsync(connectionArguments);
// Remove the disconnect information, it would break a new room
_disconnectedTick = 0;
_disconnectedFrame = null;
}
// Load the map if AutoLoadSceneFromMap is not set
if (QuantumUnityDB.TryGetGlobalAsset(RuntimeConfig.SimulationConfig, out Quantum.SimulationConfig simulationConfigAsset)
&& simulationConfigAsset.AutoLoadSceneFromMap == SimulationConfig.AutoLoadSceneFromMapMode.Disabled) {
if (QuantumUnityDB.TryGetGlobalAsset(RuntimeConfig.Map, out Quantum.Map map) == false) {
throw new Exception("Map not found");
}
using (new ConnectionServiceScope(_client)) {
await SceneManager.LoadSceneAsync(map.Scene, LoadSceneMode.Additive);
SceneManager.SetActiveScene(SceneManager.GetSceneByName(map.Scene));
_loadedScene = map.Scene;
}
}
var sessionRunnerArguments = new SessionRunner.Arguments {
RunnerFactory = QuantumRunnerUnityFactory.DefaultFactory,
GameParameters = QuantumRunnerUnityFactory.CreateGameParameters,
ClientId = _client.UserId,
RuntimeConfig = new QuantumUnityJsonSerializer().CloneConfig(RuntimeConfig),
SessionConfig = QuantumDeterministicSessionConfigAsset.DefaultConfig,
GameMode = DeterministicGameMode.Multiplayer,
PlayerCount = Quantum.Input.MAX_COUNT,
Communicator = new QuantumNetworkCommunicator(_client),
// Set the initial tick
InitialTick = _disconnectedTick,
// Set the serialized frame
FrameData = _disconnectedFrame
};
// Add a player to the game
var runner = (QuantumRunner)await SessionRunner.StartAsync(sessionRunnerArguments);
for (int i = 0; i < RuntimePlayers.Count; i++) {
runner.Game.AddPlayer(i, RuntimePlayers[i]);
}
}
async System.Threading.Tasks.Task Stop() {
// Save the serialized frame
_disconnectedTick = QuantumRunner.DefaultGame.Frames.Verified.Number;
```
--------------------------------
### Adding Static Assets at Runtime with Deterministic GUID
Source: https://doc.photonengine.com/quantum/current/manual/assets/assets-simulation
Provides an example of how to create a new asset object at runtime, assign it a deterministic GUID using QuantumUnityDB.CreateRuntimeDeterministicGuid, and add it to the asset database before the simulation starts.
```csharp
// create any asset
var assetObject = AssetObject.Create();
// set its name
assetObject.name = "My Unique Asset Object Name";
// get a deterministic GUID
var guid = QuantumUnityDB.CreateRuntimeDeterministicGuid(assetObject);
// add the asset to the asset database
QuantumUnityDB.Global.AddAsset(assetObject);
// set the GUID
assetObject.Guid = guid;
```
--------------------------------
### Start Quantum Game with Replay Arguments in Unity
Source: https://doc.photonengine.com/quantum/current/manual/replay
Starts the Quantum game session using the configured arguments. This is the final step to initiate the replay.
```csharp
_runner = QuantumRunner.StartGame(arguments);
```
--------------------------------
### .Net Runner Example Command Line Execution
Source: https://doc.photonengine.com/quantum/current/manual/replay
Example command line execution for the .Net Quantum runner, specifying paths for the replay file and LUT folder.
```bash
Quantum.Dotnet\Quantum.Runner.Dotnet\bin\Debug>.\Quantum.Runner.exe
--replay-path ..\..\..\..\Assets\QuantumUser\Replays\MapTestNavMeshAgents-2024-06-17-14-19-46.json
--lut-path ..\..\..\..\Assets\Photon\Quantum\Resources\LUT
```
--------------------------------
### ReplayStart Request JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the ReplayStart webhook request. This includes game session details.
```json
{
"AppId": "d1f67eec-51fb-45c1",
"AppVersion": "1.0-live",
"Region": "eu",
"Cloud": "0:",
"RoomName": "1.2-party-2349535735",
"GameId": "0:eu:e472a861-a1e2-49f7",
"SessionConfig": { },
"RuntimeConfig": "H4sIAAAAAAAACnWNPQvCMBCG/4ocjkWuye
X6sXZycNCCe8AogSYtNBlK6X/3UHQR4Zb35X2eW2GflslBC+ds
Y8rhcMkx+eC6Md79o9h96t6HPNjkxwgF9M7doMUCTnaCdoWjpB
WudshiUkxYsVHacE11KWe2TZiv4K3+4YjQkECKNJcVMuoXtszJ
hfkPI1tUVc1sqFGN1g3Kr+0J+3ktedUAAAA="
}
```
--------------------------------
### Load and Start Quantum Game from Snapshot
Source: https://doc.photonengine.com/quantum/current/manual/game-session/starting-from-snapshot
Connects to a private room, loads a snapshot from a file, and starts a Quantum game session from that snapshot. Ensure the snapshot file and config file exist in the Application.dataPath. The initial tick is automatically managed by the simulation.
```csharp
using Photon.Deterministic;
using Photon.Realtime;
using Quantum;
using System;
using System.IO;
using UnityEngine;
public class Foo : QuantumMonoBehaviour
{
[EditorButton("LoadAndStart")]
public async void LoadAndStart() {
// Delete the debug runner if it exists, it interferes with adding players.
var debugRunner = FindAnyObjectByType();
if (debugRunner != null) {
Destroy(debugRunner);
}
// Connect to a room with a secret room name.
var arguments = new MatchmakingArguments {
PhotonSettings = PhotonServerSettings.Global.AppSettings,
MaxPlayers = Quantum.Input.MAX_COUNT,
// This name has be unique and must be shared with the other players that are expected to join this room
RoomName = "my secret room name",
PluginName = "QuantumPlugin",
AuthValues = new AuthenticationValues(),
// Don't use for random matchmaking
IsRoomVisible = false
};
var client = await MatchmakingExtensions.ConnectToRoomAsync(arguments);
// Load the snapshot from file, or just from a member variable.
byte[] snapshot = default;
if (File.Exists(Path.Combine(Application.dataPath, "savegame.quantum"))) {
snapshot = File.ReadAllBytes(Path.Combine(Application.dataPath, "savegame.quantum"));
}
// Use global session config
var sessionConfig = QuantumDeterministicSessionConfigAsset.DefaultConfig;
// Set and use custom runtime config instead
var runtimeConfig = JsonUtility.FromJson(File.ReadAllText(Path.Combine(Application.dataPath, "config.quantum")));
// Start and wait for the game, if snapshot is set, it will start from that snapshot.
// Initial tick does not have to be set explicitly, the simulation will always restart from the beginning.
var sessionRunnerArguments = new SessionRunner.Arguments {
FrameData = snapshot,
RunnerFactory = QuantumRunnerUnityFactory.DefaultFactory,
GameParameters = QuantumRunnerUnityFactory.CreateGameParameters,
ClientId = "client secret",
PlayerCount = Quantum.Input.MaxCount,
GameMode = DeterministicGameMode.Multiplayer,
RuntimeConfig = runtimeConfig,
SessionConfig = sessionConfig,
Communicator = new QuantumNetworkCommunicator(client),
};
var runner = (QuantumRunner)await SessionRunner.StartAsync(sessionRunnerArguments);
// Add player back to the game and wait for the confirmation.
var completionSource = new System.Threading.Tasks.TaskCompletionSource();
using (QuantumCallback.SubscribeManual(c => completionSource.TrySetResult(true)))
using (QuantumCallback.SubscribeManual(c => completionSource.TrySetException(new Exception(c.Message)))) {
runner.Game.AddPlayer(0, new RuntimePlayer());
await completionSource.Task;
}
// Invite other player to the room
Debug.Log($"Invite to room {client.CurrentRoom.Name} on region {client.CurrentRegion}");
}
}
```
--------------------------------
### System Start Disabled Configuration
Source: https://doc.photonengine.com/quantum/current/manual/quantum-ecs/systems
Configures a Quantum system to start in a disabled state by overriding the StartEnabled property.
```C#
public override bool StartEnabled => false;
```
--------------------------------
### Starting QuantumRunner with ClientId
Source: https://doc.photonengine.com/quantum/current/manual/game-session/reconnecting
When starting a QuantumRunner, provide the `ClientId` to uniquely identify the client. This `ClientId` must be the same for reconnecting players.
```csharp
var sessionRunnerArguments = new SessionRunner.Arguments {
ClientId = Client.UserId,
//Other arguments are needed
};
var runner = (QuantumRunner)await SessionRunner.StartAsync(sessionRunnerArguments);
```
--------------------------------
### Quantum Demo Input Top Down Example Use Case
Source: https://doc.photonengine.com/quantum/current/game-samples/sports-arena-brawler
Example of how to retrieve and use player input within a Quantum frame. Assumes input structure is defined.
```C#
// Example use case
public override void Update(Frame frame, ref Filter filter)
{
QuantumDemoInputTopDown input = *frame.GetPlayerInput(filter.PlayerStatus->PlayerRef);
// . . .
}
```
--------------------------------
### Invoke Map Initialization in System
Source: https://doc.photonengine.com/quantum/current/addons/flow-fields/flow-field-map
Example of triggering the initialization of multiple maps from within a Quantum system.
```C#
// Initialize it in any system
public override void OnInit(Frame frame)
{
var threadCount = frame.SimulationConfig.ThreadCount;
frame.Context.InitFlowFieldMaps(frame, frame.RuntimeConfig.TileMapSetup, threadCount);
}
```
--------------------------------
### ReplayStart Response JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the ReplayStart webhook response. Setting 'Skip' to true disables replay streaming for the session.
```json
{
"Skip": true
}
```
--------------------------------
### AISystem with BTAgent Initialization
Source: https://doc.photonengine.com/quantum/current/addons/bot-sdk/bt
Example of an AISystem that initializes the BTAgent when added to an entity.
```csharp
namespace Quantum
{
public unsafe class AISystem : SystemMainThread, SystemMainThreadFilter
{
public struct Filter
{
public EntityRef Entity;
public BTAgent* BTAgent;
}
public void OnAdded(Frame frame, EntityRef entity, BTAgent* component)
{
var btRootAsset = frame.FindAsset(btReference.Id);
BTManager.Init(frame, myEntity, btRoot);
}
public override void Update(Frame frame, ref Filter filter)
{
BTManager.Update(frame, filter.Entity);
}
}
}
```
--------------------------------
### Start an Online Quantum Session
Source: https://doc.photonengine.com/quantum/current/manual/game-session/starting-session
Configures and initiates a Quantum session runner using SessionRunner.Arguments.
```C#
var sessionRunnerArguments = new SessionRunner.Arguments {
// The runner factory is the glue between the Quantum.Runner and Unity
RunnerFactory = QuantumRunnerUnityFactory.DefaultFactory,
// Creates a default version of `QuantumGameStartParameters`
GameParameters = QuantumRunnerUnityFactory.CreateGameParameters,
// A secret user id that is for example used to reserved player slots to reconnect into a running session
ClientId = Client.UserId,
// The player data
RuntimeConfig = runtimeConfig,
// The session config loaded from the Unity asset tagged as `QuantumDefaultGlobal`
SessionConfig = QuantumDeterministicSessionConfigAsset.DefaultConfig,
// GameMode has to be multiplayer for online sessions
GameMode = DeterministicGameMode.Multiplayer,
// The number of player that the session is running for, in this case we use the code-generated max possible players for the Quantum simulation
PlayerCount = Input.MAX_COUNT,
// A timeout to fail the connection logic and Quantum protocol
StartGameTimeoutInSeconds = 10,
// The communicator will take over the network handling after the simulation has started
Communicator = new QuantumNetworkCommunicator(Client),
};
// This method completes when the client has successfully joined the online session
QuantumRunner runner = (QuantumRunner)await SessionRunner.StartAsync(sessionRunnerArguments);
```
--------------------------------
### Add Player After Session Start (Async)
Source: https://doc.photonengine.com/quantum/current/manual/player/player
Adds a player to an online simulation after the session has started asynchronously. Ensure the session runner is awaited before adding players.
```csharp
var runner = (QuantumRunner)await SessionRunner.StartAsync(sessionRunnerArguments);
// adding player to the online simulation
var runtimePlayer = new RuntimePlayer { PlayerNickname = "whiskeyjack29" };
runner.Game.AddPlayer(runtimePlayer);
```
--------------------------------
### Add Start UI To Scene
Source: https://doc.photonengine.com/quantum/current/getting-started/release-notes
Adds a new and simpler graphical connection menu UI. Access this through the Tools menu.
```csharp
Tools > Quantum > Setup > Add Start UI To Scene
```
--------------------------------
### Initialize and Update BTAgent
Source: https://doc.photonengine.com/quantum/current/addons/bot-sdk/bt
Initializes the BTManager with the root asset and entity, then updates the agent on each frame. This starts the BT execution.
```csharp
BTManager.Init(frame, myEntity, btRoot);
BTManager.Update(frame, filter.Entity);
```
--------------------------------
### Sample System for HFSM Initialization and Update
Source: https://doc.photonengine.com/quantum/current/addons/bot-sdk/hfsm
A sample system demonstrating how to initialize and update HFSMAgents for entities. It implements ISignalOnComponentAdded for initialization and overrides Update for regular updates.
```csharp
namespace Quantum
{
public unsafe class AISystem : SystemMainThreadFilter, ISignalOnComponentAdded
{
public struct Filter
{
public EntityRef Entity;
public HFSMAgent* HFSMAgent;
}
public void OnAdded(Frame frame, EntityRef entity, HFSMAgent* component)
{
HFSMRoot hfsmRoot = frame.FindAsset(component->Data.Root.Id);
HFSMManager.Init(frame, entity, hfsmRoot);
}
public override void Update(Frame frame, ref Filter filter)
{
HFSMManager.Update(frame, frame.DeltaTime, filter.Entity);
}
}
}
```
--------------------------------
### .Net Runner Command Line Help
Source: https://doc.photonengine.com/quantum/current/manual/replay
Displays help information for the .Net Quantum runner command line interface. Shows available options for starting a runner with replay and other configurations.
```bash
Quantum.Dotnet\Quantum.Runner.Dotnet\bin\Debug> .\Quantum.Runner.exe --help
Description:
Main method to start a Quantum runner.
Usage:
Quantum.Runner [options]
Options:
--replay-path Path to the Quantum replay json file.
--lut-path Path to the LUT folder.
--db-path Optionally an extra path to the Quantum database json file.
--checksum-path Optionally an extra path to the checksum file.
--version Show version information
-?, -h, --help Show help and usage information
```
--------------------------------
### Get Next Waypoint for Agent Movement
Source: https://doc.photonengine.com/quantum/current/technical-samples/tilemap-pathfinder
Retrieves the world position of the next waypoint in an agent's path. This is used to guide the agent's movement.
```csharp
// gets the asset reference
var asset = f.GetSingleton().Asset(f);
// gets the next point
var pathfinder = f.Get(entity);
var index = pathfinder.Waypoints[pathfinder.CurrentWaypoint];
var nextPoint = asset.IndexToPosition(index);
```
--------------------------------
### Generate Deterministic GUID for Runtime Asset
Source: https://doc.photonengine.com/quantum/current/technical-samples/brick-builder
Use this method to generate a deterministic GUID for an asset created at runtime. Ensure the asset's name is set before generating the GUID. The generated GUID is then used to add the asset to the asset database.
```csharp
var assetObject = AssetObject.Create();
assetObject.name = "Generated Map";
var guid = QuantumUnityDB.CreateRuntimeDeterministicGuid(assetObject);
QuantumUnityDB.Global.AddAsset(assetObject);
assetObject.Guid = guid;
```
--------------------------------
### Connect to Photon Room with Realtime Client
Source: https://doc.photonengine.com/quantum/current/getting-started/whats-new
Establishes a connection to a Photon room using the Realtime client and MatchmakingExtensions. Ensure PhotonServerSettings are configured and a RealtimeClient instance is created with ClientAppType.Quantum.
```csharp
MatchmakingArguments connectionArguments = new MatchmakingArguments {
PhotonSettings = PhotonServerSettings.Default.AppSettings,
PluginName = "QuantumPlugin",
MaxPlayers = 8,
UserId = Guid.NewGuid().ToString(),
NetworkClient = new RealtimeClient { ClientType = ClientAppType.Quantum }
};
RealtimeClient client = await MatchmakingExtensions.ConnectToRoomAsync(connectionArguments);
```
--------------------------------
### SessionConfig JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
An example of the JSON representation for a SessionConfig, used for game session settings.
```json
{
"PlayerCount": 8,
"ChecksumCrossPlatformDeterminism": false,
"LockstepSimulation": false,
"InputDeltaCompression": true,
"UpdateFPS": 60,
"ChecksumInterval": 60,
"RollbackWindow": 60,
"InputHardTolerance": 8,
"InputRedundancy": 3,
"InputRepeatMaxDistance": 10,
"SessionStartTimeout": 1,
"TimeCorrectionRate": 4,
"MinTimeCorrectionFrames": 1,
"MinOffsetCorrectionDiff": 1,
"TimeScaleMin": 100,
"TimeScalePingMin": 100,
"TimeScalePingMax": 300,
"InputDelayMin": 0,
"InputDelayMax": 60,
"InputDelayPingStart": 100,
"InputFixedSizeEnabled": true,
"InputFixedSize": 24
}
```
--------------------------------
### RuntimePlayer JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
An example of the JSON representation for a RuntimePlayer, including the $type property for deserialization.
```json
{
"$type":"Quantum.RuntimePlayer, Quantum.Simulation",
"Loadout": {
"Id": {
"Value": 440543562436170603
}
},
"PlayerAvatar": {
"Id": {
"Value": 2430278665492933905
}
},
"PlayerNickname": "foo"
}
```
--------------------------------
### Initialize DynamicAssetDB with Assets
Source: https://doc.photonengine.com/quantum/current/manual/assets/assets-simulation
Create a DynamicAssetDB instance and populate it with initial assets before starting the simulation. This ensures deterministic initialization across clients.
```csharp
var initialAssets = new DynamicAssetDB();
initialAssets.AddAsset(mageSpec);
initialAssets.AddAsset(warriorSpec);
...
```
--------------------------------
### RuntimeConfig JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
An example of the JSON representation for a RuntimeConfig, including the $type property for deserialization.
```json
{
"$type": "Quantum.RuntimeConfig, Quantum.Simulation",
"GameMode": 1,
"Seed": 0,
"Map": {
"Id": {
"Value":2640765235684814815
}
},
"SimulationConfig": {
"Id": {
"Value":440543562436170603
}
},
"SystemsConfig": {
"Id": {
"Value":2430278665492933905
}
}
}
```
--------------------------------
### GameResult JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
An example of the JSON representation for a GameResult, including the $type property and winner information.
```json
{
"$type":"Quantum.GameResult, Quantum.Simulation",
"Frame": 200,
"Winner": 2
}
```
--------------------------------
### New SystemSetup.User.cs for Custom Systems
Source: https://doc.photonengine.com/quantum/current/getting-started/whats-new
This snippet shows the new way to add custom systems using DeterministicSystemSetup.AddSystemsUser in SystemSetup.User.cs. It allows for more flexible and data-driven system configuration.
```csharp
namespace Quantum {
using System.Collections.Generic;
public static partial class DeterministicSystemSetup {
static partial void AddSystemsUser(ICollection systems, RuntimeConfig gameConfig, SimulationConfig simulationConfig, SystemsConfig systemsConfig) {
systems.Add(new TestSystemMainThreadGroup("TestSystemsGroup", new SystemMainThread[] { new TestSystemImmediateRemoveDestroy(), }));
systems.Add(new TasksTestSystem());
```
--------------------------------
### Get Player Command by Index
Source: https://doc.photonengine.com/quantum/current/manual/player/player
Iterates through all players to get their respective commands using their player index.
```csharp
var command = f.GetPlayerCommand(p);
```
--------------------------------
### WebhookError JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
An example of the JSON structure for a WebhookError, including status code, error name, and message.
```json
{
"Status": 400,
"Error": "PlayerNotAllowed",
"Message": "LoremIpsum"
}
```
--------------------------------
### Create and Configure NavMesh Agent
Source: https://doc.photonengine.com/quantum/current/manual/navigation/workflow-agents
This C# snippet demonstrates how to create a Quantum system that initializes an entity with a transform, a NavMeshPathfinder, and a NavMeshSteeringAgent. It sets a target position on a specified navmesh and activates the agent.
```csharp
namespace Quantum
{
using Photon.Deterministic;
using UnityEngine.Scripting;
[Preserve]
public unsafe class NavMeshAgentTestSystem : SystemMainThread
{
public override void OnInit(Frame frame)
{
base.OnInit(frame);
var entity = frame.Create();
// Add a transform 3d component or 2d component
frame.Set(entity, new Transform3D()
{
Position = FPVector3.Zero,
Rotation = FPQuaternion.Identity
});
// Create the pathfinder component using the factory method, optionally pass a NavMeshAgentConfig
var pathfinder = NavMeshPathfinder.Create(frame, entity, null);
// Find the navmesh by name and set a target before adding the component
var navmesh = frame.Map.NavMeshes["Navmesh"];
pathfinder.SetTarget(frame, new FPVector3(12, 0, 0), navmesh);
// Add the pathfinder and steering components to the entity
frame.Set(entity, pathfinder);
frame.Set(entity, new NavMeshSteeringAgent());
}
public override void Update(Frame frame)
{
}
}
}
```
--------------------------------
### PlayerRemoved Webhook JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the PlayerRemoved webhook. Includes the reason for player removal.
```json
{
"AppId": "d1f67eec-51fb-45c1",
"GameId": "0:eu:db757806-8570-45aa",
"UserId": "db757806-8570-45aa",
"ActorNr": 1,
"PlayerSlot": 0,
"Player": 21,
"Reason": 0,
"AuthCookie": {
"Secret": "**********"
}
}
```
--------------------------------
### Multithreaded System Sample with Quantum Tasks
Source: https://doc.photonengine.com/quantum/current/manual/multithreading
Demonstrates a system that registers and schedules array and threaded tasks using Quantum's task system. It shows how to manage task dependencies and process data in parallel.
```C#
// The System
using Quantum.Task;
namespace Quantum
{
public unsafe class MultithreadedSystemSample : SystemBase {
private TaskDelegateHandle _arrayTaskDelegateHandle;
private TaskDelegateHandle _threadedTaskDelegateHandle;
public override void OnInit(Frame frame) {
frame.Context.TaskContext.RegisterDelegate(ArrayTaskMethod, "Array Task", ref _arrayTaskDelegateHandle);
frame.Context.TaskContext.RegisterDelegate(ThreadedTaskMethod, "Threaded Task", ref _threadedTaskDelegateHandle);
}
protected override TaskHandle Schedule(Frame frame, TaskHandle taskHandle) {
// the size of an array task must be known in Schedule-time
var arrayCount = frame.Unsafe.GetPointerSingleton()->UsedCount;
var arrayTaskHandle = frame.Context.TaskContext.AddArrayTask(_arrayTaskDelegateHandle, taskArg: null, taskSize: arrayCount, dependancy: taskHandle);
// get indexer to be used for slicing the data in the threaded task
// optional: instead of temp-allocating, the indexer could be persisted in a partial declaration of Frame (use AllocUser and FreeUser)
var threadedTaskIndexer = frame.Context.TempAllocateAndClear(sizeof(AtomicInt));
var threadedTaskHandle = frame.Context.TaskContext.AddThreadedTask(_threadedTaskDelegateHandle, taskArg: threadedTaskIndexer, dependancy: arrayTaskHandle);
// we return the LAST task here, so next SYSTEM scheduler DEPENDS on it...
return threadedTaskHandle;
}
public void ArrayTaskMethod(FrameThreadSafe frame, int start, int count, void* taskArg) {
var arrayOfFoo = frame.GetPointerSingleton()->MyStructFooArray;
// start/count are only meaningful in an array task
for (int i = start; i < start + count; i++) {
var foo = arrayOfFoo.GetPointer(i);
// do work on foo ...
}
}
public void ThreadedTaskMethod(FrameThreadSafe frame, int start, int count, void* userData) {
// the task indexer was passed in as task argument
var taskIndexer = (AtomicInt*)userData;
// get array and its count
var myStructFooData = frame.GetPointerSingleton();
var arrayOfFoo = myStructFooData->MyStructFooArray;
var arrayCount = myStructFooData->UsedCount;
// start/count are meaningless for a threaded task.
// as the size the data was NOT known in schedule-time, it must be sliced now
var slices = stackalloc TaskSlice[frame.Context.TaskContext.SlicePerThreadMaxCount];
var slicer = frame.Context.TaskContext.SlicePerThread(taskIndexer, arrayCount, slices);
while (slicer.Next(out var slice)) {
for (var i = slice.StartInclusive; i < slice.EndExclusive; ++i) {
var foo = arrayOfFoo.GetPointer(i);
// do work on foo ...
}
}
}
}
}
```
--------------------------------
### ReplayStart
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
The ReplayStart webhook is sent when the simulation and input recording begin on the server. It allows for capturing game replays directly from the server. A response must be received before the first replay slice is sent, or recording will be canceled.
```APIDOC
## POST https://{WebHookBaseUrl}/replay/start
### Description
This webhook is sent when the simulation and input recording starts on the server. It's a trusted source for capturing the game replay directly from the server.
### Method
POST
### Endpoint
https://{WebHookBaseUrl}/replay/start
### Parameters
#### Request Body
- **AppId** (string) - Required - The Photon AppId.
- **AppVersion** (string) - Required - The AppVersion used when creating the room/game session.
- **Region** (string) - Required - The Region code of the Game Server that the room/game session was created in.
- **Cloud** (string) - Required - The `Cloud Id` of that the Game Server is running on.
- **RoomName** (string) - Required - The room/game session Name.
- **GameId** (string) - Required - A unique `GameId` which is composed of `{Cloud:}{Region:}RoomName`.
- **SessionConfig** (SessionConfig) - Required - The `SessionConfig` that the simulation started with.
- **RuntimeConfig** (byte[]) - Required - The GZipped Json of the `RuntimeConfig` that the simulation started with.
### Request Example
```json
{
"AppId": "d1f67eec-51fb-45c1",
"AppVersion": "1.0-live",
"Region": "eu",
"Cloud": "0:",
"RoomName": "1.2-party-2349535735",
"GameId": "0:eu:e472a861-a1e2-49f7",
"SessionConfig": { },
"RuntimeConfig": "H4sIAAAAAAAACnWNPQvCMBCG/4ocjkWuye\nX6sXZycNCCe8AogSYtNBlK6X/3UHQR4Zb35X2eW2GflslBC+ds\nY8rhcMkx+eC6Md79o9h96t6HPNjkxwgF9M7doMUCTnaCdoWjpB\nWudshiUkxYsVHacE11KWe2TZiv4K3+4YjQkECKNJcVMuoXtszJ\nhfkPI1tUVc1sqFGN1g3Kr+0J+3ktedUAAAA="
}
```
### Response
#### Success Response (200 OK)
- **Skip** (bool) - The replay streaming is disabled for this game session.
#### Response Example
```json
{
"Skip": true
}
```
```
--------------------------------
### Deprecated SystemSetup.CreateSystems
Source: https://doc.photonengine.com/quantum/current/getting-started/whats-new
The old SystemSetup.CreateSystems method is still functional but deprecated. It's recommended to migrate to the new SystemSetup.User.cs approach.
```csharp
namespace Quantum {
public static class SystemSetup {
public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
return new SystemBase[] {
// ..
}
}
}
}
```
--------------------------------
### EnterRoomParams JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON structure for EnterRoomParams, used in CreateGame webhooks. All members are optional and can be null or omitted.
```json
{
"RoomOptions": {
"IsVisible": true,
"IsOpen": true,
"MaxPlayers": 8,
"PlayerTtl": null,
"EmptyRoomTtl": 10000,
"CustomRoomProperties": {
"Foo": "bar",
"PlayerClass": 1
},
"CustomRoomPropertiesForLobby": [
"Foo"
],
"SuppressRoomEvents": null,
"SuppressPlayerInfo": null,
"PublishUserId": null,
"DeleteNullProperties": null,
"BroadcastPropsChangeToAll": null,
"CleanupCacheOnLeave": null,
"CheckUserOnJoin": null
},
"ExpectedUsers": [
"A",
"B",
"C"
]
}
```
--------------------------------
### PlayerAdded Webhook JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the PlayerAdded webhook. Includes details about the player and game session.
```json
{
"AppId": "d1f67eec-51fb-45c1",
"GameId": "0:eu:db757806-8570-45aa",
"UserId": "db757806-8570-45aa",
"ActorNr": 1,
"PlayerSlot": 0,
"Player": 21,
"AuthCookie": {
"Secret": "**********"
}
}
```
--------------------------------
### Initialize Compound Agents and Blackboards
Source: https://doc.photonengine.com/quantum/current/addons/bot-sdk/snippets
Initialize HFSM agents and AIBlackboard components for movement and attack using ISignalOnComponentAdded.
```csharp
public void OnAdded(Frame frame, EntityRef entity, CompoundAgents* compoundAgents)
{
// Initialise Agents
HFSMRoot hfsmRoot = frame.FindAsset(compoundAgents->MovementHFSMAgent.Data.Root.Id);
HFSMManager.Init(frame, &compoundAgents->MovementHFSMAgent.Data, entity, hfsmRoot);
hfsmRoot = frame.FindAsset(compoundAgents->AttackHFSMAgent.Data.Root.Id);
HFSMManager.Init(frame, &compoundAgents->AttackHFSMAgent.Data, entity, hfsmRoot);
// Initialise Blackboards
AIBlackboardInitializer initializer = frame.FindAsset(compoundAgents->MovementBBInitializer.Id);
AIBlackboardInitializer.InitializeBlackboard(frame, &compoundAgents->MovementBlackboard, initializer);
initializer = frame.FindAsset(compoundAgents->AttackBBInitializer.Id);
AIBlackboardInitializer.InitializeBlackboard(frame, &compoundAgents->AttackBlackboard, initializer);
}
```
--------------------------------
### CloseGame Webhook Request JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the CloseGame webhook request. Includes GameId and CloseReason.
```json
{
"GameId": "0:eu:db757806-8570-45aa",
"CloseReason": 0
}
```
--------------------------------
### Quantum Project Structure
Source: https://doc.photonengine.com/quantum/current/addons/plugin-sdk/setup
Illustrates the expected folder structure after unzipping the Quantum Unity project and Plugin SDK.
```text
MyQuantumProject
├─Assets
├─Library
├─..
└─PluginSDK
```
--------------------------------
### Define, Trigger, and Subscribe to a Simple Event
Source: https://doc.photonengine.com/quantum/current/manual/quantum-ecs/game-events
Demonstrates the basic workflow of defining an event in Quantum DSL, triggering it from the simulation, and subscribing to it in Unity.
```qtn
event MyEvent {
int Foo;
}
```
```csharp
f.Events.MyEvent(2023);
```
```csharp
QuantumEvent.Subscribe(listener: this, handler: (EventMyEvent e) => Debug.Log($"MyEvent {e.Foo}"));
```
--------------------------------
### PlayFab Webhook Response Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON response body for PlayFab webhook integration. The webhook will fail if ResultCode is not 0.
```json
{
"ResultCode": 0,
"Message": "success"
}
```
--------------------------------
### LeaveGame Webhook Response JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the LeaveGame webhook response. Typically an empty object for confirmation.
```json
{
}
```
--------------------------------
### JoinGame Webhook Response JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the JoinGame webhook response. Allows specifying RuntimePlayer and MaxPlayerSlots.
```json
{
"RuntimePlayer": {
"Name": "player1"
},
"MaxPlayerSlots": 1
}
```
--------------------------------
### Connect and Join Room Async
Source: https://doc.photonengine.com/quantum/current/manual/game-session/async-extensions
Establishes a connection using app settings and joins or creates a room asynchronously. Ensure RealtimeClient and AppSettings are properly initialized.
```csharp
var appSettings = new new AppSettings();
var client = new RealtimeClient();
await client.ConnectUsingSettingsAsync(appSettings);
var joinRandomRoomParams = new JoinRandomRoomArgs();
var enterRoomArgs = new EnterRoomArgs();
var result = await client.JoinRandomOrCreateRoomAsync(joinRandomRoomParams, enterRoomArgs);
```
--------------------------------
### Get Player Input (Fixed)
Source: https://doc.photonengine.com/quantum/current/tutorials/asteroids/5-player-spawning
This snippet shows the original method of getting input from a fixed player index (player 0).
```csharp
var input = frame.GetPlayerInput(0);
```
--------------------------------
### ReplayChunk Request JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the ReplayChunk webhook request. This includes input history data for a simulation segment.
```json
{
"AppId": "d1f67eec-51fb-45c1",
"GameId": ":eu:e472a861-a1e2-49f7",
"ChunkNumber": 0,
"IsLast": false,
"LastTick": 302,
"TickCount": 243,
"TickCountTotal": 243,
"IsCompressed": false,
"Input": "JQAAADwAAAAIAAMKFsCUwYDggOB/UCAAAPgfDMhgEoAHA////4PRBwATAAAAPQAAAAgAA2PaSK"
}
```
--------------------------------
### Initialize Host Profiler
Source: https://doc.photonengine.com/quantum/current/manual/profiling
Initializes the Quantum Host Profiler. This is typically done within the QuantumRunner script.
```csharp
Quantum.Profiling.HostProfiler.Init(..)
```
--------------------------------
### AddPlayer Response JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the AddPlayer webhook response. Can include a RuntimePlayer object to overwrite the client-sent value.
```json
{
"RuntimePlayer": {
"Name": "player1"
}
}
```
--------------------------------
### Start Quantum Game Session Asynchronously
Source: https://doc.photonengine.com/quantum/current/getting-started/whats-new
Initiates a Quantum game session asynchronously using the SessionRunner class. This requires configuring SessionRunner.Arguments with a RunnerFactory, GameParameters, ClientId, RuntimeConfig, SessionConfig, GameMode, PlayerCount, StartGameTimeoutInSeconds, and a Communicator.
```csharp
SessionRunner.Arguments sessionRunnerArguments = new SessionRunner.Arguments {
RunnerFactory = QuantumRunnerUnityFactory.DefaultFactory,
GameParameters = QuantumRunnerUnityFactory.CreateGameParameters,
ClientId = client.UserId,
RuntimeConfig = runtimeConfig,
SessionConfig = QuantumDeterministicSessionConfigAsset.DefaultConfig,
GameMode = DeterministicGameMode.Multiplayer,
PlayerCount = 8,
StartGameTimeoutInSeconds = 10,
Communicator = new QuantumNetworkCommunicator(client),
};
QuantumRunner runner = (QuantumRunner)await SessionRunner.StartAsync(sessionRunnerArguments);
```
--------------------------------
### GameConfigs Response JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the GameConfigs webhook response. Can include RuntimeConfig and SessionConfig to overwrite client-sent values.
```json
{
"RuntimeConfig": {
"Map": {
"Id": {
"Value": 94358348534
}
}
},
"SessionConfig": {
"PlayerCount": 8,
"ChecksumCrossPlatformDeterminism": false,
"LockstepSimulation": false,
"//..": ""
}
}
```
--------------------------------
### Initialize HFSMAgent using OnComponentAdded Callback
Source: https://doc.photonengine.com/quantum/current/addons/bot-sdk/hfsm
Example of initializing an HFSMAgent using the OnComponentAdded signal. This is useful when the HFSMRoot asset reference is set on the EntityPrototype.
```csharp
// At any system...
public unsafe class AISystem : SystemMainThread, ISignalOnComponentAdded
{
public void OnAdded(Frame frame, EntityRef entity, HFSMAgent* component)
{
// Get the HFSMRoot from the component set on the Entity Prototype
HFSMRoot hfsmRoot = frame.FindAsset(component->Data.Root.Id);
// Initialize
HFSMManager.Init(frame, entityRef, hfsmRoot);
}
// ...
}
```
--------------------------------
### CreateGame Request JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
An example of the JSON payload sent to the CreateGame webhook. This includes details about the game session and the initiating user.
```json
{
"AppId": "d1f67eec-51fb-45c1",
"AppVersion": "1.0-live",
"Region": "eu",
"Cloud": "1",
"UserId": "db757806-8570-45aa",
"AuthCookie": {
"Secret": "**********"
},
"RoomName": "e472a861-a1e2-49f7",
"GameId": "0:eu:e472a861-a1e2-49f7",
"EnterRoomParams": {
"RoomOptions": {
"IsVisible": true,
"IsOpen": true
}
}
}
```
--------------------------------
### Quantum DSL Compiler Options
Source: https://doc.photonengine.com/quantum/current/manual/quantum-ecs/dsl
Use pragmas to define maximum players, component counts, and custom constants within Quantum's DSL files.
```qtn
#pragma max_players 16
// increase the component count from 256 to 512
#pragma max_components 512
// numeric constants (useable inside the DSL by MY_NUMBER and useable in code by Constants.MY_NUMBER)
#define MY_NUMBER 10
// overriding the base class name for the generated constants (default is "Constants")
#pragma constants_class_name MyFancyConstants
```
--------------------------------
### Create Custom Deterministic Plugin
Source: https://doc.photonengine.com/quantum/current/addons/plugin-sdk/setup
This code demonstrates how to create a custom DeterministicPlugin with a custom DeterministicServer that utilizes a DotNetSessionRunner. This is useful for running Quantum simulations outside of Unity.
```csharp
public override DeterministicPlugin CreateDeterministicPlugin(IPluginHost gameHost, String pluginName, Dictionary config, IPluginLogger logger, ref String errorMsg) {
var sessionRunner = new DotNetSessionRunner {
AssetSerializer = new QuantumJsonSerializer()
};
return new DeterministicPlugin(new DeterministicServer(sessionRunner));
}
```
--------------------------------
### JoinGame Webhook Request JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the JoinGame webhook request. Includes AppId, GameId, UserId, and an optional AuthCookie.
```json
{
"AppId": "*******************",
"GameId": "0:eu:db757806-8570-45aa",
"UserId": "db757806-8570-45aa",
"AuthCookie": {
"Secret": "**********"
}
}
```
--------------------------------
### KCC Processor OnEnter Method
Source: https://doc.photonengine.com/quantum/current/addons/kcc/processors
Invoked when the KCC starts colliding with a collider. The return value controls the start of the interaction, allowing for deferred registration.
```csharp
public virtual bool OnEnter(KCCContext context, KCCProcessorInfo processorInfo, KCCOverlapHit overlapHit) => true;
```
--------------------------------
### OnDeterministicStartSession Callback
Source: https://doc.photonengine.com/quantum/current/addons/plugin-sdk/quantum-server-api
Callback invoked when the Quantum simulation begins.
```csharp
void OnDeterministicStartSession()
```
--------------------------------
### AddPlayer Request JSON Example
Source: https://doc.photonengine.com/quantum/current/manual/webhooks
Example JSON payload for the AddPlayer webhook request. Includes AppId, GameId, UserId, ActorNr, PlayerSlot, RuntimePlayer, and AuthCookie.
```json
{
"AppId": "d1f67eec-51fb-45c1",
"GameId": "0:eu:db757806-8570-45aa",
"UserId": "db757806-8570-45aa",
"ActorNr": 1,
"PlayerSlot": 0,
"RuntimePlayer": {
"Name": "player1"
},
"AuthCookie": {
"Secret": "**********"
}
}
```