In the previous article I demonstrated how latency impacts the smoothness of the gameplay experience. The deterministic part of the game simulation i.e. the asteroids are not affected as much due to the fact that their movement will follow a predictable course. The player movement however, is very unpredictable with the jittery effect being magnified by frequent player movement changes.
The way to correct the snapping of networked entities is to make use of some kind of smoothing technique. The simplest of these is to make use of Linear Interpolation. Here are some links to articles I found useful.
http://gafferongames.com/game-physics/networked-physics/. This article is really good with a LOT of useful information in the comments section.
This Gamasutra article contains a good general description of dead-reckoning.
The Source engine articles from Valve are also really useful.
You will notice that this code makes use of the ideas from Shawn Hargreaves’ Blog entry.
In my previous articles I showed how I keep three versions of the EntityState (Position, Velocity, Rotation) for each of my sprites. The reason for this is so that we can separate the display state from the simulation state. The simulation state will be used for all game logic decisions such as collision detection. The display state will be used for rendering.
In order to perform linear interpolation we will be interpolating between the previous display state (where we last rendered) and the simulation state (where the object currently is). A linear interpolation requires the from state, to state and an interpolation factor. I’m sure there are sophisticated techniques for determining the interpolation factor. My calculation is based on the fact that by default XNA calls the update method 1000ms / 60 = every 16.67ms. I reasoned that I would like the display state to reach the simulation state after 200ms meaning I would need 200ms / 16.67ms = 12 updates to achieve it. The resulting interpolation factor is therefore 1 / 12.
The following code shows the Update method for all my sprites.
First I get the elapsed seconds since the last update. This value is used to advance the sprite position by it’s velocity.
The FrameTimer code is just looping between the animation frames every 200ms.
Next we use the elapsed time to update the SimulationState of the entity.
If smoothing is disabled then the DisplayState is equal to the SimulationState.
When smoothing is enabled we need to apply smoothing. Firstly we update the previous display state position based on its velocity and the elapsed time. Then we apply the smoothing based on the interpolation factor as described above.
Using the Vector2.Lerp method our DisplayState values now become the interpolated value from the PrevDisplayState to the SimluationState according to the interpolation factor.
Lastly the PrevDisplayState is updated to the current DisplayState. The above code will enable a smooth linear interpolation of the DisplayState to the SimulationState within 200ms.
I have modified the Draw method to render the SimulationState when smoothing is enabled. This is purely for debugging purposes and will be disabled during actual gameplay.
The HandleUpdatePlayerStateMessage method in the game class is now as follows.
No major changes, except for the fact that smoothing is now enabled. I had some code referencing the PrevDisplayState. This was a bug and has been removed.
I played around with the idea of ignoring LOCAL player state updates. The effect of is a 100% match between the DisplayState and SimulationState for the local player with no snapping. The only issue is that the player will never be updated with the server values which could lead to some inconsistencies. I did however, reduce the player update heartbeat to once per second, reasoning that I could tolerate local player adjustments in high latency conditions every second.
The following video shows the effects of enabling player smoothing.