Welcome to my tutorial on creating a simple 3D dungeon crawler using GameMaker Studio. Here is a GIF of what we will be creating:
This course is going to be fun but before we jump in, make sure you already have a basic understanding of code and of GameMaker's UI. If you don't, check out my free GameMaker Course to get you started.
There is a video version of this tutorial as well. Check it out.
The first thing you need to do is download this file and double click on it. The file will be named "3D Dungeon Crawler Start.gmz" and GameMaker should automatically open it. If you already have GameMaker open you will need to import the file using file > import.
Once the file is opened in GameMaker you will see 4 sprites. 3 of these sprites will be used as textures and the other will be used as a collision mask for the player object.
Open up the sprites named s_wall, s_floor, and s_ceiling. In each of these make sure and check the box that says "Used for 3D (Must be power of 2)". This is what that checkbox looks like:
This will allow us to create textures from these sprites and use them in our 3D world.
Now we need some objects. Let's start with the player. Create a new object and name it o_player. Give this new object a sprite of s_player, add a new Create Event, and add these lines of code:
In this event we create the z variable and the dir variable. GameMaker doesn’t have a built in z value so we create our own. Then we set the default draw color to white (if you don’t do this all your textures will render black). Lastly, we tell GameMaker we want to use 3D drawing by calling d3d_start.
Add a new Draw Event to the o_player and place this code in it:
If you haven’t seen the lengthdir functions before take a quick minute to look them up in the help/documentation. Basically, they just allow us to get an x and y offset using a direction and a length. Once we have those offsets we can use them in the d3d_set_projection function. This function takes 3 points. The first point is the “from” point and, in our game, should be the location of the player. The second point is the “to” point and represents the direction the camera/player should look towards. You can see we are using the x and y offsets we got using our dir variable. The final point is the “up” point and is used to tell the projection which direction is up. I pass in 0s for the x and y and then a 1 for the z.
Before I forget, you need to make sure to set the depth for the o_player to something like 10,000. Because this object is drawing our camera we need it to be draw with the lowest depth.
Create a new object and name it o_wall. Set the sprite for the wall object to s_wall, add a Create Event, and place this code in it:
In 3D we won’t be using sprites but instead textures. The sprite_get_texture function allows us to convert our sprite into a texture. It takes two arguments, the sprite, and then the image index of that sprite (you can’t have animated textures without programming that yourself).
Add a Draw Event to the o_wall and add this bit of code to it:
There are a few different ways to draw walls in 3D but I chose to use d3d_draw_block because it is the easiest for beginners to understand. The d3d_draw_block takes 2 points, a texture, horizontal repeat, and vertical repeat values as its arguments. Notice that the +32 is actually the size of our texture. You may want to use sprite_width and sprite_height instead if you decided to change the texture size.
We are almost ready to test the code we have so far but we need a room. Create a new room and name it r_one. Set the width to 256, the height to 256, and the room speed to 60. Add the player and some walls. Here is a screenshot of my room. Make sure you don’t stretch the walls but add each one individually.
Go to the view’s tab, check “Enable the use of Views” and “Visible when room starts”. Set the view width and height to 640x360 and the port width and height to 1280x720.
Before we run our game lets and a ceiling and floor. Create a new object and name it o_ceiling_and_floor. Add a Create Event and place this code in the event:
In this bit of code we create two textures using the s_ceiling sprite and the s_floor sprite. Then we get the horizontal repeat and vertical repeat values for the ceiling and floor.
Add a Draw Event and drop this code into it:
Here we just use the d3d_draw_floor function to draw the floor and ceiling. For the ceiling I use z values of 32 and for the floor I use 0. Pretty straight forward.
Open your room up again and add the o_ceiling_and_floor. Now you can run the game. You should see something like this:
Really awesome for not very much code!
Let’s add movement. A couple things you should know before we get started. The default in GameMaker is 2D collisions. If you want to have 3D collisions you will need to work that out yourself. The good news is that 2D collisions will work most of the time. Let’s get started.
Open the Create Event for the o_player and add the highlighted green sections:
We are going to use some of what we learned in my last blog and apply a basic easing to the player’s movement and turning. For that we need target variables to approach. We create a target_dir, target_x, and target_y that we can lerp towards. At the end we also use d3d_set_fog to add some depth to the world.
Add a new Key Press <Up> Event and insert this code:
First we use the lengthdir functions again to get an x offset and y offset based on the target_dir. Once we have this we can use the place_meeting function to check for a wall. Notice we are using the target_dir, target_x, and target_y. This is because the actual x and y for the player will be use in the easing and if we used it our collision check wouldn’t be accurate. Once we know there is no collision we can update the target_x and target_y.
Add a new Key Press <Down> Event with this code:
This is exactly like the above code only we subtract in our check and in our movement to allow the player to move backwards.
Add a Key Press <Right> Event with this tiny bit of code:
All we need to do is subtract 90 from the target_dir.
Add a Key Press <Left> Event with this code:
Basically, just like turning right but this time we add 90.
Open up the Draw Event for the o_player and update it so it looks like this:
Now that we have the correct target_dir, target_x, and target_y we can lerp our dir, x, and y to those values. Remember, if you don’t understand how lerp works check out my last blog. Quick note, it might actually be a good idea to move the lerp functions into a step event since minimizing code in the Draw Event is generally a good idea.
Hit run and you should have a great looking little dungeon. If something doesn’t look quite right, go back through the tutorial and make sure everything is the same. You can also compare your project with the finished version.
Congratulations! If this was your first experience with 3D I hope it was a good start. GameMaker wasn’t designed to do high fidelity 3D games but if you want to make a retro looking dungeon crawler it might be just what you need!
Thanks for reading,
- Ben (@uheartbeast)