This is part 4.3 of the multipart Trap Labs Code Design and Architecture Series.
When I was learning about the different networking models it was immediately obvious to me that client and authoritative server model was the way to go. Considering how cheap VPS and cloud storage are today, there is pretty much no reason not to do this. It’s easier to maintain, scale and cheating resistant. The downside is that it’s more difficult to implement, as the game simulation and the game client needs to be effectively decoupled.
BUT! If you read the previous part on state based animations, then this is not more difficult to build at all! If you separate all your visual states into testable logic, then all you have to do is have the server send out state updates and the client will simply interpolate the visual state of the game. You keep the game logic on the server, and client is completely passive. And may I remind you that almost all of the code is unit testable.
Trap Labs uses 100% authoritative server, which means the client does not require client side simulation. This also means that all clients see the exact same thing on screen perfectly synchronized and does not need to be deterministic. For single player it’s actually running a local server playing with a local client. As a result, Trap Labs’ single player runs as two threads, the client thread (visual only) and the server thread (game loop), making it a naturally a multithreaded game.
Here are the components breakdown for server side simulation:
Here’s the breakdown of the updatable components:
ElementUpdater
, TrapUpdater
, EventUpdater
etc. I won’t list them all. In simple terms, each category of element or event has an Updater implementationIt’s important that you keep an Updatable
interface for all your updatables. This ensures you extensibility when it comes to adding new element types and events. This is especially important for events as communication between events can become very complex once you start adding various types of triggers. The event system will be discussed in more detail in the next subpart.
Remember the command engine from part 3? Trap Labs uses this exact engine as the underlying driver for all execution for both server and client. All operations from the game engine are in form of commands. This means things like simulation update and processing client inputs are in the form of commands. The command engine orders the incoming commands and buffers them until they are needed for execution.
Trap Labs’ simulation runs on a fixed time step, which means each loop update advances the same time step. I call each timestep a tick (this term is often used by FPS). If all clients have ticked for a particular game state, then the simulation continues. If a tick from certain client is missing the server waits. When the server waits too long it results in what is known as lag for all clients because everyone else is waiting for the lagging client to be synchronized.
The actual Update()
function for ServerSimulationUpdater
is quite simple:
Afterwards simply wrap ServerSimulationUpdater
in a StepSimulationCommand and fire away in the command engine as you receive sync requests from clients. Remember the command engine does the command delegation per client, so the server itself doesn’t have to worry about command delegation.
Similarly, I also have a InputCommand that processes incoming client inputs which I won’t go over (basically handle the input first then call Update()
on ServerSimulationUpdater
).
The client receives state updates from the server and needs to interpolate between two game states to update the game. Game states are received as the server sends them, and as long as there are two ordered states (doesn’t need to be consecutive) then interpolation can happen. Trap Labs interpolates the both physical and visual state of player and in-game elements, respectively.
The client’s components are made up purely of interpolation components. Trap Labs uses linear interpolation for physical states. In game character’s position and velocity is sent to the client and once the client has two ordered states it will be able to interpolate the position in between the states. I won’t go over how interpolation works because it’s fairly straight forward and well covered elsewhere. If need more information on how interpolation works in including some source code, then I would recommend reading Gaffer on Games series on video game physics and client side interpolation.
Visual interpolation is much simpler. Remember how the in-game element states are separated from their visual classes above? You can just send that state over network. Whenever the visual state changes simply play the appropriate animation as stated in the earlier part.
In case you’re curious how to inputs commands are injected into the client’s command queue: when the client receives an input (such as mouse/touch/keyboard) from the player, the input command is sent to the server as described above.
The result of client side interpolation is that the client is 100% passive. This means that the client does not need to run physics simulation to run the game. It simply looks at the game state offered by server and updates the visual accordingly. This means as long as you don’t have access to the server, cheaters can cheat all they want and it wouldn’t affect the server’s game state. At best the cheaters can increase their APM via bots, but that’s also easily detectable by the server side should such anti-cheating mechanic be implemented.
Some of the early feedback I got for the pre alpha build was that people keep chirping at me to add keyboard support. I was always very reluctant to do so for a very good technical reason. In order for Trap Labs to be perfectly synchronized for all clients there has to be an inherent input lag to compensate for the latency from all clients. For example, everything you see on screen is actually sometime in the past because of the network latency. In WAN gameplay, there needs to be a fixed input lag (say 200ms) in order for all clients to buffer up enough game states to interpolate the game. This input lag is easily compensated by player’s brains if the controls were purely clicks and touches. Most RTS games like StarCraft and WarCraft do this to keep the games synchronized. If I were to switch keyboard controls then the game play would be horrible because you’d expect the player to move instantaneously as you press the keys like most FPS, not 200ms after. The only way to fix this is to use client-side prediction.
I had a feeling that PC gamers were going to trash the game for not having keyboard/gamepad controls. So it was important for me to design the system to be able to extend client-side prediction as needed. What I ended up implementing in Trap Labs is a synchronized state sync system that is extensible to client-side prediction. I’ll make an update this discuss this topic further in the future.