This week, I ported my "game" to two different Rust game engines. Why two engines? Why Rust? Let's dive in!
As a reminder, I'm aiming for a 2D game with "pixel physics for PC", which means it has some high level requirements from any engine...
- Input handling: support for keyboard, mouse and multiple gamepad controllers
- Rendering: support for rendering 2D images, ideally with support for sorting
- Audio: play basic sounds and music (probably the least complex requirement)
- UI support: very basic text rendering and buttons (not expecting to have the game be heavy on UIs)
- Platform support: PC, Linux, MacOS, Web (in order of priority - with web only being important for the initial prototyping stage as I'd like to let people playtest the game idea on this site without having to install anything)
- Correctness a statically typed language to implement game logic in will save me from myself; I have a toddler so I need to be able to operate on low sleep and catch my own mistakes easily.
- Performance: to implement pixel physics (water flows, sand forms into piles, etc), I need the engine to provide a high performance language with support for multithreading
- Rigid body physics: in addition to pixel physics, things like boxes need to be able to fall and collide realistically (I'll also be relying on collision handling to implement character movement!)
- Nice to have: compute shaders (allow offloading complex computations to the graphics card, which might be useful for pixel physics), networking (not aiming for networked multiplayer initially but would be nice to have the option) and console support (consoles aren't my initial target, but again, nice to have the option).
So what are my options?
Big name engines
When it comes to game engines used by indie developers, there are 2 3 engines that come to mind.
Epic's Unreal Engine
The case for: Unreal Engine is the only freely available engine that can reasonably be considered "state of the art" - particularly when it comes to graphics. It costs 5% after you hit $1M in gross revenue" (calculated before the app store/publisher cut is subtracted), which is an insanely good bargain given the quality of the engine. Epic is also competing with Steam by creating its own online store and "online services" (friends list, chat, matchmaking, etc), and competition is good & worth supporting. Source code for the engine is available, which helps immensely with debugging. Epic makes a highly popular game with its own engine (Fortnite).
The case against: The power of unreal comes at a significant complexity cost: it's a behemoth, and consequently it is mostly used by bigger studios who have whole teams of artists, programmers and designers. You are more or less required to write your game logic in either C++ or use their custom Blueprints visual scripting language; C++ is a notoriously complex language which I have only a bare grasp of, and Blueprints suffers from being difficult to maintain as it gets complicated. Theoretically it has support for 2D, but as I researched it I found far more people recommending to stay away from it than people singing its praises.
Unreal's heavyweight nature is just too much for me as a solo developer: I don't have a single dedicated artist, let alone a team of artists who can set up an art pipeline. The complexity more or less rules it out, and its half-hearted support for 2D is the nail in the coffin.
Unity
The case for: Unity is the undisputed king of indie games. Originally it made a name for itself in the early 2010s in the mobile gaming space, but it has since moved upmarket: it doesn't quite compete with Unreal in graphical quality, but it's getting there. At least 50% of the top selling games on Steam are made with Unity, both 2D and 3D, usually by medium or small teams, or even solo developers like myself. Making games is helped significantly by being able to buy third-party premade components from their Asset Store (e.g. water that looks realistic and ripples when your character moves through it), which has a truly impressive amount of content on it. You write your code using C# (a much more forgiving language than C++!), and they compile it to C++ under the hood to make your game work on the web and on consoles (Xbox, Playstation, etc).
The case against: Unity the company went public a few years ago, hired a ton of people and thoroughly lost its focus - or at least, its focus does not seem to be on helping game developers succeed anymore. They merged with IronSource, an ads company, and now have twice as many staff as Epic despite their own engine being considerably less state of the art than Unreal. They bought half of a movie visual FX studio then laid them almost all off 2 years later. They are making a net loss of hundreds of millions a year. They tried to retroactively change licensing terms of their engine to significantly increase its price, then only partially walked it back. Almost every aspect of the engine (rendering, networking, UI, input, etc) has an unfinished replacement available (each of which tends to have weird incompatibilities with new versions of other aspects), which is made worse by the fact that the old version of each aspect is usually deprecated & not receiving bug fixes. Unity does not make games with Unity, only tech demos. Source code is not available, though in some cases you can use a third party IDE to decompile and get the gist of some of the code.
I just can't get over the apparent misalignment between Unity and game developers. Epic wants developers to succeed otherwise Epic doesn't get paid; meanwhile Unity wants to push developers into selling ads using its own adtech, and make its stock price go up? Apparently making a good game engine stopped being a core focus about 5 years ago. I used Unity for a few game experiments in the last 1-2 years and the "deprecated, replacement not ready yet", no source code and lots of little paper cuts piling up made it frustrating. I suspect that Unity isn't going away, but its golden age is over, and it will have to do a lot of work to raise its quality again.
Godot
The case for: Godot (pronounced "ge-doh") is 100% free and open source, so no licensing issues and source code is available for debugging. It has great 2D support and okay 3D support. It has a strong focus on being easy to use. Documentation is pretty good, at least for an open source project. It has some reasonable funding from donations, including from Epic. When Unity's shot itself in the foot with its own licensing kerfuffle, a good chunk of small and solo developers jumped ship to Godot (and some developers started funding it too). It supports a python-like scripting language called GDScript and C#, plus with some hoop-jumping you can use other languages such as Rust too.
The case against: you can count the number of "feature length" released games that use the engine on one hand - I almost left it out of this section for that reason. The focus on ease of use seems to often come at the cost of performance - which is okay for simple 2D games but a problem for non-trivial games. For example, GDScript is easier to learn but slower than C#, but if you use C# (or any other language) then you still pay the performance cost of GDScript due to how the engine is designed. The leader of the open source project is extremely adamant that ease of use beats all other considerations, so this is unlikely to change. C# support exists but anecdotally I have heard that there are many rough edges compared to GDScript. Version 4.0 was released last year after several years of development but was apparently rushed out with many critical bugs. There's no official support for consoles (though this is true of all open source options due to NDAs and console manufacturer licensing terms).
Godot is an impressive creation! The fact that the #3 game engine is open source is remarkable and a testament to everyone who has worked on it. However, for my use case, GDScript isn't going to perform well enough to implement pixel physics, so I would need to implement at least that portion in another language. I am also wary of using GDScript in general: it's a dynamically typed language that they created for (IMO) dubious reasons, and so support for it outside of their own editor is quite minimal. If I had to pick one of the big 3 engines, I would pick this one, but I decided to keep looking for a better fit.
Exploring other options
Once you get outside those big 3, you're looking at more niche options: you can get a better fit for your needs, at the cost of a smaller community (which means having to solve more things yourself).
So what would be the perfect fit for my needs?
- Engine with editor, framework, or library? Game engines exist on a spectrum of completeness vs flexibility: at one end they provide a full editor to lay out your game world and everything must be done the engine's way, and at the other end they provide some basic abstracions over platform-specific implementation details. I don't need an editor built-in because third party level editors like LDTK should be fine for my 2D game.
- Programming language: I really like the Rust programming language. I have been using it on and off for the last 8 years, it gives exceptionally good performance by default (typically the same as C and C++, it fits my way of thinking about the world, and (unlike C/C++) programs I write in it don't crash at the slightest provocation.
- Unfortunately choosing Rust rules out any easy console support, as console manufacturers require use of their custom C++ compilers. That would be a nice problem to have, as the bigger problem is that console support also requires having produced a successful enough game that the manufacturers are willing to accept you as a "registered developer"! So we'll worry about that later.
- Rust is also infamous for having slower compile times than many other languages (though not as bad as C++). In my experience this is mostly mitigated by having a processor with many cores (I have 16, or 32 with hyperthreading) and using a faster linker such as lld or mold.
Rust game engine options
Well, first off, not needing an editor is great, because basically no (2D) game engine in Rust offers an editor!
The major 2D-compatible options available are:
Engine | Elevator pitch | Status |
---|---|---|
Bevy | "Let's build a state of the art engine in Rust, using the ECS paradigm for absolutely everything!" | ✅ Very active development, but usable |
Tetra | "Let's build Monogame using SDL" | ❌ Unmaintained |
GGEZ | "Let's build LOVE 2D but in Rust!" | 〜 Maintained on and off |
Macroquad | "Let's make a basic-as-can-be abstraction layer over old graphics APIs" | ✅ Maintained |
Comfy | "Macroquad, but over new graphics APIs, and slightly more batteries included!" | 〜 Active development, unstable |
Here's how they stack up with platform support:
Engine | Windows | Linux | Mac | Web |
---|---|---|---|---|
Bevy | ✅ | ✅ | ✅ | ✅ |
GGEZ | ✅ | ✅ | ?* | ❌ |
Macroquad | ✅ | ✅ | ✅ | ✅ |
Comfy | ✅ | ✅ | ✅ | ✅ |
Tetra | ✅ | ✅ | ✅ | ❌ |
(* GGEZ says it both supports and does not support MacOS)
GGEZ and Tetra don't support web and also don't seem to be maintained much anymore (Tetra even suggests going elsewhere). Neither thing is the end of the world (one reasonably successful game was released using Tetra recently), but it's enough to make them not the first choice.
So that leaves Bevy, Macroquad, and Comfy.
Bevy
Bevy is well known in the Rust community for having a significant ecosystem around it, and I had played with it a bit before, so I ported my little pixel simulation to it first.
I used the Bevy game template as a starting point, and the Bevy Pixel Buffer plugin for rendering my pixels. Bevy operates by having you register functions ("systems"), which then operate on a mix of global variables ("resources") and your game objects ("entities"), so I set up my pixel world state as a resource and registered various systems to handle input, progress the simulation, update the render state, etc. After about an hour of messing around, I had my simulation ported, though (as you can see) the pixels texture isn't resized to properly cover the available window yet:
How I felt about using Bevy:
- The engine takes complete control of the flow of execution: it doesn't just invoke your functions for you - it decides whether they should be invoked, it orders their execution and even magically multithreads everything based on your functions' type signatures. This is scary to me because it screams "execution order bugs waiting to happen"; I did specify ordering of systems relative to each other, but it's easy to miss a constraint!
- The system/entity/resource ECS approach is surprisingly ergonomic. I know there's a whole bunch of Rust trickery which is used to make it happen, but I didn't need to know any of that to make progress. That's good! I hacked in window resizing with minimal pain.
- On the other hand, the ratio of engine boilerplate to gameplay code is quite painful. Although the ECS approach is well done, it encouraged me to split my state into tiny pieces, and define tiny single purpose functions. That's (probably?) great for maintainability in the long term, but I spent more time making the code appease my design sensibilities than actually making progress on the game.. which is not good.
- It's so easy to forget to register a resource or system, which then gives you either a (very clear) crash or a "why isn't that working when it should?!" moment. This is especially the case when rewriting & reorganizing code: you can easily get carried away and forget.
- Slow compile times are real: my game compiled from scratch in about 50 seconds - with close to 100% CPU utilization and my computer's fans spinning up to maximum! Iteration speed when making a small change is still only 1-2 seconds, so this isn't a big concern, but it's a little annoying (it will be a bit more expensive to run automated builds for, for example).
- Official documentation is almost non-existent. There's an unofficial cheat book which has some good information, but much of it is out of date. In practice, I relied heavily on the examples (which have great coverage of features) and API documentation in the source code (which is of great quality).
Overall, I like Bevy, but it seems like a poor fit for prototyping.
Macroquad
To compare, I went with the polar opposite of Bevy: Macroquad, a library so minimalist that the author first created their own minimalist window & graphics management, random number generation, and audio libraries! I admire the dedication.
This time I had to implement my pixel rendering from scratch; turning my pixel data into an image texture that could be rendered was straightforward, but it took a bit of digging and a fair bit of experimentation to figure out how to ensure my pixels were never stretched, squashed or blurry at various different window sizes! As part of fixing that, I also made the pixel simulation resize itself to the available window space; of course in the longer term it'll be necessary to have the window act as, well, a window onto the game world, such that the pixel simulation can be of arbitrary size.
How I felt about using Macroquad:
- It's very DIY! That's a good thing (you can easily see what's going on) and a bad thing (you have to do it all yourself). As a relative amateur in the field of game development, this is good for my learning, but it does slow down prototying. As an example, I haven't implemented any animation yet, but it looks like there's no support for that baked in.
- There's basically zero engine-required boilerplate. Want a line? Call the globally available
draw_line()
function. Text? Type outdraw_text()
. I'm sure that as my needs get more complex I'll have to introduce my own abstractions and conventions that will reduce this, but for now, this goes a long way towards making prototyping easier. - Compiling is super quick - I can currently compile everything from scratch in under 5 seconds. Obviously this will also get slower as I add more libraries, but it shows how lightweight Macroquad and its child libraries are.
- Both book/guide and API documentation is almost non-existent, but fortunately the API is simple, the code is fairly easy to understand, and there are sufficient examples to understand basic usage. Usually I find myself discovering the right way to do things via experimentation (which can be frustrating, as was the case with my getting my pixel sizing consistent).
Overall, I think Macroquad is good enough for this early phase of prototyping: it's simple and basic, like my "game".
Playable web build
Both Bevy and Macroquad support web builds, so I got them both working. Bevy's performance seemed to be slightly better, and Macroquad's performance on Firefox was particularly awful, at least until I worked out that sending 16MB of pixel data to the graphics card each frame was maybe not optimal.
Anyway, here's a build of the "game":
Controls:
- 1 to 5 select between drawing empty, solid, bricks, sand and water
- 6 to 0 change drawing cursor size
- P to toggle pause; Space to step frame by frame
- C clear the screen
- - and = to decrease and increase the size of pixels
As you can see, there's not much there! The white & red pixels in the corners are markers to show me that the texture isn't getting cut off. And yes, sand and bricks stack on top of water still.
But, it's better to share early and often, and I figure I can't possibly be more embarrassed about the state of this thing than I am now, when it doesn't even have a player character to control yet. Still, if you have feedback, shoot me an email.
Anyway, that's all for now!
Appendix: Non-rust options
If I were to drop the "programming language must be Rust" constraint, there are some more non-Rust options worth considering:
- Heaps.io is a 2D and 3D engine that has you write code in Haxe, which is a language that itself compiles to many other languages.
- FNA or MonoGame are reincarnations of Microsoft's C#-based XNA framework.
All 3 options have successful commercial games using them, and they support consoles (again, once you're a registered developer etc). If Rust ends up being too painful, those are what I think I'd try next.