Unity Authoritative Server Part 1

2 years 9 months ago by damagefilter

NOTE: This is a copy of the original text and might lack photos and proper formatting. I'm leaving it here for reference and will eventually come around to fix these articles again!

Hi folks!
The last couple of weeks (relative to the time of writing this) I was scanning the internet for some useful and compact resources about making authoritative servers with the Unity 3D networking API. I was positive about it as there is more than enough and really awesome bundled documentation for all facettes of Unity. Not for networking though. It is shed all over the web and much of it is contradicting itself and you never know what really is right and what is wrong. So I went and read pages upon pages of generic stuff aswell as Unity documentation, watched tutorials and scanned the Answers board for every piece of information I could get to form my own opinion on things. In the end I wrote this documentation. Please enjoy and remember, those are not "tutorials" and I'm not a professional :)

There is one tutorial that pushes you through a bunch of ready-made code and shows you briefly how things work, which is okay but really, not what I was looking for. It was helping me to figure stuff out, but I was wishing for something more in-depth. It is a great piece of information, nevertheless! There is also this video which is a very good introduction to Unity networking.
I highly recommend watching it: Introduction to Unity Networking
(I also recommend subscribing to the Tornado Twins on Youtube, their Unity stuff is awesome. (Aswell as their music stuff))
However, the aforementioned video does not introduce the concepts of authoritative servers. It's really great to get you started with the basic ideas of Unity networking but again, not exactly what I was looking for. So in the end I found myself just collecting pieces of information from countless forum threads, filled with discussions about all sorts of things, mostly Do's and Don't's for the API which greatly deviated from each other in every thread. So I decided I should write down my own thoughts and perhaps some people find it useful. However, I want you to know that this is no walk-through. I encourage you to go and play with the code I provide and form your own thoughts and ideas out of it. Also, what I write may not be 100% correct as, again, I'm not a professional network guy. Feel free to research and correct me if I'm wrong. You can contact me via my twitter handle, @damagefilter.

So lets get down to the matter by fleshing out, what exactly I understand as authoritative server (you know, that term is vague and implementations are various). So if you're not looking for what I describe, then you may look somewhere else or still read on to find inspiration.
An authoritative server is ... :

  • ... the master, what it dictates, must happen
  • ... doing all the game logic and communicates it to the connected clients
  • ... validating data the client sends to it to minimise cheating possibilities
  • ... not a player - it must be dedicated to compute the clients logic

Now that we have the "specifications" for this thing, lets start thinking about how to do this.
Here I must say, I saw a lot of scripts that relentlessly combined client and server code into one file without separation at all. That's not going to happen here. We'll put client code into its own script aswell as server code, paste them both onto our networked prefabs an be happy with clean code.
I'm not going to make you use my directory structure, however I'll be using it and if you happen to deviate, you should adjust those parts accordingly. So for my script's I'll have:

  • client - Contains all the code that is executed on the client
  • player - Contains player-specific code (requests to server etc)
  • server - Contains all the code that is executed on the server
  • player - Contains player-specific server-side code (processing client input requests etc)
  • shared - Everything that can be happily used on both ends of the Network and non-networked things

As another convention I am going to follow is prepending "C_" to client-side classes so it's clear what is inside them. For small-scale projects that might seem a bit unnecessary but just in case your codebase grows larger than expected, it sure helps a lot.
Another thing: I'm going to use UnityScript (that means not C#) for this - however I am positive that it will be absolutely no problem to adapt that to C# - not sure about Boo. I'm also going to violate a bunch of conventions so if you're a strict follower of those, please forgive me my "Javaness".

Right I'm done with my talk, lets start coding!
We will start with a basic player movement control - just to demonstrate how the whole authoritative thing is done - also the network manager we're going to write later depends on it. At first create a new script in src/server/player, called PlayerManager. This will be the server-side thing that controls player movement. As we already concluded, the server does all the brainy work so here's what we write to make a player appear to be moving. In my example I expect a top-down camera and my player is a floaty one, that does not have gravity. You may change that according to your movement logic.

#pragma strict

@RequireComponent(NetworkView)
class PlayerManager extends MonoBehaviour {
    
    public var speed : float = 10;
    
    private var controller : CharacterController;
    
    private var horizontalMotion : float;
    private var verticalMotion : float;
    
    public function Start() {
        if (Network.isServer) {
            controller = GetComponent(CharacterController);
        }
    }
    
    public function Update() {
        if (Network.isClient) {
            return; //Get lost, this is the server-side!
        }
        //Debug.Log("Processing clients movement commands on server");
        controller.Move(Vector3(
        horizontalMotion * speed * Time.deltaTime, 
        0, 
        verticalMotion * speed * Time.deltaTime));
    }
    
    /**
     * The client calls this to notify the server about new motion data
     * @param   motion
     */
    @RPC
    public function updateClientMotion(hor : float, vert : float) : void {
        horizontalMotion = hor;
        verticalMotion = vert;
    }
}

In case you're wondering: This is the (for me) proper way of writing UnityScript. I have the impression not many people use it so I thought I mention it in case anyone is confused.
You see, we require a NetworkView component to be attached to the GameObject this script runs on. In case you're not familiar with those things, they are the connection between the client and the server end of the network, and serve two purposes.
Observer and update data between client and server
Enable GameObjects to use RPCs (I'll explain later on, hang in there)

In the Start() method
you can already see the first implication of the authoritative server. Only if the server end starts (Network.isServer) we assign the controller. You could go further and say if Network.isClient disable the controller component for the client. Just to make sure.

The updateClientMovement(...) method is marked as @RPC.
That is a flag, some may call it annotation (Java, anyone?), that makes this function available for network calls. That means the client can call this function on the server end, in this case to notify the server of the human input. I will show you which input, as we're now going to do the client side of the PlayerManager. It's important to note that if you want to use RPCs (Remote Procedure Calls) your GameObject needs to have a NetworkView attached.

What is an RPC?
A Remote Procedure Call is a networking method of communicating between client and server. In our case, we would like the client to update the server on the player input. Since the server is somewhere else we cannot just do (example) Server.updateClientMotion(h,v); as that would update the server on our local machine. And that is not only weird, it doesn't make sense at all.
That is why we have RPCs. We can then ask the attached NetworkView to issue an RPC for us and the other end will receive it along with the data we have provided.
For more information about RPCs, have a look here and here. It's worth a read.

For the client side, we make a new script in in src/client/player called C_PlayerManager (or however you feel like calling the client-side script for this). It should contain something like this:

#pragma strict
@RequireComponent(NetworkView)
/**
 * Client-side PlayerMovement implementation, only send
 * motion data to the server.
 */
class C_PlayerManager extends MonoBehaviour {
    //That's actually not the owner but the player,
    //the server instantiated the prefab for, where this script is attached
    private var owner : NetworkPlayer;
    
    //Those are stored to only send RPCs to the server when the 
    //data actually changed.
    private var lastMotionH : float; //horizontal motion
    private var lastMotionV : float; //vertical motion
    
    @RPC
    function setOwner(player : NetworkPlayer) : void {
        Debug.Log("Setting the owner.");
        owner = player;
        if(player == Network.player){
            //So it just so happens that WE are the player in question,
            //which means we can enable this control again
            enabled=true;
        }
        else {
            //Disable a bunch of other things here that are not interesting:
            if (GetComponent(Camera)) {
                GetComponent(Camera).enabled = false;
            }
            
            if (GetComponent(AudioListener)) {
                GetComponent(AudioListener).enabled = false;
            }
            
            if (GetComponent(GUILayer)) {
                GetComponent(GUILayer).enabled = false;
            }
        }
    }
    
    @RPC
    function getOwner() : NetworkPlayer {
        return owner;
    }
    
    public function Awake() {
        //Disable this by default for now
        //Just to make sure no one can use this until we didn't
        //find the right player. (see setOwner())
        if (Network.isClient) {
            enabled = false;
        }
    }
    
    public function Update () {
        if (Network.isServer) {
            return; //get lost, this is the client side!
        }
        //Check if this update applies for the current client
        if ((owner != null) && (Network.player == owner)) {
            var motionH : float = Input.GetAxis("Horizontal");
            var motionV : float = Input.GetAxis("Vertical");
            if ((motionH != lastMotionH) || (motionV != lastMotionV)) {
                networkView.RPC("updateClientMotion", 
                                RPCMode.Server, 
                                Input.GetAxis("Horizontal"), 
                                Input.GetAxis("Vertical"));
                lastMotionH = motionH;
                lastMotionV = motionV;
            }
        }
    }
}

There is a private NetworkPlayer owner here.
Right now, it should suffice to know that we use it to track GameObject-Player relations so we can identify what belongs to who.

@RPC calls setOwner/getOwner
Those are called from the server as the client has no authority to determine what it is owning. The required data will be provided by the Netman which we will write later on. Optionally you could add a check if Network.isServer when calling those methods to make sure no client can actually use this. Although, logically, this should never happen.

More about RPC calls
There is a lot of crazy debating going on with those things, really. Unity provides overrides for it of which only one should be used for reasons that no-one seems to be able to explain but everyone does it without asking why. The discussion is about using only targeted RPCs, that means only a specific player will get them. However, you don't always have that data at hand (you could make sure you have, I know) and those calls are NOT buffered that means the resulting data is not available for "late coming players", except you implement your very own buffering mechanic. It's up to you to decide, I guess. In case you think it's okay to use Unity's buffer, you have a couple of different buffer modes. Here's an excerpt from the ref manual:

  • Values
  • Server Sends to the server only
  • Others Sends to everyone except the sender
  • OthersBuffered Sends to everyone except the sender and adds to the buffer
  • All Sends to everyone
  • AllBuffered Sends to everyone and adds to the buffer

setOwner in detail:
As you can see, there's some disabling/enabling action going on in setOwner. Now this might be a bit tricky to understand but let me try it this way: If we're not disabling the GameObject's camera when it's not yours, then Unity will start freaking out on two (or more) active scene cameras, and you don't want that.
So only if you are the owner of this object (Network.player == player) you need all those things, if not, disable them. That state is exclusive on YOUR machine, another connected player will have the camera on your prefab disabled but active on his. ... Yeah that does make sense, does it not?
That leads to the ultimate assumption that Network.player represents the client that is currently processed on the script.

Awake()
Here we disable our player manager script to ensure only the real owner can use it and no other clients. It is enabled again in setOwner() (see above) if the player in question is you.
If you feel like keeping it enabled, you will notice that every connected client will start to be able to move every game object at any time. Duh!

Update()
Here we're using an RPC call to the server (updateClientMotion()) to send the server updates about the movement input.
You may adjust those according to your required input data. Also, we're missing prediction here but I will cover a very basic prediction method on a later occasion.
Note: the RPC that is called resides in the PlayerManager class and updates input data on the server. We discussed that earlier (scroll up!)

If you were reading carefully you'll have noticed that we're now doing updates that sends data from client to server but there's nothing that tells the client the positions the server has calculated. At least it looks like it. In fact, we already said we require a NetworkView component to be on the GameObject in question. I also mentioned already that you can observe something with this component and that's what is going to make the clients see the new movement. The NetworkView will observer the players Transform component on both sides (server and client) and will synchronise the states (State Synchronisation). This is how it might look like:

I think that should clear the fog a bit.
Let me just tell you a bit about the "State Synchronization" property. It contains three values you can choose from.
Reliable Delta Compressed - This will transmit data only if data actually has changed
Unreliable - This will always transmit data, regardless of if it has actually changed
Off - This is used, for example, if you just want to fire/receive RPCs on that GameObject.
In most cases you want to use the RDC method, however this poses a little bit of an overhead as (as the name suggests) there is a check of how much the data has actually changed before it is sent.
If you happen to work on a crazy fast racing game or if your player is always moving (perhaps as part of the gameplay) there is no need to check if data hs changed because you know it has. In such cases it is advisable to use the Unreliable method.

And this is it for this article. If you want to read more please look down at the related content part, there should be (or should soon be) a link to the next part, in which we're going to write the network manager. It will be a tricky thing as I try to keep things generic and easy for you to customise.
However, feel free to already experiment with your ideas. Here are some hints:
Sending connecting clients data directly can cause trouble
Scene loading must be #1 priority in the connection process
The client should not be allowed spawn itself a player prefab instance

Thanks for reading.