Server-authoritative setup
Last updated
Last updated
Sometimes, distributing authority is not the way to go. Certain types of games require a model where the server (or, we should say, the Simulator) is in control of the simulation of the whole game, and the players only send inputs to it. The Simulator elaborates these inputs, and in response updates the Client about the new game state.
This way of doing things is usually referred to as server-authoritative.
In competitive games. Many game genres use client inputs and centralized simulation to guarantee the fairness of actions or the stability of physics simulations.
In situations where Clients have low processing power. If the Clients don't have sufficient processing power to simulate the World it makes sense to send inputs and just display the replicated results on the Clients.
In situations where determinism is important. RTS and fighting games can use CoherenceInput component and rollback to process input events in a shared (not centralized) and deterministic way so that all Clients simulate the same conditions and produce the same results.
coherence currently only supports using CoherenceInput in a centralized way, where a single Simulator is setup to process all inputs and replicate the results to all Clients.
Setting up an object for server-side simulation using CoherenceInput is done in three steps:
The Simulate property needs to be set to Server Side with Client Input.
At this point, a CoherenceInput component is automatically added to the object.
Setting the simulation type to this mode instructs the Client to automatically transfer State Authority for this object to the Simulator that is in charge of simulating inputs on all objects, and only retain Input Authority.
Each simulated CoherenceSync component is able to define its own, unique set of inputs via the CoherenceInput interface.
An input can be of types:
Button. A button input is tracked with just a binary on/off state.
Axis / Axis2D / Axis3D. An axis input is tracked as one/two/three floats from -1 to 1.
String. A string value representing custom input state. (max length of 63 characters)
Rotation. A rotation is represented by a Quaternion.
Integer. Represented as an int.
In order for the inputs to be used, they must be baked.
If the CoherenceInput fields or name is changed, then the CoherenceSync object must be re-baked to reflect the new fields/values.
When a Simulator is running it will find objects that are set up using CoherenceInput components and will automatically take over State Authority, and start simulating them.
During gameplay, scripts from both the Client and Simulator work with the inputs defined on the CoherenceInput of the replicated object: the Client uses the Set*
methods to set input values, and the Simulator uses the Get*
methods to access them.
In all of these methods, the name
parameter is the same as the Name field defined on the CoherenceInput component.
Check the CoherenceInput API for a complete list of the available methods.
For example:
The mouse click position can be passed from the Client to the Simulator via the "Move" field in the setup example.
The Simulator can access the state of the input to perform simulation on the object.
The elaborated state is then reflected back to the Client, just as any replicated object is.
Each object only accepts inputs from one specific Client, called the object's Input Authority.
When a Client spawns an object it automatically becomes the Input Authority for that object. The object's creator will retain control over the object even after State Authority has been transferred to the Simulator.
If an object is spawned directly by the Simulator, you will need to assign the Input Authority manually. Use the TransferAuthority
method on the CoherenceSync component to assign or re-assign a Client that will take control of the object:
The ClientId used to specify Input Authority can currently only be accessed from the ClientConnection class. For detailed information about setting up the ClientConnection Prefab, see the Client Connections page.
Use the OnInputAuthority
and OnInputRemote
events on the CoherenceSync
component to be notified whenever an object changes input authority.
Only the object's current State Authority is allowed to transfer Input Authority.
The OnInputSimulatorConnected
event can also be raised on the Simulator or host if they have both Input and State Authority over an entity. This allows the session host to use inputs just like any other client but might be undesirable if input entities are created on the host and then have their Input Authority transferred to the clients.
To solve this you can check the CoherenceSync.IsSimulatorOrHost flag in the callback:
Compared to Client-side simulation, server-side simulation takes a significantly longer time from the Client providing input until the game state is updated. That's because of the time required for the input to be sent to the Simulator, processed, and then the updates to the object returned across the network. This round-trip time results in an input lag that can make controls feel awkward and slow to respond.
If you want to use a server-authoritative setup without sacrificing input responsiveness, you need to use Client-side prediction.
With Client-side prediction is enabled for a binding, incoming network data is ignored, allowing the Client to calculate (predict) its value locally. A typical use case is to predict position and rotation for the local player, but you can toggle Client-side prediction for any binding in the Configuration window:
By processing inputs both on the Client and on the server, the Client can make a prediction of where the player is heading without having to wait for the authoritative server response. This provides immediate input feedback and a more responsive playing experience.
Note that inputs should not be processed for Clients that neither have State Authority nor Input Authority. That's because we can only predict the local player; remote players and other networked objects are synced just as normal.
With Client-side prediction enabled, the predicted Client state will sometimes diverge from the server state. This is called misprediction.
When misprediction occurs, you will need to adjust the Client state to match the server state in one way or another. This is called server reconciliation.
There are many possible approaches to server reconciliation and coherence doesn't favor one over another. The simplest method is to snap the Client state to the server state once a misprediction is detected. Another method is to continuously blend from Client state to server state.
Misprediction detection and reconciliation can be implemented in a binding's OnNetworkSampleReceived
event callback. This event is called every time new network data arrives, so we can test the incoming data to see if it matches with our local Client state.
The misprediction threshold is a measure of how far the prediction is allowed to drift from the server state. Its value will depend on how fast your player is moving and how much divergence is acceptable in your particular game.
Remember that incoming sample data is delayed by the round-trip time to the server, so it will trail the currently predicted state by at least a few frames, depending on network latency. The simulationFrame
parameter tells you the exact frame at which the sample was produced on the authoritative server.
For better accuracy, incoming network samples should be compared to the predicted state at the corresponding simulation frame. This requires keeping a history buffer of predicted states in memory.
This feature is in the experimental phase.
A client-hosted session is an alternative way to use CoherenceInput in Server Side With Client Input mode that doesn't require a Simulator.
A Client that created a Room can join as a Host of this Room. Just like a Simulator, the Host will take over the State Authority of the CoherenceInput objects while leaving the Input Authority in the hands of the Client that created those objects.
The difference between a Host and a Simulator is that the Host is still a standard client connection, which means it counts towards the Room's client limit and will show up as a client connection in the connection list.
To connect as a Host all we have to do is call CoherenceBridge.ConnectAsHost
:
CoherenceLiveQuery is a component that can be used to constrain the area of entities that are replicated on the client.
When using a LiveQuery, The Replication Server filters out networked objects that are outside the range of the defined range. This can be useful as an optimization, and a security mechanism, to ensure clients can't exploit the system by inspecting the incoming network traffic, outside of what they are allowed to see. However the question arises: what if the player exploits by moving the position of the LiveQuery?
When a query component is part of a CoherenceSync that is set to Server Side With Client Inputs, the query visibility will be applied to the client that owns Input Authority (i.e., the Client) while the component's state remains in control of the State Authority (i.e., the Simulator).
This prevents clients from viewing other parts of the world by simply manipulating the extents or the position of the LiveQuery.
See CoherenceLiveQuery and Area of interest for more information on how to use queries.