Breakthrough | GML


Breakthrough | GML

Welcome to the BreakThrough tutorial! This tutorial is designed to get you up and running using GameMaker Studio 2 in as short a time as possible and will take you through the steps required to make a "breakout" style game. In this tutorial you'll be making a basic but playable version of the game from scratch using GML (the GameMaker Language). Should you prefer a more visual approach to game making, this tutorial is also available using DnD™ on this page.

undefined  

Sprites

When using GameMaker Studio 2, all your visuals start off as sprites. These are simply images made up of pixels that we will draw to the screen to represent what is happening, and in the case of a "BreakThrough" game, we will need the following:

  • A bat - This will be what the player moves to direct the ball

  • A ball - This will bounce around the screen and destroy bricks

  • Bricks - These are what you must destroy to complete the game

  • Borders - What the ball will bounce off so it doesn't go out the screen

To create a sprite, go to the Asset Browser, then use the Right Mouse Button undefined on the Sprites folder and select Create > Sprite, then name the sprite "spr_Bat":

undefined

This will open the Sprite Editor window where we show the properties for the sprite:

undefined

The first thing we need to do is make a the sprite the correct size, as we don't want a square bat! For that click the Resize Sprite undefined button and change the size of the sprite to 64 pixels wide by 16 pixels tall:

undefined

Back in the Sprite Editor again, click on the Edit Image button to open up the Image Editor. The Image Editor is used in GameMaker Studio 2 to draw and edit all the graphical assets that make up your game. In this case we want to draw a "bat" for the game, so, use the different tools in the toolbox to do this now, specifically:

  • The Fill tool : Fill the canvas with a colour

  • The Rectangle tool : Draw an outline around the canvas

  • The Eraser tool : Remove the corners to give the bat a "rounded" look

undefined

You can take a moment if you wish to play around with the other draw tools (you can find full descriptions for each of them in the manual) to see what they can do or make the bat image prettier, then when you are finished simply close the image editor and the image will be saved and assigned to the bat sprite.

We need to set the sprite origin now. This is the point where the sprite will be positioned within the room (think of it like pinning an image to a board... the origin is where you stick the pin) and we need to set it to the Middle Center position: 

Set The Sprite Origin

With that done, let's make the rest of the basic sprites for the game, starting with the bricks:

  • Create a new sprite and call it "spr_Brick"

  • Click the "Resize" button undefined and set the size to 32 width and 16 height

  • Click the "Edit Image" button and in the image editor fill the rectangle with white and give it a black border (we are going to colour the bricks programmatically so they need to be black and white)

  • Close the Image Editor and the Sprite Editor, without changing the origin of the sprite as it's fine set to the top left

The Brick Sprite

The next sprite to make is for the ball, so go ahead and do the following:

  • Create a new sprite and call it "spr_Ball"

  • Click the "Resize" button undefined and set the size to 15 width and 15 height

  • Click the "Edit Image" button and in the image editor draw a filled circle of any colour. You can do this freehand or use the Ellipse tool undefined

  • Close the Image Editor, and in the Sprite Editor set the origin to the Middle Center (like we did for the bat sprite)

undefined

Okay, that's all the sprites for now. Let's move on to creating some objects to use them!


Objects

We are now going to make our game objects. Objects are the resource that we use to control aspects of a game and to do specific things. They can be given a sprite, have behaviours, and they can be instructed to react to certain game events as well as to each other.

So, let's make our first object! This object is going to be for the "bricks" that we want our player to break. To create this, go ahead and right-click on the Objects resource in the resource tree and select "Create Object". This will open up the Object Editor:

  The Object Editor

In the "Name" section give the object the name "obj_Brick", then click the button labelled "No Sprite". This will open the Asset Explorer where you can select the brick sprite that you drew previously:

Adding A Sprite To An Object

We don't need to change any other properties for this object, and can now move on to adding some Events. An Event is simply a moment within the game loop when something happens. The main ones are:

  • Create: This is triggered the first time an instance of the object is created in a room and is where you would initialise variables and set values etc...

  • Step: Each game step (or frame) this event will be triggered, and it's where you would place the majority of the objects logic

  • Draw: This is where things are drawn. If you have no code in this event, then GameMaker Studio 2 will automatically draw whatever sprite is assigned to the object

  • Destroy: This event is only triggered when an instance of the object is destroyed (removed from the room) and can be used for a variety of tasks like creating an explosion, or adding score, or playing a sound, etc...

For more detailed information on all of the events that an object can have, please see the manual.

In the case of our brick object, we will need to use the Create Event only (for now), so in the Event window, click the Add Event button and select Create. This event will now be added and a code window will open up ready for you to add your code to it. In this case we want to add the following:

/// @description Set Colour
image_blend = choose(c_red, c_yellow, c_blue, c_green, c_fuchsia, c_orange);


Brick Object Create Event

The image_blend variable is a built-in variable that controls the colour to be blended with the object sprite. In this case we are telling GameMaker Studio 2 to choose from one of six colours, and this will make our bricks all be different colours when they are created in a room.

With that done, you can close the Object Editor for the brick, and we can move on to creating the bat object...


Bat Object

Now we'll create an object for the player to control, and in this case it's going to be a "bat" that will be used to bounce the ball back up the screen. For this, you need to:

  • Create a new object (use the right mouse button on the Objects resource and select "Create Object")

  • Name the object "obj_Bat"

  • Assign the object the sprite "spr_Bat" (click the button with "No Sprite" and select "spr_Bat" from the asset manager)

  • Add a Create Event (click the Add Event button and select Create)

The Bat Object

The Create event you've just added should contain the following code:


		/// @description Init Vars
spd = 7; // the base movement speed

This object will also need a Step Event to check for keyboard input. Note that there are actually three types of step event: the Begin, the Step and the End events. You'll mostly use the main Step event for your game logic (the others are handy for games that require a more precise order of execution than our "BreakThrough" game), so go ahead and add that event that now and give it the following code:


		/// @description Move
if keyboard_check(vk_left) // Check for the left arrow key being held down
    {
    // This check is to make sure the bat doesn't go out of the room to the left
    if x > sprite_get_xoffset(sprite_index) + spd
        {
        x -= spd; // Move the bat
        }
    else
        {
        x = sprite_get_xoffset(sprite_index); // Clamp the bat to the leftmost side
        }
    }
if keyboard_check(vk_right) // Check for the right arrow key being held down
    {
    // This check is to make sure the bat doesn't go out of the room to the right
    if x < room_width - sprite_get_xoffset(sprite_index) - spd
        {
        x += spd;
        }
    else
        {
        x = room_width - sprite_get_xoffset(sprite_index);
        }
    }
// Move the ball object with the bat if the ball isn't moving yet
with (obj_Ball)
    {
    if !go x = other.x;
    }

Here we poll the keyboard for the left and right arrow keys and if one is held down we move the player left or right accordingly, all the while maintaining the bat within the room boundaries. Note that at the end of the code block, we call a couple of lines of code that will have the ball object "stick" to the player object when it's not moving. This means that at the start of the game the player can position the ball before pressing "space".

That last bit of code targets the ball object, but we haven't actually got one yet! However, before we add that, let's take a moment to add a few sound effects to our game...

We're going to add three sounds: a bounce sound, a break sound and a button sound. To start with, go to the resource tree and right click the "Sounds" resource and select Create. This will open the Sound Editor:

The Sound Editor

Name the sound "snd_Bounce" and then click file explorer button and browse to an appropriate sound (it should be *.wav format) then add it. We don't need to change any other settings here, so close this window.

Now create two more sounds and name them "snd_Break" and "snd_Click" and add appropriate sounds for them.

With that done, we can move on to creating the ball object.


Ball Object

To complete our game, we need a ball object. So for that, do the following:

  • Create a new object (use the right mouse button on the Objects resource and select "Create Object")

  • Name the object "obj_Ball"

  • Assign the object the sprite "spr_Ball" (click the button with "No Sprite" and select "spr_Ball" from the asset manager)

  • Add a Create Event (click the Add Event button and select Create)

The Ball Object

In the Create event we want the following code:


		/// @description Init Vars
go = false; // Controller variable to set when the ball is moving
spd = 3; // The base speed of the ball
dir = random_range(45, 135); // The initial direction of the ball

This object will also need a Step Event, so we'll add that now and give it the following code:


		/// @description Ball Control
// First check if the ball is moving...
if !go
    {
    // Ball isn't moving so let's see if the game can be started by pressing "Space"
    if keyboard_check_pressed(vk_space)
        {
        // Set the ball speed and direction and the "go" variable so this code won't run again
        go = true;
        speed = spd;
        direction = dir;
        audio_play_sound(snd_Click, 0, false);
        }
    }

All we're doing here is polling the "Space" key while the "go" variable is set to false, but if a press of the key is detected, we play a sound and then set the ball in motion, setting "go" to true so we don't poll the keyboard anymore.

That'll set our ball in motion, but what about when it reaches the edges of the game area? If we don't stop it, then it'll fly out the game room, which isn't really much fun! To prevent this we'll detect when the ball is in "collision" with the room boundary and react accordingly. Luckily for us, in the Other event category we have the Intersect Boundary event: 

The Intersect Boundary Event

Here we add the following code:


		/// @description Bounce Or Die
// Check if the ball is "colliding" with the left or right side of the room
if bbox_left < 0 || bbox_right > room_width
    {
    // Make sure the ball stays within the room bounds
    x = clamp(x, sprite_get_xoffset(sprite_index), room_width - sprite_get_xoffset(sprite_index));
    // Reverse the horizontal component of the speed vector
    hspeed *= -1;
    }
// Check if the ball is "colliding" with the top of the room
if bbox_top < 0
    {
    // Reverse the vertical component of the speed vector
    vspeed *= -1;
    }
else
    {
    // Check if the ball is leaving the bottom of the room
    if bbox_bottom > room_height
        {
        // The ball is leaving the room, so create a new one and destroy this one
        instance_create_layer(xstart, ystart, layer, obj_Ball);
        instance_destroy();
        }
    }
// With each bounce, increase the ball speed up to a maximum of 12px per step
if speed < 12 speed += 0.1;
audio_play_sound(snd_Bounce, 0, false);
direction += 2 - random(4);

This code will check to see which side of the room the ball is about to exit and then reverse the horizontal or vertical speed accordingly (making it look like it's bouncing) while playing a sound, except if it goes out the bottom. In that case, the ball instance will be destroyed and a new one created. Note that at the end of the code we increase the ball speed by a tiny amount, play the "bounce" sound we added earlier, and we also add a small random amount between 2 and -2 to the direction of the ball. This is to prevent the ball possibly getting "stuck" bouncing at perfect 180° angles between the wall and anything else.

The next piece of code required for the ball will go in a Collision Event. A collision event is triggered when the object with the event comes into collision with the instance of another object which you specify. In this case, we are going to add a collision for the ball with the brick object, so click the Add Event button now and add a Collision event with the object "obj_Brick": 

Ball And Brick Collision Event

And in the script editor we will add this code:


		/// @description Destroy Brick
var _dir = direction - 180; // Get the direction back along the way the ball moved
while (place_meeting(x, y, other)) // This loop will only run while a collision is detected
    {
    // Move the ball back along the direction it came from until no collision is detected
    x += lengthdir_x(1, _dir);
    y += lengthdir_y(1, _dir);
    }
move_bounce_all(true); // Set the bounce angle
if speed < 12 speed += 0.1; // Make the ball faster
audio_play_sound(snd_Break, 0, false);
instance_destroy(other); // Destroy the "other" object in the collision, ie: the brick

When a collision event is triggered it is because the sprite bounding-box of one object is overlapping the sprite bounding-box of another, which in this case means that the ball is overlapping the brick. We want to simulate a bounce off the edge of the brick, but if they are overlapping (even if it's only by a couple of pixels) then this bounce will be wrong, so what this code does is it first moves the ball back along the direction it came from until the bounding-boxes of each instance in the collision are no longer overlapping (they could be considered "touching") and then uses the a GML function to calculate the bounce direction, before destroying the brick itself and playing a sound.

Our final peice of code will go in another Collision event, this time with the bat. So, add a Collision event with "obj_Bat" and give it the following code:


		/// @description Bounce
vspeed *= -1; // Reverse the vertical speed
var _s = speed; // Store the current speed
var _dir = point_direction(other.x, other.y + 32, x, y);
var _dist = point_distance(x, y, other.x, other.y + 32)
// Add to direction based on position of bounce
motion_add(_dir, _dist / 5);
// Maintain the speed
speed = _s;
audio_play_sound(snd_Bounce, 0, false);

With this code we are setting the direction of the ball based on how far from the center of the bat the ball hit, so in this way we are giving the player a bit of control over the direction of the ball.

It's time now to place all this into a room and test our game!


Rooms

When making a game in GameMaker Studio 2, it requires at least one room to run, and so GameMaker always adds a room to your new projects. In this case it's called "room0" and can be found in the Resource Tree Rooms section. Like all other resources, you can add further rooms by using the right mouse button menu and selecting "Create Room", but for our game, we'll only need one so let's just edit that.

To start with, double click on the room in the resource tree to open the room editor:

The Room Editor

Everything that you place into the room is placed onto a layer, and you can see that there are two layers pre-created for you - one for "Instances" and one for the "Background". You don't need to add any other layers as these are fine for what we're making, so click on the "Instances" layer to select it ready for adding our game objects to.

NOTE: It's important to understand that you don't actually place objects directly into the game rooms, but rather you place instances of these objects which are basically copies (or clones if you prefer) of the object resource. So, in a typical game, all the characters, monsters, balls, walls, etc... are all objects you create in the resource tree, which you then place as an instance in the Room Editor. This instance can then be changed in the room editor itself or through code, so that it can be scaled, or coloured, or have other details modified from the "base" object. So when we talk about something affecting or changing an instance, we mean it affects that one particular copy of an object in a room, but, when we talk about affecting or changing an object, we mean that we are modifying the object in the resource tree and anything we do to it will be reflected in all the instances created from that point on too. So, the object is the template for the instance, and the instance is what we place in a room to make our game.

To start with, lets make the room a little smaller. The default size is a playing area of 1024x768 pixels, but we want to set it to 640x480. This is done from the Room Properties tab: 

Changing The Room Size

We now want to place some brick instances into the room. To make things easier, you should set the editor grid to be 32x16 rather than 32x32: 

Grid Settings

To add a brick, simply click the object "obj_Brick" and then drag it onto the room to the position you want: 

Add Bricks To The Room

It can be time consuming to constantly drag instances of objects from the Resource Tree, so instead you can simply select the brick object and then "paint" it into the room by holding down and then clicking and dragging where you want: 

Paint Bricks In The Room

Add about 6 layers of bricks, and then add a single instance of the ball object and then a single instance of the bat object. Your room should now look like this: 

The Finished Room

Time to test the game out!


Gameplay

The time has come to test our game! This is the easy part as you're required to do is press the "play" button at the top of the GameMaker Studio 2 IDE. If all has gone correctly, your game will run and you can play it using the "Space" key and the Arrow keys: 

Finished Gameplay

This is a great start, but it's missing a few things to make it a "complete" game - namely player lives and a score. We want the game to give you three "lives" and keep track of the score you get from each brick destroyed, so for that we are going to make a controller object (A controller object is simply an object in your game that is designed to control the things that go on in the background, and it usually doesn't have a sprite assigned to it although it can have, and it can also draw stuff independently as we'll see).

Before adding the controller however, let's add a Font resource, as we'll want our controller to draw some text to the screen later. To add a font, simply right click on the Fonts resource and select Create Font. In the font window that opens, name the font "fnt_Game" and then select something that you think is appropriate, setting the size to something like 20:

Adding A Font Resource

You can close the Font editor now.

For our controller you will need to create a new object, call it "obj_Control", and then add a Create event with the following code:


		/// @description Init Vars
state = "START"

We'll be using this variable to set the "state" of the controller so that it knows whether the game has to start, is being played, or is finished. Apart from this, we'll also use the Game Start event to initialise some global variables:

The Game Start Event

In this event add this code:


		/// @description Init Global Vars
global.player_score = 0;
global.player_lives = 3;
global.high_score = 0;

These global variables will permit us to keep track of important values without having to worry about which instance of an object is accessing them or changing them, and by initialising them in the Game Start event, they will only be initialised once even if we restart the room (but if we restart the game they will be reinitialised).

We now need to draw the score and lives of the player to the screen so you'll need to add a Draw event to the object. Note that like the Step event category, the Draw event has a number of different event types that can be used, but for this game simply use the regular Draw event (you can find out more about the different draw events from the manual):

Add A Draw Event

In this event we'll need the following code:


		/// @description Draw Values
// Set draw properties
draw_set_colour(c_white);
draw_set_font(fnt_Game);
// Draw the player score
draw_set_halign(fa_left);
draw_text(8, 8, "Score: " + string(global.player_score));
// Draw the high score
draw_set_halign(fa_right);
draw_text(room_width - 8, 8, "Hi Score: " + string(global.high_score));
// Draw the player lives as sprites
var _x = (room_width / 2) - (32 * (global.player_lives - 1));
repeat(global.player_lives)
    {
    draw_sprite_ext(spr_Bat, 0, _x, room_height - 16, 0.75, 0.75, 1, c_white, 0.5);
    _x += 64;
    }

Here we are drawing the score across the top of the room, and then a series of sprites (scaled down) to represent the player lives at the bottom.

We need to add one final event to this controller object, a Step event. Add this now, and give it the following code:


		/// @description Check Room Restart
if instance_number(obj_Brick) <= 0
    {
    room_restart();
    }
else
    {
    if state == "GAMEOVER"
        {
        if keyboard_check(vk_anykey)
            {
            audio_play_sound(snd_Click, 0, false);
            global.player_score = 0;
            global.player_lives = 3;
            room_restart();
            }
        }
    }

Here we're going to simply restart the room when the player destroys all the bricks (and because we are using global variables, this will not reset the score or lives), or we'll wait for the player to restart the room if all the lives are gone.

Now that we have that done, and before continuing on to do anything else, open the Room Editor and add this object into the game anywhere then close the room editor again...

Before we go on to test this, we need to add a few extra lines of code to our object "obj_Ball", so open that now. We need to calculate the score and tell the controller when the player has a lost a life, etc... so to start with we'll edit the Collision event with the brick object so that it adds to the global score variable. You want to add the following after the move_bounce_all() function call:


		global.player_score += 15; // Add to the score
	

The code will now look like this: 

Add Score When Colliding With A Brick

We also need to modify the Intersect Boundary event with this code added into the block if bbox_bottom > room_height:


		global.player_lives -= 1;
if global.player_lives <= 0
    {
    // Check for new highscore
    if global.player_score > global.high_score
        {
        global.high_score = global.player_score;
        }
    // Set controller state
    with (obj_Control)
        {
        state ="GAMEOVER"
        }
    }
else
    {
    // Only create a new ball if the player has lives
    instance_create_layer(xstart, ystart, layer, obj_Ball);
    }

This code block will now look like this: 

Restart Game If No Balls Left

Go ahead and play the game now! If all has gone well, you should see intro text on the screen, a score that changes as you play, lives as markers along the bottom and the game should change according to whether you have no lives left or you destroy all the bricks.


Powerups

The way our BreakThrough game is right now, you could probably consider it finished, and it is to a certain extent, but it's missing one final thing to make it into a "proper" BreakThrough game... powerups! So we're going to add them now.

To start with, create a new sprite and set the size to 32 width and 16 height and call it "spr_Powers". Open the sprite in the Image Editor and draw two arrows, something like this:

Expand Bat Icon

We're going to add another image to this sprite to be used in a second powerup type. For that click the large "plus" icon to create another frame: 

Add New Frame

The next frame should have two arrows pointing down, something like this: 

Slow Down Ball Icon

Now create a new object and call it "obj_PowerUp". Assign it the sprite we've just made, then add a Create Event with the following code:


		/// @description Init Vars
image_index = choose(0, 1); // Choose which type of powerup it will be
image_speed = 0; // Set the sprite to not animate
speed = 5; // Set the speed
direction = 270; // set the direction

This code will select a sprite frame to use for the powerup and set it moving down the screen. We need to remove the instance from the game if it is no longer visible, so the next thing to add is an Outside Room event (from the Other category) with this code:


		/// @description Clean Up
instance_destroy();

Finally, we need to add a Collision Event with the object "obj_Bat". In this event we're going to have the game change based on the image index (frame) of the sprite that the powerup uses. In this case, frame 0 will make the player bat larger, and frame 1 will slow the ball down. The code to do this is as follows (and should be added to the event):


		/// @description Powerup!
switch(image_index) // Check to see what frame is being used by the sprite
    {
    case 0: // Make the player bat bigger
        with (obj_Bat)
            {
            image_xscale = 1.5;
            alarm[0] = room_speed * 10;
            }
        break;
    case 1: // Slow down the ball
        with (obj_Ball)
            {
            speed = spd;
            }
        break;
    }
instance_destroy();

With this code, if the powerup uses frame 0 then we set the player controlled Bat object to scale along the x-axis (stretching it), or if the frame is 1 we set the ball speed back to it's original speed. Note that if it's frame 0 we also call an Alarm Event in the player object. This event won't run immediately, but will instead count down for 10 seconds before running (our game has an FPS of 60, so room_speed equals 1 second of time).

Let's add that Alarm event now, so open up the object "obj_Bat" and add an Alarm[0] event: 

The Alarm[0] Event

And here we place this code:


		/// @description Reset Size
image_xscale = 1;

The last thing we need to do for the game to be finished is have the Brick objects spawn one of the powerups. So open up the object "obj_Brick" and give it a Destroy Event with the following code:


		/// @description Create Powerup
if irandom(2) == 0
    {
    instance_create_layer(x, y, layer, obj_PowerUp);
    }

Press the Play button now to see the game running... And then give yourself a pat on the back for having created a complete game in virtually no time!


Summary

If all has gone well, you should now be playing a your own, complete, version of BreakThrough! 

BreakThrough!

Now what? Well, you have a firm base on which to build so why not take some time to try things out based on what you've learned so far? Here are some suggestions for you:

  • Add more sounds - We should have a sound for when the ball leaves the room and also for when the player gets a powerup, so why not add them?

  • Add more powerups - Adding more powerups should be fairly easy now. Add a new image to the powerup sprite, add the frame number into the choose on Create, and then edit the switch in the collision event to do something. Maybe have one to make the bat smaller? Or try making one which has the ball pass through and destroy bricks rather than bounce off them?

  • Add a background - The game is played over a simple black background, by why not add your own colourful one? You'll need a 640x480 sized sprite and then set it in the Room Editor to the Background layer.

  • Make proper levels - At the moment the game is played on a single level that regenerates, but why not try making custom levels? You'll need to make more rooms, add the instances to them, and then in the controller object set it to go to another room instead restarting the current one when no bricks are left.

Those are just a few things that you can do with a base like this, but there is so much more! You could try adding a title screen and menu, or making a pause mechanism, or even adding persistent leaderboards and achievements... The list could go on and on, but the important thing is that you learn how to make games using GameMaker Studio 2 and, above all else, you have fun doing it!