Your game needs a great UI to match its great gameplay, and one of the most important UI elements is a button, which you can easily make in GameMaker.
In this tutorial, you'll learn how to make a button that's easy to customise, scale, and modify to fit your needs.
Before we start
If you'd like to follow along with this tutorial, you can download the sample GML Code project or the sample GML Visual project.
In the sample project, I've set up a room, a camera which follows an object, and included all the assets (sprites, sound, and font) I'll be using.
If you run the sample project, you should see the camera panning along the room.
Creating the button
Create an object and name it obj_button.
Give your object a sprite. You can use any sprite you want, but I'll be using spr_button_nine_slice.
Using GameMaker's built-in Nice Slice feature is one easy way to scale sprites for things like buttons, menus, and text boxes.
To set up your sprite, open its Sprite Editor. Go to the Nine Slice section, and check the “Activate Nine Slice” box.
Position your guides to create “nine slices”. For this sprite, I've found 36 pixels on each side to work nicely, and I've set the center to stretch.
You can test out how the sprite will scale in the Preview panel on the right. Read more about Nine Slicing in the manual.
Our sprite has three frames, but an animation speed of 0. This is because we don't actually want our sprite to animate. We want the sprite's origin point to be centered for this demo, but you can experiment with different origin points in your project.
Programming the button
Next, add a Create event.
In the Create event, we need two variables: hovering and clicked. Set these variables to false.
In GML Code, that looks like this:
hovering = false;
clicked = false;
In GML Visual, it looks like this:
Go to the object’s “Variable Definitions” tab and add a new variable. Name the variable button_text, set the type to String, and set the default value to "Default" (including quotes).
Next, add a Draw event. GameMaker automatically draws your sprite, but you can override that behavior by creating your own Draw event.
Since we want to draw our button to the GUI layer, leave this Draw event blank.
Make sure the event has at least one comment, which it does by default.
If it’s completely empty for you, add a //comment.
In GML Visual, place an Exit code block.
Next, add the Draw GUI event.
Add the following line of code:
draw_self();
This tells GameMaker to draw the sprite exactly as if the Draw event held no code or actions. Now we need the following four lines of code.
draw_set_font(fnt_button);
draw_set_halign(fa_center);
draw_set_valign(fa_middle);
draw_set_color(c_white);
This code tells GameMaker which font to use, how it should be aligned, and what colour to draw it with. In our case, we're using fnt_button, aligning it to the center and drawing it white. You can experiment with different values here to learn more.
Note: If you’re not using the base project given above, you'll need to create the fnt_button Font asset.
Finally, we need one more line of code:
draw_text(x, y, button_text);
This line of code draws our text to the screen.
The full Draw GUI event, therefore, looks like this:
draw_self();
draw_set_font(fnt_button);
draw_set_halign(fa_center);
draw_set_valign(fa_middle);
draw_set_color(c_white);
draw_text(x, y, button_text);
Note: GameMaker draws your game to the screen a bit like a painter adding paint to a canvas - when something is drawn, it's drawn over what has already been drawn. So, it's important that we draw our button first using draw_self(); and then draw the text over it.
Our button is now ready to be placed in our room.
The GUI layer
Before we place the button in our room, we need to talk about the GUI layer, how it's different from GameMaker's normal draw event, and why you would want to use it. If you already know this information, you can skip to Making the Button Interactable.
Many things in GameMaker work based on their position in the room. For example, the built-in draw event draws an instance of an object using its x and y values as room coordinates. This means that the button will not move with the camera.
If you zoom or rotate the camera, the problem gets even worse!
The GUI layer solves this problem by using a different coordinate system. On the GUI layer, the top left of your screen is always 0, 0 and the bottom right of your screen is always the width and height of your GUI layer, regardless of how your camera moves, zooms, or rotates.
This means a couple of different things. First, when you draw something on the GUI layer, it will not move or rotate when your camera moves or rotates.
Second, it also means that your GUI layer can have a different resolution than your camera. For example, in this project, I've set the camera's width and height to be half the width and height of the viewport.
Note: While there are other ways to control your GUI's width and height, by default GameMaker will automatically set your GUI size to be the same as your viewport’s size.
However, even though our game's camera is both zoomed in and moving, it'll remain at the correct size and in the correct position because we're drawing our button to the GUI layer.
Making the button interactable
Now that we understand how the GUI layer works, we can put the button in our room, but we want to place it where it should appear on the GUI.
For this project, I've kept it simple. Since our room is the same width and height as our viewport (and therefore our GUI layer), we can use our room as a guide for placing our button. If we want the button to appear in the center of our screen, we should place it in the center of our room.
Note: Most of the time, your GUI Layer will not be the same size as your room and this trick won't work. Just remember that when you're drawing objects to the GUI layer, they need to be placed in the room where they should appear on your GUI layer.
You can also use sequences to solve this problem. We'll cover this at the end.
Now, we can run the game and the button will appear in the center of the screen, even though the camera is moving across the room and is actually half the resolution of our button.
Using the GUI layer does come with one drawback; we can no longer use GameMaker's built-in events for mouse collision, or the built-in mouse_x and mouse_y variables.
However, GameMaker also provides us with several functions to get the mouse's position on the GUI layer, which we can use instead.
Add the Step event.
Our first line of code is:
hovering = position_meeting(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), id);
This line of code will tell us whether our mouse is hovering over our button in GUI space. It uses the device_mouse_x_to_gui and device_mouse_y_to_gui functions to get the mouse position on the GUI layer.
These built-in functions take one argument, which is the device you want to convert. When using a mouse, it will always be device 0.
Next, add the following lines of code:
if (hovering && mouse_check_button_pressed(mb_left))
{
clicked = true;
}
Note that we're checking two conditions, hovering and mouse_check_button_pressed. If and only if both of these are true, we know that we've clicked on the button and can set clicked to true.
Next, we check for a release:
if (mouse_check_button_released(mb_left))
{
clicked = false;
if (hovering)
{
audio_play_sound(snd_button, 1, false);
room_restart();
}
}
When the mouse button is released, we set clicked to false.
After we've set clicked to false, we check if we're still hovering over the button, and if we are, we can play a sound and perform the button's action. For the sake of this tutorial, we'll start with room_restart(); however, what happens when we click the button is completely up to you.
Finally, we want to add the following code to control the button’s image index.
if (clicked)
{
image_index = 2;
}
else if (hovering)
{
image_index = 1;
}
else
{
image_index = 0;
}
If we are clicking on the button, we want the image index to be 2. If we're hovering but not clicking, we want it to be 1. And, if we're neither hovering nor clicking, we want it to be 0.
The entire Step event in GML Code should now look like this:
hovering = position_meeting(device_mouse_x_to_gui(0), device_mouse_y_to_gui(0), id);
if (hovering && mouse_check_button_pressed(mb_left))
{
clicked = true;
}
if (mouse_check_button_released(mb_left))
{
clicked = false;
if (hovering)
{
audio_play_sound(snd_button, 1, false);
room_restart();
}
}
if (clicked)
{
image_index = 2;
}
else if (hovering)
{
image_index = 1;
}
else
{
image_index = 0;
}
Our basic button is now done and we can test it. You can hover over it and click to restart the room.
However, we have one very important step that will make this button much more useful: Inheritance.
Using inheritance to make your button reusable
Right now, we have a button. This is useful, but what if we wanted a second button?
We could duplicate this button and change some code, but what if we wanted to change how our buttons work? We'd have to make that change in every button.
What if we wanted a way to refer to all of our buttons at once? We'd have to list every button.
This might work if we only have two buttons, but what if we had ten or twenty? Eventually, it would become very difficult to manage.
Duplicating our entire button object every time we need a new button will create problems down the road. Instead, we'll use inheritance.
Using inheritance, we'll turn our current button into a parent and then have button children inherit the button parent's code. This means that any changes we make to the button parent will apply to the button children.
Additionally, it means we have a way of referencing all of our buttons at once by referencing the button parent.
Rename this object obj_button_parent and remove the sprite.
In the Create event, add the following code:
activate_button = function()
{
}
This is a method. A method is a customized command that you create, and it can then be used as a built-in function.
Note: If you want to learn more about methods, check out their page in the manual.
Normally, you would put the code you want to run when the method is called inside the brackets. However, in this case, we want the method to be blank. We'll override this method in the children.
In GML Visual, use the New Function Code block and leave it empty.
In the Step event, replace room_restart(); with activate_button();
This will make sure the method assigned as activate_button is called when a button is pressed.
Now comes the cool part. We'll create two new objects and name them obj_button_restart and obj_button_exit and give them the same sprite we were using before.
Make them children of object_button_parent.
Modify their object variables. We'll use “Restart” and “Exit”.
The last thing we need to do is right-click on the Create event and choose “Inherit”.
For each button, we override the button_action method by recreating it. You can write code to have each button do something unique.
For the restart button, use room_restart() and for the exit button use game_end().
Add this in obj_button_restart’s Create event (after inheriting it).
activate_button = function()
{
room_restart();
}
Add this in obj_button_exit’s Create event (after inheriting it).
activate_button = function()
{
game_end();
}
Go to the room and replace obj_button_parent with these new buttons (position them how you would like), run it, and everything works.
To create more buttons, all you need to do is:
Create a new object and give it a sprite
Make it a child of the button parent
Change its object variable (which you can also do in the room editor)
Inherit the create event and give it its own activate_button method
BONUS: Using sequences to animate your buttons
The above is all you really need, but I want to leave you with one slightly more advanced feature that can make your buttons a lot more interesting and professional looking - using Sequences to animate them.
Sequences are a big topic and there are lots of other tutorials on them, but here's a short and very basic demonstration.
To create this animation, make a sequence asset and name it seq_menu.
Add your buttons to that sequence asset and make sure your playhead is at the start before continuing.
Stretch the asset keys for your tracks so they last for the whole Sequence, and then position and scale your buttons however you'd like. You can see my version below.
Next, add a colour multiply channel to your button track.
Set the alpha to zero.
Move the playhead to the end and set the alpha to 255.
Do this for both buttons.
If you hit play, you should see the buttons fade in. Make sure looping is disabled (which it is by default).
Playing the Sequence
Add a Key Press event for the space bar in obj_game, and insert the following code:
if (!layer_sequence_exists("MenuSequence", sequence_id))
{
sequence_id = layer_sequence_create("MenuSequence", display_get_gui_width()/2, display_get_gui_height()/2, seq_menu);
}
else
{
layer_sequence_destroy(sequence_id);
}
This code will create a sequence if none exists and destroy it if one already does.
In the first block, the sequence is being created in the center of the GUI layer. We use the display_get_gui_width (and equivalent height) function and divide the values by 2 to get the center.
The code above creates the sequence in a layer called “MenuSequence”, which we’ll create in a second.
The sequence_id variable that we’re setting here needs to be initialised in the Create event, so add this line of code there:
sequence_id = noone;
Finally, go to the room and remove the buttons that already exist. Add an Asset Layer instead and title it MenuSequence.
Now, when we run the game and hit the spacebar, the buttons will fade in, and if we hit it again, they'll be destroyed. And, of course, you can make the sequence as complicated as you want. Here's one where the buttons slide in.
You can download the full GML Code project here and the full GML Visual project here.
Summary
Some key takeaways from this tutorial:
You can use the Draw GUI event to draw things on the GUI Layer, instead of drawing them in the room.
You can use the device_mouse_x_to_gui (and_y) function to get the mouse position in GUI space.
You can use Object Inheritance to create one parent with the base code and create multiple children from it.
You can create your own methods in objects and then override those methods in children objects to make the child object perform different behavior.
You can use Sequences to lay out and animate your buttons.
If you have any questions, feel free to reach out to @itsmatharoo on Twitter.
Happy GameMaking!