I am working on a video game for programmers. The game is played by writing code that interacts with the game through an API. This post explores the design of the API that connects the player's code, the API layer, and the game simulation.
I have been thinking about a video game for programmers for a while now. What differentiates such a game from others is that it is played by writing code. The player cannot interact with the game world themselves but only through an API. Essentially, they create a program that plays the game for them.
The time has come to make this dream a reality and build a working game. I am working on Auto Traffic Control (ATC), a game in which players have to safely route airplanes to an airport.
Over the past two years, I experimented with different ideas for the API and the architecture of the game. One prototype had a REST interface, another tried to wire RPC calls into the core loop inside the game engine, and there were a few that explored various asynchronous interfaces such as message queues. But all of them eventually ran into problems, with a root cause that in hindsight is totally obvious:
At its core, the game that I want to build is a distributed system.
There are three components that are independent of each other: the player's code, the API layer, and the game simulation. The player's code runs in its own process and is totally decoupled from the game. But even the API layer and the game simulation are only loosely coupled, and run in parallel in different threads. The result is a system in which all components run concurrently, and any attempt to introduce tight coupling between them is bound to fail.
The architecture for the game takes a lot of inspiration from Event Sourcing and CQRS. The player sends a command to the API, which validates it and then puts it into a queue. The different systems that power the simulation inside the game take commands from the queue, and make the corresponding change to the simulation. The change in turn triggers an event, which is send back to the API via another queue. The API receives the event, and sends it over a stream to the player.
The diagram above visualizes this architecture inside the game. The API on the left has different services that each manage a single resource. The event source streams events to the player, while the airplane service allows players to interact with the airplanes in the game. The API is coupled with the simulation on the right through two queues: the event bus and the command bus. The game is built with the game engine Bevy, which runs many systems in parallel to simulate the game. All systems are connected to the event bus so that they can publish any change they make to the game world. And systems that can be influenced by the player are connected to the command bus as well.
This architecture has worked really well in experiments, and I can't wait to see how well it'll work for a full game. Stay tuned to learn more about that in the future!
If you're interesting in this game or programming games in general, make sure to follow along. I am not sure where the road will take us, but I am very excited for the journey!
Subscribe to my blog to receive weekly updates about the progress on the project.
I also stream some if not all of the development of this game on Twitch, so follow me there as well.