If we just accept the fact that latency exists, but chose not to do anything about it - we would implement what is known as "dumb terminal". Dumb Terminals do not need to understand anything about the simulation they are visualizing for the client - all they do is:
- Send inputs from Clients to the Server
- Receive the resulting state from the Server and render it appropritely
This is a conservative approach that makes no attempt to mitigate delay, but also never shows an incorrect user state. The problems with this approach is that not only will the user feel the latency, the frequency of updates from the server would dictate how choppy our gameplay experience is for the clients. In effect, regardless of the potential framerate that the client could achieve, the game would only run at the cadence the server (with its limiting networking factors) is capable of. This could reduce a good 60 FPS experience into a bad 15 FPS experience with perceivable input lag.
Not only does this approach cause some unresponsiveness (which may be acceptable in certain game genres), it also makes it more difficult to aim at other players. The non-up-to-date rendering of the world forces the player to aim ahead of their target to compensate for lag. As a worst case scenario, the player could be legitimately aiming at the enemy player, but due to the fact that the enemy was actually a 100-150ms "ahead" (as in forward in time, not necessarily ahead in a positional sense) of what is being rendered, they may be unable to hit the enemy unless he runs predictably in a straight line.
Fortunately we can mitigate the effects of latency and jitter.
Visual choppiness is caused by infrequent (in comparison to the speed at which clients are rendering their screens) updates from the server and it could also be exasperated by bad network conditions causing jitter. We can employ Client-Side Interpolation to reduce this effect.
In client-side Interpolation instead of just snapping objects to their positions that are transmitted from the server the client smoothly interpolates to this state over time. This approach is still conservative - the client just smoothens out the transition between valid states that were sent from the server.
Normally a client in a server-authoritative topology, barring any additional tricks and techniques, would be able to render state that is approximately half the Round Trip Time (RTT) behind the actual state of simulation on the server. In order for client-side interpolation to be able to work it needs to be somewhat behind (catching up to) the most recent state passed to us from the server. In effect, our latency would increase by our Interpolation Period. In order to avoid stutter, we want that period to be less than the Packet Sending Period. When the client is done interpolating to the previous state, it would always have received a new state to repeat the process.
Future MLAPI Feature
This implementation of Client-side Interpolation provides some improvement to the choppiness problem, but it does not completely solve the issues caused by jitter.
An improvement that produces even smoother gameplay at the cost of even more added latency is Snapshot Interpolation, where instead of interpolating towards the most up-to-date state, we introduce a buffer that keeps several snapshots of incoming state and interpolates through them. This technique provides better handling of Jitter, but, again, it introduces slight additional latency on top of what we had in Clientside Interpolation.
Snapshot Interpolation is not implemented in MLAPI at this time.
This technique is implemented in the BossRoom sample. A brief implementation description is as follows (with further documentation available on the Boss Room GitHub page):
Before reviewing into code, define the
GameObject composition model for player characers, and similarly AI characters, inside BossRoom:
- The first, a non-visual,
NetworkObject: Contains both server and client
NetworkBehaviourcomponents, namely ServerCharacterMovement.cs and ClientGenericMovement.cs. We refer to this
- The second, a visual
GameObject: Displays the character's model and play animations. This is the
GameObjectwhich performs client-side interpolation. We refer to this as "Graphics".
We first take a look at NetworkCharacterState.cs, a
NetworkBehaviour component attached to a "PC".
It is a
NetworkBehaviour that defines two
NetworkVariables are defined with default permissions, meaning that only the server is able to modify the values of these
You will also notice that they're also initialized with a particular
Next look in ServerCharacterMovement.cs where these
NetworkVariables are modified on the server:
The first thing to point out is that this is done inside
FixedUpdate() with Boss Room's
FixedTimestep set to 0.02s. This setting can be found in ProjectSettings > Time. This is the maximum possible frequency of updates of position and rotation. We highlight this because a server will not send a
NetworkVariable update if the value is unchanged.
Next take a look at client-side code, which interprets what to do with this
A PC's position and rotation are modified inside the
Update() method of ClientGenericMovement.cs:
This is what we refered to as a "dumb terminal". Position and rotation data are applied as quickly as the client can render them.
"Graphics" smoothes the transition of valid position and rotation data. The ClientCharacterVisualizaton.cs component is attached to the "Graphics"
The Graphics' transform is modified inside the
Update() method of ClientCharacterVisualizaton.cs:
VisualUtils.SmoothMove(...), which is a "Graphics" transform:
The Graphics transform moves according to the following:
- For position syncing, it moves in the direction of the PC transform by an amount that is proportional to the distance between the two transforms, until the positions are identical.
- For rotation syncing, it rotates towards the PC transform by a minimum amount every frame, until the rotations are identical.
As mentioned, these calculations introduce additional latency. However, interpolation is what effectively masks jitter and makes the player movement "feel" smooth.