Welcome to Part 6 of the Hero’s Trail tutorials!
This tutorial expands on the Hero’s Trail project provided with GameMaker Studio 2. We’ll add all sorts of sound effects to the game, learn how to change the music, program a pause menu screen and a new help screen that shows the controls!
This tutorial has the following sections:
Any sound asset present in the project can be played using the “Play
What do the three options do?
“Sound”: This is the sound asset that should be played.
“Loop”: This controls whether the sound should re-play after it has ended. This is useful for music loops, as you can enable this to keep them playing forever. However it should be disabled for sound effects as they should only play once for each action.
“Target”: If you specify a variable name here, that variable will store the unique ID of the sound that was played. You mostly won’t need this unless you want to change the properties for that sound (like pitch and volume).
Let’s use this to implement sound effects for the various mechanics in our game!
We’ll first play the sound for when the player picks up a coin:
Go to obj_coin and open its collision event with obj_player.
This event runs when the player touches the coin.
Add the “Play Audio” action and use it to play the snd_coin sound.
This sound can be found under “Sounds” -> “Items”.
Let’s now play a sound for our power ups and pickups:
Go to obj_player and open its collision event with obj_heart_pickup.
Here, play the snd_powerup sound (under “Sounds” -> “Items”). Make sure to attach it to the main condition.
You can now play the same sound for the collision events with obj_powerup_speed and obj_powerup_star.
Here are some other pickup sounds you can implement - assets for these are already present in the project:
snd_key: This should play when the key is collected by the player (hint: there is a collision event in the obj_key object)
Let’s play a sound when the player swings the sword:
Go to obj_player and open its “Key Press - Space” event.
At the end of the event, play the snd_sword_swing sound.
This sound is present under “Sounds” -> “Characters”.
Here are some other sounds you can implement - assets for these are already present in the project too:
snd_lever_pull: Play this when the player interacts with the lever (hint: this happens in a collision event in the obj_lever object)
snd_finish_level: Play this when the player finishes the level (hint: see the obj_next_level object)
snd_player_defeated: Play this when the player runs out of all hearts and is defeated (look at Alarm 0 in obj_player, or add a Create event to the obj_player_defeated object)
You can also play a sound in a Sequence, so let’s do that for our “baddie defeated” Sequence:
In the Asset Browser, find the seq_baddie_defeated Sequence and open it.
Make sure your playhead is on the first frame.
In the Asset Browser again, go under “Sounds”, then “Characters” and find the snd_baddie_defeated sound.
Drag that sound into the Track Panel of the Sequence.
You can now change when the sound plays by dragging its asset key on the Dope Sheet.
The Sequence will now play with the sound when a baddie is defeated!
We’ll now play a looping sound for the bat flapping its wings. Let’s do the following:
Go to obj_bat and add the Create event.
Here, play the snd_bat_fly sound with looping enabled. We’ll store its ID in a variable called fly_sound so we can stop this specific sound later.
This sound can be found under “Sounds” -> “Characters”.
The variable should not be temporary, as we will later need to access it in a different event.
Let’s stop this sound when the bat no longer exists:
Add the Clean Up event to the bat object.
This event runs when the instance stops existing.
Here, use “Stop Audio” to stop the bat’s fly sound -- enter the variable that was created in the Create event (fly_sound).
Note: Lower your volume now as the sound of all bats will be loud. We will fix this in the next section.
If you run the game, you will hear the bats flapping their wings, however there is one big problem: you’re able to hear all the bats in the level at once! Let's make it so you can only hear those near you.
Ideally, the sound from an object should only play when the player is near it, and should fade away as the player walks away from it. This is implemented into the base project with the enemy, fountains and the torches, and now we’ll add that feature ourselves to the bat.
For this, we'll need to learn what functions are.
A function is similar to an action, as it takes a certain number of inputs and performs an action. There are a vast number of functions available in GameMaker that can be used with the “Function Call” action:
Here’s what each field in this action means:
“Function”: This is the name of the function that we’re calling (or using).
“Argument”: This is simply an option that is passed into the function. You can press the plus button on the left to add another argument, if the function requires it.
“Target”: This is the name of the variable that stores the result of the function. You can make it temporary by enabling the “Temp” option.
We’ll now use a function to implement our “proximity audio” feature.
We’re already playing the bat’s sound in its object, so we simply need to find its distance from the player, and change the volume of its sound accordingly. The closer the player is, the louder the sound will be.
Let’s do the following:
Go to obj_bat and open the End Step event. We’re using End Step as it runs after all instances have moved.
Here, add the “If Instance Exists” action (this is a condition) and check if obj_player exists. We’re doing this to make sure the main player object is active (and not the defeated one).
Attach a “Function Call” action to the condition and use it to call the point_distance function. This function calculates the distance between two points.
It takes four arguments: the first two are the coordinates of the first point, and the last two are the coordinates of the second point.
Our first point will be the bat's own position (just x and y) and the second point will be the player's position (obj_player.x and obj_player.y).
You need to add three more argument fields by clicking on the plus button.
This distance will be stored in a temporary variable called distance.
After that, use the “Set Audio Volume” action to change the volume of the bat’s fly_sound to 50 / distance. This means that the sound will play at full volume when the bat is exactly 50 pixels away from the player, and after that it will get fainter.
Use the following settings for the new actions:
Run the game now, and you will notice that the bat sounds can’t be heard anymore. You will only hear it if you go near a bat, which is exactly what we wanted!
Here is another bat sound that you can play:
snd_bat_shoot: Play this when a bot shoots (Hint: Do that in its Alarm 0 event).
snd_bat_defeat: Play this when the bat stops existing (Hint: Do that in the collision event with the sword).
- Chests. You can play snd_chest_locked when the player tries to open a locked chest, and snd_chest_open when it actually opens.
- Energy Shield. You can play snd_shield_activate in the shield object's Create event, and snd_shield_deactivate in its Clean Up event.
- You can also play snd_shield_loop with looping enabled when the shield is created, and stop it when the shield is removed.
- You can also play snd_shield_loop with looping enabled when the shield is created, and stop it when the shield is removed.
- Gates. You can play snd_gate_iron_open when the lever is pulled and the gate opens.
We’re now going to switch the game music to another track (snd_music_rampage) while the invincibility power up is active. After the power up is over, the music will switch back to the regular track (snd_music_game).
Here’s how we’ll play the rampage track:
Go to obj_player and open its collision event with obj_powerup_invinc.
This event runs when the player collects an invincibility power up, and we’ll change the music track here.
First add a “Pause Audio” action and use this to pause the game music (snd_music_game).
Then add a “Play Audio” action and use this to play the rampage music (snd_music_rampage). Make sure to enable looping.
This will now switch the music from the game track to the rampage track when you collect the invincibility power up.
Let’s now handle how the music will switch back once the power up is over:
Open the Alarm 1 event of the player object, which handles deactivating a power up.
Here, add the “If Audio Is Playing” action to check if snd_music_rampage is playing.
If it is, use “Stop Audio” to stop that track (attach it to the condition).
Then add the “If Audio Is Paused” action to check if snd_music_game is paused.
If it is, use “Resume Audio” to resume the game track (attach it to the condition).
This will stop the rampage music if it's playing, and resume the game music if it's paused.
Now run the game and collect the star power up. You will hear the music change, and after the power up is over, the game will switch back to the old track.
One of the most essential parts of any game is the ability to pause it. Our pause system will involve two mechanics:
Stopping the game’s activity
Displaying a pause menu
In this section, we’ll work on the first one - stopping the game’s activity. This simply involves deactivating objects so that they stop running their events, and later reactivating them so the game resumes. All of this will happen with the press of the “Escape” key.
Let’s first create a new variable for pausing:
Go to obj_manager and open the Create event.
This object can be found under "Objects” -> "Game".
Here, create a variable called pause and set its value to false.
We’ll now tell the object to pause the game when Escape is pressed, by doing the following:
Go to obj_manager and click on “Add Event”.
Go under “Key Pressed”, then “Others” and select “Escape” -- this event will run when the Escape key is hit.
Here, add a condition to check if pause is equal to false, meaning that the game is not currently paused.
In that case, use “Function Call” to run the instance_deactivate_all function, which simply deactivates all instances.
This function takes one argument that tells whether the current instance should stay active or not. In this context the “current instance” is the manager as that’s where the event is.
We want the manager to stay active so that it can resume the game when it needs to, so we’ll set the argument to true (meaning it should keep the current instance active).
Then change the pause variable to true.
The event above will pause the game, but we also need actions to resume it. Let’s do the following in the same Step event:
Add the “Else” action below the “If Variable” condition. Since that condition runs when the game is not paused, the “Else” action will run when the game is paused.
Attach a “Function Call” action to the “Else” action and use it to run the instance_activate_all function.
This function takes no arguments, and simply reactivates all instances, effectively resuming the game.
After that, change the pause variable back to false.
You can now run the game, press Escape and you will see all instances disappear. At that point they’ve completely stopped running their events and are essentially “paused”. You can then press Escape again to return them back to the exact state they were in when the game was paused.
So that handles the actual pausing, now let’s work on displaying a nice pause menu!
Let’s now create a pause menu screen which will consist of a background image and two buttons: Resume and Menu.
We have a parent object called obj_button_parent that already has all button behaviour programmed.
We simply have to create objects that are children of that parent, and we’ll have fully functional, custom buttons in no time!
Let’s do the following to create a Resume button:
In the “Objects” group, select the “Menu” group and create a new object called obj_resume_button.
Assign the spr_button sprite to this, which can be found in the “Menu” group under “Sprites”.
Open its Parent menu and set its parent to obj_button_parent.
You will now see all of the parent’s events in this object.
- Find the "Create" event for this object, right-click on it and select "Inherit Event".
- This event will automatically get a "Call Parent Event" action, which simply runs the parent object's version of the event.
- This way, both the parent's and the child's versions of the event are called.
- This way, both the parent's and the child's versions of the event are called.
- Add an "Assign Variable" action and use it to set the value of the text variable, which stores the text string that is displayed on the button.
- Set its value to "Resume" (with quotes, as this is a string).
To program this button’s behaviour, we need to add to the “Left Released” event, which runs when the mouse has been lifted off the button.
Right click on the “Left Released” event and inherit it.
This will allow us to add actions to this event, and will also run all existing actions in the parent’s event because of the “Call Parent Event” action that was automatically added.
Add the “Assign Variable” action here and set the obj_manager’s pause variable to false.
Then add the “Function Call” action and call instance_activate_all to resume the game.
The "menu" button will take the user back to the main menu. Creating this button will be similar to creating the resume button:
Create an object called obj_menu_button and assign the spr_button sprite to it. Make it a child of obj_button_parent.
Inherit its "Create" event and change its text variable to "Menu" (or some other text that you'd like for this button!)
Inherit the “Left Released” event.
In the event, add the “Go To Room” action, which is used to change the room.
Set the “Room” option to rm_menu so the user is taken back to the menu room.
This is how simple it is to change rooms!
The buttons for our pause menu are ready, so we’ll now design the menu interface in a Sequence.
Let’s do the following:
In the Asset Browser, select the “Sequences” group and create a new Sequence asset. Name this seq_pause_menu.
In the Toolbox, open the Canvas Frame Settings and change the settings to the following:
The Width and Height are set to 1280 and 720 as that’s the resolution of our game.
The Origin X and Origin Y are set to negative values that are half that resolution, so that the origin is placed in the top-left corner of the Sequence (by default the origin is in the centre).
At the top of the Dope Sheet, on the very right, you will see a field that stores the total length of the Sequence (in frames); by default it may be set to 60.
Change this to 1 instead as this will be a static, non-animated Sequence.
This finishes up our Sequence set-up, and we can now start adding tracks to it to build our pause menu interface!
We’ll add a background image to this Sequence, which will serve as the background for the pause menu. We'll use the same image that is used in the main menu!
Let’s do the following:
In the Asset Browser, open “Sprites”, then “Menu” and find the spr_menu_background sprite.
Drag this sprite into the Track Panel or Canvas of the Sequence.
It's better to drag it directly into the Track Panel as it will automatically be placed at the correct position, filling the Sequence completely:
We’ll now place our newly created Resume and Menu buttons in this Sequence:
First of all, make sure your playhead is on the first frame on the Dope Sheet, otherwise, by default, the buttons will only be visible on the selected frame (if you placed it at another frame by mistake, you can simply move the asset key back to the first frame).
In the Asset Browser, open the “Objects” group and then go into “Menu”.
Find the obj_resume_button object (the one we just created) and drag it into the Canvas. You can move it around and place it wherever you like!
The button looks empty right now since its actions are not being executed, but once you're in-game, you will see the "Resume" text on it!
Do the same for obj_menu_button: drag it into the Canvas and place it wherever you like.
Our pause menu is now ready, and we only have to display it in the game!
We want the pause menu to appear above everything in the game, so we’ll create a new layer for it and display it there. Let’s do the following:
Open the rm_level_1 room.
In the Layers panel, click on the button shown below to create an “Asset Layer” -- this type of layer is used to display sprites and Sequences.
Name this layer "GUI” (without quotes) and place it at the top of the Layers list.
Now repeat the same for rm_level_2 as it also needs to have this layer for the pause menu to work.
We’ll now show the pause menu Sequence when the Escape key is pressed. For that we’ll first need to get the X and Y position of the camera, and then display the pause menu at the camera’s position so the player can see it.
We'll need to use the camera's position to create the Sequence because Sequences are drawn inside the room, and not on the screen directly.
Let's do the following:
Go to obj_manager (inside "Objects" -> "Game") and open its “Key Press - Escape” event.
We’ll add new actions to the first “If Variable” condition, which checks if pause is false.
Attach a “Function Call” action to run the camera_get_view_x function. This gets the X position of the camera.
The argument will be view_camera, which points the currently active camera in the room.
Store the result in a temporary variable called camera_x.
Attach another “Function Call” action to run the camera_get_view_y function, which gets the Y position of the camera.
The argument will be view_camera.
Store the result in a temporary variable called camera_y.
Then add the “Create Sequence” action to create the seq_pause_menu Sequence on the newly-created “GUI” layer (enter it with quotes).
We’ll create this Sequence at the camera’s X and Y position, and store the ID of the Sequence in a variable called pause_seq (not temporary).
Use the following settings for your actions:
This will now show our pause menu Sequence when we press Escape!
We now need to close this menu when the game is resumed. In the same event (“Key Press - Escape”), we’ll do the following:
Add a “Destroy Sequence” action and attach it to the “Else” action. This will run when the game is resumed.
Use this action to destroy the pause_seq Sequence (which is the variable storing the Sequence instance we created).
This will now close the pause menu when the game is resumed, however there is one more place where the game can be unpaused: the Resume button. Let’s make it destroy the Sequence too:
Go to obj_resume_button and open the Left Released event.
Add the “Destroy Sequence” action here too, and use it to destroy the pause menu Sequence.
The pause menu variable will have to be retrieved from the manager object, as that’s where it’s created; so you will need to write obj_manager.pause_seq.
You can now run the game, press Escape and see your pause menu! Pressing Escape or clicking the Resume button in the menu should take you back into the game.
Since we’ve created our pause menu in a Sequence, you can leverage this by animating your background and buttons!
Here’s how you can do it:
First of all, you will need to change the length of the Sequence, as it’s currently set to 1 frame. As an example, let’s set it to 30 frames.
You will then need to extend your asset keys so they fit the whole Sequence:
We'll make the background slide in. To do that, use the "Record a new key" button to create two keyframes on the background track.
On the first keyframe, move the background outside the canvas. Then play the animation and you will see the background slide in!
Note: This sub-section is optional, and shows you how the sliding movement can be made smoother using curves. If you are not interested, skip to the "Finishing the Animation" sub-section below.
- Expand the spr_menu_background track and select the position track (since we're only changing the position of the sprite):
- Switch to Curve Mode, which allows you to apply Animation Curves to the selected sub-track (in our case, "Position"). This button can be found above the Dope Sheet:
- Convert your track to a Curve. This means that the selected sub-track ("Position") will now use an Animation Curve instead of keyframes.
- Finally, open the Curve Library menu and change the curve from "Linear" to "Smooth". Notice how the curve changes:
- This will result in movement that looks and feels much smoother, as can be seen in the next section below.
Finishing the Animation
This will make your game look much more polished, however do take care to make this animation quick since you don’t want to keep players waiting!
Make sure that your Sequence is not marked as looping: near the play controls, the loop button should be unselected as shown below:
Let’s now move on to the last part of this tutorial, where we’ll create a help pop-up!
We’ll add a new help pop-up in the main menu that displays the controls of our game. This is an essential part of any game and will be made using Sequences too!
Let’s create an object that will serve as our help window:
In the “Menu” group under “Objects”, create an object called obj_help_window.
Assign the spr_help_window sprite to it.
We also need an object for the close button on this window, so let’s add that too:
Create an object called obj_help_close and assign the spr_help_close sprite to it.
Make this object a child of obj_button_parent, as this is also a button. We’ll use it to simply close the help window.
- Inherit its "Create" event and set its text variable to an empty string ("") as we don't want any text to appear on this button.
Let’s design the actual pop-up now!
We’ll create a new Sequence and set up our help window inside it. Here’s how we’ll do it:
Create a new Sequence and name it seq_help_window.
Open the Canvas settings from the Toolbox and use the following values (they’re the same values we used for the pause menu):
In the Asset Browser, under "Sprites" -> "Menu", you will find a sprite called spr_black_background.
Place it in the Sequence and make it cover the whole canvas - so this acts as our background.
- Stretch the track's asset key so it lasts throughout the whole Sequence.
You’ll notice that this sprite is slightly transparent, to allow the main menu to be visible behind the pop-up. You can make it even more transparent by adding the “Colour Multiply” parameter to the track and lowering its alpha!
Let’s place the actual help window now:
First of all, make sure that your playhead is on the first frame.
In the Asset Browser, find the obj_help_window object and place it in the centre of the Sequence Canvas.
Place the obj_help_close object into the Sequence, near the top-right corner of the help window.
Make sure to stretch the asset keys for both these tracks so they last throughout the whole Sequence.
Our help window is now complete, so let’s now create a new button to open this pop-up!
We’ll create a new button which will be present in the main menu and will open the help pop-up Sequence. Here’s how we’ll do it:
In "Objects" -> "Menu", create a new object called obj_help_button.
Assign the spr_button sprite to it.
Make this object a child of obj_button_parent, as we do with all button objects.
Inherit its "Create" event and set its text variable to "Help".
Let's program its behaviour now:
Right-click on the “Left Released” event and select "Inherit Event" to inherit it.
Add the “Create Sequence” action to create the seq_help_window Sequence. Create it on the “GUI” layer, which we’ll create later in the menu room.
Set the “Target” for the Sequence to global.help_popup so it stores the Sequence ID in a global variable. We’ll later use it to destroy the Sequence.
What is a Global Variable? It's a variable that any instance can access, as opposed to a regular instance variable which can only be accessed in the instance that created it. That's why it's called a "global" variable -- because it's available globally in the game.
Add the “Jump to Point” action and apply it to obj_button_parent. We’ll use this to move all buttons down by 1000 pixels so they disappear, as they should not be usable while the pop-up is active.
Use the following settings for your actions:
Now go into the rm_menu room and add a “GUI” Asset Layer at the top:
Placing the New Button
Let's place the new Help button in the main menu:
- Under "Sequences", find seq_menu and open it. This contains the main menu animation and buttons.
- Place the Help button (obj_help_button) in the middle of the two existing buttons -- you may need to move your playhead so you can see the buttons.
- Stretch its asset key so it lasts till the end of the Sequence.
- Look at the existing buttons and how they're animated: each of them has two keyframes; let's called them Keyframe A and Keyframe B:
- Keyframe A has the button at 50% scale on both axes. This is where their asset keys begin.
- Keyframe B has the button at 100% scale. This is where it stops animating.
- Use this same keyframe set-up to animate your newly-placed Help button, so it animates along with the other buttons.
- Of course, this is only optional and not a requirement!
All of this should now enable us to open the help window, however we also need a way to close it.
The close button (obj_help_close) should be able to close the pop-up and move the menu buttons back to their original positions. Let’s do the following:
Open the obj_help_close object and inherit its “Left Released” event.
Here, add the “Destroy Sequence” action and use it to destroy the Sequence stored in the global.help_popup variable.
The global variable is now coming in handy, as we created it in a different object (obj_help_button) but are now using it in obj_help_close!
Then add the “Jump to Point” action and apply it to obj_button_parent.
Use this to move the buttons a 1000 pixels up, so they're back in their original positions (as we moved them a 1000 pixels down to make them disappear).
You can now run the game, open the help window and close it; you will also notice that the main menu buttons disappear while the help pop-up is open, so the player won't accidentally click on them!
You will see that the window is still blank, so we’ll now display the controls inside it!
We’ll draw our controls as text, so we need to create a special font first to make our text look fancy:
In the Asset Browser, select the “Fonts” group, go to the Create Asset menu and create a Font asset.
Name this fnt_controls as this will be used to display our controls.
The font will open in a Font Editor window in the Workspace. Here you can select a font from the ones installed on your system, change its size and formatting.
Here are the settings we’re going with for our font:
You can download more fonts at 1001fonts.com. Make sure to check the licence of any font you download if you plan to release your game!
We’ll first set up a “string” variable that will store our controls text. A string variable is simply a kind of variable that stores text instead of numbers.
To create a string variable, you can simply make a new variable and assign some text value to it. That text value has to be inside double quotes, as shown in the first example below:
Since the second action doesn’t have quotes around the text, it’s actually just looking for a variable called “Gurpreet”, which is not what we want!
Let’s now set up our controls text by doing the following:
Open obj_help_window and add the "Create" event.
Add an “Assign Variable” action and create a variable called controls.
Apply the “CONTROLS\n” text to it. This is a regular string that says “CONTROLS”, and the “\n” part simply inserts a line break into the string (which is like pressing Enter on your keyboard!).
Add another “Assign Variable” action and assign a string to the controls variable, but this time, enable the “Relative” option. This will add that string to the text that already exists in that variable, essentially joining them together.
Using this technique, we can keep adding controls to the variable by adding strings to it:
You will notice that each new string has “\n” in front of it. This is simply adding a line break in the beginning of each string, so that all the controls don’t appear in just one line.
Also make sure that all actions except the first one have "Relative" checked!
Let’s now draw this text string by doing the following:
Add the "Draw" event to the obj_help_window object.
Add “Draw Self” to make sure the instance itself is drawn.
Add “Set Font” to change the font to the newly created fnt_controls font, and “Set Text Alignment” to change the text alignment to Center and Middle.
Add “Set Draw Colour” to change the draw color to black so the text is easier to read on the white help window.
Add “Draw Value” and draw the controls variable as the “Value”. The “Caption” field should be empty.
Draw it at the relative position of the instance.
Finally, reset the draw color to white and the text alignment to Left & Top -- these are their default values.
The origin of the spr_help_window sprite is set to middle-centre, which is where we’re drawing the controls. We’ve changed the text alignment to make sure it appears with the correct alignment, however if your sprite’s origin is set to top-left, you will not get the correct results.
Run the game now, click on the Help button and you will see the controls!
Now you can animate this help window just like we animated the pause menu. Just place keyframes for animation and optionally apply Animation Curves to make it smoother! You can also add more controls (like "E" for interacting):
We have completed Hero's Trail! You are now familiar with how Drag and Drop™ works and how GameMaker Studio 2 can be used for fast game development.
You are now ready to create your own game from scratch! Read or watch the Fire Jump series and create your own infinite scroller using Drag and Drop™.