A Few Rules Of Thumb
First of all, welcome to the series! π
This first instalment of The IOs of Game Netcode will cover a few rules that I’ve come to follow whenever I approach a new netcode project. Now, as I’ve been writing this I’ve come to realise that because netcode is mainly backend code, there isn’t going to be a whole lot to look at except large walls of text. I’m afraid I can’t do much about that. Hopefully, you find the posts interesting enough to persevere π So, without further ado, here are my rules of thumbβ¦
Text is BAD
What I’m referring to here is text-based netcode serialisation is bad and you shouldn’t do it. If you do it, then you are a bad person and should feel bad. Of course, there are exceptions to this rule. The first being web services, which in all fairness is a pretty big exception. Turn-based games can get away with it as well. Also, you may not have a choice if you’re developing a mobile game due to unreliable Internet connections and are forced down the RESTful web-service route here too. I’ve played a few real-time multiplayer mobile games and they’re alright, if you’re on wifi, otherwise it’s a terrible experience.
So, if you don’t fall into one of the above exceptions, you’re going to want to go with object serialisation at least otherwise you’re going to get a lot of overhead on parsing your netcode into usable formats. Now, for a lot of you this is pretty obvious and you may be asking yourself “Why would any sane person try and implement a real-time game’s netcode with text-based serialisation?” Well, for some of the more inexperienced developers out there, it may seem like a good idea to use a flexible text format like XML or JSON for your netcode because “everything can read it” and “other people can write clients to consume it like a web service”. Just save yourself the headache and stop yourself now.
If you haven’t guessed, yes, this is something I chose to do while developing Root Access. This was a long time ago now and what roped me in was the flexibility, you don’t need to deserialise everything you receive and different clients can select only what they need, but this was an inexperienced train of thought. Of course you want to deserialise everything, if you don’t you clearly don’t have efficient netcode. Even though it did work, and pretty well at first, it got progressively slower the more data we shoved through it. Eventually, I went back and recoded all of the netcode to use Java’s object serialisation framework, which took a long time and was a massive headache. So save yourself the trouble and don’t do it! π
Everything is Multiplayer
It is my firm belief, that if you are making a game with multiplayer, you should bite the bullet and make your singleplayer multiplayer. What I mean by this is your singleplayer is actually a private multiplayer session consisting of one player using the system’s local loopback interface. Of course, this basically means you need to START with your netcode framework before you can make any kind of progress. Yes, this does slow down the initial project start quite considerably and also makes developing the singleplayer aspect of the game a bit slower and require more effort, but I believe the benefits are definitely worth it.
For starters, you’ll finish your game faster. If your singleplayer and multiplayer modes use exactly the same code, then you’re killing two birds with one stone. All you have to do is flip a switch and you go from singleplayer to multiplayer and vice versa. Writing two separate systems to manage singleplayer and multiplayer will just be a maintenance nightmare down the road. There shouldn’t be any latency issues running singleplayer through the loopback interface. If you are experiencing any kind of noticeable delay while playing a local singleplayer, this just points out that there may be an issue with your netcode design / implementation, forcing you to fix it and resulting in a better multiplayer experience as well! π
Lastly, and this is the big one, you’ll avoid the gargantuan headache that is retrofitting multiplayer into your game. If you focus on singleplayer only to get your game out as soon as possible, with the idea of multiplayer coming much later on, you’re in for a world of hurt. I’ve seen so many game developers do this as well, and it basically appears that they’ve hit a brick wall in terms of progress because they are so busy behind the scenes refactoring, debugging and generally rewriting a huge portion of their game. I was actually impressed when the guys at Mojang managed to retrofit Minecraft’s singleplayer into a multiplayer system to support LAN play, but it required huge changes including the way their singleplayer stored its map and player data. So, if you’re planning multiplayer at ANY point during a game’s development, DO IT FIRST! You’ll thank me later π
Stand Alone, Together
Now this one may not be as obvious and a lot of people may actually disagree, but I think it’s something that can help a game’s design, development and maintainability. When writing your multiplayer framework for your game, separate the game client and server into separate projects, and also build them as separate binaries. It may be tempting to implement your multiplayer server into your game client as it’s easier to work in a single project, especially if the game doesn’t really require a dedicated server. Do it anyway. The reason I say this is because you’re going to end up with a lot of duplication, particularly in regards to the game data (which is understandable due to the client and server’s own knowledge of things). If you’re not careful you’ll start cross-referencing data without going through the netcode properly and before you know it, your multiplayer isn’t truly multiplayer and it’ll be a development and maintenance nightmare.
If you do separate your client and server into separate projects, but plan to compile and distribute together as one application, then I don’t really see a problem with this. I just prefer to go all the way and build as two separate applications, then you have a dedicated server from the beginning. Then for locally hosted games, you just get the game client to launch the server in the background, telling the server that the player is the host / has admin privileges by providing a player’s token or account ID to the server process. This also works for singleplayer games as mentioned in the Everything is Multiplayer section raised above, just you keep the game private, skip the game lobby screen and launch straight into the game.
What I have suggested so far makes the assumption that your client and server are written in the same language. If they’re not, then you’ll probably never encounter these issues, but there is also an advantage of working in a single language and that is shared projects. Back in the day when we were very much Java, Java, Java we usually started a new game with the same triad of projects. Client, server and shared. The client and server were dependent on the shared project, which housed all common functionality between the client and the server. It included the netcode framework, netcode commands and data structure classes. This removed a whole tonne of duplication in the projects and is something that worked very well for us while we worked in a single language. It’s a lot harder to do when you work in Unity and C++ π. Anyway, since those days, I’ve refined my thinking a bit and come to the conclusion that sharing the netcode framework and potentially the data structure classes is potentially a bad idea, and that only the netcode commands should be shared.
If you share your netcode framework you have to start writing in special cases depending on whether the process using the framework is the server or the client, as well as identifying the source of a netcode command (whether it is the server or a client and how to handle it). I actually saw someone tweet a code snippet that made reference to this exact check recently and, to be honest, I don’t think it’s needed. Of course, there’s always an exception to the rule. If you’re developing a peer-to-peer model, then you may not have a choice, depending on how your client gets its peer list. Anyway, the reason I mention that you shouldn’t be sharing a data model either is because your client should never know everything your server knows. The client should only know portion of your server’s game data, only what is relevant to it. Also, you should only be sending client required data for each entity class. Your server versions of the entity classes will contain a lot more and some of it may be sensitive. Your server’s game data classes will also contain a tonne of functionality regarding how the entity behaves, that the client should never need. If it does, then your design is wrong as your server is not fully authoritative and then you have another whole lot of problems, such as players being able to headshot everyone in the server with a press of a button or players setting their own stats (Yes, I’m looking at you Battlefield).
Some of you may be thinking “I don’t see the problem, I’ll just create pure abstract data classes with only the common data between the client and server, with no behavioural functionality. Then subclass in each of the client and server projects”. By all means, you can do this. I just find this to be a pain because you end up maintaining three data structures.
Anyway, this post has gone on far longer than I thought it would so wrapping it up now. Sorry for that, I’ll try to keep future posts more concise and maybe provide code snippets to illustrate my points π
If you have any questions or just want to chat netcode, just comment below or fire me a tweet at @Jargon64.
Thanks for reading! π