This is my try at a simple authoritative server: where the clients send their commands to the server, the server decides where everyone moves to on the map and sends back the location of all clients to everyone. There is no delta compression when serializing, no client-side prediction or server reconciliation, no lag compensation. In other words: very very simple.
In the picture you can see red capsules (on the Host) that show where the server says everyone is. We do our physics movement on these objects.
The blue capsules are where the server is telling clients everyone is. They have no physics associated with them, they are just moved to the locations received in a message every 1/20 seconds. Notice that the client window has no red capsules: this is because it is not a server and is not calculating where everyone is.
There are six scripts in this project.
Defines the PlayerCmd type which is what the client sends to the server (mouseButton0, horizontal, vertical, jumpButton)
Defines the PlayerState type which is sent from the server to the clients (List of clientId, position, rotation)
Defines PlayerObjectDictionary which is how a server keeps track of all non-networked red capsules and how clients keep track of non-networked blue capsules
There is code in here that explains to MLAPI how to serialize PlayerCmd and PlayerState over the network
The client will save inputs to PlayerCmd every 0.01 sec. After it has saved 5, it sends out an array of PlayerCmds to the server (every 1/20 sec)
Server code is in this file as well. It shows the server saving a client PlayerCmd to a dictionary (uses clientId as a key)
This is where the server has a bunch of fake red gameObjects representing clients that it moves around. It is using Unity’s CharacterController to figure out where the objects move. It uses input from each client’s PlayerCmds array.
After 1/20 sec has passed, the code sends out a PlayerState to all clients which is a List of the locations of all clients.
This is where the client receives the PlayerState List from the server. It will take each PlayerState and create a fake blue gameObject that represents that client. It simply Lerps each transform to the location reported by the server.
This is attached to the fake blue gameObject that ClientPlayerObjects creates that shares our clientId.
Displays a menu and bytes sent/received. Note the bytes sent/recv seems to be broken in MLAPI?
The Issue of Networking
The client records user input every 0.01 sec to a PlayerState. Every 0.05 sec it sends this size 5 array to the server. Perfect world: the server takes index 0 and moves the red client object between time 0.0 and 0.01 sec. Between 0.01 and 0.02 it uses index 1, etc. The server sends a record of where every red gameObject was calculated to be to the clients every 0.05 sec. We are using MLAPI and transport Ruffles. We can turn on the Simulator and make it so PlayerState doesn’t get to the server all the time, that messages are delayed, etc. So what we do on the server is (currently) use index 0 between time 0.0 and 0.02 sec (double!), then use index 1 from 0.02 and 0.04, index 2 from 0.04 to 0.06. We still have 2 more PlayerStates we can use. Hopefully by this time a new PlayerState set has arrived from the client. If it hasn’t, we will continue to use the last index 4 to move our red client object. Networking is hard.
Pretty much the same as the Simple MLAPI Test, with these differences:
Window > MLAPI > Transports > Ruffles > Install Latest GameObject > NetworkingManager > Select transport... > RufflesTransport NetworkingManager > Create Player Prefab > Off (We are not using any NetworkTransport) Ruffles Transport > Log Level > Warning Ruffles Transport > Simulator > Use Simulator (If you want) Layer > Add Layer... > Added "Server", "Client", and "Local" Edit > Project Settings... > Physics > Uncheck Server/Local, etc. (We don't want Host w/ client and server obj to interact) Important: Attach a Player prefab to ClientPlayerObjects > playerPrefab
There’s a lot wrong here. I really struggled with how Unity organizes things vs. a regular C# project with classes. The hardest part of this small project was just trying to organize each script in a logical way! I have a CustomTypes which is used by two other classes but does nothing itself, really. I have a ThirdPersonCamera that needs ClientPlayerObjects to set what it points at. I have HandlePlayerCmds that remembers (on server) what all client input was, and that’s used by ServerPlayerObjects. I have classes exposing “static public” variables for ease of use.
For the networking memory usage, we create a new List every time we send out/receive PlayerState. Create a new array of PlayerCmds when those come in. We constantly send PlayerState even if the player is standing there. This stuff really bothered me.