BIT Nurse - Networked Gameplay Programming Consultant

Basic Networking

Client-Server Setup

Client is the game-version on the players computer.

Server is the game-version that is run separately and connected to through a network or internet.

The client connects to the server and there is a 2 way communication between them via different procedures.

 

The Server does not need any graphics since it is not an active player and should be run without a camera in console mode or batchmode without any window at all to interact with.

Having graphics, particle systems, etc. on the server is a waste of CPU that can be used to handle more players in your game.

Networked GameObjects

Server = GameObject on the Server.

Client = GameObject on Your computer.

Proxy = Your GameObject on Others computers.

Networked GameObjects have a Network-View Component on them that share an ID through it's different versions (Client, Server, Proxy).

That way the messages sent from the Client GameObject finds the correct GameObject on the Server that receives the messages.

GameObjects that are synced over the network should run different scripts depending on where they are used, it's not good practice to have the server-code and the client-code in the same script using only one GameObject. Split it up so that the server version of a GameObject only have the code that it needs to function as it should on the server, same goes for the client and proxy versions of all networked GameObjects.

Player Movement Example

Your Player Character is directly controlled by you, the player, therefore it needs to have code to receive input for movement from your keyboard.

While the proxy version of your Player Character that other players see on their screens only need to have code that receives your position and rotation to make it sync with your movement.

The server version of your Player Character need to receive the input from the client-version and to check for illegal movement done by the client.

Combat Example

Spells are initiated by the player but not controlled after initialization.

Therefore the same code can often be used for both the Client and Proxy version, they will both use the same Hit-Prediction for example.

The Server version of the Spell uses a Hit-Detection system and it communicates with the players stats-system that is present on the server only.

Stats on the client-side is just a number that gets synced by the server, the clients have no control over the stats.

Best Practice is to use three different versions of all GameObjects that are to be synced but some can work with two versions.

An important part about splitting up GameObjects this way is that you can take away all graphics and extra stuff from the server versions, that way your server will be able to run cheaper, smoother with less loading times and it will be more stable.

RPC, State-Sync / BitStream

There are a couple of different ways for the Server and Clients to talk with each other over the Network.

RPC (Remote Procedure Call)

An RPC is about the same as calling another method but over the network, unlimited variables can be sent in an RPC but they will have a large effect on the network traffic (be smart when choosing what to sync and how often to sync).

Both clients and the server can send RPCs but on Authoritative Servers, clients can only send RPCs to the Server, never to other clients.

RPCs can be sent to Specific Clients or to All (including the sender) or Others (excluding the sender).

RPCs can be buffered, a buffered RPC will be stored and when a new client connects that client will receive all the RPCs that has been buffered.

This is a good way to sync values that are only changed ones or very infrequent, like which GameMode the Server is running.

RPCs can be sent Reliable (default) or Unreliable.

Reliable RPCs will all be delivered, it's checked via a callback to the server which adds slightly to the network traffic and server load.

Unreliable RPCs have no guarantee that they get delivered.

An RPC is formed like this:

 networkView.RPC("Name", uLink.RPCMode.OthersBuffered, "Variable1", "Variable2"); 

 "Name"  = The method-name of the RPC in String.

RPCMode.Server = This RPC is only sent to the Server.

"Variable1" and "Variable2" = Strings, int, float, etc.

Code Example - RPC (Remote Procedure Call)

Code written in C# with uLink as the Network Library.

Server Code - Buffers an RPC with variable telling which GameMode the Server is running.

 void uLink_OnServerInitialized() // Called when the server is initialized and ready for connecting clients. 

 { 

    networkView.RPC("SetGameMode", uLink.RPCMode.OthersBuffered, "Battle Royale"); 

 } 

Client Code - Receives RPC on Connection to Server and sets GameMode.

 [RPC] // This declares an RPC Receiving Method 

 void SetGameMode(string gameMode) // "Battle Royale" is received in string gameMode. 
 { 

    // Set the Game Mode State to run on the Client 
    if (gameMode == "Conquest")                { gameModeState = GameModeState.Conquest; }  
    else if (gameMode == "Battle Royale") { gameModeState = GameModeState.BattleRoyale; } 
    else if (gameMode == "Invasion")         { gameModeState = GameModeState.Invasion; } 
    else if (gameMode == "Survival")          { gameModeState = GameModeState.Survival; } 
    else if (gameMode == "Arena")              { gameModeState = GameModeState.Arena; } 
 }

State-Sync / BitStream

While RPCs are good for sending infrequent variable-changes and to call methods over the network,

State Synchronization is perfect for sending variables that change very often between Server and Clients.

State-Sync is done using a BitStream that is linked between the Networked GameObject on the Client, Server and Proxy.

State-Sync can be sent Reliable (default) or Unreliable.

Reliable RPCs will all be delivered and in the correct order.

Unreliable RPCs will be delivered in the correct order but there is no guarantee they get delivered.

On Authoritative Servers only the Server can send State-Syncs, if a Client want to sync a variable an RPC needs to be sent to the Server.

The BitStream for each Network-View Component can either Write or Read at any given time, never both at the same time.

This shapes how we write the code to send and receive State-Sync.

This method is called by the NetworkView to run a BitStream with custom variables.

 void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { Code } 

The method is called with a fixed interval, that is set by  Network.sendRate = "int"; 

Recommended SendRate is 15-25 but don't change this value lightly, it has a Huge effect on network performance!

Note also that State Synchronization and BitStream is done slightly different depending on which Network Library / Solution you are using, but this is to show the idea and the general function behind it.

Code Example - State-Sync (State Synchronization)

Code written in C# with UNET as the Network Library.

Server Code - Buffers an RPC with variable telling which GameMode the Server is running.

 private int healthMax = 100;

 private int healthCurrent;

 private int manaMax = 100;

 private int manaCurrent;

 private bool bleeding;

 // This method is called when the GameObject has a NetworkView Component and it's called at the rate of Network.sendRate = "int".

 void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
 {
    if (stream.isWriting) // The BitStream can either be in Writing or Reading state, when in Writing, run this code.
    {

        int healthRef = healthCurrent;

        int manaRef = manaCurrent;

        bool bleedingRef = bleeding;

        stream.Serialize(ref healthRef); // write health stat to BitStream
        stream.Serialize(ref manaRef); // write mana stat to BitStream
        stream.Serialize(ref bleedingRef); // write bleeding state to BitStream
    }

 }

Client Code - Receives RPC on Connection to Server and sets GameMode.

 private int healthCurrent;

 private int manaCurrent;

 private bool bleeding;

 void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
 {
    if (stream.isReading) // The BitStream can either be in Writing or Reading state, when in Writing, run this code.
    {

        int healthRef = 0;

        int manaRef = 0;

        bool bleedingRef = false;

        // NOTE: Read in the same order that the variables are Written!

        stream.Serialize(ref healthRef); // read health stat to BitStream
        stream.Serialize(ref manaRef); // read mana stat to BitStream
        stream.Serialize(ref bleedingRef); // read bleeding state to BitStream

 

        healthCurrent = healthRef;

        manaCurrent = manaRef;

        bleeding = bleedingRef;

    }

 }

Authoritative vs. Non-Authoritative Server

If the server is Authoritative the Client is not allowed to tell the server what to do, unless you actively allow it yourself.

A non-authoritative server allows each client to change their variables as they please.

Both are good for different games, you cannot switch between them so you need to choose one of them and design your game around that system.

 

Authoritative Movement Sync

The player presses forward, clients sends the direction it want to move in to the server, the server then moves the Server Player in that direction and then sends the new position to the client and proxy of the player that then move to that position.

This eliminates all possibilities of cheating, but creates lag since when you hit forward that information need to be sent to the server and then the new position need to be sent back again before your player starts to move.

This can be addressed with a client-side prediction system and an advanced movement sync system.

READ MORE

Non-Authoritative movement sync

The player presses forward, the client stats to move forward and sends its new position to the server that both relays that information to the proxy and moves the server player to the specified position.

This leaves the game completely open for cheating and hacking by players, but in a non-competitive game or a coop game this does not matter as much as in a competitive PvP game where an authoritative design is to be prefered.