David Snopek's Retro Tank Party, as it's now known, is a 2-4 player network game where you drive a tank and shoot your friends.
(It was originally a gamejam game that I was calling "Battle Tanks", but I've continued working on it, and it's turned into a persistent project, so I figured it needed a real name that was somewhat unique.)
Over the last couple of weeks, I've been converting the game from using a client/server model over UDP, to a peer-to-peer model over WebRTC. This also included setting up a persistent backend (based on Nakama) so players can have user accounts, and, among other things, support matchmaking.
The most exciting thing about this, is that you can now PLAY THE GAME ONLINE on Itch.io:
Check it out! If you want to play a match with me, feel free to ping me @snopekgames on Twitter.
Read more to find out why and how this was done!
Why change the way networking works?
The networking in the original game was done by simply following the High-Level Multiplayer documentation for Godot, which means using the ENet multiplayer implementation, which works over UDP.
This was great, because I just copy-pasted stuff from the docs, and it basically worked. :-) It was also simple because there didn't need to be persistent server running anywhere, each player could choose to be the server, and have the other players connect to them.
However, there were some downsides:
- The person who wanted to be the server needed to setup port forwarding on their router, so that external connections could reach them. Not all people have access to their router (at school, at a coffee shop, etc) and not all people even know how to do this.
- The game's networking logic was essential peer-to-peer where each game client controls their own tank and tells the other clients what's going on with it, but all messages have to pass through the server, before being relayed to the other clients. This adds latency by having to pass through the server, rather than directly broadcasting to the other clients.
- The game couldn't be a web game, without being able to spin up server instances in The Cloud somewhere for the web clients to connect to. That's the model used by Agones, which I was originally going to use to make this into a web game! But that requires quite a lot more resources in The Cloud, and this is just a hobby game! I want lots of folks to play it, but I don't wanna pay much for it, because I'm not making any money from this. :-)
- Without a persistent server with user accounts, I couldn't do leaderboards, tournaments, etc. Of course, I could setup something that did that even with the client/server model - each client (including the server) would just also connect to another server to manage the users accounts - but I already had this problem to solve, so I decided to solve them both at once.
Nakama is an Open Source realtime server backend for games.
It has user accounts, friends, clans, chat, data storage (for cloud saves, stats, or whatever), leaderboards, tournaments, matchmaking and multiplayer. It's cool. :-)
I actually hadn't even heard of it until I saw the announcement that the company behind Nakama, Heroiclabs, was going to sponsor Godot development.
So, why did I choose it:
- It's pre-existing. Sure, I could code up some microservice with a REST API to do the same stuff, but I can just drop Nakama in and focus on my game.
- It's Open Source. This is my free time and I love Open Source, I don't wanna mess with any proprietary stuff (even if it's better).
- It can do all the stuff I need (and more). All I really need is accounts, matchmaking and multiplayer - the rest is just scrumptious gravy.
Nakama does have a multiplayer API, and I could totally implement a Godot module in C++ to allow Godot's High-level Multiplayer API to use it. I even started working on that and learned a ton about Godot development and read through a bunch of its source code. It was fun! And I'm sure I'll use that knowledge later.
However, my Nakama module was taking quite a while to write, and I had persistent misgivings about all the traffic that would pass through Nakama. As it's currently implemented, the game sends like 60 messages per second (yeah, I need to optimize that :-)). That'd be a lot of traffic that my server would need to support, but I just want to setup one cheap server running Nakama, and have it support all my stupid little hobby games. I don't want the server to turn into a job.
Godot 3.2 (which is in alpha currently) is adding a WebRTC implementation of its High-level Multiplayer API, which is peer-to-peer, so the real heavy traffic doesn't need to pass through the Nakama server. It also means I don't have to finish my Godot module in C++, I can just grab the latest Git of Godot 3.2 and use what's there.
How did I implement it?
WebRTC is actually pretty complicated. In order to establish the WebRTC connections to all your peers in the match, you need a back channel to exchange messages, which is called a signalling server in WebRTC-speak. I'm using the Nakama multiplayer API, not for the actual multiplayer gameplay, but as the signalling server!
At a high-level, here's how it works:
- It logs in to your user account on Nakama via the REST API
- Then it opens a WebSocket to Nakama's realtime API
- It uses the WebSocket to either create a match, join a match or join the matchmaking pool
- Once you're in a match, it uses Nakama's multiplayer API to establish the WebRTC peer connections
- The WebRTC connections are passed to Godot's High-Level Multiplayer API, and then you can start playing!
The WebSocket connection to Nakama remains open, to let peers know when someone leaves the match, or to re-establish WebRTC connections, which seems to happen quite frequently when playing over the real internet.
Nakama Client written in GDScript
There are a couple existing Nakama Clients in GDScript or C++ for Godot, but they appear to be pretty incomplete. And, apparently, the C# client works in Godot, although, I had trouble getting it to work because of some .NET version compatibility thing.
So, I ended up making a simple Nakama REST/WebSocket client in GDScript:
It currently support all of the realtime API (the one over WebSockets) but only a little bit of the REST API. I'm planning to make its REST support complete, add some documentation, and then submit it to the Godot asset library. I'll get that done in a week or so.
I'd still like to translate it to a Godot module in C++, because that could potentially be faster (it could use gRPC rather than REST) and would allow using the Nakama Multiplayer API for Godot's High-level Multiplayer API (ie. taking WebRTC out of the equation). I'd love to try all those things, and I think there's a use-case for them (it'd be much simpler without WebRTC), but I don't think I actually need that for this game.
I've been implementing my game so that there's a separate component that is responsible for setting up WebRTC over Nakama, which has no game logic in it. The goal is that this could also be put in the Godot asset library, and any game could use it, by just connecting to some signals and calling some APIs.
I want this to be a tool for me to create lots more multiplayer games using this same networking model, but I think it would also be useful to other people!
It still needs some more testing, clean-up and documenting, but after I get the Nakama Client released, I'll split this into its own repo and release it as well. But you can see the current code in my game here:
Play it, please!
The main motivation for making this into a web game, was that it was just hard to get people to play my game. :-) Even just downloading a build and running it, was too high a bar in many cases. Now it's just "go click a link and make up a username/password."
So, please play it! Here's the link again:
Feel free to send me any thoughts or feedback that you have!