Commands
Command Description
Commands are completely optional constructs within Bolt intended to support client prediction with server authority.
You do not need to use commands at all in your game if you do not want to support this.
In fact, with the easiest implementation in Bolt - completely client authoritative - you do not need to use commands, as they are not needed.
In addition, anything completely server driven - such as NPCs, do not need to use commands since they are not client predicted.
They should use simple transform syncing through the Bolt States.
Using Commands adds an extra layer of complexity to the game overflow, and you need to fully understand it before using it extensivelly.
The Advanced Tutorial
implements a simple character motor with commands and it is highly suggested you start with this motor.
Trying to use your own motor without first understanding commands is not recommended.
Prediction
When using command, the local entity (usually the player) is always predicted.
It uses the command system to move on his local machine, instantly, as rapid response is critical for the player.
The player’s inputs are sent to the server as part of the command.
The server plays the same inputs as well, resulting in, most of the time, the same exact simulation that the client already predicted.
The server then it returns the result (the final position, speed, etc.) back to the player for a certain frame, who then essentially resets his position and other state back in time to the server’s on that frame (the "correction" in the command result) and then he replays his inputs from that point back to the present to hopefully be back where he predicted he would be.
Authoritativity
Following the predicted behaviour of the client, the server perform what we call state authority.
If the server's simulation is different, the player will end up at a different position, por example.
The simulation is authoritatively running on the server and the player simply tries to predict what that simulation will be.
If the player sets his speed to a very high speed, the server doesn’t care, because the client simulation has no bearing at all on the server.
The player’s proxy - i.e. the representation of his player on other player’s boxes, typically just uses the Bolt state system for movement with synced transforms.
Since the controlling player is predicting his own movement, you may want to set the Transform
property Replication Mode
of your Player to Everyone but Controller
, because you don’t want his own Player objects transform to be synced back from the server.
If it was set to Everyone
, the player would predict his own movement, but then the non-predicted state sync for his own player would also come back from the server, which would essentially collide with his predicted value and you would see obvious artifacts.
As a suggestion, if you are working on testing your client predicted, server authoritatve movement motor on the network, make sure you do it with latency simulation enabled and set to something reasonable, like the defaults Bolt comes with.
This is because the larger the latency the more pronounced (and obvious) errors will be with your implementation.
With no simulation and the server being local you might think it works great - turn on latency sim and you will very quickly see where there are problems.
Note that Commands are essentially network streams being sent on your send rate back and forth from the controller to the server (inputs to the server, results back).
This can be a useful construct even if you are not using commands for client prediction.
Defining a Command
In order to create or modify a command, go to Bolt Assets
window, and using the right-click, create a New Command
, or select one from the list, to edit its definition.
On the Bolt Editor
window, you will be able to include Inputs
and Results
, those are the fields that will be used to send the input commands from the clients and to return the result values from the server.
The Command has also some settings that can change its behavior:
- Is Instant - All queued inputs of this command are executed immediately in the following frame upon reaching the server.
- Enable Frame Limit - You are limited to queue only one input per SimulateController(). This can prevent speed-hacks.
- Correction Interpolation - Interpolation of result received from the server.
- Enable Delta Compression - To reduce network traffic and allocations for the price of slightly increased processing time. Always measure results! Do not use for input/result with only bools as they are never delta-compressed.
Using Commands
The Bolt API for using Commands is relatively small, but you need to understand all the moving parts, in order to take advantage of Commands.
When you create a new Bolt Entity
, define it's state and associate component class that extends Bolt.EntityEventListener<[your state here]]>
, you get access to three main methods:
public override void SimulateController()
: is used to collect inputs from your game and putting it into a Command.SimulateController
executes one time per frame.public override void ExecuteCommand()
: runs on both the owner and the controller of an entity, so if you have a player character which the server spawns and then gives control of it to a client,ExecuteCommand
will run on both the server and the controlling client. It will not run on any other clients.entity.QueueInput(cmd)
: you call this method from inside theSimulateController()
function in order to schedule the command passed as argument for both local executution on the client and and also to be sent to the server for remote execution. This is what lets Bolt do client side prediction: the command will execute on both the server and client.
When the server executes a command, it will send the Result
of the command back to the client which created the command, and override the state of that specific command on the client with its own correct state.
The resetState
parameter asks you to reset the state of the character motor to the Result
of the command passed in when resetState is true.
This will only happen on remote controlling clients, never on the server.
This happens once at the beginning of every frame, and the command which is passed in is the command which has received its correct Result
from the server.
After the command with resetState
has executed, Bolt will run all other commands again on the client from the frame of the reset to the current frame, in order to "catch up" to the current state.
This happens every frame (this is your simulation rate).
A common question: Why does my player move really fast when I comment out his reset state logic on the client?
The reason is that every single tick, Bolt rewinds you to where you were on a certain frame (with reset state), and then it replays every queued command from that frame to the current frame.
This should in most scenarios place you back at the same position you were before the tick, but with the additional input from the new tick.
Replayed inputs generally are at least 10+ commands, even when playing on a server on the same network.
If you comment out the reset state logic, Bolt will end up executing 10+ commands (with forward input if you were pressing forward), without resetting your position back in time first.
So you will end up executing 10+ "move forwards" per tick! This is why you move so fast. Keep in mind this is completely client side and the server doesn’t reflect this rapid movement.
You are basically just ignoring the server completely.
Queuing Input with Commands
A common question from users of Bolt is how to queue input correctly.
Most users find that their one shot input is being acted on multiple times in a row or in some cases the opposite occurs and their input is simply missed. The reason for this is quite simple but requires knowledge of how Unity’s Update
/ FixedUpdate
works.
Note that Unity collects input in the beginning of Update and Bolt’s input queuing occurs in FixedUpdate.
Update
fires once per frame. FixedUpdate
fires on a fixed interval.
If your frame rate is high, you will fire multiple Updates
between each FixedUpdate
.
If your framerate is low, Unity will fire a number of FixedUpdates
in a single frame in order to try to keep the physics ticks in sync.
Imagine the following scenario: you are running in the editor on an empty test scene. Your frame rate is very high.
In this case, say your simulation rate was 60 (60 physics ticks per second), and your frame rate was 180. This means three updates fire between each tick.
So in this scenario you have three updates per one fixed update:
md
Update - collect input for jump == false
Update - collect input for jump == true (you pressed the jump button)
Update - collect input for jump == false
FixedUpdate - queue input (false)
In this example you have a data structure that is tracking your input so you can queue it in SimulateController
.
You click the jump button on the second update. However, you don’t end up actually jumping in the game, because your data structure resets the jump back to false on the third update before you queued it.
The solution is simple: only set the jump flag to true - never reset it to false while polling input.
Instead you reset all of your one shot inputs when you have finished queueing your input.
Of course in low frame rate situations the opposite can happen:
md
Update - input polled
FixedUpdate - queue input
FixedUpdate - queue same input (again)
FixedUpdate - queue same input (again)
In this case if you don’t clear your one-shot input after Bolt’s SimulateController
, you will queue the same on-shot input three times in a row.