Quantum Inputs and Commands
This page accompanies the Photon Quantum Explained: Inputs vs Commands (Deterministic Multiplayer Guide) video and explains how player intentions are sent from Unity to the deterministic Quantum simulation.
Use this page when deciding whether a gameplay action should be represented as continuous player input or as an occasional command.
Overview
Quantum uses deterministic simulation. Given the same initial state and the same sequence of inputs, every client calculates the same simulation result.
Because the simulation state is reproduced locally, Quantum does not need to continuously synchronize transforms, velocities, health values, or other gameplay state between clients.
Instead, clients send player intentions to the simulation through:
- inputs, sent continuously for real-time controls;
- commands, sent on demand for occasional actions that require additional data.
Inputs and commands both enter the deterministic simulation, but they have different delivery, prediction, bandwidth, and serialization characteristics.
This video covers:
- how deterministic state synchronization works;
- how to define Quantum input in QTN;
- how Unity polls and submits input;
- how to use Unity’s Input System with Quantum;
- how to handle button state correctly;
- how to use a Unity UI button as input;
- how to consume input in a Quantum system;
- how to validate input inside the simulation;
- when commands are more appropriate than input;
- how to define, serialize, register, send, and consume a command;
- the practical differences between inputs and commands.
Video Timeline
| Time | Section |
|---|---|
| 00:00 | Introduction |
| 00:28 | State in a deterministic game engine |
| 00:43 | Creating input in Quantum |
| 01:23 | Sending input from Unity to Quantum with the Input System |
| 04:21 | Consuming input in a system |
| 04:28 | Input validation and cheat protection |
| 05:09 | Completing input consumption |
| 05:28 | Testing input in Unity |
| 05:42 | Using a UI button as input |
| 07:39 | Introduction to commands |
| 08:10 | Creating a command |
| 08:42 | Sending a command from Unity to the simulation |
| 09:22 | Consuming a command in the simulation |
| 09:57 | Inputs compared with commands |
Deterministic Multiplayer State
Quantum runs the same deterministic simulation on every client.
At the same verified frame, all clients are expected to have the same:
- entity state;
- component values;
- physics state;
- gameplay state;
- simulation result.
For this reason, Quantum does not continuously transmit full gameplay state between clients.
The exchanged data primarily represents what players intend to do.
For real-time gameplay, this normally means transmitting small input structures every simulation tick.
For less frequent actions that require more data, commands can be used instead.
Inputs
Inputs represent continuous player controls.
Typical examples include:
- movement direction;
- aiming direction;
- jump state;
- fire state;
- acceleration;
- steering;
- blocking;
- crouching.
Input is defined once in Quantum DSL and submitted for each simulation tick.
Defining Input in QTN
Create a QTN file or open an existing QTN input definition.
Define the input block:
c#
input {
FPVector2 Direction;
button Jump;
}
This example contains:
| Field | Purpose |
|---|---|
Direction |
Two-dimensional movement direction. |
Jump |
Button state used to detect jump presses and releases. |
Use deterministic Quantum types such as FPVector2 inside the input definition.
Quantum Button Type
For button-like input, use Quantum’s button type instead of a plain bool or integer.
The button type tracks state transitions required by gameplay code, including concepts such as:
- currently held;
- pressed;
- released;
- first press;
- first release.
Unity only needs to provide the current held state. Quantum handles the internal button state transitions across simulation frames.
Polling Input from Unity
Unity-side code collects local device input and passes it to Quantum.
Create a MonoBehaviour responsible for input polling.
Subscribe to CallbackPollInput from OnEnable:
c#
private void OnEnable()
{
QuantumCallback.Subscribe(
this,
(CallbackPollInput callback) => PollInput(callback)
);
}
Quantum invokes this callback according to the simulation’s input polling requirements.
The callback creates the Quantum input structure, populates it, and submits it to the simulation.
Input Source Independence
Quantum is agnostic about where input originates.
Input can come from:
- Unity’s Input System;
- Unity’s legacy input API;
- touch controls;
- UI buttons;
- gamepads;
- custom device integrations;
- third-party input packages;
- recorded input;
- AI or automated test tools.
The important requirement is that Unity-side input is converted into the Quantum input structure before it enters the deterministic simulation.
Using Unity’s Input System
When using Unity’s Input System, wrap the namespace import in the relevant compilation symbols:
c#
#if ENABLE_INPUT_SYSTEM && QUANTUM_ENABLE_INPUTSYSTEM
using UnityEngine.InputSystem;
#endif
This prevents the code from depending on the Input System when the required packages or Quantum integration symbols are not enabled.
Configuring PlayerInput
During initialization:
- Check whether the GameObject already has a
PlayerInputcomponent. - Add it if required.
- Assign the project’s input action asset.
- Enable the input actions.
- Cache the actions required by the Quantum input provider.
For this example, cache:
- movement;
- jump.
Caching input action references avoids repeatedly resolving them during input polling.
Reading Movement Input
Read the movement action as a Unity Vector2:
c#
Vector2 movement = moveAction.ReadValue<Vector2>();
Quantum expects FPVector2, so convert the value before assigning it:
c#
input.Direction = movement.ToFPVector2();
The conversion from floating-point Unity input to Quantum fixed-point input occurs before the value is shared with the deterministic simulation.
Do not perform arbitrary floating-point gameplay calculations inside the Quantum simulation.
Reading Button Input
For jump input, read the current held state:
c#
input.Jump = jumpAction.IsPressed();
Use IsPressed() rather than frame-specific Unity methods such as:
c#
WasPressedThisFrame()
Quantum’s simulation tick rate and Unity’s rendered frame rate are not necessarily the same.
A Unity frame-based press check can be missed if no Quantum input poll occurs during the specific Unity frame in which the press was reported.
By passing the held state, Quantum’s button type can calculate press and release transitions in simulation time.
Unity Frame Rate and Quantum Tick Rate
Unity’s frame rate can vary and is often higher than the Quantum tick rate.
For example:
- Quantum simulation:
30ticks per second; - Unity rendering: variable and potentially above
30frames per second.
Input collection should account for this difference.
Avoid assuming that one Unity frame corresponds to one Quantum simulation tick.
Using a UI Button as Input
Input can also originate from a Unity UI button.
Create a local boolean:
c#
private bool _jump;
Add a public UI callback:
c#
public void JumpPressed()
{
_jump = true;
}
Assign this method to the Unity Button’s OnClick event.
During input polling:
c#
input.Jump = _jump;
_jump = false;
This temporarily stores the Unity-side event until the next Quantum input poll consumes it.
Resetting _jump after polling ensures that the press is submitted once rather than remaining active indefinitely.
Submitting Input
After populating the input structure, submit it to Quantum:
c#
callback.SetInput(
input,
DeterministicInputFlags.Repeatable
);
The Repeatable flag allows the input to be reused when appropriate if a later input value has not arrived in time.
This is useful for continuous real-time controls such as movement or held buttons.
Consuming Input in a System
Input is consumed inside Quantum simulation code.
A common player setup stores a PlayerRef in a component such as:
c#
PlayerLink
The system retrieves the current input for that player:
c#
Input* input =
frame.GetPlayerInput(filter.PlayerLink->Player);
The system can then use the deterministic input values to update the player entity.
For example:
- apply movement force;
- change velocity;
- rotate the character;
- trigger a jump;
- fire a weapon.
Processing Movement
Before applying movement, validate or normalize the movement direction:
c#
FPVector2 direction = input->Direction;
if (direction.SqrMagnitude > FP.One)
{
direction = direction.Normalized;
}
The exact implementation can vary, but the simulation should enforce the legal range of received input.
The validated direction can then be used by the character movement logic.
Processing Jump Input
Check the Quantum button state inside the simulation:
c#
if (input->Jump.WasPressed)
{
// Apply upward force or jump velocity.
}
Quantum derives WasPressed from the button’s current and previous simulation state.
The simulation does not depend on Unity’s rendered-frame timing.
Input Validation and Cheat Protection
Quantum’s deterministic architecture prevents clients from directly authoring arbitrary simulation state, but input should still be validated.
A modified client could attempt to submit invalid input values.
For example, a malicious client could change a movement vector from a normal input range to an extremely large value.
Validate player intentions inside deterministic simulation code before using them.
Common checks include:
- normalizing movement directions;
- clamping aim values;
- validating target ranges;
- enforcing cooldowns;
- checking resource costs;
- verifying that actions are currently allowed;
- rejecting impossible state transitions.
For movement, normalization ensures that the direction magnitude does not exceed the expected limit.
c#
FPVector2 direction =
input->Direction.Normalized;
The simulation remains responsible for deciding the actual gameplay outcome.
Commands
Commands represent occasional player actions that are not suitable for continuous per-tick input.
Typical examples include:
- teleporting to a selected location;
- buying an item;
- selecting a dialogue option;
- choosing a character;
- submitting a turn;
- casting a spell with a target;
- changing a loadout;
- triggering a menu action;
- sending configuration data.
Commands are submitted only when required.
They can contain more complex data than the compact input structure, but they must be explicitly serialized.
When to Use a Command
Consider a teleport action.
A teleport request may contain a complete world position calculated from:
- a mouse click;
- a raycast;
- a map selection;
- a UI interaction;
- a random position;
- another Unity-side process.
Adding this position to continuously transmitted input would increase input size even though teleporting occurs only occasionally.
A command is more appropriate because it is sent only when the teleport action is requested.
Creating a Command
Create a Quantum command from the simulation folder:
Create > Quantum > Command
Name it:
CommandTeleport
Add the destination field:
c#
public FPVector3 Position;
The command represents the player’s request to teleport to the specified deterministic position.
Serializing the Command
Commands must serialize their fields explicitly.
Inside the overridden serialization method, serialize the position:
c#
public override void Serialize(
BitStream stream)
{
stream.Serialize(ref Position);
}
The exact method signature depends on the generated Quantum command template and SDK version.
Every field that must reach the simulation needs to be included in command serialization.
Sending a Command from Unity
Create a Unity MonoBehaviour:
TeleportPlayerToRandomPosition
In its update logic:
- Detect the left mouse button.
- Calculate a random position within a selected radius.
- Convert the Unity position to
FPVector3. - Add vertical offset if required.
- Create and send the command.
Conceptually:
c#
var position =
Random.insideUnitSphere * 5f;
position.y += 2f;
QuantumRunner.Default.Game.AddCommand(
new CommandTeleport {
Position = position.ToFPVector3()
}
);
The Unity-side code creates the intention. The Quantum simulation decides how to process it.
Registering the Command
Custom commands must be added to the command factory.
Open:
CommandSetup.User.cs
Register CommandTeleport in the command factory configuration.
If the command is not registered, Quantum cannot reconstruct the serialized command when it arrives in the simulation.
Consuming Commands in the Simulation
The simulation checks the commands submitted by players.
A typical workflow is:
- Iterate over players.
- Check whether a player submitted
CommandTeleport. - Read the command payload.
- Find the entity controlled by that player.
- Validate the requested position.
- Update the entity’s deterministic transform.
Conceptually:
c#
foreach (var player in frame.PlayerConnected)
{
var command =
frame.GetPlayerCommand(player);
if (command is CommandTeleport teleport)
{
// Find the player's entity.
// Validate teleport.Position.
// Apply the position.
}
}
The exact command retrieval API depends on the Quantum version and system setup.
Validating Commands
Commands should be validated just like inputs.
A command being reliably delivered does not mean its requested action should automatically be accepted.
For a teleport command, validate:
- whether teleporting is currently allowed;
- whether the destination is inside the playable area;
- whether the destination overlaps invalid geometry;
- whether the player has sufficient resources;
- whether the ability is on cooldown;
- whether the requested distance is legal.
The simulation remains authoritative over the result.
Inputs Compared with Commands
| Criteria | Input | Command |
|---|---|---|
| Frequency | Sent every simulation tick and required continuously. | Sent only on demand. |
| Reliability | Late input may be repeated, replaced, or zeroed according to the input pipeline and flags. | Reliably delivered and confirmed through the command pipeline. |
| Bandwidth | Higher continuous usage because the structure is sent every tick. | Lower for infrequent actions because data is sent only when triggered. |
| Prediction | Other clients can predict repeatable input before verified input arrives. | The local client can react immediately, but remote clients cannot predict the exact tick and payload before the command arrives. |
| Data size | Should remain small and is optimized for frequent transmission. | Can contain larger or more varied payloads. |
| Best use case | Continuous real-time control such as movement, aiming, firing, or jumping. | Discrete actions such as purchasing, teleporting, spell targeting, or submitting a turn. |
| Typical game type | Real-time action games such as shooters, platformers, racing, or action games. | Turn-based games, menu interactions, and infrequent gameplay events. |
| Serialization | Defined through the QTN input structure and handled by Quantum. | Fields must be serialized explicitly. |
Choosing Between Input and Command
Use an input when the action:
- may occur every simulation tick;
- must be predicted by remote clients;
- represents continuous control;
- can be expressed with a small fixed data structure;
- should support repeatable input behavior.
Use a command when the action:
- occurs occasionally;
- requires a larger or variable payload;
- represents a discrete decision;
- does not need to be predicted every tick;
- should be sent only when triggered.
Example Classification
| Gameplay Action | Recommended Mechanism |
|---|---|
| Movement direction | Input |
| Aim direction | Input |
| Jump held state | Input |
| Fire button | Input |
| Vehicle steering | Input |
| Buy item | Command |
| Select spawn point | Command |
| Teleport to selected position | Command |
| Submit turn | Command |
| Change character class | Command |
| Cast a targeted spell | Command |
| Confirm menu selection | Command |
Some systems may use both.
For example, a spell system could use:
- input for continuous aiming;
- a command for confirming a target and submitting a larger payload.
Recommended Input Workflow
- Define a compact input structure in QTN.
- Collect device or UI state in Unity.
- Convert Unity values to deterministic Quantum types.
- Use held-state values for Quantum buttons.
- Submit input through
CallbackPollInput. - Retrieve input using the corresponding
PlayerRef. - Validate values inside the simulation.
- Apply the validated gameplay result.
Recommended Command Workflow
- Create a Quantum command.
- Add the required deterministic fields.
- Serialize every field.
- Register the command in
CommandSetup.User.cs. - Create the command from Unity when the action occurs.
- Submit it through the active Quantum game.
- Retrieve it in simulation code.
- Validate the request.
- Apply the deterministic result.
Key Concepts
| Concept | Description |
|---|---|
| Deterministic simulation | The same state and input produce the same result on every client. |
| Input | Compact player intention submitted continuously per simulation tick. |
| Command | On-demand player intention containing explicitly serialized data. |
| QTN input block | DSL definition of the input structure. |
button |
Quantum input type that tracks held, pressed, and released states. |
CallbackPollInput |
Unity-side callback used to populate Quantum input. |
DeterministicInputFlags.Repeatable |
Allows input reuse according to the deterministic input pipeline. |
PlayerRef |
Identifies the player whose input or command is being processed. |
| Input validation | Simulation-side checks that constrain player-provided values. |
| Command serialization | Explicit definition of how command fields are transmitted. |
| Command factory | Registration used to reconstruct custom command types. |
Best Practices
Use this checklist when implementing inputs and commands:
- Keep input structures small.
- Use input for continuous real-time controls.
- Use commands for occasional actions with larger payloads.
- Use deterministic types inside input and command definitions.
- Convert Unity floating-point input before it enters simulation logic.
- Use
IsPressed()for Unity Input System buttons. - Let Quantum’s
buttontype calculate press and release transitions. - Account for differences between Unity frame rate and Quantum tick rate.
- Buffer UI button presses until the next input poll.
- Submit input through
CallbackPollInput. - Use
Repeatableonly where repeating the previous input is appropriate. - Validate all input values inside the simulation.
- Validate command payloads before applying their effects.
- Serialize every custom command field.
- Register every custom command type.
- Keep the simulation responsible for the final gameplay result.
Common Pitfalls
| Pitfall | Why It Is a Problem |
|---|---|
Using Unity WasPressedThisFrame() for Quantum input |
The Unity frame containing the press may not align with a Quantum input poll. |
| Sending large data in the input structure | Input is transmitted continuously and should remain compact. |
| Using commands for movement | Commands are not intended for continuous predicted control. |
| Using input for rare large payloads | The data would be transmitted every tick unnecessarily. |
| Performing floating-point gameplay logic inside the simulation | Unity floating-point calculations are not part of Quantum’s deterministic type system. |
| Trusting movement vectors without validation | Modified clients could submit values outside the expected range. |
| Forgetting to serialize a command field | The receiving simulation will not reconstruct the complete payload. |
| Forgetting to register a custom command | Quantum cannot create the command from the received data. |
| Automatically accepting a command | Reliable delivery does not make the requested gameplay action valid. |
| Treating Unity frame rate as the simulation tick rate | Input presses and releases may be missed or duplicated. |
| Resetting a buffered UI press before Quantum polls it | The simulation may never receive the action. |
Related Links
Summary
Inputs and commands are the two main mechanisms for sending player intentions into the Quantum simulation.
Inputs are compact, continuous, and suitable for predictable real-time controls such as movement, aiming, and jumping. They are defined in QTN, populated through CallbackPollInput, and consumed every simulation tick.
Commands are sent only when required and can carry larger, explicitly serialized payloads. They are suitable for discrete actions such as teleporting, buying an item, selecting a target, or submitting a turn.
Both inputs and commands must be validated inside the deterministic simulation. Clients provide intentions, while the simulation determines the valid gameplay result.
Back to top- Overview
- Video Timeline
- Deterministic Multiplayer State
- Inputs
- Defining Input in QTN
- Quantum Button Type
- Polling Input from Unity
- Input Source Independence
- Using Unity’s Input System
- Configuring PlayerInput
- Reading Movement Input
- Reading Button Input
- Unity Frame Rate and Quantum Tick Rate
- Using a UI Button as Input
- Submitting Input
- Consuming Input in a System
- Processing Movement
- Processing Jump Input
- Input Validation and Cheat Protection
- Commands
- When to Use a Command
- Creating a Command
- Serializing the Command
- Sending a Command from Unity
- Registering the Command
- Consuming Commands in the Simulation
- Validating Commands
- Inputs Compared with Commands
- Choosing Between Input and Command
- Example Classification
- Recommended Input Workflow
- Recommended Command Workflow
- Key Concepts
- Best Practices
- Common Pitfalls
- Related Links
- Summary