When we talk about types of servers in multiplayer games, we usually find two abstract classifications. One allows you to centralize the control of all, or at least most of the core logic in a game, and the other lets you manage things in a more decentralized and independent way. Although each of these classifications can have many types of servers that branch off of them, at the very core, they still follow certain rules and guidelines that classify them under one of these two.
An Authoritative Server allows you to control most aspects and logic of the game from a centralized and dedicated application, a dedicated server. Although you can also have a dedicated server in a Non-Authoritative approach, they manage things in a very different way. They both have some pros and cons and how you choose which one is right for your application is more tied to the type of game rather than how you want to do things, more on this later.
THE SERVER IS THE PUPPET MASTER:
The server manages almost all of the game logic. From the physics and time scale of the game, bullets and changes in the scene, everything about the characters, and even the invisible states of various things in our game. Basically anything that can have any type of change over the duration of the game. Leaving all the responsibility of the functionality to the server and the rendering and audiovisuals to the client.
The Clients are the puppets:
Since all the logic is being managed by the server, the clients act like puppets waiting for the next update from the server; this includes the general state of the game and all its moving parts, and the next actions to be performed by the clients, meaning: animations, sounds, etc. As stated above, the server is the brain, and the clients are the audiovisual representation of the game.
The server also has the role of the central communication node. Every message sent over the network between every computer connected to the game, goes through the server. i.e. If a user wants to send an in-game private message to another user(if supported), this message has to go through the server first and then re-routed to the target user. Therefore, there is no peer to peer communication.
Data and message validation:
Since all the messages go through the server, we have to validate all the messages and data being communicated. This makes it much more difficult to cheat in our game, since every action is being double-checked by the server.
Now onto more technical details of Authoritative Servers. I’m sure you already know by now, that the server code is completely different from the client’s code. Just wanted to make sure that was clear.
I’ll start by listing the things we need to manage on the server side: List of users, and list of movable objects. Simple right? Not exactly, each of these objects can contain a variety of attributes. It could be beneficial to put the objects into their own separate lists.
Lets take a MOBA for example, there are users that connect to the game to play, but there are also users that spectate the game that have no specific rules on what they’re allowed to see, or they don’t even have a character assigned to them. Now we have 2 lists of users, one for playing users and another for spectating users. On this note, characters are a type of movable object, but this is a very special type of movable object that we want to have easily accessible for faster indexing and easier management. Lets say we also have Bullets, this is a very special type of list that can grow and shrink very quickly and very often. Why is this so special? Well, we might want to put just the bullets in a separate list and have them object-pooled. The same applies for minions. All these things that are “volatile” and recurrent can be put into separate lists just to optimize performance by pooling them.
We may want to have as many specific lists as possible, from which we can check things against. But we don’t want to have way too many lists that it’s actually difficult for us to keep track of which list contains what. A good rule is to have one list for every type of object that we want to be pooled, a list for players and a list for spectators.
In our MOBA example we could have the following lists: players, spectators, minions, jungle monsters, bullets, abilities. It is important to note that each character in a game, whether it’s a player or a minion, can have within itself a list of buffs, debuffs, states, and so on. This is more related to how you inherit your classes and manage your architecture. For instance if you have a Buff-Manager, you might want to have a list just for buffs and have them reference both the source and target players in order to manage these on their own.
When thinking about the Server try to always think about performance, try to optimize as much as you can while trying to keep things organized. When things go wrong, you’ll benefit from having an elegant, readable and organized code. Even though design patterns are considered a good practice, most of the time, please don’t over use them. Design patterns tend to generalize too much on how things are executed and this could incur on some big performance costs.
Even if you use all the design patterns that you could use in your game, it might still work pretty fine. Then, why do I say this? It’s all about costs! Lets say you finish your game and now you are in the process of launching your game to the masses (Yay!). You will most likely , and I recommend that you do, be using a cloud service solution for hosting your servers. You will have to perform a load test to see how many games can run simultaneously on each server. Sadly it’s never as many as you would expect, specially if your code is not optimized for performance! You’ll eventually find yourself trying to optimize your game just to reduce costs instead of investing that time and effort in developing newer and cooler features to expand your game.
Many times I see people overusing design patterns even when they’re not necessary. I personally never understood this, I will write a post about the abuse of design patterns in the future.
There are some other optimizations that can relieve the server from doing all the work. These are the things that are not necessary to keep centralized, that each client could validate without risking the game to be easily hacked, since the server will always check the validity of actions.
Managing of the direct inputs from the users is one. Instead of sending an array of bits representing each of the user’s keys, the client can validate each key-press and decide if it’s allowed to perform the action. A message will then be sent to the server and will then be re-validated. This not only reduces the load on the server, but also reduces a great amount of messages and data sent by the clients. From sending a big message per “Tick” ,containing the state of all the user inputs (per user), to sending a message on specific events containing only the action to be performed.
Another optimization would be to code your own socket communication library, and optimize the messages to send a smaller stream of bytes in comparison to the already developed serialized version used in most game engines. If you’re using Unity you’ll most likely be using RPCs for your message communication, and while it’s a great and stable solution, coding your own library will not only make things a little faster, but it will also grant you a vast amount of knowledge that you cant learn by just making games.
DO NOT – I would highly discourage the use of the state synchronization that comes with the Unity’s network communication solution. Although it has greatly improved a lot over the years, it still has a lot of overhead when sending data, unnecessary data sometimes. Two years ago I ran a Network stress test to compare Unity’s State synchronization against just using RPCs for communications. You’ll be amazed at the difference in performance.
The test was to have 30 people shoot as fast as they could, the bullets would last for 90 seconds, the bullets would move at different speeds and accelerate or decelerate (I was checking the Doppler effect on sound within Unity). Let’s be conservative and say that on average every player was doing 220 clicks per 90 seconds (2.4 per second), these were all gamers btw. So if we do the math, it comes up to almost 6,600 movable objects having their position, speed and acceleration updated to the server, a full Vector3 for each of these. I’m sure it was way more than that, I’m just being conservative. The RPC server had no issue whatsoever keeping up with this, while the state synchronization couldn’t even get 15 people to click over 20 seconds before the lag was noticeable and it started to choke. That should at least give you an idea on the performance differences.(BTW, the Doppler effect in Unity is pretty awesome, it worked surprisingly well)
What about the Client? As you might imagine, taking care of the client is not as confusing as it can be with the sever. We still have to take care of pretty much the same lists, but with a lot less logic involved. Receiving the messages that update the objects in these lists is pretty much all we need to do to complete the communication process. The rest of the audio-visual stuff is in your hands.
Remember, the server can actually send the client a message when to do an action like cast a spell. Hence, when you receive that specific message, you can start the character animation and spawn some particles here and there depending on positions and spawn the spell object that is being casted. I’m trusting you on knowing what to do in these cases. Please ask if you need some help with this, I’ll be happy to help.
Although it’s great to have everything as organized and centralized in an Authoritative Server, this all comes with a cost. The responsiveness on this type of system is slower, and it can get extremely slow, depending on distance, quality of connection, wifi, and many other factors.
In games that depend on fast reaction time like an FPS, this can make them feel unresponsive. Enter Non-Authoritative Server, we will discuss this in the next article.