In November of 2020 I had the idea to write my own game engine as part of a second year coursework for my CS degree. Despite making a game in an existing engine being already more than enough for the brief I, ever the fool, thought something custom would be a better fit, and so set about making my first game engine. It turned out to be the most complex project I had ever worked on.
The concept most core to the game engine came from the coursework brief and was why I didn’t want to use anything already existing. I was tasked with writing an application that used a decision map (which I read as a state machine) to control the flow of execution. Since I wanted to make a game, I thought it a good idea if every object could have state machines that controlled their behaviour. Of course if you have states, you need some kind of stimulus to cause a change of state, so this lead to engine becoming very event driven. The more I explored this way of organising logic, the more fascinated by it I became. It has an almost mathematical quality to it. Being a rather inexperienced programmer, I don’t know if this approach is common or perhaps something unusual and new.
As I thought through this more, I became quite fond of maximising its capabilities. I noticed that it had already removed a lot of conditional logic, and so took on tailoring the state machine system’s features to remove conditionals as much as possible. I became quite interested in pursuing the purity of the way it organised an object’s state. In order to make game code less reliant on storing data on game objects directly, I introduced a new concept, states with data. Instead of having to have 100 different states to represent something having up to 100 health points, it could now have a single alive state with embedded data as to it’s exact health points. If behaviour should change when low health, you could have a separate low health state where the event passes the data across. This meant that there should be no need for two states to have to repeat the same behaviour code entirely. One enhancement that could still be made is to allow states to themselves have inner states. If multiple states partially share behaviour code, this could be the behaviour of the outer state, with the inner states possessing the code that differs.
One particularly useful result of this approach was the parallelism it leant itself to. Since state was bundled up with the logic that operated on it, as long as only the events system was used for communication, the behaviours could be executed concurrently. This almost free thread safety may well be the reason to use a game engine like this, and it was a complete accident. Once I noticed this, I decided to adopt it as a core design goal, reworking the event and state update systems to be more parallel in their execution.
The future of this project will be rewriting it in Rust and changing the graphics API code to use Vulkan instead of OpenGL. The render code needs a lot of work anyway, as it currently makes very poor use of texture units in the GPU. I’m also interested in adding networking support. I think the event driven approach could lend itself well to that end. I’m hoping that moving away from Java will give a performance uplift or a least simplify the code as Java makes working with native libraries quite a lot of work. I would never have chosen to develop in Java for this reason, but it was a requirement of the coursework brief so I had to. Since I’ll be continuing the game engine’s development as my final year project, I’ll be free to chose the language and I’ve recently been enjoying Rust.
If you’re interested in the project, you can find the project on GitHub.