- Investigating Libraries: We built and ran Valve's GameNetworkingSockets library, but found it was too messy and half-baked for us to comfortably use, especially regarding documentation. We found yojimbo as an alternative, which seemed cleaner and more prepared for us to use.
- The Decision: Based on documentation and apparently feature richness, we chose yojimbo for our low-level networking library. We extracted the test code from yojimbo's own test project and integrated it into our testbed.
- Connecting our Computers: We developed a basic client-server model that we (for the time being) assumed would always be on, then cleared it on a single-machine setup. We became thirsty for progress and pushed forward to test a computer-computer setup, and we got sounds playing across the network!
- Memory for the Network: We mapped out some future plans regarding client and server needs, then we transitioned the networking systems onto our own managed memory, which involved some important high-level decisions.
- Messages to Our Future Selves: With our base network functionality out of the way, we began implementing the system that would support most if not all of our networking functionality: Remote procedural call messages.
- First and Second Versions of Network Messages: We made some attempts at a robust, convenient message creation system...
- Network IDs: ...then we established a system for tracking entities across the network using those messages...
- Third Version of Network Messages: ...and we redid the remote procedural call messages again! This time, we got it to utilize templating and classes instead of gross string identifiers like
- Transform-ing the Network: We used our previous RPC message framework to develop our networked transformation synchronization system! After the initial pass, we basically had functional network transforms, so we spent the rest of our time on nice things to have like interpolation and optimized network usage.
- Missing Features: When developing the second game in our engine, we realized that we were missing some key things like parenting and snapping over the network. So we implemented them!
- Patch Notes:
- Making the Network "Local": Our
NetworkTransformclass originally performed all of its calculations in world space to keep things uniform, but that meant sending way more synchronization messages than needed when parenting! As a result, we swapped world space out for local space.
- Making the Network "Local": Our
- Networked Game Management: Although all of our in-game needs were pretty much covered, we still lacked many out-of-game things, such as connection callbacks and networked level loading.
- Network Discovery: Another big out-of-game feature we were missing was a lobby system! Our previous systems required the player to know the IP of their host, but we implemented a LAN broadcasting system using some basic socket programming.
Short disclaimer: We never dove deeply into game networking for various reasons, so we can't speak much towards advanced network programming or any beyond-basic features like client-side prediction or world state delta encoding; if you're interested in those things, there are some great papers from games like TRIBES and Doom III that have good information about them. Instead, what we can write about is how one would even start doing network programming for games, especially as someone who hasn't done any network programming before.
First, just because you're doing network programming for a game does not mean that you have to use the TRIBES Engine networking model—especially if you're not even making a first-person shooter! We mistakenly believed that we needed to encode the world state and intelligently map out all of our entities as serializable objects when we were starting our networking features, largely due to Multiplayer Game Programming by Glazer and Madhav (which is a great introduction to network programming by the way!). We didn't know what all of that meant, or even what the data was supposed to look like, but we believed that we needed to create a robust, effective, and optimized network model for our engine, so you can imagine what we looked like when we were trying to design an architecture for something we had no idea about!
Fortunately, one of our programmers had some higher level networking experience and wisely suggested that we start small: All we need are network messages. We were building on top of yojimbo so we avoided having to program below the session layer, and we were able to implement some rudimentary network messages very quickly. The moment we got messages going across the network, we were ecstatic! This was the cornerstone of anything that we would need for game networking, and we managed to make it happen!
We only added one more advanced feature to our network repertoire beyond network messages because we never really needed anything else. Fundamentally, all you need in a networked game is a signal that will tell other computers to run some code, which is what those messages can do. By getting over this bump in the road, we were able to move onto doing networked spawning, networked object transforms, and more, and we didn't waste much of our time trying to over engineer the networking simply because we didn't understand it better.
The networking features of a game engine cannot be predicted. Even if you're targeting a specific genre with that engine (like twin-stick shooters or something weird like that), you'll be adding in features that you think games will need and not the ones they actually need. We spent time on things that we thought would be important, such as optimizing our network transforms to take up less bandwidth, and it turned out that we were missing much more important features, like an API for parenting objects across the network. The former wasn't important because in the end, our networking was never able to extend beyond a LAN setup, whereas the latter was needed for the immediately next game that we made after finishing our network manager API. But we believed that optimizations would be really important because that's stressed a lot by network programmers who are far more experienced than anyone on our team.
We talk about this elsewhere, but what's important when developing any game engine system (networking included) is to actually make games when you're doing that. A game engine in isolation would yield networked tech demos, and tech demos test what you already know you have. What's more is that making games also tests the real use cases of the games; as it turns out, a LAN doesn't really need packet optimization, but it does need gameplay quality of life features regarding networking like snapping objects into position. Knowing that requires stepping up and making something real with the system, not just setting up test suites.
More things to know:
- Every new feature that you get working over a network will be an exciting moment, and you should celebrate it with as much of your team as possible.
- Building your networking system on top of a 3rd party library will get you most of the way there, especially if you don't have great needs from your networking.
- Our network message system is flexible and effective, but it requires a lot of duplicated code, which makes it a hassle to set up for new functionalities over the network.
- Know your end goal before designing your network system architecture. LAN networking should prioritize different things than long-distance networking, like responsiveness over packet reduction.