Hello everyone and welcome to the second entry in this series on GameMaker basics. If you are following along from the previous entry, great! We will be picking up right where we left off. Today I want to show you how to set up a simple state machine. A state machine is a data structure that, as the name implies, keeps track of different states. For example, our game may have three states: “Game Running,” “Game Paused,” and “Game Over.” We might use a state machine to remember which one is active, and define how to transition from one to another (see image above).
By design, state machines can only ever be in one state at a time. It’s up to us to define the states that make sense for our situation, and the relationships between them. In this article, we’ll use a state machine to control what player actions are available at any given time, allowing us to set up a character and define what that character can do. Note that even though this example uses GameMaker and some GameMaker-specific code, state machines can be used in any dev environment.
Basic Setup
This entry requires a few more animations than the previous entry, so before we get started you will want to add those to your project. Download the sprites from this link and add them to your project. I’ve named the files appropriately, so just make sure the name of the sprite matches the name of the file once you’ve added it to GameMaker. Go ahead and add all of the sprites—even the enemy sprites—since we’ll need those in a later blog entry. Make sure the origin of each sprite is (16, 32).
Enums, controllers, and persistence
In order to set up our state machine, we first decide which states will be possible and how we’ll identify them in our code. Since this example is all about character actions, let’s define what those actions are and give each one an integer id. The easiest way to do this is with an enum (short for Enumeration), which is a collection of constants held under a custom variable type. If you are familiar with Macros in GameMaker, enums are sort of like that. I prefer using enums as they are much easier to manage and to keep track of than Macros.
Create a script and name it enum_init. Add the following lines.
//states
enum states {
normal,
crouch,
attack,
hit
}
Note that we don’t have to explicitly set the value of each entry; the enum automatically assigns the value 0 to “normal” and increments each entry after that. We can obtain the value at any time:
var example = states.attack;
show_message(string(example)); //output: 3
You can actually override an enum’s automatic numbering by specifying a value for each entry, but it’s important to reiterate that enums are constant. They cannot be changed after they have been defined!
Enums are also global, which means they can be accessed by any object. This is perfect for our state machine.
Now that we have our enum, where do we actually instantiate it? Calling these variables from a non-persistent object, like our oPlayer object, isn’t the best idea. What we want to do is create a controller object that is persistent (it always exists), to manage things like enums and other data types that many objects could access. Go ahead and create a new object and name it “con”. I like to keep my controller names short as it makes it easier to refer back to. Click the “Persistent” box on your new object. Finally, add the Create Event to your object, add the Execute Code command, and add the following lines:
///init
enum_init();
Place the con object in your room. Since this object is persistent, it will continue to exist unless explicitly destroyed by you! There is no need to place this object in every room.
Switch Cases
Now that we have defined our states in an enum, we are ready to access them from our player object. Open up your oPlayer object we created in the last entry and add the following lines to the create event.
attack = false;
//states
currentState = 0;
lastState = 0;
//movement
xSpeed = 0;
ySpeed = 0;
Add the End Step event to oPlayer, and let’s add some code.
xPos = x;
yPos = y;
x += xSpeed;
y += ySpeed;
//animation
frame_reset();
Now let’s hop on over to the step event. We can delete almost all of the code that we added in the previous blog entry, as most of it was just to show off the different parts of draw_sprite_ext. Check out the code below, and make sure your step event looks exactly the same.
//buttons
player_buttons();
//animation
frame_counter();
//state switch
switch currentState {
case states.normal:
normal_state();
break;
case states.crouch:
crouch_state();
break;
case states.attack:
attack_state();
break;
}
If you have never seen a switch statement before, you may be wondering what the heck is going on. I’ll explain in just a moment, but first we need to create three new scripts: normal_state, crouch_state, and attack_state. I like using scripts for different states, as it makes the code a lot easier to read. You can pop open whatever script you need and work just in that particular state.
All right, so what does all of this mean exactly? What is a switch statement and how does it work? Think of a switch statement as a more specific version of an if statement. Where if statements are used to perform checks such as if a boolean is true, a switch statement is used to perform code based on a variable’s value. So, for example, take a look at the block of code below.
//if statement
if(currentState == states.normal){
normal_state();
}else if(currentState == states.crouch){
crouch_state();
}
//switch statement
switch currentState {
case states.normal:
normal_state();
break;
case states.crouch:
crouch_state();
break;
}
Both of these statements are functionally the same. They will both run the scripts we want based on the currentState variable’s current value, but the switch statement is much more clear. As we add states, using if statements becomes unmanageable. A switch statement is much easier to manage. For a more detailed explanation of switch statements, check out the links at the end of this blog.
One last thing before we move on. We need to add a new button variable to our player_buttons script. Open that script and add this line:
attack = keyboard_check_pressed(ord("Z"));
State Machine
We’ve already defined an enum of possible states, along with the variable currentState to keep track of which one is active. Now that we know how a switch statement works, we can create the code that executes in each state, as well as the rules to transition between them. A switch statement makes it easy to visualize what our state machine is and what it’s doing. If our currentState variable is equal to one of the cases in the statement, then perform the code relevant to that case. Since we created scripts for each state, go ahead and open up the normal_state script and add the following code:
//movement
if(left){
xSpeed = -2;
}else if(right){
xSpeed = 2;
}else{
xSpeed = 0;
}
//change to crouch state
if(down){
currentState = states.crouch;
}
//change to attack state
if(attack){
currentState = states.attack;
}
This code is pretty straightforward. For me, a normal state means the default state of the character. They aren’t performing any special actions, like attacking or using an item, and the player has full control over the character. Here we have left and right movement, and transitions into the crouch and attack states. If you run the game now you won’t be able to see the full effect of our state machine yet. If you push down, or Z, you will change states and no longer be able to move. Next let’s define the crouch state. Open your crouch_state script and add the following code:
xSpeed = 0;
if(!down){
currentState = states.normal;
}
While crouched (holding the DOWN arrow key) we stop the player’s horizontal movement (xSpeed = 0). If they release the down key, we return to the normal state. This would be a good place to add different actions while in crouch, like crawling or maybe a crouched attack.
Open up the last state script we created, attack_state, and add the following code:
xSpeed = 0;
if(frame > sprite_get_number(sprite) - 1){
currentState = states.normal;
}
Again we are zeroing out the horizontal speed, and we are setting the player state back to normal when their animation is over. But… we haven’t set up our animations yet, have we? Animation control is another great use of a state machine—and switch cases! Create a new script and name it animation_control. Add the following code:
xScale = approach(xScale,1,0.03);
yScale = approach(yScale,1,0.03);
//animation control
switch currentState {
case states.normal:
if(left){
facing = -1;
}else if(right){
facing = 1;
}
if(left || right){
sprite = sprPlayer_Run;
}else{
sprite = sprPlayer_Idle;
}
break;
case states.crouch:
sprite = sprPlayer_Crouch;
break;
case states.attack:
sprite = sprPlayer_Attack;
break;
}
//reset frame to 0 if sprite changes
if(lastSprite != sprite){
lastSprite = sprite;
frame = 0;
}
By using another switch statement, we are easily able to control player animations. Notice that we are able to use if statements inside of our switch cases! There are a few reasons we are not combining our animation control with the initial switch statement we created. First, we want our animations to happen at the very end of all of our code. Animation is the result of everything that happens prior to it! Second, it makes it much easier to read. The last expression at the bottom of the above code will reset our frame to 0 whenever a sprite changes. This prevents animations from starting on the wrong frame when changing sprites.
Notice that we moved the xScale and yScale code to the top of animation_control. This is important for later.
Open the end step event in your oPlayer object and add the following line to the bottom of your code. This will ensure it happens after everything else.
animation_control();
Go ahead and run your game. You should have a character that can run left and right, idle, crouch, and attack! We were able to compartmentalize our character’s actions based on its current state. Aside from the managerial benefits, setting up a state machine makes it a lot easier to track down bugs, add new behaviors, and keep track of the overall structure of the object. I often use state machines and switch statements to control things like what menu screen to display, what game mode is being played, and to define what kind of AI to give an enemy.
Thanks for reading! Follow me on twitter, and on my website for more gamedev related stuff!
Important links
- GameMaker Basics: Drawing Sprites
- Sprites and animation by Alexander Prokopiev
- Data Types and Enums
- Switch Statements
- State Machines
- GameMaker Project File
Nathan Ranney is the founder of punk house game dev shop, RatCasket. He’s best known for the creation and development of Kerfuffle, an online indie fighting game.
Source: Alexa