Using Rust for Gamedev
I’ve recently created a new website and finished an online game named Adventures of Pascal Penguin. One of the most unique things about this game is that it was written in Rust and runs in web browsers thanks to WebAssembly. WebAssembly has only been mainstream for about a year or so, so not many games have been created like this. I want to share a little about my journey with Rust: how I started with it and how I got to this point.
Game development has been a hobby of mine for a long time. Prior to mid-2015 when Rust 1.0 was released, I had been doing gamedev in Java with LibGDX. Since this was a hobby, I chose the technologies I enjoyed using, but that isn’t to say there weren’t frustration. The biggest frustration I had with Java, aside from its verbosity, was that I found myself forced to choose between writing idiomatic Java code and writing Java code that got decent performance. For example, the LibGDX API encourages using their object pooling utilities and specialized resizable array implementations with public native arrays as fields. These are not idiomatic Java practices, but they are useful for making a game that runs smoothly. When you need to be stingy about creating objects, I’d say it’s time to find a new language.
I learned Rust by reading their online book, which looked a lot different at the time. It was shorter back then, more like a tutorial than a full book. There were many things that I found appealing: zero-cost abstractions, traits and generics with monomorphization, the ownership paradigm, flexible enums combined with pattern matching, ergonomic error handling, memory-efficient collections, etc. But on a fundamental level, Rust is a safe language, meaning developing in Rust is not error-prone, while still being a highly and predictably performant language. I observed that when I write Rust code and I get to the point where my code compiles without error messages, it probably works correctly and is efficient. I think this is what makes the language seem appealing and empowering to a lot of people.
There is also some excellent tooling around Rust. The Rust compiler itself gives helpful warning and error messages. The Cargo dependency management tool is refreshingly simple and creates reproducible builds. The crates.io central repository is easy for anyone to upload to. The generated html API docs are the most useful I have seen in any language, and docs for all public crates are built and hosted on docs.rs.
So I experimented with game development in Rust, but not (initially) with the intention of running a game through a web browser. At the time, there was an excellent tutorial named ArcadeRS that demonstrated how to make a Rust game using SDL2. Unfortunately, that tutorial is no longer up-to-date and the site has been taken down. While working on a project, I decided it would be good practice to extract a reusable “layer”, specific to my needs, that separated the game logic from the resource management, rendering, audio, and other interfacing that is needed for a game. I also decided to make this layer open-source, and it became the Gate library. To go along with this, I created a brief open-source 2D platformer that has since been named Chirperjax.
Around this time, I heard about a new technology called WebAssembly. Thanks to this, Rust could be compiled to run in a browser and achieve near-native performance. I knew I had to try it, although it was further along the cutting edge than I normally like to work with. One of the issues with developing games as a hobby is that people tend to be wary about downloading and running an executable on their computers, and rightfully so. Hosting a game that runs in a sandboxed environment in a browser allows more people to play the game. My first attempt was to compile Chirperjax using emscripten’s SDL2 support, but I was not able to get it to work correctly. So I went through some tutorials for Rust’s wasm32-unknown-unknown target and learned how to call JavaScript methods from Rust and how to call Rust methods from JavaScript. Since modern html is equipped to create an interactive audiovisual experience on its own, I did not need to depend on SDL2 or other additional libraries, with the exception of howler.js. This made the whole application lightweight and gave me a large amount of control.
Keep in mind I was doing this while Rust’s WebAssembly tooling was still immature. But I did manage to get Gate building to WebAssembly, and I posted Chirperjax online in January 2018. After doing that and seeing people enjoy playing it, I decided to create a more ambitious game with the same tooling. That was the beginning of Adventures of Pascal Penguin, which was completed in December 2018. It was essentially a year-long project for me.
So that was my experience in a nutshell. There are certainly other paths I might have taken, such as learning MonoGame or Unity. But when I stumbled upon Rust for the first time, I felt it was exactly what I wanted from a programming language, and since I was just doing this for fun I decided to learn it over the more mainstream gamedev options. I did not have much difficulty learning Rust. Sure, there was a time when I was, quote, “fighting the borrow checker,” but I learned that I could re-arrange my object models and my thinking to work more naturally with the borrow checker, which also improved my code quality. If I had some complaints, it would be the lack of a quick tutorial and immature libraries. But overall I have been very productive with Rust, and I encourage others to try it out.