This tutorial will take you through the process of creating a small game using GameMaker Studio 2, with the aim to get you familiarised with the interface and the work flow. The game we've chosen for this tutorial is a typical asteroid shooter game, which we'll call Space Rocks. Almost all novice programmers make this game at some point, as it's a fun game to play, but fairly simple to make and shows all the most basic components of a game - things you'll find in the most basic arcade game up to the most advanced RPG - like movement, logic and gameplay design.
We know that getting started with any new skill or tool can be tough at first, which is why this tutorial will attempt to make the introduction to game making as smooth and fun as possible. The tutorial itself is split into 4 parts which are listed below:
- Chapter 1 - Setup And Moving
- Chapter 2 - Attacking and Collisions
- Chapter 3 - Score, Lives and Effects
- Chapter 4 - Sound Effects and Polish
For those of you that have not used the GameMaker Studio 2 IDE before, we have prepared a short introductory video here, and you can also check out the Quick Start Guide in the manual.
Chapter 1 - Setup And Moving
What We Need
In this chapter, we're going to start building our game, but before we do that, we need to just take a moment to think about what we'll actually need. In general, before starting any project, it's a good idea to plan out what you want to achieve, even if it's in very broad terms, as that gives you something to work towards when you sit down to work.
Our tutorial game is going to be called "Space Rocks", and as mentioned in the introduction, it's going to be a game about shooting asteroids and getting high scores:
What does a game like this need? Let's list the two most basic of the gameplay components:
- First we'll need a ship for the player to control, and it will have to rotate and move forwards as well as shoot.
- Second we'll need some asteroids for the player to actually shoot at
Now, before continuing, you should make sure that the game is set to run at 60FPS. For that you should open the Game Options now (from the Quick Button at the top of the IDE), and set the Game Frames per Second to 60:
The first sprite we'll make is the "ship" sprite for the player to control. To do this, go to the "Sprites" resource and click the right mouse button and select Create:
This will open the sprite editor and permit you to set the name of the sprite. In this case we'll call it "spr_ship", as shown in the image above.
You may now also need to resize the sprite, as for this game we want it to be 32x32px. To resize a sprite, you simply click on the Resize Sprite button to open up the resize window:
You'll want to choose the Resize Canvas option and set it to 32x32px:
With that done, the next thing to do is create our sprite in the Image Editor. You can open this by clicking on the Edit Image button, which will bring up the editor with a blank canvas ready for you to start drawing:
Our sprite is going to be a simple line-drawn polygon, much like an arrow head, so let's draw that now. First you should probably enable the grid overlay and set the grid to 8x8px (this will help you to position the drawing tool):
Now we can draw the sprite using the Draw Polygon tool (be sure to click the top left of the icon to get the outline tool and not the fill tool):
Once you're happy with the result, simply close the Image Editor workspace and the image will be saved to the sprite. We now need to set the sprite origin. This is a point on the sprite where it will be "anchored" to the object that uses it, and you can set it by simply clicking anywhere on the sprite in the canvas view, or by setting the values at the top, or even by selecting one of the options from the drop down menu:
Since this is also the point around which any rotation will occur, setting it anywhere other than the center of the sprite wouldn't look right, so go ahead and select the Middle Centre option from the drop down menu:
We can now go on and make the asteroid sprites. So, for this, you'll need to create three new sprites, as we are going to make three different sizes of asteroid. Call the sprites "spr_asteroid_small", "spr_asteroid_med" and "spr_asteroid_huge". All the sprites will have been created at the default image size, so you will want to change their size to be 16x16px, 32x32px and 64x64px respectively (as shown at the beginning of this article). When finished, you can take a moment to draw the asteroids, again using the outline polygon tool to create some irregular shapes. You can see some examples below (shown at twice the size required for the game):
When finished, make sure the origin for each of the sprites is set to Middle Centre. The idea now is that the asteroids in our game will start of huge and then break into the medium and smaller parts as the player shoots them.
Let's jump right in and make our first object. As we did with sprites, simply go to the "Objects" resource and click the right mouse button and select Create to create the object. Give it the name "obj_ship" to identify it, and then assign it the ship sprite we made earlier. To assign the sprite, click the button that currently says "No Sprite" and select the sprite "spr_ship":
While we're at it, we'll also create an asteroid object, so do that now (create a new object) and call it "obj_asteroid". Assign this object any one of the asteroid sprites that you created. We don't need to worry about which one, as we're only going to have a single asteroid object in the game, and we'll set the sprite_index (the sprite the object will draw) using code in the Create Event. We'll come to that part in a moment, but first we'll do an experiment...
All projects in GameMaker Studio 2 start with a room already in the resource tree called "Room_0". This means we don't need to create one for now, but you should rename this room to "rm_game" so it has a less generic name (this is done by performing a slow double-click on the name in the resource tree and then typing the new name). Now, what happens if we add instances of our objects to the room and test it?
Let's find out! Open the Room Editor by double clicking on the room in the resource tree. When the Room Editor opens, you'll see a lot of new tabs appear in the IDE. We're not going to worry too much about them right now, so just click and drag an instance of the object "obj_ship" into the room:
Now drag in a few instances of the object "obj_Asteroid", so that it looks like this:
If you click the Play quick button now, the game will compile and you should see a window open with the instances we've placed in the room visible:
Okay, that's not very interactive, but it's a start! Seeing the game like this though does show up a minor problem... the game window is too big. We want the game to have a retro feel, so that means a smaller resolution and play area. Close the game window and go back to the room editor now.
We are going to edit the size of the room, so go to the Room Properties and change the Width and height to 500px each:
This will change the room size, but may leave some instances outside of the room. You can simply click on them then drag them back into the playable room area.
If you test play the game again, you can see that the game window is a much more appropriate size.
We can now go back to our game objects and start to add some logic into them so they actually do something...
When programming, everything can be broken down to a fairly simply rule:
if this then that
So, all a program does is check if this happens, and if it does then that happens - like, "if a key is pressed then the player will move". Put into a more GameMaker Studio 2 friendly format it would be expressed as
if event then act
Which means that if an event is triggered, then a specific action must be performed. An event is simply a moment in time when something happens, and some events can be triggered once (like the Global Mouse Left Down event) or can be triggered every game frame (like the Step Event). Let's look at how to use these events to make our ship perform an action, in this case, move.
You'll need to open the object "obj_ship" (if it's not already open) by double clicking it in the resource tree. When open, you can then click on the Add Event to bring up the Event List:
There are two ways that we can check if the player is moving:
- Use the discreet Keyboard Events, which will only be triggered when a key press is detected, or
- Use the Step Event and check using code for a keypress every step (game frame)
What you use in your projects is largely a matter of choice and will depend on how you like to work and the structure of your project. In this case, we are going to use the keyboard Down Events, which are events that will be triggered as long as the specified key is held down. Any action code we add in here, will be run once for every game frame that the key is held down for, so if we held the key down for a second, it would have run the actions the event contains 60 times, since our game speed is set to 60FPS.
Go ahead and add a Keyboard Down Event for the left arrow down and then another one for the right arrow down (both are highlighted in the image below to illustrate the ones you need, but you need to add them one at a time):
These events are our "if" - asking "if a key is held down" - but we haven't told GameMaker Studio 2 what to do if that is actually true. For that we'll need to add some DnD™. You'll see that when you added the events, each one added a tab within the DnD™ Editor for you to add your actions into:
Here you drag actions from the different Toolbox groups on the right and "chain" them together to create the game logic. So, the basic interpretation of what we want to do first would be "if the left key is pressed, rotate the ship to the left", and we have the "if" part done using the Keyboard Down Event, which means we have to do the "rotate the ship" part now.
Click on the "Key Down - Left" left tab in the DnD™ Editor to select that event, then go to the Instances section of the Toolbox and drag Set Instance Rotation into the editor:
With this action we are adding 5 to the image_angle of the instance every step (game frame) that the key is held down. The image_angle variable governs the angle at which the assigned sprite will be drawn, and the action Set Instance Rotation sets this value. Note that we have also checked the relative flag on the action. If we don't check that then the action will set the image_angle to an absolute values, i.e. it will always equal 5. But by checking relative, we are saying to GameMaker Studio 2 that the value should be added to the current value, i.e. image_angle = image_angle + 5.
Why are we adding 5 if we are turning left? That's because angles in GameMaker Studio 2 go anti-clockwise with 0° being to the right:
This is why we made our ship sprite facing right - it makes setting the angles when rotating much easier!
We can use almost the exact same action for turning the ship to the right, so right click on the action, then select Copy, then click on the tab for "Key Down - Right to select it. Right click again in the DnD™ editor workspace and select Paste. You need to edit the amount to change the angle by to -5, since we want it to rotate to the right:
You can press the Play button now, and if you press the left/right arrow keys you should see your ship instance rotating.
Our player ship can turn left and right, but it's not able to move yet - let's fix that now! To start with, go back to the object obj_ship, and add a new Keyboard Down event the Up arrow key:
In this event we actually need to use a bit of GML, as there is no corresponding action for the functionality that we want. We could chain together a large DnD&tm; solution using the maths actions, but why complicate things whenGameMaker Studio 2 already has a GML function we can use? To add a function into a DnD™ action chain, simply select the Common toolbox section and then drag a copy of the action Function Call into the workspace.
Once you have the action, set its values like this (you can click the (+) icon of the action to add more arguments):
So, what does the motion_add() do? Well, we're going to use it to set the speed and direction of the instance. The "speed" of an instance in GameMaker Studio 2 is the number of pixels the instance should move each step of the game, so setting speed = 2; means that the instance will move 2 pixels every step of the game. However, speed means nothing without a direction, and so we also have the direction variable to set the direction of movement when speed is anything other than 0. Together these create a vector.
Now, explaining vector maths is outside of the scope of this tutorial, but luckily there's no need as GameMaker Studio 2 has functions that will help you , without having any advanced mathematical knowledge. In this case it's the motion_add() function. This function takes an angle of direction, and then adds a certain amount of momentum to the speed of the instance in that direction - in the above code we are using the current image_angle (which we set in the left/right keyboard down events) to get the direction of motion and setting the amount of momentum to add as 0.05 pixels per step. This might seem a small amount, but adding to the ship momentum is a cumulative effect, so each step that we add this value, the ship will get faster and faster (in the same direction). We use this method over setting the x/y position directly, as it enables us to easily create a "floaty" feel for the movement, perfect for a game set in space!
With that done, you can test the game again, and you should be able to move the ship around by tapping or holding the Up Arrow key, and if you rotate then the direction of movement should change over time too.
Wrap The Room
Testing the game, you likely noticed an issue: the ship can go out of sight beyond the room! This isn't very much fun and certainly not what we want to happen. Instead, we want the ship to "wrap around" the room, so when it goes out one side it will appear again on the opposite.
We could add in some code to check the x and y position and then move the player instance ourselves, but there is an easier way...
We're going to use another action from the DnD™ Toolbox, Wrap Around Room. This action permits you wrap horizontally and/or vertically, as well as set a margin for wrapping to occur in. To use this action, however, we need to give our ship object a Step Event:
This event is where much of a games logic goes, as it is triggered once for every step (game frame), and so if our game is set to 60FPS, the Step Event will be run 60 times every second. In this event we place the Wrap Around Room action, from the Movement section of the toolbox:
This will wrap the ship around the room along both the horizontal and vertical axis (if you unchecked either of those options, then the room will only wrap in the checked direction). You can test the room now and see what happens!
That's working okay, but there is a visible error as the ship reaches the edge of the screen. Because we have set the wrap margin to 0, the wrapping will occur the moment the player ship x or y position leaves the room. This means that the ship can be seen to "disappear" and then just as abruptly appear on the other side of the room. To resolve this we need to set the margin to a different value:
Here we use another instance variable, sprite_width, and set the wrap margin to be half the width of the sprite. This means that the instance will not be considered out of the room and be wrapped if it's not gone at least half the sprite width outside. So, since our sprite is 32px wide (and tall), it won't be wrapped until the position is greater than 16 pixels outside the room bounds. If the instance is moving right, for example, it will wrap when the x position is greater than 516 (room width is 500 plus half the sprite width which is 16).
If you test it again, you'll see that the wrapping looks much better.
With the player ship movement completed, let's go ahead and get the asteroids moving too.
Currently, the asteroid object does nothing. It has a single sprite and doesn't move, so we need to fix that and make things more interesting!
We are going to add some DnD™ into the object "obj_asteroid" Create Event. Unlike the Step Event, this event is only triggered once, when an instance of the object is created, and because of this it's an ideal place to initialise variables and run any actions that you need to set up the instance.
We want our asteroids to be different from each other when they're created, and so we'll set the instance variable sprite_index to use a random sprite from our resource tree. The sprite_index is the variable that holds the assigned sprite ID value, or "index", and so we can change it at any time to change the sprite the object will draw. We'll also use the Choose action - from the Random section of the toolbox - to randomly pick from one of the three sprites we previously created.
So, open up "obj_asteroid" and add a Create Event to the Event Editor:
This will open the DnD™ Editor editor where we'll add the Choose action with the following options:
So, what this action does is it will choose randomly between one of the given options, and then set the Target Variable to the returned value. In this case, we are choosing a sprite and setting the sprite_index instance variable, changing the sprite that will be drawn and used for collisions for the instance. Note that to get more options to choose from, simply click the (+) to the left of the action.
If you test the game again you'll get something like the following:
We need to set the asteroid moving in a direction now, and we want that direction to be random. We briefly mentioned the direction built-in variable earlier, and we'll use it now. You want to go back to the Random section of the toolbox and drag the action Get Random Number into the DnD™ Editor, just underneath the Choose action, and set it as shown:
As we did with Choose, we are getting a random integer value from 0 to 359 and setting the direction instance variable to it. Now, direction is not the same as the image_angle, since one is the direction of movement, and the other is the angle a sprite will be drawn at, so we might as well change the image_angle to a random value too, as that will increase the visual variety:
The next thing to add in to this event, is an action to set the speed of the instance, so we'll use the Set Speed action which can be found in the Movement section:
The asteroids will now move 1px per step in a random direction and be drawn at a random angle. But before we test, there is one final thing we need to do, and that's add the wrapping action we used on our player ship object to also wrap the asteroids, otherwise when they leave the room they'll be lost forever!
Add a Step Event into the "obj_asteroid" and copy/paste the Wrap Around Room action from the player ship object:
And since we're here, we'll also make the asteroids spin a little as they move, again to add more visual interest to the game. For that, you'd add a Set Instance Rotation actionwith these settings into the Step Event, after the wrapping action:
This will simply add 1 onto the image_angle instance variable and so the sprite will be drawn spinning slowly. Test the game again now and you will see that the asteroids now move, spin and wrap around the room!
Chapter 2 - Attacking and Collisions
In this chapter, we're going to add in bullets and have collisions for them with the asteroids, as well as collisions for the asteroids with the player ship.
Since we haven't defined any bullet objects or sprites, let's first deal with the collision between the player ship object "obj_ship" and the asteroid objects "obj_asteroid". Now, collisions in GameMaker Studio 2 are based off of what is called the collision mask, which is defined in the Sprite Editor:
This "mask" is the area that will be used by GameMaker Studio 2 to detect collisions, so that two instances are considered "in collision" when their collision masks overlap, or a mouse click is detected when the mouse x/y position is inside the defined mask area. There are a number of different mask options to choose from, including the mode - whether the mask should be defined automatically or manually - and the type - the shape that should be used to generate the mask.
If you look through the type options, you'll see that there is a precise option. This makes the collision mask "pixel perfect", so that transparent areas will be ignored. You might think that this is the best option of all, as it means that all your collisions will be realistic, but it's not. Calculating collisions is costly at the best of times, and using a precise pixel-perfect check for everything would increase the cost of doing collisions greatly. This is why we generally use rectangular collision masks, as they are relatively fast to check and deal with.
That said, for our player object, we'll be using a Diamond collision mask. This is still a faster mask type than "precise", and has the benefit that is fits the "nose" of our ship sprite nicely. So, select the diamond type now, and set the mode not manual, then position the mask over the ship sprite, as shown below:
You'll notice that the "wings" of the player ship sprite aren't covered by the mask, which means that they will not detect collisions. In general, this is fine and pretty much normal for video games. Players don't want every little thing to kill them, and as long as the collision mask is consistent and they can discover what area is okay and what area isn't, then it's fine. What would be worse would be a rectangular collision mask, as some things would be considered in a collision when visually they aren't, and so the player would feel cheated.
With that done, let's open each of the asteroid sprites and set the collision mask for them. We don't need anything fancy here, so leave the mode on Automatic and set the type to ellipse:
When you've given each asteroid sprite an ellipse collision mask, we can move on and code the collisions themselves...
We need to add another event to our player object "obj_ship", so open that now and add a Collision Event between the ship object and the asteroid object "obj_asteroid":
We could instead add a collision event to the asteroid object to look for collisions between them and the player ship, but in general you want to have the instance there is least of (in this case "obj_ship") do the checking, as less checks means better performance, so having one instance checking for a collision is better than having 10 instances checking for the same collision.
In this event we're simply going to add the Destroy Instance action from the Instances section:
One thing to note about the DnD™Editor, is that a quick way to move around the different actions in the Toolbox is to use the Search Box at the top. Just typing in the keyword from your action will present you with a list of only those that are appropriate:
We'll add more things to this event later (to control lives and stuff), but for now we will simply destroy the instance to remove it from the game. If you play the game now you'll see that the ship disappears when it collides with any asteroid.
Asteroids can now destroy the player, but we haven't got any mechanism for the player to destroy the asteroids! Let's add that now...
The first thing we need to do is make the bullet sprite, so make a new Sprite Resource, call it "spr_bullet" and set its size to 2x2px:
Now, edit the sprite in the Image Editor and colour it white, so that you get a 2x2px white square. Close the Image Editor, and then set the origin of the sprite to Middle Center and leave the collision mask on its default properties, which are perfectly fine for this sprite.
The next thing to do is make a new object, call it "obj_bullet" and assign it this new sprite that you've just created:
We can go back to the player object "obj_ship" now, and add a new event - the Keyboard Pressed event for the Spacebar:
The Pressed keyboard events will only be triggered once when the key is initially pressed down. If the key is held down, no further Pressed events will be triggered until the key is released and pressed again. We're using this event because we only want to create 1 bullet for every key press. If we just used a Keyboard Down event then we'd be creating a new bullet instance every step the key is held down (so 60 bullets in one second if the key is held down that long). We obviously don't want that and by using the Keyboard Pressed event, we avoid that.
In this event we are going to add the action Create Instance from the Instances section of the toolbox. Add this action no to the Keyboard Pressed - Space event using the shown values:
This action means that every time the player presses the spacebar , a bullet will be created on the layer named "Instances" at the ship X/Y position with no relative offset. A layer is simply a plane on which we place instances in the room editor, and if you look in the room editor you can see in the top left corner (by default) the list of layers in the room. You can add or remove layers here too, and each layer has a "depth" value associated with where the higher the depth, the "further" from the viewer it is and the lower the depth, the "closer" to the viewer, so that a layer at depth 100 will be drawn under a layer at depth -200.
We're not quite finished with that action yet though... When created, the bullet won't be moving and even if it was, it wouldn't know which direction to move in. So, to solve that, we need to tell it the direction to move in using actions.
Let's change the action slightly to store this value and then we'll explain what's going on:
The action Create Instance will return a unique integer value which is the instance ID value for the instance just created. If we store this ID value, we can then use it to set information on the newly created instance. So the action returns this ID value and we store it in a temporary (or local) variable called "newBullet". A temporary local variable is a "use and throw away" variable, that will only exist for the duration of the script or the event that it was declared in. This is useful for data that we don't need to hang around (for more information on variables and variable scope, please see the manual).
We can now use this variable to set the direction of movement for the instance we just created using the Assign Variable action from the Common section of the toolbox. Drag that action in after the previous one and fill out the values as shown:
When we use the ID value of an instance followed by a point "." like this, we are telling GameMaker Studio 2 that the "direction" variable we are setting is in the instance stored in the "newBullet" variable, and not in the instance running the main action block. So, in this way we are setting the bullet direction to match the angle of rotation of the ship sprite.
The final thing we need to do is set the speed of the bullet. Now, you could do this in the player object using Assign Variable and the newBullet.speed value, as we did for the direction, but that's unnecessary as the speed for all bullets is always going to be the same, and generally accessing an instance this way is only used for values that are going to change (like the direction). So, we need to go back to the bullet object "obj_bullet" and add in a Create Event with another Assign Variable action with these values:
All that does is assign a value of 6 (pixels per step) to the bullet instance speed.
You can test the game now, and you should see that every time you press the keyboard spacebar down, a single bullet will be released, and you have to release and press the spacebar again to create more:
We need our bullets to destroy the asteroids, so to do that we need to open the object "obj_bullet" (if it's not already open) and add a Collision Event to it with the object "obj_asteroid":
The first thing we're going to do in this event is tell the bullet to destroy itself using the Destroy Instance action from the Instances section of the Toolbox:
You might think that this will prevent any further actions from running after the function is called, but in GameMaker Studio 2 destroying an instance doesn't happen until the end of the event, so although we've called this function, it doesn't exit the event and the instance won't actually be removed from the room until the collision event is resolved.
What other actions do we need? Well, we want the bullet to destroy the asteroids it hits, and we also want it to "split" the bigger asteroids into smaller ones. To do this we need to be able to access the asteroid that is being detected as colliding with the bullet, and for that we'll use the Apply To action, found in the Common section of the toolbox, so drag that in after the Destroy Instance action:
What this action does is tell GameMaker Studio 2 that any DnD™ actions added to it, will be applied to (run on) an instance or group of instances, and not the instance that actually contains the DnD™. In this case we are going to apply some code to the "other" instance in the collision. If you click on the drop-down arrow it will open up the Asset Explorer and on the left you can see a list of keywords to select from, one of which is "other". Select that now:
The other keyword, when used in the collision event like this, will reference the "other" instance in the collision, so in our game, the bullet is colliding with an asteroid and "other" will reference the unique ID of the asteroid, and the DnD™ - although in the bullet object - will be run as if it was in the asteroid object.
Now we need to add some DnD™to have the asteroid destroy itself, so we fill in the actions like this:
IMPORTANT! Actions that you want to be added to the Apply To chain should be dropped to the right of the action, and not underneath. By adding them to the right (as shown in the image above) you are telling GameMaker Studio 2 that the actions should be run on a different instance, while if you place the actions below, you are telling GameMaker Studio 2 to run them after anything chained to the Apply To block.
Now, because we changed the scope of the code to the "other" instance in the collision (the asteroid instance) using Apply To, the Destroy Instance action will destroy the asteroid that the bullet is colliding with. We also want to "split" the asteroid based on what size the sprite is, so for that we'll be creating a chain of actions using:
- If Variable - to check what value the sprite_index has.
- Repeat - to create a loop that will create 2 asteroids.
- Create Instance - to create the asteroids.
- Assign Variable - to set the new sprite on the asteroids created. You'll remember that we set the asteroid sprite to a random sprite in the Create Event of the object, and here we'll overwrite it with a different value. This works because the moment an instance is created, its create event is run and then the actions continue in the event that created the instance.
Put together in the DnD™ Editor, it will look like this:
Don't forget to drop actions to the sides of the If Variable and Repeat actions to chain them correctly!
The next check will be for the medium asteroid sprite, so click on the If Variable to select it, and then use to open the action menu and select Copy. Now, use the again and select Paste:
You need to make the appropriate changes to these actions, first changing the sprite being checked for to the medium sprite:
Then change the sprite_index of the asteroids being created:
We could add another If Variable after that to check for a small asteroid sprite, but instead we'll do something slightly different...
We're going to add a "debris" effect into our game, and not just for the small asteroids, but for whenever any asteroid is destroyed. For that you need to create a new sprite, set it to be a 1x1 pixel only, then colour it white. This will be our debris sprite, so give it an appropriate name like "spr_debris", and then you can close the Sprite Editor, as we don't need to change its collision mask or do anything else.
Now we need to make a new object, called "obj_debris". Create it and name it now, and assign it the sprite you just created, "spr_debris". We'll give this object a Create Event where we'll use the following actions to give it a random direction and some momentum:
We also want to give this object a Step Event, so do that now. In this event we'll make instances of the object fade out and then destroy themselves when they are no longer visible. To do this we'll be working with the image_alpha value, which is a built in variable that controls the transparency (alpha) of the sprite assigned to the instance. A value of 1 is fully opaque and a value of 0 is fully transparent, and what we'll have our object do is gradually lower the image_alpha from 1 to 0 with this set of actions:
This will take a small amount off the image_alpha and when it gets equal-to or below zero, the instance destroys itself. Note that we don't do the check to see if the image_alpha value is exactly equal to 0! Most numbers in GameMaker Studio 2 are floating point which means they can get minute rounding errors that can accumulate and cause issues with exact "equals" checks. In the above case, it may be that the image_alpha value never reaches exactly zero and instead hits a number like 0.0000002, which would then roll over to be -0.0900002 and so never be exactly zero... which is why we check if it's less than or equal to 0. This may seem a bit contrary to common sense, but it's a fact of life when programming!
The final thing to do now is add some DnD™ to create these instances when the bullet hits the asteroids, and, just because we can, let's add them into the player object when it hits an asteroid and is destroyed too. So, open up the bullet object "obj_bullet" and in the Collision Event with "obj_asteroid", edit the DnD™ to have this, still with the Apply To block, but after the second If Variable check:
The whole action block for the event should now look like this:
Then, open the player ship object "obj_ship" and in its Collision Event with "obj_asteroid", and add the following after Destroy Instance (you can copy/paste the actions from the bullet object):
Run the game now, and shoot some asteroids! If all has gone well, then they should explode into smaller asteroids and create a nice puff of debris:
Before we finish this section of the tutorial, we need to do some cleaning up. In programming, there are many ways you can leave things lying around that will "clog-up" the computers memory and cause performance issues or worse. In general this kind of error is called a memory leak, and it's something you want to avoid at all costs in your own projects, meaning that you have to be careful to make sure that your game is programmed efficiently, and you don't leave things when no longer needed, but instead destroy them in some way.
In our game as it stands, we have a memory leak! Our room is only 500x500px, and we wrap our player and our asteroid instances if they go outside that area. But what about our bullets? They fly out the room space... and then what? Well, then nothing! Once outside the room, they are just taking up memory space without actually performing any useful task in our game, so we want to destroy them when they can no longer be seen.
We want to add an Outside Room event to our bullet object "obj_bullet", so do that now:
This event will only be triggered when the instance x/y position goes outside the room edges. In this event we'll simply add a Destroy Instance action:
That's all we need to tell the instance that if it leaves the room, it should destroy itself. Memory leak averted!
Chapter 3 - Score, Lives and Effects
In this chapter we're going to be creating lives and score to make our game more interesting, as well as a few more rooms to deal with different game "states".
To get started we're going to make a new object. This will be a "controller" object, which means that we won't be assigning it a sprite as it's going to sit in a room and deal with things "behind the scenes". So, make a new object now and call it "obj_game" and give it a Create Event:
We want this object to track the player's score and lives values, so we'll just use the actions that GameMaker Studio 2 has specific for these values. To start with we'll add a Set Score:
And then we add the Set Lives action, setting the lives to 3:
We want to show these values to the player too, so for that we'll add in a Draw Event:
As the name implies, this event will draw things to the screen. Like the Step Event, it will run every game frame, otherwise the things you drew would only be visible for one frame and then disappear. We want to use it to display the player score and lives, so we'll use some different Draw actions for this.
The first to add is the action Draw Instance Score, leaving the caption as it is (the caption is the text that will be displayed beside the score value), and setting the X/Y coordinates to 20. This will draw the text in the top-left corner of the screen:
We'll then use the action Draw Instance Lives to draw the lives to the screen, but before we can add that we should take a moment to make a sprite that symbolises a player life.
Create a new sprite and call it "spr_lives". You can either make a new sprite image for this - in which case it should be 24x24 pixels in size and have a few "empty" pixels around the edge for padding - or simply load the one supplied in the Tutorial Resources project folder. You don't need to do anything else to the sprite, as we won't be using it in any collisions and the default Top Left for the origin is fine for our needs:
Going back to the object "obj_game" Draw Event, we can now add the Draw Instance Lives action, with the following settings:
This will draw the sprite we just created "stacked" one after the other from left to right.
We can add this controller object into the room now, so open up the room "rm_game" and drag an instance of this object up to the (0,0) position in the room (you'll see it is shown with a (?) symbol in the room editor - this is because we haven't assigned it a sprite):
Press play now and test the game! The score and lives values should be displayed in the top-left corner:
Before we continue with the programming of the display we're going to add a new resource type to our game: a font resource, which is simply a collection of characters to use when drawing text. To create this, use the right mouse button on Fonts in the resource tree and select Create Font, which will open the Font Editor:
Call the font "fnt_text" and in the Select Font drop-down menu pick Consolas, then we will also switch off the anti-aliasing option to give a more pixelated and "retro" look to the font.
With that done, we need to tell GameMaker Studio 2 to use this font for the text, and for that you can call the action Set Font. If your project uses multiple fonts, then you would need to call this action to set the font in the Draw Event before the lines you want to write using the different fonts, but in our small game we only want to use one font for all text, so we'll add it to the Create Event of "obj_game" after the existing Set Score and Set Lives actions:
After calling this action, all text in our game will be written using "fnt_text".
Setting Score and Lives
We can now look at updating the score and lives values as we play. For that, open up the object "obj_bullet" and go into the Collision Event with the object "obj_asteroid"
Since the score variable is an instance variable, if we simply add to it in this event, we will be adding to the bullet object score variable, which isn't what we want. Instead we need to add to the "obj_game" variable, as that's the object that is controlling the score and lives for us. We've seen how to do this already in this event using the Apply To... action, so we'll use that here again along with the Set Score action, like this:
Note that when we add the Set Score action we check the relative flag, as that will add the value onto the score for us.
For the lives, you need to open the player ship object "obj_ship" and go to the Collision Event with the object "obj_asteroid":
Here we're going to deduct 1 from the lives, so go ahead and add the following actions above the rest of the DnD™ in the event:
This will subtract 1 from the lives variable in the controller object.
If you test the room now you'll see the score go up when you shoot the asteroids and if the player ship collides with an asteroid the lives will go down. There's still work to be done here, but we'll come back to it later after we've set up some more rooms.
In order to add things like menu screens into our game we will create a few more rooms. Duplicate "rm_game" (use the right mouse button on the room and select Duplicate) and name this new room "rm_start". Open "rm_start" and delete everything apart from the "obj_game" instance:
You can hold down and then click and drag to select the instances to remove and then press to remove them.
This leaves an instance of our controller object "obj_game" in the room. We are now going to make this object persistent. Persistent objects are retained as you move from room to room; unlike regular objects, which are cleared from memory each time you leave a room. Note that a persistent object will therefore not trigger its Create or Destroy events when changing rooms, but it will trigger its Room Start and Room End events if it has them.
Open the object "obj_game" now,, and check the box marked Persistent:
We can go back and remove the instance of "obj_game" from the room "rm_game", as the instance created in the "rm_start" room will now persist into all subsequent rooms.
We also need to reorder the two rooms so "rm_start" is above "rm_game", as GameMaker Studio 2 will always start by loading the first room in the resource tree when your game is run:
Now we can add the rest of the rooms that our game requires. For that, duplicate the room "rm_start", rename the new room as "rm_win" and remove the instance of "obj_game" (so the room should have no instances in it).
We need one last room before we can go back to the programming, so duplicate the room "rm_win" and call it "rm_gameover". Your resource tree should now look like this:
Room Text 1
Open up the object "obj_game" (if it's not already) and go to the Draw Event. Since our object is persistent now, the DnD™ we have for drawing the score and lives will run in all the rooms the instance is persisted across and not just the main game room, but we want to draw different text based on the room we are actually in. Now, we could resolve this using a few "if... else if..." checks to see which room we are in and draw the text that's appropriate, but instead we'll use a Switch action.
Using Switch action we can check the room global variable, which holds the ID of the current room this instance is in, and add different Cases for each of the possible values. In each Case we can have the controller draw different things.
So, let's change the draw DnD™ to look like this:
These actions will only be performed when the current room is "rm_game".
We'll set up the rest of the DnD™ now too, with the next room we'll deal with being the "rm_start" room, so add a case for that:
In that Case we want to add the following actions:
Here we are first using the Draw Transformed Value action to draw the game title scaled by 3, halfway across the room, and then we follow that with some calls to Draw Value to draw the game instructions.
Why not try running the game now and seeing how it looks?
That doesn't look quite right, does it? What's happened is that GameMaker Studio 2 has left justified all the text, so we need to tell it to center justify it using the action Set Text Alignment and set it to the constant fa_center for the horizontal align, with no need to set the vertical align. This action will be placed at the beginning of the chain, and then also placed at the end of the chain, but this time setting the justification back to the left (using fa_left) to reset the alignment for all the other text. The complete Case should look like this:
Now, if you run the game again you'll get something like this:
That's looking good, but it needs a bit of colour to live it up. For that we'll need the Set Draw Colour action. This action sets the draw colour for all text and shapes drawn to the screen, so we'll use it before the "Space Rocks" title to tell GameMaker Studio 2 to draw the title in yellow.
Like setting the font or the text alignment, setting the draw colour will change how everything drawn after the action is called is coloured, and since we only want to use the colour to accent the title, we need to reset it again to white after the Draw Transformed Value action. We'll also use it to add an accent to the "PRESS ENTER" text, so add another action changing to yellow before that, and another changing to white again after it. The chain should look like this now:
Room Text 2
The final thing to do now is add in similar code for each of the other room cases, only changing the colour and position slightly to suit the different text. To start with we'll do the "rm_gameover" case, so add a new case now after the previous case and set it to "rm_gameover" and we'll also set the alignment and the colour, only this time we'll make it red:
We'll use the Draw Transformed Value action again now to set show the game over text with the following values:
We'll then add some text to show the player their score. For that we'll use the Draw Instance Score action, but we want it to draw in white so we'll need to set the draw colour again:
Finally, we need to add an instruction to tell the player to press the "Enter" key to restart and another to reset the text alignment. This means that the final action block should follow this:
For the room "rm_win" we'll just do a quick copy/paste of the gameover case, changing the case to be "rm_win", changing the main text colour to green, and setting the main text to read "YOU WIN", as shown below:
The complete chain of actions for this event should now look like this:
Most of the text we've just added can't be seen, as we haven't actually added any DnD™ to change between the different rooms. We'll do that now, by adding in a method to detect the press of the key to start/restart the game, depending on the room the player is in.
To start with, in the object "obj_game", add a Keyboard Pressed Event for the Enter key:
This event will check for the keypress. When a press is detected we want to run a Switch on the room variable to see what action should be taken, so add a Switch now, like this:
Now we'll add a Case for the room "rm_start", followed by the Go To Room from the Rooms section of the toolbox. This tells GameMaker Studio 2 that we should go to the room "rm_game" if we are in the room "rm_start":
The next Case to add is the one for the room "rm_gameover", and in this one we'll add the Restart Game action, as we want the game to start again after the player dies:
You can copy this Case and paste it in again, then change the constant to "rm_win", so that the full switch will look like this:
We want to add in some more DnD™ now to detect the "win" and "lose" conditions, which in the case of our game is going to be getting 1000 points for the score to win, or 0 lives to lose. So, we'll want to first check that the current room is the game room (we don't want to perform these checks in any other room), and then we want to check the lives and score variables. This requires us to add a Step Event into the controller object:
In this event we are first going to use the If Variable... action to check if the current room is the game room, and if it is, we're going to check the two variables, score and lives to see if the score is greater than (or equal to) 1000 or the lives are less than (or equal to) 0. For that we'll use the following DnD™ which you should add as shown:
Here we are using the If Score... and If Lives... actions to check the two variable values and then go to the appropriate room as required, but only in the main game room.
To test this you could always just play the game for a while and try to get the win and lose states, but there is an easier way. Open up the Create Event of the object "obj_game" and change the initial score and laves values to 990 and 1 respectively - the event actions should look like this:
And now if we test the game we will get the "Win" and "Game Over" screens depending on whether we shoot an asteroid or crash into it:
Chapter 4 - Sound Effects and Polish
In this final chapter we're going to be looking at making the game a bit more polished and interesting for the player.
NOTE: If you close the accompanying video then you can get it back by clicking here NOTE: Some action chains in this chapter will be collapsed to save space - this simply hides the contents of the action and is done to keep the images smaller and clearer:
To start with, we're going to change how the asteroids are created, so open the room "rm_game" and remove all the instances of the object "obj_asteroid":
You can remove an instance by clicking on it to select it and then using the key.
With that done, we go back to our controller "obj_game" and add a Room Start event:
This event will be run at the start of every room, so our persistent object will trigger this event each time a new room is entered. In this event we're going to first check that the room is the game room, as we don't want to spawn asteroids anywhere else, and then we're going to add a Repeat so that all the following actions will run 6 times and create 6 asteroids. We don't want the asteroids to spawn near the player, so we will get a random X and Y position for the corners of the room using a combination of Choose (from the Random section of the Toolbox) along with an If Variable... check, before finally creating the instances.
To start with add an If Variable... check for the room "rm_game" and the Repeat:
Then add the Choose action, which will choose either of the two values 0 or 1, assigning the choice to a temporary local variable:
We'll now check the choice value using an If... Else..., such that if it is 0 we will generate an X value in the first third of the room, and if it is 1 we will generate an X value in the last third of the room. This X value will be stored in the temporary local variable xx:
To get the Y position, we'll just repeat the same code, so you can copy and paste the Choose and the If... Else... DnD™, and then edit it to look like this:
The final thing we need is to spawn the asteroids, so add a Create Instance action using the local variables for the X/Y position of the asteroid object:
The full chain of the actions should look something like this:
This gives the player the best possible starting circumstances as there will be no asteroids created near them.
Spawning More Asteroids
We now need to continue to create asteroids as the player progresses and destroys them, otherwise there'll quickly be no asteroids left for them to shoot at, so we'll use the Set Alarm Countdown action to trigger an Alarm Event, and in that we'll spawn more asteroids. An alarm is an event that will be triggered some time after it is set, and we'll set it to 60 so that an asteroid will spawn every second in the game.
So, before we leave the Room Start event of the object "obj_game", you want to add in one further action to set the Alarm 0 Event, positioned as shown in the image below:
With that done, we can add the Alarm 0 Event to the object now:
In this event, we're going to spawn the asteroids not in the corner of the room, but at the boundaries of the room. This will make it a lot less obvious to the player when they are created. For this to work we need to choose either a random position along the x-axis and a value for Y of either 0 or the room height, or a value of either 0 or the room width for X and a random value along the y-axis. The following actions do just that, so add them into the Alarm 0 event as shown:
We also need to add in the DnD™ to spawn the asteroid and also to reset the alarm so that it will loop and continually create asteroids:
To set the alarm we have used the room_speed global variable. This variable holds the number of steps the room will perform in a second (the game speed), which is what we set right at the start of this tutorial: 60FPS. So, by setting the alarm to 4 * room_speed we are setting it to trigger again in 4 seconds.
There is one problem with this event, however... Because the object "obj_game" is persistent and the alarm is always reset, we would end up with asteroids in rooms other than the game room, since the alarm will be running even after the player has won or lost. To avoid this, add these following actions at the start of the chain, before the actions shown above:
Ticking the "Not" flag in the above action is checking to see if something is not equal to the given value, so these actions are checking if the current room is not the game room, then the rest of the event will be skipped (the Exit action will end the event that it is called in immediately, so any code after it will not be run).
If you run the game now and wait a few seconds you should see that asteroids are spawning constantly around the room edges.
The time has come to add sounds to our game, but before we get on with that, take a moment to reset the Score and Lives variables in the Create Event of "obj_game". These should be set to 0 and 3 respectively, as we no longer need them set to other values for testing:
We'll now need some sounds for our game to use...
The sounds can be *.wav, *.ogg or *.mp3 format and have a "retro" sound to them. There are a number of different programs available free online for making sound effects and music for you to make your own sounds with.
Once you have located the example sounds or created your own, we need to add them to our project. Create a Sound in the resource tree and this will open up the Sound Editor, ready for you to add your first sound:
The sounds we'll need are as follows:
- "msc_song" - Some kind of background music
- "snd_die" - A sound for the asteroid or the player exploding
- "snd_win" - A sound for the player winning
- "snd_lose" - A sound for the player losing
- "snd_zap" - A sound for the player shooting
Go ahead and create each of those sounds now (naming them as shown in the list above) and give them an appropriate sound to use. When you're finished your resource tree should look like this:
The first thing we'll do is add the music for the game when playing. For that, open the object "obj_game" and go to the Room Start Event. We want the music to play when we enter the room "rm_game", so we need to modify the DnD™ already in the event to use the Play Audio action to start the music, so add this now:
Make sure to tick the Loop flag, otherwise when the music finishes there will be silence and we want it to play continuously. Once you've added that we can then add in the win/lose sounds to this object. For that, go to the Step Event and modify the score and lives checks to look like this:
Note that this time we set the loop argument to false, as we don't want these sounds to play more than once.
You need to go to the object "obj_ship" now, and open the Keyboard Press - Space Event. In this event, we want to add another Play Audio action to play the "zap" sound after the other actions:
The player ship object also needs to have a sound for when it collides with an asteroid, so open the Collision Event with the object "obj_asteroid" and add this action to the top to play the sound "snd_die":
We'll use the same sound in the object "obj_bullet" for when it hits an asteroid, so open that object too and in the Collision Event with the object "obj_asteroid" and add the same action:
And that's it! You should run the game now and see how it sounds... it should feel a lot different playing!
Before we can call the game finished, there is one loose end that we need to fix up. Currently, when the player dies, a life is removed and nothing else happens. What we really want to happen is to have the room start again so the player can keep playing until the 3 lives are lost and the game ends. To achieve this we need to add another Alarm Event into the object "obj_game", and in that we'll restart the room, so that when the player dies there is a short pause, and then they can start to play again with a life less.
Open the object "obj_ship" now, and add an Alarm 1 event to it:
In this event we simply want to call the following action:
The Restart Room action does just what it says and restarts the room as if it had never been entered, so the player and asteroids are all created again and the player can keep playing.
To set this alarm, we need to open the object "obj_ship" again, and in the collision event with the object "obj_asteroid" add the following actions:
The last thing we are going to do is fix the music so it restarts when the room restarts too. As we have it now, we'll be playing the song again when the room is restarted, so we'll have two, out of sync, versions of the song playing (test it and see!). This is because sounds will not stop playing when a room is changed or restarted, so you must explicitly tell GameMaker Studio 2 to stop a sound if you don't want to hear it after a restart or change. To have our music restart and only place once, we need to open the Room Start Event of the object "obj_game", and add the following in just before the call to Play Audio:
All we're doing is checking to see if the sound "msc_song" is playing using the action If Audio Is Playing..., and if it is then we stop it using Stop Audio (the next line will restart it again).
With that, we come to the end of the "Space Rocks" tutorial! You can run the game now and test that all is working as it should, in which case you should have a start screen, hear music when the game starts, be able to shoot and destroy asteroids, and if you get hit by one the game should restart with one life less.
Congratulations, you've made your first game!
However, that's not the end of the story for "Space Rocks". We've set out the groundwork, but there is a lot here that you can build upon and make the game more advanced or more tailored to how you'd like it to be. Consider the following list of things that you could add to the game now:
- Give the game multiple levels.
- Have enemy ships that shoot at you.
- Maybe add in a boss fight?
- You could add in power ups, like shields or spreading bullets.
- Change the rate of spawn of the asteroids (produce more over time, perhaps?).
- Add some other kind of objective that isn't just getting a high score
You can add one or all of the above, or you can add anything else you can think of if you want! The important thing is to have fun and enjoy making games with GameMaker Studio 2.
Before we leave this tutorial, it's worth mentioning that if you are on any licence other than the Free licence, you can click the Compile Button to quickly make an executable file for testing or distributing to friends, etc...