In the previous post, we explored how building a server authoritative game can offer huge advantages to developers, including quick and easy control over in-game functionality, fraud and cheat detection/prevention, and providing reliability and consistency between connected peers. We also saw how using an authoritative server can help you to deal with unexpected issues and increase player retention and engagement. To learn more, you can read the full article here.
In the second instalment in this series on how to write a highly scalable, sever-authoritative game, we look at how to optimize the communication between client and server, helping to deal with poor network connectivity and scaling your game to deal with large numbers of players.
Don’t get chatty
As the old saying goes, it’s good to talk. But when it comes to client-server communication, talking is a necessary evil. Obviously your client and server need to pass data from one to the other, otherwise there’s no point in having a server. But keep it to a minimum.
Maybe you haven’t considered this to be a problem – but you should. Players won’t thank you for using up their broadband or mobile network allowance. And if it is a game running on mobile devices, they won’t always be connected to a nice fast broadband router – in extreme cases, they may have no mobile signal at all, a slow Edge network connection or be on a throttled public WiFi network.
Design your client so that it can cope if the network goes down – even wired home networks can suffer outages for various reasons. The worst case scenario is that your game is unplayable in such a situation. The best case scenario is that your player barely notices any difference. Wherever possible, build redundancy into the client so that if the network isn’t available, the game carries on as normal – any data that needs to be uploaded (e.g. player progress in the game) can be stored on the device and uploaded later when a network connection is available.
As a simple example, imagine your game stores all of the game state in a single file. You don’t want to lose all of this if the game is uninstalled for any reason. So rather than store the game state on the client, you decide to store it on the server instead – a sensible decision on the face of it.
Imagine if this game state file is 2Mb in size. It’s trivial for a client to simply save the entire file to local storage every time an update happens (e.g. at the end of each level, when a life is lost, etc.). Saving or loading such a file would typically take a fraction of a second on most modern hardware, and saving the entire file every time is certainly simpler than trying to modify only the sections that have changed. Saving the entire file every time is the easiest option and causes no performance issues on a client.
However, using the same strategy to persist data to a server is a different scenario. First, you have to pass that 2Mb of data to the server, over the internet. Imagine how long it would take simply to transmit the data over a low quality connection. An Edge network would typically take several minutes to transmit 2Mb of data. Even a fast home broadband connection would take several seconds to upload this data. (Remember, upload speeds are usually not particularly fast on home networks – for some reason many broadband providers take a dim view of people running servers from home without signing up to a business account. Technical reasons such as ADSL near end cross-talk, and frame synchronization issue, also limit upload speeds).
Then of course, you have to download all that data again every time you want to access it… This all adds up to a lot of network time, which reduces the performance of your game and leads to a worse gaming experience for the player.
A much better design pattern is to keep the game state file on the client, and only replicate changes to that state to the server.
This way, loading the game state is always nice and fast, since you always load from the client device. If your game is re-installed and the local file is lost, when the player logs in to GameSparks (and they have no local state file) they can download the entire file at that point – this is a “one-off” operation, so a small delay of a few seconds here is certainly acceptable.
When the state changes, send this information to the server. But don’t just blindly send all of the data – only send the changes to the data. This minimizes the data sent over the network, keeps your game running quickly, and gives a better overall experience for the player.
Try to keep this principle in mind in general, not just for the example given here of maintaining game state. If you don’t need to send data to the server because it already has it (or retrieve data from the server that the client already has) – don’t do it.
Think a little bit outside the box – just because you traditionally stored the entire game state as a single blob of data, that doesn’t mean you need to carry on doing this. Consider storing game state as a number of smaller data items, which you can manipulate individually. This pattern is a much better fit when you need to replicate those updates to a server.
Minimizing the number of requests that each client makes, and the data contained within those requests, is probably the single most important thing you can do to ensure your game will work well over a network, and will be highly scalable to hundreds of millions of players.
In the next part of this series, we will look at some simple server-side techniques to optimize your code, and improve performance of your game when the server is handling large numbers of players simultaneously.