Railink background: writing a game in Go

3 July 2024

Our colorful strategy/puzzle game Railink started as a little 3D demo project in Go. From there it kind of grew, and things kept being being added until it was so close to being a real game that it might as well be finished and released. That happened, and it is available on Steam!

Some might be wondering what it's like to develop a game in Go, so we put down some findings here.

Go's low latency oriented GC

The Go garbage collector is very low latency. And it's true that Railink does not seem to suffer from frame drops, even on slow hardware. However, on the other side the GC also does not seem to free that much. This means you have to be mindful of allocations.

In Go, there is the typical NewSomeInstance() function pattern that returns a pointer (like bytes.NewBuffer). This pattern is used everywhere in Go libraries, but the downside is that it causes allocations. Allocations are slow and it can be difficult to get the memory back. Just returning the struct instead of a pointer seems to be much faster in practise. This is an important lesson for writing fast Go code.

Also, whenever a struct gets converted into an interface, like with fmt.Println which takes any arguments, an allocation happens. It helps to try and avoid this, or otherwise try to reuse the interface.

Quick build times are nice

Go build times are really quick. This allows for quick iterations, which is really important when making a game. You are constantly tuning everything, and consistently getting the game to run in a second is nice.

Limited game oriented libraries

There are not many purpose made libraries for making games in Go. You can interface with C however, but for a game this means you will use a lot of C interop.

Performance of C interop does not seem to be an issue. Railink does on the order of a hundred C calls per frame, and manages good FPS on low hardware.

Build times take a hit when using C. This is inconvenient, but there is a trick: write a thin Go wrapper package around a C library. The wrapper won't be modified much, so usually does not get rebuilt which greatly speeds up the build times.

Writing wrappers for C code is also very easy. So easy in fact that we found it faster to write our own than reuse an existing wrapper for some C projects.

Conclusion

Overall we did not run into any significant obstacles, and it seems pretty doable to write a game in Go. That is, as long as you are content with using libraries written in C(++), and you do not need the absolute highest performance (but it won't be slow either).