Coffee-Break Tutorials: Finite State Machines (DnD)


Coffee-Break Tutorials: Finite State Machines (DnD)

Welcome to another one of our Coffee-break Tutorials! We hope you have 10 minutes of your time to dedicate to reading this, as today we'll be discussing a very important subject that is applicable to just about every game in some shape or form... Finite State Machines.

NOTE: If you prefer to use GML instead of Drag and Drop, check out our companion tutorial.

If you've been programming or hanging around in-game dev circles for even a small amount of time then you'll have heard of this term, and it simply refers to a method of programming that uses sequential "states" to perform tasks in a logical form. A real-life example of a state machine would be making some pasta, for example:

  • initial state: put water on to boil, go to waiting state
  • waiting state: if the water is boiling add pasta and go to cooking state otherwise keep waiting
  • cooking state: test pasta by throwing it at a wall and if it sticks then go to serving state, otherwise keep cooking
  • serving state: put the pasta on a plate and go to eating state
  • eating state: eat the pasta and if finished and still hungry go to the initial state, otherwise end.

For a more precise explanation of state machines that doesn't make you hungry, you might want to read this article, but hopefully, you get the idea!

State machines are incredibly useful for many things. most notably AI in games, but that's a very complex subject and since this is only a quick tutorial to get you introduced to the idea, we'll be using a finite state machine to make modular buttons for a menu. For this, you'll need to get a copy of the Brick Breaker demo as well as some extra assets that we'll be adding into it. You can get both of these from the links below:

Brick Breaker Demo YYZ

Tutorial Assets ZIP


GETTING STARTED

Once you've downloaded the files linked above, import the YYZ into GameMaker Studio 2 and then unzip the assets to a folder somewhere safe. If you run the Brick Breaker demo as it is, you'll see that we are presented with a black screen and some text asking you to press fire to play the game. This isn't very user friendly or pleasant to look at, so let's add in some buttons to Play the game or to Quit the game.

to start with, you need to add the button sprites that we'll be using, and you can do this by simply dragging them from the assets folder and dropping them on the IDE. They have been saved with the "_stripXX" suffix to their name, so they should be split into the correct sub-images automatically:

Button Sprites In The Project

Make sure to set the origin for the sprite to the "middle-center".

While we're at it, let's just add the sounds we'll be using too. These can also be dragged from the asset folder and dropped on the IDE, and will create the required sound resources for you:

Button Sounds In The Project

Finally, we are going to make three objects. The first will be a parent object for all buttons in the game, and the second and third will be the Play and Quit buttons respectively. So go ahead and make a new object called oButton_Parentand then create two more called oButton_Playand oButton_Quit. You should make the Play and Quit buttons children of the parent object too:

Button Sounds In The Project

This is important, as we want to make everything as modular as possible, so all our state code will go into the parent object and we will only need to add one or two extra lines in each button to modify the behaviour according to the type of button that it is.


SETTING UP THE STATE MACHINE

Now we have everything set up, we need to edit the current object that we have for the title - oTitle - so that it will create the buttons and also stop drawing text. We'll also use this object to create macros to hold the different states (for information on macros, please see here. We could use strings or even integers, but I find macros (or even enums) make the code a lot clearer and easier to edit if required later.

So, open up oTitle and the first thing we'll do is remove the Global Left Released event, as we won't be needing that anymore (use the Right Mouse button on the event and select delete). Now open the Draw event and remove all the actions except Set Draw Colour and Draw Rectangle. Again, we only need this object to clear the screen, but we don't want it to draw any text.

With that done, open up the Create event. Here we'll be adding in the following actions after the ones that are already there:

DnD™ To Set Up State Machine Macros And Create Buttons

This code is creating the different macro states that the buttons can be in as well as creating the buttons themselves. You can close this object now, as most of the rest of the code we need to add will be done in the object oButton_Parentwhich you should open.


THE BUTTON PARENT

In this object we need to add a Create Event and give it the following set of actions:

DnD™ Initialising The Button Parent

This sets the initial state for all the buttons as well as a variable that we'll use to control what happens when a button is pressed.

With that done, we need to add a Step Event. This event will handle the different button states, and we'll be doing that using a Switch statement, where each Case will be a different state. We can prepare this now by adding the following DnD™:

DnD™ Preparing the FSM Switch And Cases

These are all the different states that a button can have and we'll be adding actions into each of them to deal with effects and user input. Each of the sections below outlines the actions required for each state.


THE CREATE STATE

In the Create Event of the parent object we set the image X and Y scales to 0, as we want the button to "grow" to full size when it is created. For that we'll use the following set of actions in the state Case s_create::

DnD™ For The Create State

All this does is increment the size of the button until it is 1, at which point it changes state to the "normal" one.


THE NORMAL STATE

The "normal" state is where the button just sits and waits for the user to do something. The following actions for this state go in Case s_normal:

DnD™ For The Normal State

Here we are checking the size of the button and if it's greater than 1 then we reduce it (this is because in the "over" state we'll make the button grow a little), and we're also checking to see if the mouse goes over the button, in which case we'll switch states again to the "over" state and set the image_indexto 1.


THE OVER STATE

When the mouse goes over the button the state changes to the overstate, so we need to add this code into Case s_over::

DnD™ For The Over State

The start of this code first checks to see if the mouse is no longer over the instance, in which case it sets the state back to the "normal" state again and also sets the image_index back to 0. If the mouse is still over the instance, then we scale it up a bit for effect and also check to see if the player presses the mouse button. If the button is pressed we change state again, this time to the "pressed" state and also set the image_indexto 2.


THE PRESSED STATE

The penultimate state is a bit more interesting than the previous ones as it's here that we'll be doing a specific task based on the button that is actually being pressed. Let's look at the actions we'll need and see how we'll do that... add this into Case s_pressed:

DnD™ For The Pressed State

Again, we're messing a little with the image scale here just to make the buttons a bit more dynamic, but you'll notice that we're also calling event_user(0). This is the first of the available User Defined Events that are available from the Other object event category. Now, we could use a script here instead, but I find the user events, in this case, to be very useful as they can be added to each object individually meaning that you never have to go looking for what something does. In this case, the Play and Quit objects will both get User Events to define how they will react when they are pressed.


THE DESTROY STATE

This state is for when the button object is being destroyed and will be set when a button is pressed. The following actions should go in the Case s_destroy:

DnD™ For The Destroy State

So, here we make the button scale down to 0 so it "disappears" and then we call another user event, this time User Event 0. This event will be used to do something when the button is destroyed, for example, say the button is for the Game Options, then in this event of the button object we would add in code to create the Game Options objects. In the case of our small demo project, we'll be using the event to either start the game or quit the game, depending on which button has been pressed.


USER EVENTS

Our state machine is set up for the button objects, so we now only have to add in the User Events to each specific button to ensure that everything works as it should. To start with we'll deal with the Play button, so open oButton_Play now and add a User Event 0 to it (from Events > Other). In this event we will add the following DnD™:

DnD™ For User Event 0

We are flagging the button as being pressed, then playing a sound unique to this button object, before using the Apply To...action to tell ALL button objects to change to the "destroy" state. We are doing this because we don't need the buttons anymore as we are going to change rooms and start playing the game, but you can do all sorts of other things in this event. For example, this could be a "Sound" button, and in this event, you could be toggling a global controller variable to switch sound on or off in your game (in which case you wouldn't need to set the destroy state), or it could be a fullscreen/windowed toggle or anything really!

Now add a User Event 1 event. This is the event that is called from the "destroy" state, and in it add this:

DnD™ For User Event 0

Here we check the pressed variable and then change rooms if this button has been pressed (the variable is true). We do this because the destroy state can be called for multiple buttons at once, and we need to ensure that only the code for the button that has been pressed is run.

Now open the object oButton_Quitand add a User Event 0 and a User event 1 with the following actions in the appropriate events:

DnD™ For User Event 0

User Event 0

DnD™ For User Event 0

User Event 1

As you can see, the code follows the same pattern as before and it should be obvious to you by now what it does.

With that done, all that's left is to run the game! We don't need to add anything to any rooms as we already set up the oTitle controller to create the buttons at the start of this tech blog, so when you run the game you should be presented with two buttons to Play or Quit the game, and they should respond to being mouse over by scaling up, and they should react correctly to being pressed, either by starting the game or by quitting it.

How The Buttons Look When The Game Is Run

SUMMARY

This tutorial is a little longer than others, but we hope it's been worth your while going through it and that you now have a good idea of how Finite State Machines work. The important points to take away from this are the use of macrosor enums to name your "states" (to make it easier to identify them), and the use of a parent object - where possible - for the "state machine", only adding code specific to the child objects in the child objects using scripts or user events.

Note that for this example we've made menu buttons, but as mentioned at the start of this blog, you can use state machines for a multitude of tasks, and there's really no limit to the number of states that one can have. You just have to ensure that there is a coherent and logical "path" through the states when you are planning it. You might want to even start with a pen and paper and design a basic flow-chart to get the state progression worked out before starting in on the programming!

For those of you that just want to see what all of the above does without adding it all into a project, you can get a version of the Ball Breaker demo with the Finite State Machine added from the link below:

Brick Breaker FSM Finished

That's it for another Coffee-break Tutorial! Happy GameMaking!