In the past two weeks I’ve been experimenting with different ways to talk to the ChipWits game server from our game, and found that using protocol buffers with UnityWebRequest is the best way forward for us. In this devlog post, I’ll discuss a few things I tried and why we arrived at this choice.
Warning: This article is a bit more technical than some of our previous devlog posts.
The Role of the ChipWits Game Server
The ChipWits game server plays an important role in the game, and does the following things:
- Manage player registration and player profiles
- Maintain the leaderboard for certain missions
- Collect usage metrics, including how difficult players are finding each mission
- Enable administrators to publish, update and remove missions from the mission catalog
The server provides these serves to the various versions of the game client as well as an administration CLI we use to manage the game:
The Challenge of Talking to the Server
To make this communication possible, we need to define a language (protocol) the different clients use to talk to the server.
Each of these components has some unique requirements:
- The server runs on Google Cloud Run and has to handle lots of different versions of clients talking to it
- The administrator CLI does not use the Unity API and is a plain command line tool we use to automate publishing missions and managing the game
- The WebGL Game Client is a version of the game that runs in a web browser
- The Steam Game Client is a version of the game that runs natively
Here are some challenges we’ve faced:
Old Versions of Game Clients
We don’t know how often (if ever) our players will upgrade the version of the game they’re playing. This is more of a problem with the Steam Game Client, but can also apply to the WebGL version. The server has to support all the client versions as we continue to evolve the game. That means we have to be careful to make the protocol used to talk between the client and server backwards and forwards compatible. At any given time there could be dozens of versions of serialization code running between the various clients and the server!
Of all of these clients, the most challenging to keep working has been the WebGL game client. The Unity Game Engine generates this client by converting our C# code to C++ and then from C++ to Web Assembly, which runs in the browser. However, browser client is single-threaded and doesn’t work with all C# libraries. We have to pick a communication mechanism that works well with Unity WebGL.
What we Tried
Here are the things we tried:
|Plain JSON + HTTPS
|+ JSON works everywhere
+ Very easy to debug
|– Have to hand-write serialization code
|+ Handles both message serialization and client/server communication
|– Could not get it to work in the WebGL client
|✅Protocol Buffers + HTTPS / UnityWebRequest
|+ Protocol buffers are great at forwards / backwards compatibility
+ Type-safety benefits of protocol buffers
+ Easy to get working in Unity WebGL via UnityWebRequest
|– Feels like we’re reinventing parts of GRPC
Protocol Buffers + UnityWebRequest
This is the option we chose. Most languages, frameworks and game engines have one or several preferred ways to send HTTPS requests. In Unity, the preferred way is undoubtedly UnityWebRequest, at least at the moment. The advantage of using UnityWebRequest is that it is guaranteed to work whether you’re compiling a native client for Windows, Mac, Linux, Android, iOS, consoles or WebGL.
Though we initially preferred to use GRPC, it uses the standard C# libraries for HTTPS communication which don’t work well in the WebGL code generated by Unity. I spent a couple of days trying various workarounds, such as providing an alternate implementation of HttpMessageHandler. However, though these worked in the native client, I could never get them to work well in WebGL (the request would go through but exhibit odd behavior in processing the response).
In the end, the solution we chose works well. By using protocol buffers, we’ve been able to avoid writing lots of delicate hand-written JSON serialization code and we have an easy way to evolve the protocol. By using UnityWebRequest, our WebGL client still works well!
For game developers: have you had to make a similar decision? What technology stack did you end up using for your game? Let us know your thoughts in the comments section, below!