RATRACE :: POSTMORTEM
RATRACE :: POSTMORTEM
Submitted for the illustrious Taser Shoes Incorporated Game Jam.
I::INTRODUCTION
RatRace is my first "completed" game. Working under the time constraints of a game jam helped to ensure that at some point I would make a submission and not just perpetually add onto a never ending project. Following the philosophy of Tyler Glaiel's blog post, I am ready to move onto another hobby project and bring some of the lessons I learned during this game jam weekend with me.
The remainder of this paper is organized as follows:
Section 2 describes the planning and design process. Section 3 describes development. Section 4 is a brief evaluation. Section 5 concludes.
II::DESIGN
"Have lots of ideas? Write lots of papers." --> Make lots of games.
I sat down the night before the start of the game jam to consider options for my project. The theme "rattical idea taser" was fairly abstract with room for interpretation. I knew I wanted to work in Godot, and I wanted to treat the game jam as an opportunity to learn more about certain aspects of the game engine. In particular, I wanted to learn more about the networking/multiplayer API, as well as how procedural generation could be implemented in Godot. I made the decision to work in 2D in order to reduce the complexity of any algorithms needed.
At the end of the brainstorming process I had come up with a (overly ambitious) 6-phase plan for a 2D, top-down, co-op/competitive game where players race around a generated labyrinth, collecting items and selling them to afford the ticket fare to the next level. I only managed to roughly complete through phase 2 of this plan by the end of the game jam. Below are the outlines of the two phases from my notes:
PHASE 1 :: BASICS
- Create demo level and player that can move.
- Add in initial multiplayer support (do this early to see what state needs to be synchronized)
- Add in items with values
- Add in ability to pick up and sell items
- Random level generation, players can now spend a portion of their score to proceed to the next level.
- Maps get larger and fare becomes more expensive with each level? Time limit?
- X levels to game completion. Survivors left win.
Although I had been overly ambitious with my planning (not everything is included here), there were still a great number of ideas I ended up cutting out from the final outline. During the game jam itself, talking to other participants, I would pick up on new ideas that I would be tempted to throw into the game. While this is tempting, it can lead to a muddled game that never gets finished. I attended a talk on paper writing where the speaker argued that each paper should have a crystal clear idea it reinforces throughout its writing. If you have lots of ideas, you should write lots of papers. I think the same can be beneficial for game development (at least for new developers). Each game should have a core idea behind its gameplay. If you have lots of ideas, make lots of games.
III::DEVELOPMENT
III.I PHASE 1 :: BASICS
Phase 1 involved creating a basic game loop prototype. Since I was fairly new to Godot, it involved learning tile maps, writing boiler plate code, and multiplayer logic. My thought process was that if I planned to make the game co-op, I should write that logic as soon as possible so I could start testing it early and understand how state is synchronized across clients. This choice paid off early on as I was able to catch some UI bugs when determining what information to show each player. I followed FinePointCGI's amazing tutorial on Godot 4 multiplayer logic and ended up using the GameManager and Players data structures created in the tutorial to track each player's inventory.
I was surprised by how much Godot's multiplayer API manages the dirty details of host/client synchronization automatically for you, as well as how little state needed to be synchronized. The only state managed was each player's position, the rest of the game state change was calculated locally on each client. While this would probably not be a good idea for a competitive game, for a slapped together game jam prototype, it works pretty well.
III.II PHASE 2 :: LEVEL GENERATION
By the end of phase 1, we would have a basic game play loop on a handmade stage. Phase 2 aimed to substitute the manually designed stage for one that was automatically generated on a set of parameters. My original plan was to use a maze generation algorithm and replace the tiles of the maze with randomly chosen handmade rooms that match the tile's constraints for door placement. Before starting I watched Herbert Wolverson's Procedural Map Generation talk to get a sense of what methods exist. I ended up modeling my map generation process off of the outline provided in Vazgriz's Procedurally Generated 3D Dungeons tutorial.
Room placement: I took the simple approach of randomly picking room sizes and positions until either a specified number of rooms had been placed, or the algorithm reached a particular failure threshold. Essentially, the algorithm is placing rectangles on a 2d grid while avoiding overlapping the rectangles. I don't require any buffer space in between rooms, so some may be placed directly adjacent, leading to non-uniform shapes. In practice, these rectangles are placed by using the TileMap script API to "paint" floor tiles onto the background.
Example room placement, the overlapping room is rejected while the others are valid:
Once the rooms have been placed, I implemented a flood fill algorithm to iterate over the 2d space and detect the rooms. This approach merged together adjacent rooms into a single room and recorded the tiles belonging to it.
After the flood fill algorithm, the two adjacent rooms are merged into one:
Hallways: Following the methodology in Vazgriz's tutorial, I used a Bowyer-Watson implementation for Godot to calculate a Delaunay Triangulation graph, using the center coordinates of each room as the vertices. The edges in this graph represent potential hallways connecting the rooms. I wrote an implementation of Prim's algorithm using the player spawn room as the starting node to create a minimum spanning tree (MST) of the room graph. So long as hallways are placed where each edge in the MST is, no room will be unreachable from the spawn. However, this map layout would be a little boring with no loops connecting rooms. Adding edges back into the graph from the Delaunay Triangulation graph with a certain % chance allows for the creation of more interesting connections.
Example Delaunay Triangulation graph and corresponding MST, spawn room is marked with S:
After all of this is done, we have a MST of the room graph with a set of hallways planned, but those hallways need to actually be placed on the TileMap. I used Godot's AStarGrid2D to map out paths in the grid between the coordinates of each room involved in an edge. The pathfinder returns a set of coordinates marking the path, which are then painted over with floor tiles.
Item Spawning: I wanted to provide an incentive to the player to explore deeper in the maze. To accomplish this, I use a distance metric to dictate the value of items that spawn. I use the MST distance from the spawn for each node to gauge the "value" of the room. I collect the list of room distances and pass them through a MinMax standardization function to map them to a value in the range 0-1.0. This mapped value functions as the probability of an item spawning on each tile of the respective room. I dampen this probability to a max of 0.5 and further reduce it for high value items.
Since item spawns are governed by the MST distance and we may have edges in the graph that don't belong to the MST, the player may be able to take shortcuts along these edges to quickly access high value rooms. A different distance calculation could be used if this was to be avoided, but it didn't seem to be game breaking when I tested it, and rather could reward the player for exploring in different directions.
Example MST distances from spawn with an extra edge added during generation. This shortcut allows the player to reach a room that is 11 units away while only traveling 8:
IV::EVALUATION
Looking back about two weeks after the fact,
Things that worked well:
Having a development plan broken up into stages with a clear deliverable at the end of each stage really helped me to organize my thoughts and still end up with a semi-playable game even if I ran out of time before I could complete everything I planned. Once I realized I would likely not make it past phase 2, I was able to adjust the game play loop to better match the features I had implemented and package things up.
Placeholder art assets were great for speeding up the initial development process. Within a few minutes I had all of the art I needed and could start developing with some sense of what things would look like. It was not until the very end of the game jam that I tried my hand at making some marginally better art.
Working in the game jam setting was a great motivator to stay focused on the project and create a finalized version to submit before the deadline. The collaborative environment allowed for some fun exchanges of code, for instance, the item bounce animation is taken from the illustrious Rodent Rush.
Things that didn't:
I think the UI is one of the weakest points of RatRace. Visiting the game after two weeks, I crashed the game on the menu as I forgot the proper arcane sequence of instructions to use to invoke the game. I think this is a reflection of what I was interested in when developing RatRace. It is more of a tech demo for procedural level generation.
V::CONCLUSION
In conclusion, RatRace was a fun and short project that I've been able to obtain some potentially useful code snippets from that I may use in future projects. I'd highly recommend game jams as a way to get started with development.
The unattainable cherry present in the default level.
Files
RatRace
Collect items in a randomly generated maze!
Status | In development |
Author | CognitiveRust |
Genre | Racing |
Leave a comment
Log in with itch.io to leave a comment.