Commands are optional constructs within Bolt intended to support client prediction with server authority.
The local entity (usually the player) is always predicted. It uses the command system to move on his local machine, instantly. Instant response is critical for the player. The player’s inputs are sent to the server as part of the command. The server plays these same inputs as well, resulting in the same exact simulation that the client already predicted. The server then 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.
If the server’s simulation is different, the player will end up at a different position. 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.
How do commands work?
SimulateController() is used to collect inputs from your game and putting it into a Command. SimulateController executes one time per frame.
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.
When you call
entity.QueueInput(cmd); the command is scheduled both for local execute on the client and is 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 State 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.
resetState parameter asks you to reset the state of the character motor to the state 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 state from the server.
After the command with resetState has execute, Bolt will execute all other commands again on the client from the frame of the reset frame to the current frame, 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.
- Is Instant - All queued inputs of this command are executed immediately in following frame upon reaching 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.