Making a Game in Go, part 1
Oh, poor neglected blog!
I’m making a game.
I’m doing it this way because:
- I’m very familiar with Go.
- I’ve done it before. Awakeman episodes 27 and 40 were written in Go.
- Go can target Windows, Linux, Mac, the web (WebAssembly), and even iOS and Android.
- I have some ideas about engine programming in Go that might not be interesting to anyone else, but entertain me nonetheless.
There are probably lots of reasons for me not use Go for making a game.
- Limited resources for doing it, because everybody else is using Unity or Unreal or Godot or löve or …
- Something something garbage collector
- Something something generics
- Something something C++
- I’m an inexperienced game developer, with less than a handful of games made, and no formal education in game making, so what would I know!
ebiten is a library for making 2D games in Go. It takes care of the following:
- Graphics across the varied platforms
- Input handling, including game controllers
- Telling your code to layout, update, and draw
But there is a conceptual gap between being able to draw stuff in a window, and using that to make an entire game - even a “simple” platformer or top-down adventure.
So in this post I wanted to start writing about the thing I’m building now (an engine) that will provide a bunch of tools for building a thing on top of it later (a game).
ebiten.RunGame requires we pass in something implementing
looks like this (comments removed, which you can read at the ebiten godoc):
1 2 3 4 5
Basically, ebiten calls these methods over and over.
Update is supposed to
read the input state and then update the game state by 1 “tick,” and
where the game should do all the graphics (
screen.DrawImage, for example).
This is a reasonably straightforward application of a Go interface.
Up the other end of a game engine is the “game”, that is, whatever is special or unique about this particular game being made. A good engine library should be useful for making lots of different games and provide a good selection of common parts, but also be flexible enough to allow custom parts to be provided by the game maker.
The subproblems the author of a game engine needs to solve are therefore:
- How to represent the various parts of various games
- Organising the parts of the current game in a useful way
- Updating each part of the game that needs updating
- Drawing each part of the game that needs drawing (in the right order)
Representing and organising the parts of a game
It occurred to me that the different parts of a game (let’s call them components from now on) need to do different things. Some components need to accept user input and update state over time. Some components need to be shown on the screen.
One approach would be to require all the components to have both
Update methods, and for each component to have a stub do-nothing
implementation when it doesn’t need to do either drawing or updating.
Another approach, that makes sense in Go, is to look at each component to see if it supports a behaviour, before calling it. This can be done with type assertions, which are fairly fast. An incredibly tiny game engine (though one that doesn’t provide much value to the game maker) might therefore look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
Need to change the order of things being drawn? Reorder the components in the
Or, instead: the engine could make it easier on the game-maker by letting them opt-in to some mechanism to figure out the draw order based on some number provided by each component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
This approach is probably less efficient than it could be.
Firstly, however fast each interface type assertion is
c.(Drawer), c.(Updater), c.(DrawOrderer)), it will happen for every component on
every call to
Update. That could mean a lot of repetitious,
unnecessary type assertions when they could all be done “up front”.
Game keeps track of whether it has done this processing or not,
and performs it if needed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
A drawback to this is that instead of having to edit just one slice to change components in the game, as many as three would have to be updated. (Additional books leads to additional bookkeeping.)
Secondly, if the draw ordering changes a lot, that could mean a lot of sorting, which may mean a bit of memory churn depending on how the Go stable sort is implemented. Optimising this would probably require knowing a bit more about the game being made - for example, are changes to draw order being made smoothly, so there are at most a few “inversions” per frame?
To begin with, though, these aren’t necessarily problems for implementing a game. The game engine just needs to be fast enough, and as long as the number of components is small enough (millions might still be fine, even on slow computers - I haven’t benchmarked it), then the basic approach seeems acceptable.