Jonot's BlogWhy I Wrote a Game Boy Advance Game in Zig<br>2024-12-03<br>12 minutes
The Game Boy Advance is an interesting game console. It has a modern-style CPU (32-bit ARM, lots of registers), but uses an old-fashioned tile based renderer like how the NES did it in the 80s. Don’t get me wrong, since its one of Nintendo’s last tile-based systems, they took the idea as far as it can go with fancy features like affine transformations, transparency, and effects applied to sprites. But at its core, it occupies a very unique space:
The only other major occupant of that quadrant is the Nintendo DS, and while I have fond memories of it, I thought that the second screen would make programming harder. So with this information, I decided to make a game for the Game Boy Advance:
I have countless hours on games about numbers in squares and rules on how they combine in rows (Picross, Picross 3D, Minesweeper), so 2048 was a good choice for a project. You can try the game out in an emulator here.<br>But the most interesting thing about this project to me was the language. My initial play-projects with the Game Boy Advance were written in C++, but I wrote my first full game in Zig. And that’s a bit weird. First of all, its a very niche language, its still in beta, and it was made 15 years after the Game Boy Advance released in Japan. But even though it’s not the most popular, I think that Zig was great for this project, and that is what this blog post will be about.<br>This blog post is not about how good Zig is in general, and is mainly focusing on the features of the language that make it good for this type of embedded programming. You can find other people on the internet talk about why you should use Zig, and I’ll leave that to them.<br>Toolchains<br>I started using Linux five years ago, mainly because I couldn’t figure out how to get Python working on Windows, and I found out that Linux came with it. For the five years since then I have been continuing my ongoing struggles with packaging, versions, dependencies, and all the other problems that come with the hard problem of getting a program working on your computer.<br>This is why, when I decided to make a simple 3d scene on the Nintendo DS around a year ago, I was immediately uncomfortable. The most popular way to develop games for “retro” Nintendo consoles is through something called devKitPro, which packages GCC toolchains, libraries, and development tools for these consoles. How do you get these packages? Well, you download the ArchLinux package manager, set it up to point towards their repositories, and install that way.<br>Even back then, this bothered me. In general, the more different package managers I have to use, the less happy I’ll be. I did end up installing this, but I tried to copy over some of the linking files into my repository so I could just use a standard arm gcc toolchain.<br>When I started my GBA demo projects, this stuck with me. I again started by copying the devKitPro linker scripts, and cobbling together some weird Meson setup to get everything working together with clang. It was a mess, but it worked. The nice thing is that since I understood the toolchains better, I was able to get newlib working, which gave me the C and C++ standard libraries to use. I made some demos to understand how sprites worked, but eventually I moved on to:<br>Zig<br>I think that the first time I heard about Zig was this blog post by Andrew Kelly about using Zig for cross-compilation. You didn’t need to setup these toolchains using weird package managers (for the record I consider rustup a weird package manager). You can just set a target, and things work.<br>That wasn’t the only part of the build process that Zig made better. Zig builds work by running the build function in the file build.zig. Here’s an overview of the build process for the game:<br>Using a linker script to get the file layout right, compile an ELF binary for the game, along with a startup file written in assembly<br>Use objcopy to extract all the data from the ELF structure (This is basically a GBA rom now)<br>Use a tool like gbafix to add in the correct header information (Neeeded on real hardware, but not on emulators)<br>Because Zig includes objcopy, I can do all of these steps in the build file itself. This makes the process less messy and error-prone.<br>Another thing that the build system makes easier is code generation. I use code generation to take png images and turn them into pixel data that the GBA will understand. The Zig build system lets me compile the program that generates the code, generate the code, and link it together all within a function. And you specify how dependencies work in this file, so the build can be parallelized on all the cores of your shiny new computer.<br>Zig made cross compiling and toolchain management, something that has always been the bane of my existence, something I didn’t have to worry about.<br>Packed Structs<br>I’m putting...