[Tutorial] Create a Random Maze Generator using GameMaker
Hi! My name is Lucas and I’m a hobbyist game developer. I just recently started learning about gamedev and have been using GameMaker 1.4 and 2.0.
If you’re interested in more tutorials I have another about creating a Nine Slice Script for GMS. Check it out!
One of the tasks that helped me learn and understand how to better use the engine was to build a Random Maze Generator (RMG) tool, that I intend on using in the game I’m working on. I’ve released the tool and you can download it and play with it at my itch.io page.
Just to make things clear, the tool is not exactly complex, if you are a more experienced programmer this is probably not for you. That being said, complete beginners might have some trouble.
Also, I won’t be putting any code here, since you can download the tool in my itch.io page, and more importantly, I really recommend you try doing this on your own the first time, as that really helps you to get a grasp of the engine. So, let’s get started!
To follow this tutorial you’re gonna need GameMaker version 1.4 or 2.0. The good thing is that GameMaker Studio 2 has a trial version. I’m not sure but you should be able to do this using just that.
Also, I didn’t create this tool entirely by myself. I used the following tutorials to guide me:
I’ll be explaining one of the three algorithms you can use in the tool, the Recursive Backtracking algorithm, as the focus here is to show how to implement one and them you can add others yourself.
One of the main features we’ll be using is a DS Grid, with DS meaning Data Structures. DS are used in GameMaker to store information, resembling a lot arrays, but with the advantage of having several built-in functions to manage and change the information, like searching and reordering specific values.
You do have to pay attention when using data structures though, as they are dynamic resources, they take memory and, if you don’t destroy them after using them, they can cause a memory leak.
Creating a DS Grid
A DS Grid is very similar to a 2D array, where you create a grid with a determined width and height, and you can store information in every cell of it. Each of the grid cells will correspond to a maze area, so it will either be a WALL or FLOOR value.
Our grid will start with all cells containing the WALL value, because we want to “carve” the walls and create a path. To do that we need to use a Controller. He will be responsible for moving through the maze and defining the FLOOR cells.
This “Controller” is nothing more than two variables containing an x (width) and y (height) position inside the maze, and for him to move we will be updating this position.
Now, the width and height of the maze will always have to be odd numbers and that is due to the Controller movement, can you guess why?
Well, if we want the Controller to create a path, he has to move from his current cell, through a maze wall, and land on the other side of it, meaning he will always move 2 cells. If you take into consideration his starting position, you will always have an odd number.
You may also have noticed already that you can’t place the controller in a random position in the maze, he has to start in a position where he can reach the other side of the maze, moving 2 cells at a time, and still be inside the defined grid size.
With the grid and Controller ready we can now move on to the main part, the algorithm!
Tips for this section:
- Take into consideration if your maze has a border or not. This will change the Controller position
- In GameMaker, the grid width and height start at 0, so if you create a grid with 9x9 cells, the number of the last row and column will be 8
- Check the GM documentation about DS Grids
Recursive Backtracking Algorithm
As I mentioned, we will be implementing the Recursive Backtracking algorithm, which is a fairly simple algorithm and easy to understand. The way he works can be explained in 4 steps:
- Choose a start position for the Controller
- Move in a random direction (up, down, right or left) and mark the path as Visited, but only if the direction the Controller is moving to has not been Visited yet
- If all cells surrounding the Controller have been visited, he starts backtracking until he finds a Not Visited cell that he can move to
- The algorithm ends when the Controller backtracks to it’s initial position
As you can see in the gifs WALL cells are the same as Not Visited cells and FLOOR cells are equal to Visited cells.
Looking at the last step you might be asking yourself, how do I know the Controller is going to backtrack to the initial position and not another dead end?
I’m certain there are several ways to do this, but in my code, when the Controller starts backtracking, he sets the cells he passes over as a Dead End, and because he only backtracks to Visited cells, we avoid repeating the same path several times.
These 4 steps represent the chunk of code that will be repeated until the maze is complete. The fun part is figuring out how to move the Controller so that he follows those rules. This is obviously the hardest part, but also the most satisfying.
Tips for this section:
- Use a repeat function to run the 4 steps until the maze is finished and then exit the repeat function. Remember: the algorithm is done when the controller backtracks to it’s initial position
- Macros help keep the code understandable. Instead of using magic numbers for the cell values, you can use FLOOR and WALL macros, as an example.
- When checking the cells surrounding the controller, remember that he moves 2 cells at a time
- Always move in a random direction, be careful to not create patterns
- When using functions to get random values, like choose(), remember to call the randomize() function at the start of the code, to get different results on the first try
- When checking positions where the Controller can move towards, if you “step” out of the grid with the check, GM will throw an error. It doesn't stop the game from running, but it might slow it down a bit and it’s annoying. A way to circumvent this is to add 2 extra cells around the maze, just take this into consideration for the Controller position
Drawing the Maze
Ok, let’s say you finished the algorithm, but we still have to draw the maze to the screen, or we will have just an empty room.
Actually, it’s a good idea to set this up as soon as you are starting to work on the algorithm, because you’re probably going to need to check if it’s working in the intended way, unless you get working on your first try, but then I don’t know what you’re doing reading this tutorial.
To draw the maze to the screen we first have to determine the size of each grid cell, after all, DS Grids only hold information and don't a have a determined size.
The width and height can be anything you want, from 1 pixel to 100 or more. You decide this based on what you’re going to use the maze for. If it’s just to draw it to screen it really doesn’t matter, in my code I use 32x32 cells.
The easiest way to do this is probably to create tiles for each cell type and then use a nested for loop statement to go through the whole maze and draw each cell. The problem is, if your maze is larger than the room, only a part will show up and if it’s smaller it’s not centered (the origin position of a DS Grid in a room is always at the top left corner).
You could solve this by setting the room size manually to fit grid size or draw the maze centered. But if you change the grid size, you have to set everything again.
To solve this you can use the functions room_width() and room_height() before adding the tiles to change the room size by multiplying the grid width/height by the cell width/height. This way your maze will always fit into the room.
When drawing the maze he might look stretched or squashed. This is probably because the window dimensions are not proportional to the grid size. If you have a rectangular grid and the window is a square, it will deform the maze to fit into the window.
Keep the proportions the same to solve this, or create a script to adjust grid size to always fit a determined window size. It’s a nice challenge!
And you’re done! This is how I created a Random Maze Generator in GameMaker. You can now add more features if you want, like the option to have a border or not, adding entrances to the maze, etc.
I hope this tutorial was useful to you! If it was too complicated, too simple, maybe you know a better way to do it or you just want to let me know I made a grammar mistake, feel free to contact me at my Twiter(NazatoGameDev) or leave a comment.
Any feedback is appreciated!
Thank you for reading and thanks to #notGDC for giving me the motivation to do this!