About a year and a half ago, I started my game programming journey. I am somehow confident in programming Kotlin and liked the idea of a complete game in my favorite language. My was to make a multiplayer game where you fight together to kill a boss. I played a lot of World of Warcraft in the old days, but didn’t like the amount of work I had to put in to be able to play my favorite part of the game. So to build this game, I needed technology for a game server and frontend.
My professional tech stack always evolved around Spring-Boot backend programming, and so I was very quick to chose it for this project as well. Some said it would be too bloated and slow for this purpose, but I didn’t have any problems getting the wanted performance. The server has a tickrate of 33 updates per second, and this loads just one of the 8 cpus on my machine.
I have to say I really love Kotlins Coroutines. Concurrency is hard, and concurrency that should be real time in the best case is a complete mess if you just work with threads. I know that coroutines are not real time and the delay()
method provides no guarantees that it really waits the wanted amount. In Spite all this it works better than I expected as long as the whole system doesn’t have much load the Kotlin coroutine timing seems almost magical correct.
I decided to use Websockets for communication. The reasons behind this were primarily to be able to host the game server with just a http loadbalancer. My game should also be runnable in browser, to allow a quick and uncomplicated entry into the game. Later I learned that because of websockets it was much easier to port to android too. (You don’t need any elevated permissions)
Frontend was somehow a more difficult story. There is a Kotlin game engine: Korge. I have to say, it was awesome to prototype and trying out things, while I was new to game programming. The problems appear if you want to implement more sophisticated features like complex particles, z-indexing, camera or performance tuning. That, when I decided to rewrite my complete frontend in Unity to get more options and build a more compelling experience.
That created a dear problem. I had to rewrite the whole code in C# and had to find a possibility to not duplicate the multitude of data structures. I love the Kotlin annotation processor kapt and I used it extensively in other projects beforehand. At first, I only exported the raw data structures and used them in the c# code. One mayor advantage is, that if you ignore Kotlin’s nullability it is quite compatible to c#. Overtime, I also generated rest-clients and json-deserializers with the annotation processor and am really satisfied with the result. I will write another blogpost detailing the technology and code used for this.
Performance was always a main consideration of my game development journey. I aim to make my game run smoothly on any system that has 2 CPU cores. To accomplish this graphically was a fairly easy task because I don’t have any talent when it comes to digital art. The game doesn’t have any ray-tracing or overly extensive physical calculations. All the relevant heavy computation are made on the game server.
The main performance challenge was to get the gamestate to the clients. I started with whole gamestate updates and transported all data 30 times per second. But as the development processed further and the gamestate got more complex the deserialization would take 3 client cpu cores to keep up with the updates. This is definitely unacceptable because I cannot assume everyone has an 8 core system with 4ghz each core.
That’s why I moved to delta compression. I evaluated JSON delta compression, but neither Spring-Boot nor Json.Net in Unity has a sophisticated Support for this. So I chose semantic delta updates, and I would suggest it to anyone who stands to make this choice. There are per definition only a certain number of different updates a game can make. In the end I have only 11 different payloads, and they are all a lot smaller than the initial gamestate that would take up to 3mb. There is still potential if I wanted to reduce sizes further, but right now the game runs with smoothly >500fps on my machine where 60 would be enough.