The following is a tutorial to help game developers learn how to make a simple game using GameMaker.
This is an (almost) utter beginner tutorial. I’m quite new to Gamemaker and this was the second project I worked on after making my own Asteroids game (occasionally referring to the excellent “Space Rock” tutorials). I’ve been programming for decades.
I assume that anyone following this tutorial has installed the software (instructions helping you to do so are available elsewhere).
This tutorial will cover the simplest elements of making a digital board game. Creating a board, a piece, rolling a die, and moving the piece across the board. Coding standards and best practices are VERY sloppy. If anyone wants to clean this up and post a nicer version, please do so with my blessing.
In terms of the general philosophy of tutorials, I get the impression that many people use them poorly. Rather than using this as a recipe, where you read, then follow each step, I would humbly suggest using it like a math textbook, where the information below is a worked example. You’ll get the most out of it if you try it yourself, rather than just transcribing my solution into your interactive development environment (IDE). If you do need more hand-holding and end up copying it as you go through, I would suggest your next step would be to take a break, then try to rebuild the entire program without referring to the tutorial (test yourself that you’ve learned it). I see complaints from many aspiring game developers who have worked through tutorials, yet remain incapable of creating their own games. I suspect that blindly following instructions is what got them into this state.
Chapter 1: Game Description
“Snakes and Ladders” a.k.a. “Chutes and Ladders” a.k.a. “Moksha Patam” is an ancient, Indian board game. On a grid of 100 squares, players roll a die then move forward that many spaces. The extra complication is that if a player ends their move on a ladder, they then climb up it to its destination; moving closer to their goal. If they end their move on the head of a snake, they are sent back to that snake’s tail, making it take longer to reach the destination. The first player to reach the 100th square wins.
It’s an interesting game, like Candyland or flipping a coin, in that players don’t make any decisions. The game plays itself, with players generating random numbers and then executing the rules.
Another description of the game, with nice pictures, is here: https://www.ymimports.com/pages/how-to-play-snakes-and-ladders
Chapter 2: Making Objects and Sprites
Objects are elements that are a part of our game, while sprites are the images, often representing an object, that are shown on the screen. We need 3 objects, two of which will use sprites: a board, a game piece, and a die.
I stole the image from the above page to create my board with 100 squares and the snakes and ladders. Do a google search for a similar image or create your own with an image editor.
Right-click on “Objects” on the right-hand side of your IDE, in the “Asset Browser”. Choose “Create”, then “Object” on sub-menu.
Change the name to obj_board or whatever seems reasonable and memorable to you. Click on the “New Sprite” icon (the square with the plus sign in the lower right-hand corner, right above “No Sprite”). This brings up the dialogue to create a sprite (an image that’s displayed on the screen) and it will be associated with this object.
Choose “import” and select the image file we choose above. A direct link to the image I used is here. If you click on the square with arrows pointing to each corner, you can resize this sprite. When we import an image, like we’re doing here, this happens automatically.
Back on the Asset Brower, we will click on the white arrow next to Rooms, which will open up the submenu and show us “Room1”, which is the default room or work area for this game. Double-click on “Room1” and it will bring up this area. Left-click and hold on our “obj_board” under “Objects” and drag it to the room area in the middle of the screen. It should show up as the board there. Press F5 to run the game and you should see a small window with your board in it and the title “Created With GameMaker Studio 2”. Close this window.
On the edges of the board in the Room, you can click to size it on any edge or corner. Resize the board until it takes up the full screen (top to bottom) when you run the game (remember, F5). There should be black areas on the left and right.
Go back and make an object again, the same way you made the board, except we’re going to call it “obj_piece” and it will be the player’s representation that moves around the board. Find an image you like or choose “Edit Image” and free-hand make a piece. The image editor is just like other image editors you may have used. I roughly sketched a shape then filled it in. I thought blue would look nice against the board I used.
Just like before, drag the obj_piece from underneath the Objects on the right into the room. Put it to the left of the board on the lower left of the screen. Press F5 and make sure it’s displaying and everything looks good.
Repeat the exact same process to make a die (singular of dice). Perhaps call it “obj_die”. Fill it in with a solid color, avoiding white (the default text color) or black (the background color). I used grey. Place it to the right of the board.
Chapter 3: Adding Functionality
Now we need to actually make our game do something. The only action players take in the game is rolling the dice. Events are basically descriptions of something that might happen, then instructions on what to do when that happens. They cover every imaginable situation. We’ll add an event to the die to let the player click on it to roll it. Double click “obj_die” on the Asset Browser menu. A blank Events area will appear. At the bottom, click on “Add Event” and choose “Draw” then choose “Draw” again from its submenu. The “Draw” event is going to happen whenever the die object is displayed on the screen (which should happen immediately when the game is run and constantly throughout play).
In the text area we’re going to write the following code:
draw_self(); draw_sprite_ext(sprite_index,image_index,x,y,image_xscale,image_yscale,image_angle,c_white,1); draw_text(obj_die.x+10,obj_die.y+10, obj_die.side);
When I first worked on this, the code I added to this event made the die that I’d created disappear. It turns out that when you add a draw event, it overrides the default behavior and stops displaying the object’s sprite. That wasn’t what I wanted, so I found the first 2 lines as a “magic spell” to make it still display the sprite. I can’t explain them.
The third line is going to create some text. It’s going to draw the text at the object’s location (obj_die.x and obj_die.y), shift it right 10 pixels, shift it down 10 pixels, and then output obj_die.side as its text. The third argument can be whatever text you want to display. If you changed it from obj_die.side to “hello”, then ran the game you should see the die with the word “hello” written on it in a white font.
obj_die.side is a variable, which we haven’t created yet, and it will give us an error if we try to run it as is. Let’s add the variable to the object
Double click on “obj_die” under the “Asset Browser” again. At the bottom (you may need to scroll down or move some IDE elements) you’ll see a “variables definition” button. Press it. On this screen, there should be a default variable showing. Change its name to “side”, its default to 6, and its type to “Integer”. What we’re saying here is that there’s a variable value connected to this object called “side”, which will represent which side is currently showing. It’s an integer (its value will always be one of 1, 2, 3, 4, 5, or 6). The default is simply the side that will be showing when it’s first created – the value doesn’t matter (although it would be weird if it wasn’t 1-6).
If you press F5, you should now see the display with the die showing 6 (or whatever value you decided on). Now we need a way to roll it. Go back to events (like you did above) and this time create a “Left Pressed” event (instead of a Draw event). This will trigger whenever the player left-clicks on the die. For the code for this event, use the below:
obj_die.side = irandom(5)+1;
obj_piece.current_square += obj_die.side
show_debug_message(obj_piece.current_square);
The first line sets the side variable to be a random value of 0-5. We then add 1 to this to make it 1-6. The second line creates another variable. This time representing which square the player’s piece is on. We INCREMENT it (take the current value and ADD to it) by the random value just generated. This will move the piece that many squares on the board. We then need to go and add this variable to the obj_piece object, just like we did when we added the side variable to the die. In this case, it will again be an integer and the default value should be 0 (since the piece begins play not on the board).
The third line just lets us output a line of text to the console (an area that displays text and messages as the game is running). We’ll use this for debugging, in case something isn’t working right. This prints out an integer to the console that is the square the piece is currently in.
After we’ve added the variable to “obj_piece” we should certainly run the game and test it. We can now click on the die and have it give us a number between 1 and 6. Give it a bunch of tries!
You MAY notice that the sequence of numbers that come up on the die are always the same. This is by design. It makes it repeatable each time the game is run (so it should run into the same problem each time and you can make sure you fix it). Once we start playing, this means every game will be exactly the same. We can change this by adjusting the “seed” to the random number generator. This is just an initial value to get it started. By default, the same seed is always used, which is what leads us to have the same “random” values each time.
To change this, we can add an event to the board (it doesn’t really matter where we add this event since it’s for our program as a whole). This will be a “Create” event, which is run once when the board object is first created when running the game. This gives us what we want, something happening early on in the program’s execution and only a single time. In this event we will add the following code:
random_set_seed(randomize());
This code uses the random_set_seed function to give it a new seed (instead of always using the same one). The randomize() function just creates a random seed. Once we’ve added this event, the die should give a different sequence of rolls each time you play. Decide for yourself which makes it easier for you to develop the game, but make sure it’s properly random before you give it to anyone else to play with!
Chapter 4: Moving the piece
GameMaker has “steps”, which can be thought of as a tick of a clock or the smallest unit of time where things happen. The “Step event” happens each step, so it’s a way of making something happen VERY regularly (many times per second).
We’re going to create a Step Event (same as we did above) and use it to update the piece’s position each time the die is rolled. It will do this by looking at the obj_piece.current_square variable we defined above and putting the piece there.
This is BY FAR the most complicated part of this tutorial. Don’t be intimidated by it! Take some time to work through what’s happening.
if (current_square > 0) { obj_piece.y = 35+72(10-ceil(obj_piece.current_square/10)); if (floor((obj_piece.current_square-1)/10) % 2) obj_piece.x = 260 + 78(10-ceil((obj_piece.current_square-1)%10)) - 78; else obj_piece.x = 260 + 78*ceil((obj_piece.current_square-1)%10); } if (current_square > 99){ obj_piece.y = 35; obj_piece.x =260; }
So, the first line is an if statement that’s just saying to only execute this code if the current square is above 0. We set 0 as the default current square since the piece begins play off of the board.
The last 4 lines are another if statement, that is only executed if the current square is greater than 99. This is used to “lock” the piece to the 100th square once it’s reached the end.
In the 2nd and 3rd to last lines, we assign the obj_piece.y the value 35 and obj_piece.x the value 260. x and y are coordinates that every (visible) object in GameMaker has. X is the number of pixels right from the left side of the window. Y is the number of pixels down from the top of the window. Together, they tell you where the object is located in the playable area. The numbers refer to pixels on the screen. I kept adjusting these numbers until the piece displayed where I wanted it to be (in the middle of the 100th square).
The remaining code is a bit of a beast:
obj_piece.y = 35+72*(10-ceil(obj_piece.current_square/10)); if (floor((obj_piece.current_square-1)/10) % 2) obj_piece.x = 260 + 78*(10-ceil((obj_piece.current_square-1)%10)) - 78; else obj_piece.x = 260 + 78*ceil((obj_piece.current_square-1)%10);
What we’re trying to do here is to take the current position integer, which tells us which square on the board the piece should be at, and turn this into an x and y coordinate to place it. The extra challenge is the direction of squares changes with each row (the piece goes right, then left, then right, then left, and so on).
The first line here is moving the piece to a specific y coordinate. This is easier than the x because the rows (up and down) always increase from bottom to top. We take the current_square (which at this point will be between 1 and 99) and we divide it by 10. This gives us a number between 0.1 and 9.9. The ceil function rounds this UP to the nearest integer, so it gives us an integer between 1 and 10 (corresponding to each row). Because we want the starting row to be at the bottom (and the finishing row to be at the top), we need bigger numbers for the lower rows (since the y coordinate is the distance from the top). Therefore we subtract the row from 10 (which makes the bottom row have the value 9 and the top row have the value 0).
The values 35 and 72 were just determined by trial and error until the piece’s position looked ok. The top row has a value of 0, so 35 was just the number of pixels from the top needed to put it into the middle of the squares in the top row (values 91 to 100). 78 pixels turns out to be the distance to move it down 1 row, so the row number is multiplied by this to get us the correct y coordinate.
This is complicated! Don’t feel bad if you’re struggling to understand it. The next part is more complicated, so it won’t make any sense if you don’t understand the above.
The remaining 4 lines accomplish the same thing for determining the x coordinate, how far right from the left-hand side we need to move the piece. This has the additional challenge of moving to the right on odd rows and to the left on even rows.
Our conditional statement (“if (floor((obj_piece.current_square-1)/10) % 2)”) is being used to determine whether its an even or odd row. First, we divide the current square by 10, which will give us a value between 0.1 and 9.9. We use floor to round DOWN to the nearest integer, giving us a value between 0 and 9. This corresponds to the 10 rows on the board with a zero-based number (starting at 0, not 1): 0 is the bottom row, 9 is the top.
We subtracted 1 from the current square before this operation in order to make them zero-based along each row as well. If we hadn’t done this, then the 10 square would give a value of 1 (floor(10/10) = 1), which would put it in the 2nd row instead of where it belongs in the first.
% is the modulo operation. It divides a number by 2 and gives you the REMAINER. 5 module 2 gives a value of 1 (2 goes into 5 2 times with a REMAINDER of 1). 14 modulo 2 gives a value of 0 (2 goes into 14 7 times with a REMAINER of 0). This is just a roundabout way for us to determine whether the number is even or odd.
The two lines in the conditional statement are then used, much like we did vertically above, to place the piece in the right square, dealing with the different directions between even and odd rows.
Chapter 5: The Snakes and Ladders
As presented this will let the player roll the die and traverse the board, but they’ll ignore the snakes and ladders. This functionality can be added by thinking about the snakes and ladders as something that takes a piece on a particular square and moves it to another square (in the case of ladders, closer to the end, while in the case of snakes back towards the beginning).
It’s easy for players to lose track of their piece, so ideally we’d like it to move to the “entrance” of the snake or ladder, pause briefly, then move to its final location. GameMaker’s “alert” functionality lets us do this. By adding:
obj_piece.alarm[0] = room_speed*0.5;
to the end of the event “Left Pressed” event on obj_die, we tell it to wait for 1/2 a second (room_speed*0.5), then trigger the alert[0] on the obj_piece. We then add an alarm[0] event to obj_piece with the following code:
start = [1,4,8,21,28,32,36,48,50,62,71,80,88,95,97];
finish = [38,14,30,42,76,10,6,26,67,18,92,99,24,56,78];
for (var i = 0; i < array_length(start); ++i) { if (obj_piece.current_square == start[i]) obj_piece.current_square = finish[i]; }
The two arrays (start and finish) connect the “entrances” and “exits” of the snakes and ladders. The for loop goes through each of the entrances, checks if it’s equal to the piece’s current position, then moves the piece to the exit if it is.
Chapter 6: Further Work
There’re certainly further extensions that can be done. This is left as an exercise for the reader. Adding sound effects to the dice rolling, movement, snakes, ladders, and when the game is over would be worthwhile. Separate screens for starting the game and ending it could be added. Additional players could be added. Smoother animation where the piece is dragged from its starting location to its destination would be nice.
Please feel free to make any suggestions to part of this tutorial that could be done in a better way (I’m happy to update it and am sure that I haven’t done everything in the best possible way). I’m happy to answer any questions in the comments.
Leave a Reply