Highlights from "I spent 6 years developing a game in Rust and it just shipped, AMA"
I recently released Way of Rhea, a puzzle game that I’ve been working on in Rust since 2018. If you like games like Braid, Talos Principle, or Portal you’d probably enjoy it:
After launch I posted an AMA on r/rust_gamedev, and people asked a lot of great questions!
This write up contains curated highlights from the AMA, with some additional editing for clarity.
Table of Contents
- Table of Contents
- Questions about Rust
- Questions about the engine
- How much time did making a custom engine add to the project?
- How did your crash reporter strategy work in practice?
- How did you handle windowing and input?
- Why didn’t you use an existing scripting language?
- How hard would it be to add support for macOS back in?
- What are your plans for the engine and scripting language going forwards?
- Questions about game development
Questions about Rust
What made you decide to use Rust?
Before Rust, I was working on a C++ game engine. A friend had been nagging me to check out this cool new language, but I was skeptical and hadn’t yet given it a chance.
At some point I got fed up with some CMake silliness I can’t remember the details of and decided to take a break for a few days and hack on something else. I found Steve Klabnik’s Rust book and read it front to back, I believe it was the official resource at the time.
It blew my mind that there was an alternative low level language I could be using that had a built in build system, built in unit testing & docgen, algabraic data types, non exception based error handling, etc. It was hard to justify sticking with such a high friction language knowing that there were alternatives. The memory safety aspect is also certainly neat, but as someone building single player video games it’s not really a primary concern for me.
Since then, I’ve gotten really into working to progress efforts to move away from C/C++ where possible. I’m also active in the Zig community, and on the Zig Software Foundation board. I know the internet has a tendency to treat these efforts like competing sports teams, but I think this is missing the mark.
When you’re a beginner, it feels important to invest your time into the “correct” language–lest it all be wasted. To avoid sitting with that discomfort, new programmers often tell themselves that their language or tool is perfect and infallible and lash out at anyone who criticizes it.
The better future is the one with multiple high quality low level langues keeping eachother honest.
I have my opinions, but I’d encourage beginners to worry less about programming language choice, and those getting into gamedev to consider all the available tools and pick what suits their current needs.
What was the most frustrating part of using Rust?
Compile times! Other than compile times…
I write game engines, which means that sometimes, I need to do low level bit fiddly stuff. This stuff is hard to get right in general, but it’s unnecessarily hard in Rust. Unsafe Rust is poorly documented and hard to work with, and people act like you’re committing code crimes by using it at all when you seek help online.
Rust is also designed with the expectation that you only make two types of allocation: heap allocation, and stack allocation. You can implement your own custom allocators for different parts of your codebase if you really want, but you’re cutting against the grain.
It’s also pretty frustrating that Rust doesn’t have support for reflection.
Ecosystem wise, it feels like we have the wrong goals sometimes. I remember in the early 2000s when OOP was all the rage, and if you complained OOP wasn’t helping you achieve your goals, people would tell you that you just needed to OOP even harder. Rust feels a little bit like that sometimes. All of these paradigms only matter insofar as they affect one thing: the end user of your software.
I think the best way to avoid this trap while still pushing for new and better paradigms is to see things all the way through. If you’re building a audio library, also build a music player that uses it. If you’re building a game engine, ship a real game in it. These examples are not call outs of anyone, I picked the first two ideas that came to mind.
By seeing things all the way through, you can see how your decisions effect the end user, even if even if there are lots of twists and turns between the decision and the end user. That keeps you honest.
What libraries and crates did you use?
I used a number of crates and libraries, but nothing “load bearing”. Of particular note…
I used gl to get the OpenGL function pointers. scopeguard was also super useful here–RAII isn’t a great match with APIs like OpenGL, scopeguard essentially adds defer to Rust making this type of code much easier to write.
I wrote my own code for windowing and keyboard/mouse input, but referenced SDL whenever I got stuck. It was kind of an annoying task–I’ll probably use SDL3 for my next game–but I learned a lot doing it for myself.
I also used notify to get file change notifications for hot swapping, and various STB headers for things like parsing TTFs.
reqwest was useful for crash reports.
Some people asked if I used a library to assist in parsing my scripting language. I’m personally not a huge fan of parser libraries—writing a parser seems tricky at first, but in practice it’s usually easy to whip up a quick recursive descent parser. In school they’ll tell you that recursive descent isn’t powerful enough for common grammars, and theoretically they’re right, in practice you can easily work around most issues—or just design a simpler grammar.
Here’s a great resource on writing compilers if you’re interested in this sorta thing.
I used Steam Input for controller support. The end result works well but I won’t be using it in the future.
Notes on Steam Input…
Steam Input is valves input remapping feature. Players can play a game that may or may not have controller support, and remap their controller to behave like a keyboard to get the behavior they want.
That part of it is great.
There’s also a developer facing side of Steam Input, that provides APIs for:
- Controller input for every controller imaginable
- Controller glyphs for in game UIs
- Input layers
- A controller rebinding UI
- Access to rumble, controller LEDs, etc
- And more…
The problem is this ties you to the Steam store, and also, the implementation is totally busted. Layers are fundamentally broken and behave different on different platforms in game breaking ways, the actual input behaves differently in subtle ways and appears to be tied into weird Steam Overlay bugs, the controller rebinding UI doesn’t always show up, etc.
You have to set up the controller bindings via a domain specific language Valve invented. The language is poorly documented–examples often have syntax errors or are flat out incorrect–and it has little to no error handling.
The only way to get these config files right is to try to do it through their UI and save it, but good luck figuring out how to do that, or how to attach the result to your build!
Also the UI often generates code like this that looks incorrect but works, so I guess it’s correct…?
"binding" "game_action GameControls Action_Interact, , "
Have ecosystem changes affected the project?
The last 6 years has seen a lot of changes in the rust gamedev ecosystem. You said you wrote your own game engine, but did you have to update or switch any underlying libraries during this time? Any major changes that were dictated by external changes in rust or underlying libraries?
I didn’t have to change very much because I wrote almost everything from scratch. You can certainly go faster by pulling in more dependencies, but I learned a lot doing things myself and sidestepped this type of churn.
What do you think of LogLog’s Leaving Rust Gamedev blog post?
I agree with pretty much everything in their article, though I’ve come full circle on ECS and think it’s a useful paradigm–I touch on that in this talk. I have some additional complaints not covered by their post here.
I haven’t been super online recently because I’ve been so busy with the launch, I only saw the LogLog article because a friend sent it to me. I hope that the reaction to it has been “wow great feedback from someone shipping stuff, lets figure out we can best address these points!”
Questions about the engine
How much time did making a custom engine add to the project?
I think people tend to assume I spent six years on the game because the engine ate up all my time, but that wasn’t really the case. Most of my time was spent on content. It just took a long time because I was initially a hobbyist, and didn’t know how to focus on the right things.
I built the scripting language as a hobby project before I started the game. If you don’t factor that in, I think engine development probably took up about 20%-30% of the dev time. Of course that doesn’t factor in whether or not the engine I build was more or less productive than what I would have used otherwise. I think it was a mixed bag?
I’ve used off the shelf engines for past jobs, and freelance projects like LUCID.
It’s certainly a lot less work to use an existing engine, but at the same time, you don’t have as much control over your workflow–e.g. I find hot swapping highly productive, and while all the major engines claim they support it, they don’t really in practice.
Making games in custom engines is sort of a high velocity low acceleration endeavor. YMMV.
How did your crash reporter strategy work in practice?
I read your blog post about the crash reporter a couple years ago, so I am curious how well the whole “running the game as a subprocess” held up now that the game is released? Did you stick with it with the release, or did you run into some unforeseeable problems over the years?
The blog post in question is here, Subtale also created a Rust library based on the post.
It’s held up pretty well–I’ve definitely used it to catch some real problems–though as someone pointed out not having a separate executable means it’s not able to report missing DLLS. Not a big issue, I only really depend on the OS libraries, but unfortunately this can be an issue for libc which I didn’t consider.
This is kind of frustrating since I don’t use much of libc, but I can’t figure out how to configure Rust to just use musl or something, so as a result I don’t currently support older versions of Ubuntu (min version listed on Steam.)
The whole sending crash logs to my Discord thing is also great compared to asking people to send them to me themselves, but it could be a lot more ergonomic. I don’t really get a lot of errors so it’s probably fine.
All that being said, while the crash reporter has held up, I’m currently writing a crash reporter for an art tool called Magic Poser that I do some engine work on, and I think I could write a much better crash reporter now. When the Magic Poser crash reporter is finished I’ll publish a new blog post (or maybe multiple?) covering how it works.
How did you handle windowing and input?
When I was writing that part the windows crate wasn’t as far along as I think it is now, or I didn’t realize that it was, otherwise I would’ve likely used it.
I ended up just up just writing out the basic function declarations I needed in Rust, but making them extern C
, and then implementing them in whatever language the target OS preferred and using CC to compile that piece and link it in.
I considered declaring extern functions for the pieces of the Windows API I wanted to use and calling them from Rust, but I was trying to support macOS at the time and doing that for Objective C stuff seemed like a nightmare.
My solution worked fairly well, but if I was doing it again from scratch I would probably just use the windows crate and save myself the trouble, especially since I’m not supporting macOS anymore anyway and IIRC the Linux stuff I used all looked very easy to call from Rust.
Why didn’t you use an existing scripting language?
I built the scripting language before I started development on Way of Rhea, I wasn’t trying to be pragmatic I was just hacking on stuff for fun in my free time. I wanted hot swapping in Rust, and my first attempts were to hot swap a Rust DLL, and to integrate existing scripting languages.
The DLL route didn’t end up being viable (would crash on my target platforms on some standard library stuff, may have been fixed since then I’m not sure.)
There may be more options now, but at the time there weren’t any Rust specific scripting languages mature enough for my use case, or at least I didn’t find them if there were. I considered Lua because it’s super easy to integrate (with the exception of the setjump/longjmp error handling, ha) but I really like static typing. I think for a long time people had this idea that high level languages were more compatible with dynamic typing, and low level with static typing, and I don’t really agree with that.
I could have tried something like Python + MyPy, but integrating Python into other languages is a pain and MyPy feels like a half-solution. I have similar opinions on Javascript/Typescript.
I spent a few days trying to prove to myself that making my own language wasn’t viable…and then realized it totally was and was a lot of fun, so I kept working on it.
By the time I started Way of Rhea I wanted to be more pragmatic, but I already had the language so there wasn’t any reason not to leverage it.
How hard would it be to add support for macOS back in?
It usually takes me about a week or two to get it playable on a new platform, maybe a few more for beta testing to work out weird edge cases. Since I already have a bunch of macOS code it would be easier than other ports.
However, the game relies heavily on OpenGL which is now deprecated on macOS. AFAICT it’ll keep working for the foreseeable future…but it would feel a bit weird to charge people money for something that could go away at any time. If I was gonna port to maOS I’d want to port it to another graphics API which would take additional time–especially since I didn’t abstract that stuff out very well.
The main blocker isn’t really technological, I dropped macOS support because Apple is too annoying to deal with from a business standpoint. More on that here.
What are your plans for the engine and scripting language going forwards?
My original plan was to use it for future games, and possibly to open source it eventually.
I’ve learned a lot in this process and don’t think I would actually recommend anyone use either of them–I’m making a new engine for game 2 myself that will carry over some of the ideas, but have a very different focus.
It’s possible that I’ll open source my second engine, or parts of it, after I ship game 2.
Questions about game development
What do you consider a successful launch?
My goals were modest since this was my first commercial game.
I knew my game was going to be featured in the Cerebral Puzzle Showcase a few days after launch, so I had a goal to get 10 reviews on launch day so we’d be featured by the Discovery Queue before the showcase started.
We passed that goal on day one, got a massive number of views from discovery queue featuring, and are now at 30 100% positive reviews from people who bought the game at the time of writing!
This is super exciting, because it’s further evidence that this stuff isn’t luck. Valve is clearly willing to give games like mine a chance, people are clearly enjoying it, if I can’t convert enough of those views then I just need to keep making good games and find better market fit.
My original goal was to break even, but I quickly realized that being stingy with spending would prevent me from experimenting, and I needed to experiment to learn. Breaking even on a game is easy…just spend $0 and sell 0 copies and you’re set haha.
My next goal is to get a game into Popular Upcoming. Once I achieve that, I’ll probably set more concrete financial goals.
Side note on events…
Most of my expenses came from showing my game at events. Events are expensive–the booths are costly, travel and lodging can be expensive, and there’s opportunity cost. I showed Way of Rhea pretty much anywhere I could, and learned a lot about which events are effective and which aren’t:
- In person events are the most effective if they’re featured on Steam (check steamdb to see if it was featured last year)
- Online events can be great because they’re often free and don’t require travel or much time investment. However…
- They probably won’t do anything unless they’re Steam featured
- Don’t give online event organizers money unless you have a really good reason to trust them
- Some free in person events are useful in ways that are hard to measure–e.g. maybe you don’t get a lot of wishlists, but you meet press or streamers.
- Events are in a sense “cheaper” if you can show multiple games at a booth, either because you have multiple games out or because you’ve teamed up with other developers. Each event has their own rules YMMV.
- Steam featured events boost your wishlists a lot, but probably don’t convert as well as organic wishlists, don’t spend too much money trying to make up for a low organic wishlist rate.
Aren’t puzzle platformers one of the hardest genres to sell on Steam?
Yeah, you probably shouldn’t try to sell puzzle platformers on Steam. Way of Rhea started out as a hobby project so I didn’t do any market research before deciding on the concept.
I didn’t understand why some people around me were finding an audience for their games so much more easily than me–it felt like I was playing on hard mode. I spent a lot of time trying to understand, and eventually realized how important it is to get genre right.
What are the most important lessons you learned?
End user matters more than everything else, you gotta focus on the end user.
If you’re making games, you have to understand what people want to play and why. If you think indie game virality is luck you’re not gonna make a viral indie game, there’s a reason some games sell and some don’t. Doing market research isn’t selling out, build something you’ll enjoy building that others will enjoy playing.
Game engines are fundamentally art tools. They also provide a game runtime, but that’s secondary and less interesting. The primary user is the game designer. If you’re building an engine, focus on the designer when making trade-offs.
Beta test as early as possible. We had players playing the game 7 days into development. This was hugely beneficial to the project. However, we didn’t run the closed beta with a build of the entire game until a couple months before launch. The beta had a huge positive impact on the quality of the final game, next time I’ll be starting that ASAP.
Learn from others. howtomarketagame.com is a great resource on stuff like this.
How did you find beta testers?
Would you mind sharing how you went about finding these people? I know r/playmygame and r/DestroyMyGame exist. I’m working towards having something playable that I can do some playtesting on Steam, which I (hope) will bring in a small amount of testers organically. But other than utilizing my social media accounts, I’m not sure if there’s other good places I should look into?
When I first started working on the game I brought it to a lot of small local events. It was short enough–and had enough low hanging fruit that needed fixing–that just watching anyone play through it was super valuable. I love in person playtesting because it’s easier to get a read on how people are feeling.
When I was living in NYC, NYU Game Center hosted Playtest Thursdays that were open to the public. Anyone could bring a game, and anyone could stop by and try out the games. When I moved to Santa Cruz I started a similar event with a friend at UCSC–if the event you want doesn’t exist, start it.
Over time I started taking it to bigger events as well, and doing Steam featured festivals with the demo.
As the game gets longer though, this type of playtesting stops being viable. Eventually you really need people to sit down and play through 10 hours of gameplay or whatever which isn’t gonna happen at an event. Another downside of in person events is that you’re not exactly testing with your target audience–people will play games they wouldn’t actually purchase in person because playing an unreleased game at an event is a fun novelty experience.
If you can’t watch people play in person, the next best thing is a recording of a playthrough, followed by a playthrough with notes. You do have to find people for this though.
When I did all the in person events, I gave out business cards with my discord server on it, and had a mailing list sign up sheet. When it came time to do the closed beta, I just shared info on how to sign up for it with everyone who had expressed interest and got a lot of sign ups. I also shared on my personal social media, and was surprised to find that I have a lot of friends who play a lot of puzzle games that were eager to try it out.
I actually did set up Steam Playtesting as well, but I didn’t end up using it since I got more than enough sign ups directly. Steam Playtest seems great though, maybe I should have drew half of my names from there and half from my direct sign up or something.
Side note on feedback focused communities…
I’m a little skeptical of communities dedicated to playtesting–I want feedback from people who would actually buy my game.
In my experience, people who give feedback for feedback’s sake tend to just suggest random polish stuff. People who would actually play your game say things like “I like it when puzzle games do XYZ, and was really let down when yours didn’t do that.” The latter feedback is way more useful to me.
I’ve never actually posted my game on these subreddits though, maybe my skepticism is unfounded and they’re actually great.
Side note on reviews…
If you’re shipping on Steam, getting at least 10 reviews from paying customers is super important for visibility.
I wanted to gift my beta testers free keys in exchange for helping me test, but then I’d be missing out on reviews from all the people who were the biggest fans of my game.
I felt bad that I couldn’t do this, but I made it clear up front and in practice everyone seemed excited to support a game they enjoyed so it was totally fine.
Why did you do a closed beta?
Any reason why you made your playtests exclusive? I’ve been leaning towards just doing an open playtest that lets anyone who wants to play, play it instantly. I figured the more exposure I get, the better. But I suppose it does make sense to maybe layer the playtests out a bit initially like you did, so that you can fix big problems before the next group plays?
Way of Rhea is very content based–once you’ve solved all the puzzles and figured out all the secrets, it’s over, unless you’re into speed running. I personally do replay puzzle games, but I’m pretty sure I’m in the minority. I didn’t want everyone to play a worse version of the game before it released and then have no reason to buy it and experience the better version when it came out. I think most of my beta testers did buy the final game, but that likely wouldn’t have been true for a larger beta.
Being part of a small group is also more exciting for people, and it let me talk to everyone one on one. Each individual tester had a much bigger impact on the game. Adding more than a few people each week also wouldn’t have been that useful to me–I needed time to make tweaks in between test groups so people wouldn’t all run into the same stuff.
I did have a free demo available on Steam for a lot of development, and it had crash reporting and stuff built in, so that helped cover my grounds from a technical perspective of making sure it ran on everyone’s computers.
All that being said, content based games probably aren’t the way to go for most indies. My next game will likely be systems based, and in turn it’s likely my beta testing strategy will change.
Thanks for checking out my post! If you want to support me, the best way is to pick up a copy of my game on Steam or sign up for my mailing list.
Feel free to leave comments on Discord, or send us an email!