April 20, 2024 | Building a Roguelike in Rust's Bevy Game Engine
A screenshot of my "game" in an emulated terminal emulator
One of my goals this year, per my Hierarchy of Fulfillment, is to write four types of software applications:
- a game
- a Command Line Interface ("CLI") application
- an embedded application (software running on a piece of hardware without an operating system)
- and some kind of application that uses a Large Language Model ("LLM")
At the end of last year, I completed the DungeonCrawler project game, but I was following a guided tutorial in the excellent Hands-on Rust book instead of writing my own code from scratch. I wanted to do a couple things differently for my game project this year:
- craft the code myself instead of following a tutorial
- build it in a formal game engine
To meet these requirements:
- I would use a project guide as a feature-list to develop a game, but would mostly figure out the libraries to use and the code myself
- I decided to use the Bevy game engine, which is written in, and uses, the Rust programming language
My Approach
Given these requirements, I found that the author of Hands-on Rust, Herbert Wolverson, had actually made a project tutorial for a Roguelike Dungeon Crawler prior to and different from that in his book called "Roguelike Tutorial". I used this tutorial as more of a feature-list of what I would implement and a bit of a "cheat sheet" for when I got stuck trying figure out how to implement a feature - which happened a lot in the beginning.
Features
After about 13 calendar weeks, but only about 9 or 10 weeks of productive development, I have a "game" that runs, but is not feature complete. What I completed:
- building a program that compiles into a "game" that runs in a desktop window that looks like an old school computer terminal
- a player character represented by an "@" which has common statistics for the Roguelike genre like "Health Points"
- that can move around a (seeded) randomly generated map
- which is composed of ASCII characters representing tiles of a map containing open spaces and trees which block player and monster line of sight and paths
- there are a couple kinds of monsters which will chase, and engage in combat, and have different combat statistics, use pathfinding, line of sight, and fog of war to determine where to move
- there are different kinds of weapons and armor which change the player's combat statistics, and potions which restore health when consumed, strewn randomly placed about the map
- the map is infinitely traversable and can be explored in any cardinal direction
- there is a game-over screen, and an inventory screen to manage items which can be dropped or used/equipped,
- the main game play screen has a dual-pane for displaying both the game play area and also the player's "Heads-Up-Display" (HUD) with statistics and player status.
- The game uses keyboard input but has (currently bugged) mouse-over support
Challenges
There were many challenges with implementation of many of the features, mainly with learning how to program in the context of the game engine, Bevy:
The Bevy game engine is unlike other game engines where you build the game by composing an entire scene, which then generates the necessary code, like Unity, Unreal Engine, or Godot. Instead, in Bevy, you build the game by writing the code using a framework called an Entity Component System (ECS).
I chose Bevy over the others because I wanted to learn how to program, not just build a game, and I wanted to do it in the Rust programming language. Bevy isn't the only game engine built in Rust, but because it encourages use of the ECS framework and relies on building the program from code, rather than composing a scene, it was the right choice for my objectives.
Bevy also has a moderately sized plug-in ecosystem. The Bevy Ascii Terminal plug-in abstracted most of the camera work in the beginning of the project. However it would later become a source of frustration as I attempted to add mouse-over support for my game and again when I needed to learn how to implement dual-pane support for the game's user-interface (UI). Still, the plug-in delivered the old-school "terminal" aesthetic I was going for and even provided an example of a limited "Roguelike" in its example documentation.
Another challenge was that after a certain point, the Roguelike tutorial I had been using as a feature-list and cheat-sheet for code became only useful for inspiration since my project had diverged so much from the reference material. This slowed down development as I had to figure out more and more problems on my own - though this is arguably the point of the entire exercise. So perhaps more a "feature rather than a bug" situation.
There were also the typical challenges associated with any programming project: poor project design (on my part), ambiguous goals, inadequate or incomplete documentation, version compatibility, and lack of experience resulting in "re-inventing the wheel".
Learnings
Regardless of, and specifically due to the various and numerous challenges, I consider the project a success.
Through the project, I learned:
- A significant amount about developing games in a game engine using an ECS framework
- The importance of clarity in design documentation prior to writing code
- How to navigate and consume programming library documentation
- The benefits of prioritizing on "done" rather than "perfect"
Most of all, I gained significant and valuable experience in writing in the Rust programming language.
One of best things I did best was promising myself to commit a new feature to the game every single week day, which I did for most weeks. That kind of promise forced me to write "good-enough" code and not get caught up trying to build bigger systems or plan for size or scale that would never be needed. As a result, I built significant momentum in the project - even when I stopped relying on the Roguelike tutorial for my feature list and cheat sheet. It wasn't until some significant disruptions in real life that forced me to re-prioritize and ultimately take a break from my project that my momentum stopped.
Wrap
With a hard deadline of End of Quarter (EOQ), or March 31st, I must now consider my project "Done" despite not having implemented even half of all the "necessary" features I had intended. I'd love to come back to this project in the future and finish all the primary features so I would feel comfortable calling it a "game" and publishing it. I'd especially like to build out some of the experimental features I've been dreaming up in my head.
I must now move on to my next project, but I did succeed in my objective: I built a game in a game engine using the Rust Programming language.