Overview & Purpose
This is a piece of the network code that I handled on Little Arm Studios' Interactive Virtual Incident Simulator. The sample demonstrates server authoritative structure, prediction, and interpolated observation of non-player entities. The system reduced the amount of boilerplate code for networking the various gameplay systems properly. This was needed because our team was crossing wires while implementing different systems. The solution enforced consistency across the programming teams network code, increasing readability and reducing debug time.
Server authoritative structure is important for having consistent and fair gameplay across all players. Player’s see an approximate version of the world and use this approximation to try to interact with the real world. The server has complete authority over the world, and is considered the true version of the world. The only thing the player has authority over is the input’s they send to the server, and even that has restrictions. Server authoritative networking has a very specific order for how players interact with and observe the world.
First, the player attempts input based on the imperfect information they have locally (the information is imperfect because of network latency). The inputs get applied locally by stepping these inputs. Stepping is the process of applying one cycles worth of input to a state, progressing it forward by one cycle. The inputs are then sent to the server.
The server, which is considered to have perfect information because it has authority, steps using the players inputs. The stepped inputs generate a new state for the objects in the world including the player. These states are then sent back to all the clients.
When the clients receive states from the server they delay applying the states, building a state queue, so they can interpolate from older states to the new states. This allows the states to look smoother and makes latency less noticeable if there is variance in network speeds, or packet loss. This is known as observation. This interpolated state will only appear correct if the player has not tried to interact with these objects.
When player's receive states for their character, the things they interacted with and the things other player’s in the game have interacted with, the local world will try to apply it. However, the states received for the player’s character and the objects the player interacted with are old. By this point the player has generated more inputs and sent them off to the server. To counteract this the inputs that have not been applied by the server yet are also stored locally and applied on top of the latest state the player has received from the server. This allows them to play without feeling a delay on their inputs. This is known as prediction.
My implementation uses Unity's High Level Networking API (HLAPI) to handle some base network functionality (sending commands and remote procedure calls RPCs). Commands have to all be called from one instance of the player, so the system hinges on the player passing inputs as a parameter for the servers command function.
The implementation for Unity's networking code does not allow the user to pass generic information over the network. The signature must be explicit for command functions. The information also can only consist of basic value types and some Unity types. For my implementation, I convert all of my class information in to a string and send them. In order to reduce some of the networking cost, we use custom serialize and deserialize functions per class.These limitations ended up being more useful than harmful though. It helped define the structure for how the dev team would pass information over the network.
One of the best limitations is that inputs are limited to the player. This by default enforces some server authoritative structure on non-player entities. Programmers can not call commands from non-player entities asking the server to change the state of the non-player entities. These entities should only be able to receive state updates from the server because the server has authority over them.
When someone needs to change an object's state they have to isolate the state change to the objects step function. The only way to interact with an object is through an interact function. The interact function is only called using player input. This enforces that only the player does things locally. The non-player objects are predicted and observed based on logic in the non-player-network-object class.
The non-player network object class implements the server authoritative structure for the programmers trying to implement game logic. It reduces network logic down to, what does it do, when is it being interacted with by player input, how does this object interpolate from one state to another and what happens to the object when states are applied. Classes need to inherit from the non-player-network-object class, implement the proper functions. Then they need to create a state class that inherits from the network non player object state class and carries all vital information to an object's state in the world.