Building a Beat ‘Em Up in Game Maker, Part 2

In the last article we discussed how to make the player move and integrated some basic combat mechanics. It was a good start, but we still have a few more things to do before this starts to feel like a real game.

Building a Beat ‘Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

This article will focus on creating a dynamic camera, making BattleRegions or areas for combat to take place, and giving the enemies the ability to start fighting back.

Just like in the last article, make sure that you download the Asset pack for this article before you get started.

Creating the Camera

Game Maker uses Views to control how the camera behaves. Normally we could tell our view to follow the Player and leave it at that, but eventually our camera will need special behaviors like a shake effect after big attacks. If the camera’s attached directly to the player, this can be very challenging to implement. Instead, we’re going to make a camera object that the view will follow, and program the special behaviors directly into it.

First let’s import the Camera sprite. It is located in the Assets folder you downloaded for the Tutorial under Images > SPR_Camera.png.

Sprite Name Images Origin
SPR_Camera SPR_Camera.png X = 14, Y = 12

Even though the camera will be invisible, the view will have issues following it if it doesn’t have a sprite associated with it, so that’s why we imported this image.

Now let’s make the actual camera object.

  1. Right-click the Objects folder and choose Create Object.
  2. Name the object OBJ_Camera.
  3. Uncheck the Visible attribute.
  4. Click Add Event > Create.
  5. Add the Action Control > Code > Execute Code.
  6. Add the following code:
view_object[0] = OBJ_Camera;

view_vborder[0] = 384;
view_hborder[0] = 512;

view_visible[0] = true;

x = OBJ_Player.x;
y = OBJ_Player.y;

TargetX = x;
TargetY = y;

State = "Player";

MoveSpeed = 15;

All this code does is set up the camera object for us. First it tells the view to follow the camera object. Then it sets the Vborder and Hborder values, which determine how close the camera object can get to the edge of the view before the view starts scrolling with the level. If you want the camera/player to stay in the center of the screen, the Vborder should be half the game screen’s height, and the Hborder should be half the width. Finally, setting view_visible to true makes this view active within the game.

After setting those values, we place the camera at the same position as the Player, and create the TargetX and TargetY variables. These variables will be used to direct the movement of the camera and tell it where to go. Then we have the State variable which will be used to control the camera’s behavior. Finally, MoveSpeed determines how quickly the Camera moves to the Target position.

Now we’ll add movement basic code to the Step Event.

  1. With the Camera object selected, Add Event > Step > Step.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
TargetX = OBJ_Player.x;
TargetY = OBJ_Player.y;

if(distance_to_point(TargetX,TargetY) < MoveSpeed){

    x = TargetX;
    y = TargetY;
    speed = 0;

}else{

    move_towards_point(TargetX,TargetY,MoveSpeed);

}

This step event tells the camera to follow the player. First it sets the target position to the player’s position. Then, if the distance to the target position is greater than the Camera’s move speed, it starts moving towards the target; otherwise, it snaps to the target position and stops.

That’s all the Camera needs to do for now, so let’s make the camera object in-game and start testing it. We could just
drop the camera in the room with the player ourselves, but we’re going to have the Player object create the camera instead. Since the camera relies on the Player to function, having the Player create the camera prevents us from forgetting the camera in the future, and ensures there are no issues with the order the objects are created in.

  1. Go to the player object OBJ_Player.
  2. Open the code for the Create event.
  3. At the very end of the code, add this line of code:
MyCamera = instance_create(x,y,OBJ_Camera);

Finally, we need to enable Views within the room, otherwise the Camera won’t work.

  1. Go into room0, the room we made in the last tutorial.
  2. Go to the views tab.
  3. Check Enable the use of Views.
  4. Then select View 0 from the View list, and check the box that says Visible when room starts.

If you test the game now, things will look the same as before since the room perfectly fills the view. To see these changes in action, we need to make the room wider or taller than it is now.

  1. Go into room0, the room we made in the last tutorial.
  2. Go to the settings tab.
  3. Set the Width = 3000.

Now when you go in-game, and you try to walk past the Enemies, you should notice that the room scrolls with the player as they move. Technically it’s scrolling with the camera, but it looks as if it’s following the Player since that’s what the camera is doing.

You can also try increasing the Height of the room, and you should see a similar effect.

Battle Regions

In classic beat ’em ups, combat is generally restricted to an area that is one or two screens large at a time. When a fight begins, the camera and player become locked to the area where the combat is occurring, and can’t leave it until the fight is over. This makes enemy encounters easier to control, AI easier to build, and makes fights more focused. If you’re still not sure what I’m talking about, take a look at this Let’s Play of Castle Crashers, and watch what happens when they enter combat.

When combat begins, their movement becomes restricted, and in some cases the camera behaviors will even change. It’s a little hard to notice when you’re not looking for it, but the camera tends to “snap” slightly to the area of combat once the battle begins. To create this effect in our game, we’ll add a BattleRegion object that will act as a sort of arena for the battle to take place in. Once the player enters the BattleRegion, it will take over the camera and restrict their movement until the battle ends.

First we need a Sprite for our Battle Region.

  1. Right-Click the Sprites folder and choose Create New Sprite.
  2. Name the sprite SPR_BattleRegion.
  3. Click Edit Sprite.
  4. Go to File > New.
  5. In the New dialog, set the image size to 1024×768, and press OK. I chose this image size because that is our game’s actual resolution. Since BattleRegions will be resizable, having the default size be the size of the screen will be very convenient for quickly designing encounters.
  6. Double-click the new image to open up the image editor.
  7. Use the Paint Bucket to fill the entire image with a single color. Battle Regions won’t be visible in-game, so you can choose any color you’d like here. Just make sure you choose a color that will be easy to see and obvious when editing the level. I chose bright blue.
  8. Use the Checkmark to save the sprite.
  9. Center the pivot point.
  10. Press OK to save the sprite and exit the sprite editor.

Next we’ll make the Battle Region object.

  1. Right-click the Objects folder and choose Create Object.
  2. Name the object OBJ_BattleRegion.
  3. Set the sprite to SPR_BattleRegion.
  4. Uncheck the Visible checkbox.
  5. Click Add Event > Create.
  6. Add the Action Control > Code > Execute Code.
  7. Add the following code:
IsActive = false;

RegionHeight = sprite_height; 
RegionWidth = sprite_width;

LeftEdge = x-RegionWidth/2;
RightEdge = x+RegionWidth/2;
TopEdge = y-RegionHeight/2;
BottomEdge = y+RegionHeight/2;

HasEnemies = false;

This is our creation code. The IsActive variable tells us whether the BattleRegion has been activated. In other words, it tells us whether the battle has begun, and whether the enemies in the region are still alive. It starts out false, but will become true when the player enters and stay true until they kill all of the enemies. RegionHeight and RegionWidth give us variables to access the size of the BattleRegion, while the four variables after that tell us exactly where the edges are.

We will end up needing these values a lot, so being able to easily reference them, without doing the math every time, will be a huge help later. Finally, HasEnemies is a simple Boolean that will tell us whether the region has any living enemies left. Once the BattleRegion activates, it will not be able to deactivate unless this variable is false.

Next we’ll add some code to activate the BattleRegion when the player enters.

  1. In the OBJ_BattleRegion, click Add Event > Collision > OBJ_Player.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
if(point_in_rectangle(OBJ_Player.x, OBJ_Player.y, LeftEdge+abs(OBJ_Player.sprite_width)/2+10, TopEdge+20, RightEdge-abs(OBJ_Player.sprite_width)/2-10, BottomEdge-20) && HasEnemies == true){

    IsActive = true;

}

While the if statement in this code is quite long, it’s actually only testing two things. First it tests if the Player is within the bounds of the BattleRegion, but it ignores a small buffer of about 20 pixels on the inside edge of the BattleRegion. It does this to make sure the player is fully inside the region, and isn’t standing on the very edge, before it activates. The second thing it checks is whether HasEnemies is true. If both are true, it activates the BattleRegion.

Finally we need to deactivate the BattleRegion when combat ends, and make sure that HasEnemies is set to true when there are Enemies in the region. We can do this in the Step Event.

  1. In the OBJ_BattleRegion, click Add Event > Step.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
if(instance_exists(OBJ_Enemy) && point_in_rectangle(OBJ_Enemy.x, OBJ_Enemy.y,LeftEdge,TopEdge,RightEdge,BottomEdge)){

    HasEnemies = true;

}else{

    HasEnemies = false;
    IsActive = false;

}

This code checks if there are any enemies remaining in the room, and if there are any enemies located within the BattleRegion. If both are true, it sets HasEnemies to true; otherwise, it sets it to false and deactivates the BattleRegion.

The last thing you should do is add the BattleRegion into the level, and test the game to see what happens. When you add it, make sure that the enemies you placed are within the bounds of the BattleRegion. You can also have multiple BattleRegions, but you should make sure they don’t overlap. You can see how I set up my BattleRegion below:

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

Restricting Player Movement

If you went in-game to test your BattleRegion, you probably noticed that nothing happened when you entered the BattleRegion. That’s because none of the movement or camera code changes when the BattleRegion is active. Right now, activating the BattleRegion is essentially a useless action because we never tell any of the other objects to do anything about the fact that it’s active. To fix this, we need to adjust the Player and the Camera objects so they account for the bounds of the BattleRegion when it’s active.

For the Player, we want to make sure that they cannot leave the BattleRegion until all their enemies are defeated. To do this, we need to modify the movement code itself.

  1. Go to OBJ_Payer.
  2. Go to the Player’s Step event.
  3. At the very beginning of the Player’s Step event, add this code:
var MyBR = instance_nearest(x,y,OBJ_BattleRegion);

This code stores the nearest BattleRegion in a variable so the Player can easily reference it within the movement code.

  1. Still in OBJ_Player’s Step event.
  2. Add the following code at the very beginning of the IsAttacking if statement, before any of the existing code in that if statement:
if(MyBR.IsActive == true){
    if(point_in_rectangle(x+XSpeed*SpeedMod, y, MyBR.LeftEdge+35, MyBR.TopEdge, MyBR.RightEdge-35, MyBR.BottomEdge)==false ||  place_free(x+XSpeed*SpeedMod, y)==false){
        XSpeed = 0;
    }  
    
    if(point_in_rectangle(x, y+YSpeed*SpeedMod, MyBR.LeftEdge+35, MyBR.TopEdge, MyBR.RightEdge-35, MyBR.BottomEdge)==false || place_free(x, y+YSpeed*SpeedMod)==false){
        YSpeed = 0;
    }
}

While this code looks long, it’s really very simple. First it checks whether the BattleRegion is active. If it is, it then looks at the Player’s potential X position based on their XSpeed. If their current speed would put them outside of the BattleRegion, or cause them to collide with an object marked Solid, it sets the XSpeed to 0. Then it does the same check for the YSpeed.

If you go in-game and test this new code, your movement should become restricted to the confines of the BattleRegion until you kill all of the Enemies.

Camera Behaviors: Introduction to Finite State Machines

Things still aren’t perfect, though. Even though the Player can’t go outside the BattleRegion, the camera can. Because of this, if we go to the edge of the BattleRegion, it’s clear the Player hits an invisible wall in the middle of the screen, and can’t move any further, but the outside of the BattleRegion is still visible.

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

Obviously it would be better if the camera was restricted in the same way as the Player, and it couldn’t move to a location where you’d see past the edges of the BattleRegion. To do that, we’re going to set up a Finite State Machine, or FSM, which will allow us to dictate how the camera behaves based on the current state of the game.

Finite State Machines let you separate each of an object’s behaviors into discrete portions of code called States. Then, using a Switch Statement, you can check what state the object is in, and run only the relevant code. This makes your code easy to follow and incredibly versatile. It also makes expanding the code easy, since we can create new States whenever a new behavior is needed, rather than complicating our existing if statements and code-base.

For our camera, we are going to make two States: Player and Region. Player will follow the Player as closely as possible when they are not in Battle, and will do basically everything our camera does now. Region will take over when the player enters an active BattleRegion, and will make sure the camera cannot move past the bounds of the region, while keeping the player in view at all times.

We already have the State variable in the Camera, from when we created it earlier, so we can move straight into building the actual FSM.

  1. Go to OBJ_Camera.
  2. Open the Step event code.
  3. Replace the code that sets TargetX and TargetY with the following:
MyBR = instance_nearest(x,y,OBJ_BattleRegion);

switch State{

    case "Player":

    //This code will determine the target position when the camera is following the player, and check if the Camera's state should switch to Region
    
    case "Region":
    //This code will determine the target position when the camera is locked into the BattleRegion, and check if the camera's state should switch to Player

}

This code lays out the basic structure of the new Step Event. First the camera finds the nearest BattleRegion and stores it in a variable called MyBR, just like the Player. Then the switch statement looks at the state and runs the appropriate code.

While this is a fairly simple switch statement, and we could accomplish something similar with an if statement, eventually we’ll need more states, so it’s good to think ahead and make the switch statement now.

First, let’s add the code for the Player case. Add this code in place of the comment in the Player case:

TargetX = OBJ_Player.x;
TargetY = OBJ_Player.y;

if(MyBR.IsActive == true){
    State = "Region";
}
break;

This code behaves exactly the same as the code we replaced with the switch statement, and sets the Target position to the Player’s position. After that it checks whether the BattleRegion is active. If it is, it switches to the Region state. Finally, we have a break; statement, which is used in switch statements to end the code and prevent any other states from being processed.

Next, we’ll add the behaviors for the Region state. The goal of this state is to keep the camera within the bounds of the BattleRegion at all times and ensure that no matter where the Player goes within the region, they will be visible. To do that, we will determine the minimum and maximum X and Y positions the camera can go to before the outside of the BattleRegion becomes visible.

Then, if the Player is within that minimum/maximum area, the camera will follow them the way it normally would, and if the Player goes out of that area, to the edges of the BattleRegion, the camera will still be able to see them when it hits those minimum/maximum bounds.

Replace the Region state comment with the code below:

var MinX = OBJ_BattleRegion.LeftEdge+512;
var MaxX = OBJ_BattleRegion.RightEdge-512;

var MinY = OBJ_BattleRegion.TopEdge+384;
var MaxY = OBJ_BattleRegion.BottomEdge-384;   

TargetX = clamp(OBJ_Player.x,MinX,MaxX);
TargetY = clamp(OBJ_Player.y,MinY,MaxY);

if(MyBR.IsActive == false || distance_to_object(OBJ_BattleRegion) > 0){
     State = "Player";
}

break;

First this code sets the minimum and maximum X and Y positions for the camera, by taking the edges of the BattleRegion and adding a buffer that is half the screen height and width on all sides. This buffer allows the camera to get as close as possible to the edge of the region, without making anything outside of the BattleRegion visible. You can see what I mean in the image below.

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

In this case the blue areas represent a BattleRegion, and the box with the red outline represents the camera. The dark blue area is the buffer, whereas the light blue area is the area that the camera can move around freely in. As you can see, the center of the camera perfectly bumps up against the edge of the buffers when it is at the edge of the BattleRegion.

After that, the camera uses a clamp function to set the TargetX and TargetY based on the Player’s position, the minimum, and the maximum. Finally, it tests to see whether the BattleRegion is still active, or whether the camera has gotten outside of the BattleRegion’s bounds, and sets the state back to Player if either is true.

Now if you go in-game to test the BattleRegion, it should behave exactly the way we want and smoothly transition between the different states when the BattleRegion becomes active or inactive.

The best way to see the Camera in action, though, would be to make the width or height of the BattleRegion greater than the width or height of the screen. If you don’t resize the BattleRegion, and leave it the same size as the camera view, then the camera will not move at all while the BattleRegion is active since the BattleRegion is exactly the size of the screen.

Once you’re done testing the camera behaviors, you should go back into OBJ_BattleRegion and uncheck the Visible checkbox. This way the BattleRegion won’t be visible in-game.

Basic Enemy AI

Beating up on defenseless enemies is fun, but our game needs more than glorified punching bags to be interesting. At the very least, we should give them the ability to hit us back.

For our AI, we’re going to use another Finite State Machine. Our enemies’ states could include Wandering/Patrolling when they don’t have a target, Queueing when they’re getting ready to attack, or Idle when they’re doing nothing at all. The States we’ll be starting today are Idle, PositionFront, and PositionBehind.

PositionFront and PositionBehind won’t be completed today, but they will be used in a later article to determine what direction the enemies will approach the Player from.

Before we start building these States, we’ll need to import two new animations that we’ll use for the Enemy Attacks. One will be for the Basic Attack, and one for the Strong Attack. Create two new sprites as laid out below:

Sprite Name Images Origin Position
SPR_EnemyBasicPunch RedEnemyBasicPunch1.png,

RedEnemyBasicPunch2.png,

RedEnemyBasicPunch3.png,

RedEnemyBasicPunch4.png,

RedEnemyBasicPunch5.png

X = 55, Y = 122
SPR_EnemyStrongPunch RedEnemyStrongPunch1.png,

RedEnemyStrongPunch2.png,

RedEnemyStrongPunch3.png,

RedEnemyStrongPunch4.png,

RedEnemyStrongPunch5.png

X = 60, Y = 124

We also need to give the Enemy a State variable, and a SightRange variable so we know how close they’ll have to be to the Player to “notice” them.

  1. Go to the Create event for the Enemy.
  2. Add these variables at the end of the code:
State = “Inactive”;
SightRange = 350;

You should notice that the initial state for the enemy is Inactive. This State will essentially act as an “off” state for the Enemy. By not including a case for it in the switch statement, we will guarantee that until the Enemy switches to one of their “Active” states, they won’t take any actions. In this instance, they will switch to an active state when the Player enters a BattleRegion that they are inside of.

The Switch Statement

Next we will add a Switch statement, similar to the one for the Camera, to the Enemy’s Step Event.

Follow these steps:

  1. Go to the Step event for the Enemy.
  2. After the if statement that checks to make sure the enemy isn’t dead, replace the code that sets the enemy sprite with the following code:
//Choose a new state based on the current situation.

switch (State){

    case "Idle":
        //animate the enemy,
        //check to see if the enemy should attack
        break;

    case "PositionFront":
        SideMod = 1;
        //Find Target position
        //Move there
        //animate the enemy, 
        //check to see if the enemy should attack
        break;

    case "PositionBehind":
        SideMod = -1;
        //Find Target position
        //Move there
        //animate the enemy, 
        //check to see if the enemy should attack
        break;
}

This is a basic shell of our switch statement which we will expand over time; however, it still gives us a good idea of how the final switch statement will work. Right now a lot of this code is just comments, but we will replace it soon, as we’ve done previously.

Before the Switch statement actually runs, the Enemy checks whether they should switch to a different state from the one they’re currently on. Then, we move on to the switch statement itself.

The first State is the Idle state, which basically just animates the Enemy and checks whether they should randomly try to attack. So if the Player happened to run by them, there’s a chance they will attack as the Player passes. The second and third states are our real attack modes. These states tell the Enemy to position either in front of or behind the player and prepare to attack.

Right now our States don’t do anything since they are just comments, but if you take a close look at the comments, you should notice something interesting. Some of the actions, like “animate the enemy” and “check to see if the enemy should attack”, are repeated by multiple states. Since these blocks of code will be needed multiple times, we are going to make custom events to handle these actions for us. This way we don’t have to retype the same thing over and over.

Choosing a New State/Behavior

The very first thing our step event does is check to see if the Enemy should change their behavior, so the first thing we’ll do is make an Event that can look at the Player’s current state, and the state of the game, to see what the Enemy should be doing.

To get an idea of how the Enemy will determine what to do next, take a look at the State diagram below:

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

This diagram explains how the first iteration of our Enemy AI will work. To put it simply, the Enemy will start off in the Idle State once battle begins. If the Enemy is within SightRange of the Player, they will randomly switch to either PositionBehind or PositionFront.

This is pretty simple AI, but we will expand on it as we get further into the series.

To make the event, follow these steps:

  1. In the OBJ_Enemy, choose Add Event > Other > User Defined > User 0.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
///Choose a State

var MyAction = floor(random(100));

switch(State){
    
    case "Idle":
        if(distance_to_object(OBJ_Player) < SightRange){
            if(MyAction <= 50){
                State = "PositionBehind";
            }else{
                State = "PositionFront";
            }
        }
        break;
}

As you can see, this event is using a switch statement to accomplish what I explained above. First it generates a random variable called MyAction which it will use to select a new State in situations where there are multiple valid options. Then, in the switch statement, if the Enemy’s State is Idle, and the Player is within SightRange, they have a 50% chance of switching to PositionBehind, and a 50% chance of switching to PositionFront.

For now, that’s all we need to do.

Now go into the Step event again, and replace the comment that says //Choose a new state based on... with the following code:

event_user(0);//Choose a State

We’re very close to having working code, but we still need to add the code that activates the enemies in the BattleRegion when the battle begins. Follow these steps to do so:

  1. In the OBJ_BattleRegion, choose Add Event > Collision > OBJ_Enemy.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
if(IsActive == true){
    if(other.State == "Inactive"){
        other.State = "Idle";
    }
}

This code runs whenever there’s a collision between an enemy and a BattleRegion, and it checks whether the BattleRegion is Active, and whether the enemy is Inactive. If both of those things are true, it activates the enemy by setting their state to Idle.

If you go in-game now, the code will technically work, but it won’t have any visible effect. This is because the actual States are still just comments explaining what they do, and not actual code.

To verify that it’s working, and see it in action, we will temporarily add this debug code to the end of the Enemy’s Draw Event.

draw_text(x,y,State);

This code will write the Enemy’s State below their Sprite in-game, and make it immediately clear what is happening.

Now when you enter a BattleRegion, you should see the enemy’s state change from Inactive, to Idle, and then immediately to PositionFront or PositionBehind.

Attacking

Choosing a state is a good start, but fighting back requires the enemy to check whether they’re within range of the player, and whether they’re ready to attack. For this we’ll use a variable called AttackRange, and one called Aggressiveness. AttackRange just tells us how close the enemy needs to be to execute an attack, whereas Aggressiveness will start at 0, and slowly increase based on the Enemy’s state. The higher it gets, the more likely the Enemy is to attack, and to use more powerful attacks when they do.

To add these variables, go back into the Enemy’s Create Event, and add this code to the end of the event:

Aggressiveness = 0;
AttackRange = sprite_width/2+OBJ_Player.sprite_width/2;

Now, to make our attacks work, we need to add three more events to the Enemy: one event which determines when to attack, one event that performs the attack, and one event that resets the enemy’s state after the attack. Let’s start with the event to check whether they should attack:

  1. In the Enemy object, choose Add Event > Other > User Defined > User 1.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
///Check Attack Chances

if(OnGround == true){

    if(distance_to_object(OBJ_Player) <= AttackRange && abs(y-OBJ_Player.y) < LayerSize){

        Aggressiveness += .02;

        if(random(1) < Aggressiveness*.03){

            event_user(2);//Attack Event

        }

    }

}

This event is very simple. First it checks whether the enemy is on the ground, since they don’t have any air attacks. Then it checks whether the player is close enough to attack based on the distance between them, and whether they are on the same Layer. If they are close enough to attack, and the enemy is on the ground, their aggressiveness increases, and they have a chance to attack.

Now let’s make the attack event itself, so that we have an event to call when the Enemy does decide to attack.

  1. In the Enemy object, choose Add Event > Other > User Defined > User 2.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
///Attack Event
if(OBJ_Player.CurrentHP > 0){

    AttackChance = random(100); 

    if((Aggressiveness>.75 && AttackChance>= 90) || (Aggressiveness>1 && AttackChance>= 75)){

        sprite_index = SPR_EnemyStrongPunch;
        MyAttack = instance_create(x,y,ATK_StrongPunch);

    }else{

        sprite_index = SPR_EnemyBasicPunch;
        MyAttack = instance_create(x,y,ATK_BasicPunch);

    }
    
    speed = 0;
    State = "Attacking";
    Aggressiveness = 0;
    MyAttack.depth = depth;
    MyAttack.image_xscale = image_xscale;
    MyAttack.image_speed = image_speed;
    MyAttack.Owner = "Enemy";
    
}

For this event, as long as the player isn’t dead, the code generates a random number called AttackChance. This number is used with the enemy’s Aggressiveness to determine whether the enemy should use a light attack or strong attack. If the Aggressiveness and the AttackChance are both relatively high, the enemy uses a Strong Attack; otherwise they use a Light Attack.

After that, it sets the enemy speed to 0 so that they’re not moving while they attack, sets the enemy’s state to Attacking, resets their aggressiveness, and matches the basic properties of the attack to the corresponding properties of the enemy.

Before we go any further, let’s see how things work so far. Go into the Step Event for the enemy and replace all instances of the comment //Check to see if the enemy should attack with the following code:

event_user(1)//Check Attack Chances Event

Now go into the game, and stand near an enemy for a few seconds. Get right up against them and see what happens. They should eventually begin attacking, and, once they do, you should keep an eye on their State and their animation. If you’re not sure how close you should be to them, use this picture as a reference:

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

You should notice that once the enemy starts attacking, they never seem to stop. The animation seems to go on forever, and their State never becomes Idle again. This is because the enemy’s animation and State never get reset after the attack begins; it’s similar to an issue we had with the Player. If you think about how the code is processed, then the last thing we did with the enemy’s animation was choose one of the two attacks, and the last thing we did with their State is set it to “Attacking”. Even though our Attacks work right now, without the third of the events I mentioned above, the Enemy never “neutralizes” and resets back to Idle.

The easiest way to reset the Enemy is by adding an Animation End event. This event will run whenever an animation completes, and will check if the Enemy’s state is set to Attacking. If so, it’ll mean they should switch back to a default state, and switch animations. So in our above scenario, the moment their first attack animation ends, it will run this event and reset everything so that the loop doesn’t restart.

  1. In the Enemy object, choose Add Event > Other > Animation End.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
///ResetAttackingStatus

if(State == "Attacking" && OnGround == true){

    State = "Idle";

}

As I said, the code checks if the Enemy’s State was “Attacking”, and if so it resets their State to Idle.

Now go into the game again, and see what happens when you do the same thing as before. You should now see the State reset if you watch the text under the Enemy, but the animation will still loop forever. The problem is that we still haven’t added any code to animate the Enemy based on their State. That’s what we’ll do next.

Animating the Enemy

The only animation we need right now is for the Enemy to switch back to the Idle sprite after their attack ends. While we could just add this code to the Animation End event, or even to the Idle case in the Step Event, we will eventually need more precise control over the Enemy’s animation.

Because of this, we are going to make another new Event specifically to handle animation. In fact, if you look back at our Step Event, you’ll see in the commented code that we already have a couple of places to put this animation event once we make it.

  1. In the Enemy object, choose Add Event > Other > User Defined > User 3.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
///Animate the Enemy
switch(State){

    case "Idle":
        sprite_index = SPR_EnemyIdle;
        break;
        
    case "PositionFront":
    case "PositionBehind":
        sprite_index = SPR_EnemyIdle;
        break;

}

Right now, all this code does is set the Enemy’s animation to Idle no matter what their state is. Eventually, though, we will add code which sets up the Walking Animation when they’re moving, and even some specialty animations that only apply to specific events.

Now we just need to go back to the Step Event, and replace all instances of the comment //Animate the enemy with the following code:

event_user(3);//Animate the Enemy

If you go back into the game at this point, you should see that our animation issue is solved, and the enemy is switching states correctly.

Adding a Health Bar

Right now our Enemy attacks seem to be working, but you might notice an issue if you let them hit you multiple times. No matter how long you stand next to the enemy and let them hit you, you don’t seem to die. On top of that, we don’t even know whether you’re losing health yet. Since this is pretty important for verifying that enemy attacks work, this is what we’ll do next.

Before we can figure out why you’re not dying, we need to know whether you’re even getting hurt, so let’s add a Health Bar for the player. To do this, we are going to give the Player a Draw GUI event.

  1. In the Enemy object, choose Add Event > Draw > Draw GUI.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
draw_set_alpha(1);
draw_set_color(c_black);
draw_rectangle(18,18,264,42,false);

draw_set_color(c_maroon);
draw_rectangle(20,20,262,40,false);

if(CurrentHP > 0){

    draw_rectangle_colour(20,20,20+242*(CurrentHP/MaxHP),40,c_green,c_lime,c_lime,c_green,false);

}

This code does not do very much, but it should give us a basic health bar for our character. First it draws a simple black rectangle to use as an outline for the Health Bar. After that, it draws a maroon rectangle to act as a background when the Player has lost some health. Finally, if the player still has health, it draws a green bar for the health itself.

The width of the green bar is set by multiplying the health bar’s maximum width of 242 pixels by the fraction CurrentHP/MaxHP. This way the Green health bar will always be sized proportionately based on the amount of health the player has left.

If you go in-game now, you should see the Health bar in the top-left corner:

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

If you go up to an Enemy again and start getting attacked, it should now be clear that you’re taking damage.

Resetting the Player’s IsHit

Despite the fact that we know the Player is being hit, things still aren’t perfect. Go in-game, and try attacking an Enemy or moving after you get hit by one of their attacks. Even though your controls worked perfectly before being hit, the moment you take damage you’re no longer able to move or attack.

This happens because the Player’s attack code in User Defined 2, and their movement code in the Step event both require IsHit to be false. You can see User Defined 2 below:

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

Unlike with our Enemy, though, after the Player gets hit, we never reset their IsHit variable. So let’s add an alarm event to do that.

If you look back at OBJ_ATK’s collision code for the Player, you’ll see that we already have some code in place to run an alarm event and stun the Player.

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

So the only thing we have to do at this point is add some code to reset IsHit, and give the Player an IsHit animation.

  1. With OBJ_Player, choose Add Event > Alarm > Alarm 3.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
IsHit = false;
  1. With OBJ_Player, go to the Step event.
  2. Open the code for the Step event.
  3. Add this else statement to the end of the if statement that runs the Player’s movement code.
else if(IsHit == true){
    sprite_index = SPR_PlayerHit;
}

Your Movement code should now look like this, with the new portion outlined in red:

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

Now when you go in-game, you shouldn’t have any issues attacking or moving, after you get attacked.

Killing the Player

Now that the Player is getting damaged, the game is really starting to come together, but we still have one more issue to deal with. If you stand in front of the enemies too long, eventually you’ll run out of health, and when you do, you’ll probably see an error like this one:

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

Decoding error messages can be a bit of an art, but this one is actually pretty clear. This error refers to the OBJ_Camera’s step event, and specifically it’s saying that the step event is having trouble gaining access to the OBJ_Player object. This happens because the Player object no longer exists. If you look back at our Player’s step event, you’ll see that the Player immediately calls instance_destroy() when the CurrentHP is less than 0.

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

That means that the moment the Player dies, the Player object is destroyed, and the Camera is no longer able to follow it.

So let’s add some code to resolve this error and give the Player the ability to retry when they die.

To fix the Camera issue, we’re going to set the Camera’s state to “Dead” when the Player dies. This way, none of the camera’s other states will run, and the camera will not try to access any of the Player’s variables. We could just as easily destroy the camera when we destroy the Player, but doing that would prevent us from manipulating the camera after the Player dies, so I find it’s better to have an actual Dead state, in case we want to add cool animations or effects later on.

  1. In OBJ_Player, go to the Step event.
  2. In the code for the Step event, replace the code in the else statement highlighted above with the following code:
MyCamera.State = "Dead";
instance_destroy();

Now if you go in-game and let the Player die, you shouldn’t have any error messages. On the other hand, though, when they do die the Player immediately disappears, and it becomes impossible to do anything, since the player never revives, and we don’t have a Game Over screen.

To fix this, we’re going to give the player a death animation, and set up a basic alarm that will restart the game 3 seconds after the player dies. This will be a bit more interesting, and allow the game to continue. Eventually we will add a Game Over screen and even improve the death animation, but for now, this should be a good alternative.

First let’s import the sprite.

Sprite Name Images Origin Position
SPR_PlayerDead PlayerDead.png X = 72, Y = 67

Now we’ll make one last change to the event that kills the Player.

  1. In OBJ_Player, go to the Step event.
  2. In the code for the Step event, replace the instance_destroy() code with the following:
sprite_index = SPR_PlayerDead;
if(alarm[1] == -1){
    alarm[1] = 90;
}

The last thing we need to do for this is create the alarm event that will restart the game.

  1. In OBJ_Player, go to Add Event > Alarm > Alarm 1.
  2. Add the Action Control > Code > Execute Code.
  3. Add the following code:
room_restart();

Now when you go in-game and let yourself get killed, you should see the Player’s death “animation” play, and the game should restart after 3 seconds. It’s not perfect, but it’s a good start.

Building a Beat 'Em Up in Game Maker, Part 2: Combat and Basic Enemy AI

Fixing the Enemy Hit

The very last thing we’re going to do is fix a problem that was created by the addition of the Switch Statement to the Enemy Step Event. If you go in-game and attack the Enemy, you’ll notice that they no longer get stunned when they’re hit. When we added the switch statement, we overwrote that code, and so we removed this effect. Thankfully, though, the animation event we made a few minutes ago will make this very easy to add back in.

Rather than coding an entirely new system, or modifying the FSM to handle the IsHit variable we created in the last tutorial, we are going to make a new State, and replace this variable with that State. The first thing we’ll do is remove the IsHit variable from the Create event, since we will no longer need it.

Delete this line of code from the Create Event.

IsHit = false;

Next, we’ll go into the Attack object and edit it so that it sets the State to Hit, rather than making IsHit true.

  1. Go to the ATK object.
  2. Open the OBJ_Enemy collision event.
  3. Replace the line of code that sets IsHit = true; with the following line of code:
other.State = "Hit";

Now we need to make some small changes to the Enemy object. First we need to modify the alarm code which resets the Enemy’s status, so that it resets the State instead of the IsHit variable.

  1. Go to the Enemy object.
  2. Open the Alarm 0 event.
  3. Replace the code with the following:
State = "Idle";

We also need to add the “Hit” state to the switch statement in the animation event.

  1. Go to the Enemy object.
  2. Open User Defined 3.
  3. Add a new case to the Switch statement with the following code:
case "Hit":

    sprite_index = SPR_EnemyHit;
    break;

Finally, we need to add the Hit case into the Step Event’s switch statement as well, so that the animation event will run when the enemy is in the Hit state.

  1. Go to the Enemy object.
  2. Open the Step event.
  3. Add a new case to the Switch statement with the following code:
case "Hit":

    event_user(3);//Animate the Enemy
    break;

After all of that, if you go back into the game, the enemies should get stunned again when you attack them.

Conclusion

While our combat still needs lots of work, since the Player will eventually need their own stun Event, and the enemies will need to be able to move around, it’s a good start.

In the next tutorial, we will tackle enemy movement and combo attacks.