I recreated the 1979 classic Atari video-game Asteroids, you can play it here and see the code here. The game is entirely written in Zig on top of Raylib—via raylib-zig. Prior to this, I had developed games in Python using Pygame when I was a teenager and I was surprised to realise how similar the experience of using Raylib was to using Pygame. The Raylib API is so simple it's genius, as the experience of programming on top of it is both trivial to get started with and monstrously fun. The API is basically: while the window should NOT close, get input events and then draw onto the screen.
I got the exact idea for this project from a jdh livestream. I watched the first 1 hour, right about where he got a ship drawn on the screen and moving, before I stopped watching because I was inspired to create my own version. After around 6 hours jdh had developed a game with Asteroids' most recognizable features; the same task took me around 6 days. I am nonetheless proud of this outcome, as I have become a better programmer in the process. At the time of writing, my Asteroids game has fully functioning ship-flying, shooting, a time bonus score, a start and an end screen and of course asteroids!
Notable design choices
There are 4 types of "entities" in the game: asteroids, the ship, bullets, explosion particles. I store the asteroids, bullets and particles as a struct-of-arrays because I watched Andrew Kelley give a talk where he says it's more cache-friendly. I'm not an expert on data-oriented programming and computer architecture but I'm reading "What Every Programmer Should Know About Memory, Drepper (2007)" to have better explanations of it. Aside from performance it makes semantic sense for me to store struct fields as arrays because I'm always changing the position of every asteroid/bullet/particle at once.
A habit of mine as a programmer is to quite rigorously avoid dynamic allocation. When I do need heap memory I prefer to either use arena allocators or to make a guess as to how much memory I need and then allocate it up-front. In my Asteroids everything is statically allocated. The buffer for asteroids has a fixed length of 16—the theoretical maximum, there are at most 5 bullets, every explosion has 20 particles and the buffer for explosions is a ring buffer with a length of 3. I feel much more confident in my code having set these hard memory limits, which can be trivially altered should the need arise.
Not all 16 possible asteroids will be active at a time, so every asteroid has a boolean is_active which I check before doing any computation, same goes for the other entities. So I iterate through the entire array of is_active, updating the ones that are. I thought of creating a linked-list of active entities but to my understanding pointer-chasing is one of the slowest things a modern computer can do (again, I am still learning about this). So just iterating through each entity, especially since there aren't many, seems to be the best and simplest thing for now.
Collision detection is naive. I treat every entity as if it were a circle by just checking if the distance from the centers of entities is smaller than the combined radii. I find that this works fine, and I haven't received a complaint about it from play-testers so far.
I make generous use of the default Zig Vector type throughout the program. In Zig, a Vector is a group of values of the same primitive type which are operated on in parallel, these operations compile to SIMD operations when the target supports it. Every entity in the game has a position and a velocity and I use a 2-length Vector for each.
Notes of appreciation
I want to once again note how great, and simple Raylib is. The raylib-zig bindings are also almost perfect, compilation targeting wasm needed a little tweaking but otherwise they worked completely out of the box! I am grateful to the maintainers of both projects!
Fin
Check out the game and let me know what you think!