Bringing AI To Life (Hero's Trail Part 4)


Bringing AI To Life (Hero's Trail Part 4)

Welcome to Part 4 of the Hero’s Trail tutorials!

Click here to see all 6 parts.

Overview

This tutorial expands on the Hero’s Trail project provided with GameMaker Studio 2. We’ll make an animation for the baddie and create a brand new AI enemy from scratch. We’ll also build a hearts system for the player and add knockback!

undefined

This tutorial has the following sections:

  1. Editing Paths

  2. Creating Animations

    1. Using Sequences

    2. Adding Assets to the Sequence

    3. Animating the Defeated Enemy

    4. Playing the Sequence

  3. Building a New Enemy

    1. Adding the Object

    2. Following the Player

    3. Enemy-Wall Collisions

    4. Idle Flying Behaviour

    5. Turning Left/Right

    6. Defeating the Enemy

  4. Shooting Projectiles

    1. Adding the Object

    2. Projectile Behaviour

    3. Projectile Destroy Animation

    4. Spawning the Projectile

    5. Managing the Alarm

  5. Player Hearts

    1. Adding the Variable

    2. Drawing Hearts on HUD

    3. Hurt Knockback

    4. Defeating the Player

Editing Paths

The enemies in our game (called “baddies”) follow predefined paths that are designed in the Room Editor. You can easily edit these to change where they walk.

The “Instances” layer in the game room contains all our enemy instances. You can double-click on an enemy, open its Variables and see its assigned path asset in the path_to_follow variable:

undefined

For the enemy shown above, the assigned path is path_enemy_1. You can find it in the “Paths” group in the Asset Browser, open it and edit it:

undefined

However, you cannot see where the path is placed as it’s only shown on a blank canvas. This is precisely why the Room Editor allows you to create Path Layers, so you can edit a path asset right inside the room!

You can read more about Path Layers under the “Paths” section on this manual page.

Modifying Enemy Paths

Let’s do the following to change where our first enemy walks:

  1. Go to the game room and find the layer called “EnemyPath1”.

  2. If you click on the layer, under the “Layer Properties” window you will see the path asset name: path_enemy_1.

    This is the path that was assigned to the enemy previously shown, so editing this layer will change that enemy’s path.

    undefinedc

  3. You can click on the eye undefined icon to turn this layer invisible and then visible, so you can find where the selected path is in the room.

    undefined

  4. You can now move any of the points around to modify the path. Right-clicking on any point will open a menu allowing you to delete it.

    undefined

  5. If you click anywhere else within the room, a new point will be created on the path, right after the last point.

  6. You can now run the game and see that the enemy follows your modified path!

Under the Layer Properties, you will see that the “Closed” property is enabled for this path. This means that the last and the first points in this path will stay connected, forming a loop. You can disable this if you want your path to have separate starting and ending points.

undefined

The "Start Following Path" action has an "On End" argument to control what happens when the instance reaches the end of its path. Our enemy object simply continues following the path (which you can see in the Create event of obj_baddie), however if you prefer to use a non-closed path with separate starting and ending points, you can use the "Reverse" option so it starts following the path in reverse when it reaches an ending point.

undefined

Creating New Enemy Paths

If you want to add a new enemy to the room and create a new path for it, follow these steps:

  1. Place a new baddie instance in the room.

    undefined

  2. In the Layers panel, select the “EnemyPaths” group and click on the following button to create a new Path Layer inside it:

    undefined


    Name this layer "EnemyPath4" (as this is our fourth enemy path in this room). You now need to create an actual path asset for this layer.

  3. Under the Layer Properties, click on “Select Path…” and then select “Create New”.

    undefined

  4. This will create a new path asset that will be assigned to this layer. You can now click anywhere in the room to start adding points to your new path!

    undefined

  5. Under the Layer Properties, you can change the “Connection Kind” to “Smooth Curve” to make the enemy's movements look realistic. You can also make it closed to form a loop.

    undefined

    We've just created the path itself though, so we need to tell the enemy instance to use it.

Using the Path

You can now assign this path to your new enemy instance:

  1. First of all, select your new Path Layer and look under Layer Properties to find the name of the assigned path asset. It may be something like “PathX” where X is a number.

    undefined

  2. This step is not required but is recommended for good organisation: find the path asset in the Asset Browser (it will usually be at the very bottom of the list) and move it into the "Level1" group under "Paths".

  3. Double click on your enemy instance and open its Variables.

  4. Edit the path_to_follow variable (by clicking on the undefined pencil icon) and set its value to the new path asset (Path6). This is the path that this enemy will now follow.

    undefined

If you run the game, you will now see your new enemy following your new path!

undefined

Using Sequences

Sequences are assets that allow you to create your own animations; you can use them to animate sprites, objects and sounds and then play the Sequence anywhere in-game!

We’ll create a Sequence animation for the baddie being defeated, and play that animation when it’s destroyed.

undefined

How Will This Work?

We'll create a Sequence asset, which is simply an animation.

This animation will show the baddie falling down.

When a baddie instance is hit with the sword, we'll remove the instance and play the Sequence.

Let’s do the following to create a new Sequence asset:

  1. In the Asset Browser, select the “Sequences” group.

  2. Go to the undefined Create Asset menu at the top and create a Sequence asset.

    undefined

  3. Name this asset seq_baddie_defeat. The “seq_” in the beginning stands for “sequence”.

    undefined

Sequence Editor

This will open the Sequence Editor in your workspace, which has the following parts:

undefined

  • The Canvas is where you create and see your animation

  • The Track Panel is a list of your tracks (which are just your sprites/objects/sounds)

  • The Dope Sheet is where you can edit the timings of your animations (when they move, rotate, etc.)

We will now start creating our enemy’s defeat animation step-by-step, but for a detailed introduction to Sequences, you may check out the following resources first:

Adding Assets to the Sequence

Let’s add the baddie's defeated sprite into our Sequence:

  1. In the Asset Browser, go under “Sprites” and open the “Baddie” group.

  2. Drag and drop the spr_baddie_defeat sprite anywhere into the Sequence:

    undefined

  3. If you placed it somewhere within the Canvas, move it so that it’s exactly in the centre.

    undefined

  4. If you hit Play in the Dope Sheet, you will see the sprite for a couple frames, and it will then disappear. This is not ideal as we want it to be present throughout the whole Sequence.

Tracks & Dope Sheet

  1. On the left, the Track Panel contains your spr_baddie_defeat track.
    On the right, the Dope Sheet contains the “asset key” for the track.

    undefined

  2. You can move and resize this asset key to change when and for how long the track is visible. We'll extend this to last for the entirety of the Sequence.

    undefined

  3. You can use the “playhead” in the Dope Sheet to see different frames. By default, any edits made to a track will be recorded at the current playhead frame.

    undefined

    Currently there is no difference between any of the Sequence's frames, however as we start creating our animation, the playhead will become more useful.

Animating the Defeated Enemy

We'll create a simple animation of the enemy falling down and disappearing. For this, we'll need to create two "keyframes".

A keyframe is a special frame that records the position, rotation, etc. of the sprite. If you have two keyframes, say keyframe A and keyframe B, your sprite will be animated so it gradually goes from its state in keyframe A to its new state in keyframe B.

Our enemy's defeat Sequence will have two keyframes:

  1. The first keyframe will be its default state, as it currently appears in the Sequence (where the enemy is standing upright).

  2. The second keyframe will be the enemy rotated and shrunken so it appears fallen down.

The Sequence will then automatically interpolate all frames between those two keyframes to create an animation, so it looks like the enemy actually fell down!

Creating the First Keyframe

The first keyframe will be at the first frame of the animation.

Make sure that the playhead is on frame 0, and then click on the "Record A New Key" button. This will record the current position, rotation and scale of the sprite and create a keyframe at the current frame.

undefined

Once you have clicked on that button, your first keyframe is ready (as no other changes are required to it).

Creating the Second Keyframe

The second keyframe will be of the enemy fallen down. We'll do the following to create it (the steps listed below are also shown in a GIF under it):

  1. Move your playhead to around frame 15 (or an earlier one if you wish to have a shorter animation).

  2. Rotate the enemy so it faces downwards.

  3. Move it down and to the right so it appears fallen down.

undefined

This will record our second keyframe at the selected frame!

Finishing the Animation

The enemy now appears to fall down, but it does not disappear. We'll now make it shrink (only on one axis) and shorten its asset key so it completely disappears.

Let's do the following (the steps listed below are also shown in a GIF under it):

  1. Keep the playhead on the second keyframe (you will see it on your asset key with a undefined diamond icon).

  2. Scale the sprite down vertically and move it up to correct its position.

  3. Shorten the asset key so the enemy disappears after its second keyframe.

undefined

The enemy's defeat animation is now ready!

Keep in mind that you can shorten your animation easily if you feel it is too long: in the Track Panel, expand your track, and in the Dope Sheet move the second keyframe(s) so the animation is shorter. Also make sure to adjust the total asset key length.

undefined

Playing the Sequence

We’ll now play our new Sequence when the enemy is defeated, and destroy the enemy instance so only the Sequence is visible. However, there is something important to keep in mind.

If you play the defeat Sequence when the sword hits the enemy, the animation will only appear after 1 frame. If you destroy the instance at that moment then you will have 1 blank frame where neither the Sequence nor the enemy is visible.

To work around this, we’ll do the following:

  1. Play the Sequence

  2. Wait 1 frame

  3. Destroy the enemy instance

How Will This Work?

We'll create an "Alarm" in our enemy object.

An Alarm is an event that you can run yourself.

What's special about it is that you can run it later.

So we'll run an Alarm event one frame after the Sequence has played, and in that Alarm event, destroy the enemy instance.

To achieve that, let’s follow these steps:

  1. Go to obj_baddie. This object has a collision event with the sword object, however it’s greyed out because it’s inherited from the enemy’s parent object.

    1. The parent object is what currently controls how the baddie loses, which is why it inherits the parent event and simply uses that.

  2. We want to redefine this event for this particular enemy, so right-click on the event and select “Override Event”.

    undefined

  3. You will now have a new, empty event.

  4. Search for the “Set Alarm Countdown” action and drop it into the event.

  5. Search for the “Create Sequence” action and drop it into the event.

Use the following settings for the new actions:

undefined

Alarm Events

GameMaker's Alarm events can be told to run after a specific number of frames. In the above event, we are telling Alarm 0 to run after 1 frame.

Let’s program this Alarm to destroy the enemy instance:

  1. Now in the same object (obj_baddie) add a new event: go under “Alarm” and select “Alarm 0”:

    undefined

  2. This event runs 1 frame after the enemy’s collision with a sword, because we told it to.

  3. Add the “Destroy Instance” action in this event.

undefined

Now run the game, attack an enemy and you will see your Sequence animation!

undefined

(Optional) Sequence Depth-Sorting

NOTE: This section is optional; keep reading to learn how to implement Sequence depth-sorting, or skip to "Building a New Enemy".

You may notice that the defeated enemy Sequence sometimes appears above the player even when it should be behind it:

undefined

This happens because the Sequence is simply displayed on the “Instances” layer and is not depth-sorted like object instances.

What is Depth-Sorting?

Depth-sorting is how the game figures out whether an instance should appear behind or in front of another instance.

It's already implemented in the base project, however it only applies to object instances, not Sequences (as Sequences don't run code like objects do).

We now have to carry the baddie instance's depth over to its defeat Sequence.

As a simple workaround, we can create a new layer that uses the depth value of the baddie instance and display the Sequence there.

Here’s how you can implement that:

  1. Go to obj_baddie and open its collision event with obj_sword_attack.

  2. Before "Create Sequence", add the "Function Call" action.

  3. Use this action to call the layer_create function. Enter depth as the argument. Set the “Target” to seq_layer so the layer ID is stored in that variable (make it temporary).

  4. This will create a new layer at that depth, so anything in this layer will appear at the same depth as the baddie.

  5. In the “Create Sequence” action, pass in the seq_layer variable so the Sequence is created in that layer.

Your event should now look like this, with the new "Function Call" action in the middle:

undefined

The baddie defeat Sequence will now always appear at the correct depth; e.g., if the enemy is behind the player, the Sequence will also appear behind it, and if the enemy is in front, the Sequence will also be in the front:

undefined

Building a New Enemy

We’ll create a new bat enemy that flies around and follows the player. It will also shoot projectiles at the player.

undefined

How Will This Work?

We'll create a new object for the bat, and it will be a child of the enemy parent.

The bat will create an imaginary rectangle around itself, and check whether the player resides within that rectangle.

If the player is in the rectangle, the bat will start moving towards the player.

Later, we will use Alarm events so the bat can repeatedly fire projectiles towards the player.

Let’s start by creating its object!undefined

  1. In the Asset Browser, open "Objects", then “Game” and select the “Enemies” group.


  2. Here, create a new object and name this obj_bat.


  3. Assign the spr_bat_fly sprite to it, which can be found in the “Bat” group under “Sprites”.


  4. Open its Parent menu and assign obj_enemy_parent as its parent.



Since we’ve made this a child of obj_enemy_parent, it will automatically hurt the player on collision, and the player’s sword will also be able to defeat it!

Go ahead and place it anywhere in the room (make sure to use the "Instances" layer):

undefined

Keep in mind that it's completely up to you how you want to organise your layers, so if you like, you can create a new Instance layer for your enemies and put the baddie and bats in there!

Following the Player

The bat should follow the player when they’re close to each other. For this, we’ll create an imaginary rectangle around the bat that spreads 200 pixels in each direction, and check if the player is simply touching that rectangle:

undefined

If the player overlaps that rectangular area, the bat will start going after it!

Let’s program that:

  1. Add the Step event to obj_bat. This event runs every frame, so we can program our AI behaviour here.

    undefined

  2. Add the “If Collision Shape” action to the event. This is a condition that allows you to check for collisions within a rectangular area.

  3. Attach the following actions to this condition:

    1. "Set Speed" (not to be confused with "Set Animation Speed")

    2. "Set Point Direction"

Use the following settings for these actions:

undefined

The bat will now constantly check for the player being in that rectangular area, and if it's found, the bat will start following it!

undefined

At the end of that GIF, you will notice a problem: the bat can move through walls! We need to give it collisions, just like the player.

Enemy-Wall Collisions

This is very simple as we can copy the player’s collision event, and paste it into the enemy parent object (so all enemies get collisions!):

  1. Go to obj_player. Right-click on the undefined obj_collision_parent event and select “Copy Event”.

    undefined

  2. Go to obj_enemy_parent. Right-click anywhere in the Events window and select “Paste Event”.

    undefined

Voila! All enemies now have collisions, just like the player! This means that bats will now be stopped by walls:

undefined

If you are curious to know how the collision event was implemented, read this section of the Hero’s Trail breakdown page.

Idle Flying Behaviour

Before the bat starts following the player, it stands still. We want it to fly around in a circle when the player is out of its range, so let’s do the following:

  1. Go to obj_bat and open its Step event.

  2. Search for the “Else” action and drop it below the “If Collision Shape” action.

  3. The “Else” action always comes after a condition. You can attach actions to it, and they will run when the original condition is not true.

    1. This means that any actions attached to this Else will run when the bat doesn't find the player near it.

    2. More detailed explanation about the Else action is given in the next section.

  4. Attach the following actions to the “Else” action:

    1. "Set Speed"

    2. "Set Direction Variable"

Use the following settings for the new actions:

undefined

What is Else?

Else is always used after a condition, and other actions can be attached to Else, just like in a condition.

  • When the previous condition is true, only the actions attached to that condition are executed.

  • When the previous condition is false, only the actions attached to the Else action are executed.

undefined

This way we can control which actions run and don’t run depending on one condition!

So what does the event do?

If the bat finds the player near it, it starts following it. Otherwise, it simply keeps adding +2 to its direction value, so that it keeps moving in a circle.

Run the game now, keep away from the bat and you will see it keeps flying in a circle!

undefined

When the player runs away, the bat starts flying in a circle again. We've got our "idle" behaviour set up!

You will notice that the bat doesn’t look in the correct direction - it’s always looking to the right. Let’s change that!

Turning Left/Right

We’re basically going to flip the bat’s sprite depending on whether it’s moving left or right.

By default, the bat’s sprite faces right. This means that we need to flip it in order to make it face left.

Let’s do the following:

  1. Open obj_bat and add the End Step event.

    undefined

  2. This event runs after the Step event (which is when the instance moves). We’re using End Step to run actions after the instance has moved.

  3. Add the “Declare Temp” action to the event. This is used to declare (create) a temporary variable, which is only usable within the same event.

  4. Then add the following actions:

    1. "If Variable"

      1. (Attach) "Set Instance Scale"

    2. "Else"

      1. (Attach) "Set Instance Scale"

Use the following settings for the added actions:

undefined

Moving to the left requires a negative X speed, which is how we know that the bat is moving left when its X speed (that we calculated) is less than 0.

Run the game now, and you will see that the bat now correctly faces left or right depending on where it’s going!

undefined

Defeating the Enemy

Currently you can defeat the bat, however it uses the generic dust effect that was implemented in the enemy parent. We’re going to change it up for this bat specifically.

Let’s do the following:

  1. Go to obj_bat. This will have a undefined collision event with obj_sword_attack, but it’ll be greyed out because it’s inherited from the parent object.

  2. We want to redefine this for the bat specifically, so right-click on it and select “Override Event”. (This is the same thing with did in obj_baddie earlier!)

    undefined

  3. In the event, destroy the instance, and then create a “Smoke Up” particle effect as shown in the image below -- you can set its colour to match the look of the bat itself:

undefined

This is the same thing the parent’s event did; we’ve only changed up what particle is shown when the bat is destroyed. Looks much nicer than before!

undefined

Tip: You don't have to stick to just one particle effect! You can place more than one "Do Effect" actions in the event and set up a combination of different particle effects!

You can also follow the same steps as the baddie and create a Sequence animation for the bat being defeated.

Shooting Projectiles

We’re going to give extra powers to the bat: it’ll shoot projectiles at the player. More difficulty, yay!

undefined

Let’s start by adding an object for the projectile:undefined

  1. In the Asset Browser, open "Objects" -> “Game” and select the “Enemies” group.


  2. Here, create a new object and name this obj_bat_projectile.


  3. Assign the spr_bat_projectile sprite to it, which can be found in the “Bat” group under “Sprites”.


  4. Open its Parent menu and assign obj_enemy_parent as its parent.



Since this projectile is also a child of the enemy parent, it means that it can hurt the player and it can be defeated with a sword too.

See how easy it is to create new objects that reuse the same features? That’s the power of object parenting!

Projectile Behaviour

We want this projectile to move towards the player. To implement that, let’s do the following:

  1. Add the Create event to this object.

  2. Add the “Set Speed” action to this event. Set the speed to 4 (or any other speed you prefer). This is the speed the projectile moves at.

  3. Add the “If Instance Exists” action to make sure that an obj_player instance exists in the room.

    1. To this condition, attach the “Set Point Direction” action and make it point towards obj_player.x and obj_player.y.

    2. Then attach a “Set Instance Rotation” action, which is used to rotate the instance’s sprite (as opposed to its movement direction). Set this to direction so it faces in the same direction it’s moving in.

      1. direction is a variable that stores the angle of the direction that the instance is moving in.

undefined

Whenever the projectile instance is created, it will automatically move and face towards the player.

Particle Trail

The projectile should leave a trail of particles behind it as it shoots towards the player. We can do the following to implement that:

  1. Add the Step event to this object (obj_bat_projectile).

  2. Each frame we’ll create a new particle, but it should be placed randomly around the projectile; so we need to calculate random values for the particle's X and Y.

  3. Add the “Get Random Number” action to calculate a random value between -5 and 5 (you can change this if you want the particle to move more or less); store the result in a temporary variable called relative_x.

  4. Add the same action again (you can also copy/paste it); this time, store the result in a temporary variable called relative_y. Now we have separate random values for X and for Y!

  5. Add the “Do Effect” action to this event and create a small-sized “Spark” particle with a green colour.

    Set “X” and “Y” to the previously calculated relative_x and relative_y variables, and make sure to enable the “Relative” checkbox for both of them.

undefined

Since this particle is being created every step (or, every frame) it will appear like a trail within the game; it will also be somewhat random and not completely straight, thanks to our “Get Random” actions:

undefined

Wall Collision

The projectile is a child of the enemy parent, and so it will have inherited the obj_collision_parent Collision event. This will stop the projectile from moving through a wall, however we simply want it to be destroyed as soon as it comes in contact with a wall.

Let’s do the following:

  1. Go to the obj_bat_projectile object and open its Events window.

  2. Right-click on the undefined obj_collision_parent event and select “Override Event”.

    undefined

  3. Add the "Destroy Instance" action in this event.

undefined

Sword Collision

Now we need to set up the effect that is shown when the player’s sword hits the projectile:

  1. In the same object, override the Collision event with obj_sword_attack.

    undefined

  2. In this event, destroy the instance and create a small “ring” effect.

undefined

Spawning the Projectile

The bat should shoot a projectile every 1.5 seconds while it’s near the player. This will be achieved using an Alarm event, so let’s create it:

  1. Go to obj_bat and add the Alarm 0 event.

    undefined

  2. Add the “Create Instance” action to create an instance of the bat projectile.

  3. Add the “Set Alarm Countdown” action to run this alarm again after 1.5 seconds. Since 1 second has 60 frames, we’ll pass in the value 90 for 1.5 seconds.

    1. Feel free to change this value if you want the bat to shoot more or less frequently!

undefined

Managing the Alarm

Our Alarm 0 event creates a projectile, so let’s make sure that the event runs when the player is in the bat’s range.

  1. Open obj_bat’s Step event.

  2. The actions that we’re going to add below should be attached to the “If Collision Shape” action.

  3. Add the “If Variable” action to check if Alarm 0’s value (alarm[0]) is less than 0, meaning it’s inactive.

  4. Add the “Set Alarm Countdown” action and attach it to the new “If Variable” action. Set Alarm 0 to 1.

undefined

This will tell Alarm 0 to run after 1 frame, as long as it is inactive. We’re checking that using a condition to make sure that the alarm is not constantly being called even while it’s active, as that would cause it to never run.

Stopping the Alarm

The actions we’ve added above would tell Alarm 0 to run when the bat has found the player, however after that it would never stop running. We have to make sure that it stops shooting when the player goes out of the bat’s range, so we’ll do the following:

  1. Search for the “Set Alarm Countdown” action and attach it to the Else action.

  2. Set Alarm 0 to -1 to make it inactive. This makes sure that it stops running.

undefined

Run the game now, and you will see that bats start shooting projectiles as soon as you go near it!

undefined

You are also able to defeat the projectile itself using your sword. Should make for some interesting gameplay!

You can change the interval between each shot by going into Alarm 0 and modifying the value in the “Set Alarm Countdown” action (which is currently set to 90 frames, or 1.5 seconds). If you think the projectile is too fast or too slow, you can modify its speed in its Create event.

Projectile Destroy Animation

Whenever the bat’s projectile is destroyed, whether it’s from touching a wall or being hit by the player, it should play the spr_bat_projectile_splatter animation:

undefined

We’ll create an object for this that only plays once and then destroys itself:

  1. Create an object called obj_bat_projectile_splatter, then assign the spr_bat_projectile_splatter sprite to it.

    undefined

  2. Add the Animation End event to it.

    undefined

  3. In this event, add the “Destroy Instance” action. This will make sure that the instance is destroyed once its animation ends playing.

undefined

We’re now going to create this splatter instance when the projectile itself is destroyed, which should be straightforward to implement:

  1. Go to obj_bat_projectile.

  2. Add the Destroy event -- this runs when the instance is destroyed using the “Destroy Instance” action.

    undefined

  3. Here, add a “Create Instance” action to create an instance of obj_bat_projectile_splatter. Create it at the current instance’s relative position, inside the “Instances” layer.

undefined

The splatter animation will now play whenever a projectile is destroyed!

undefined

Player Hearts

With all these powerful enemies, it’s unfair that the player goes down with just one hit. We should allow the player to have multiple hearts so they can strike back!

undefined

Adding the Variables

The player needs a variable to store the number of maximum hearts that it can have, and the number of hearts it currently has. Let’s do the following:

  1. Go to obj_player and open its Create event.

  2. Add the “Assign Variable” action, which we’ll use to create new variables.

  3. Create a variable with the name max_hearts and a value of 4.

  4. Then click on the undefined plus sign to add a new variable. Name this hearts and set its value to max_hearts.

undefined

We’re setting hearts to max_hearts, which means that the hearts variable will start with the same value that was assigned to max_hearts (which is 4). As a result the player will start with 4 hearts, but you can change that value to make the game easier or harder.

Drawing Hearts on HUD

We’ll now draw the hearts on the HUD. This is done using the Draw GUI event, which is used to draw sprites/text on the “GUI Layer”. This layer is automatically drawn above the game and stays on-screen, no matter where the in-game camera moves.

Existing Draw GUI

  1. In the Asset Browser, open “Objects” and expand the “Game” group.

  2. Open the obj_manager object. This object “manages” our game in the background, and it also draws the HUD.

    undefined

  3. Open the Draw GUI event.

    undefined

  4. This event will already have some actions, which handle drawing the player’s coins. The existing actions do the following in the given order:

    • If the player exists in the room,

      1. Draws the coin HUD sprite

      2. Sets the font

      3. Draws the player’s coins value (by writing obj_player.coins)

We’ll now add another action to this event so it draws the player’s hearts.

Drawing Hearts

Let’s do the following to draw the player’s hearts:

  1. Add the “Draw Stacked Sprites” action to the Draw GUI event. Make sure it is attached to the existing "If Instance Exists" condition.

  2. This action draws the same sprite a specified number of times, either horizontally or vertically (as specified in the options).

  3. Set the “Sprite” to spr_hud_heart. You will find this sprite in the “HUD” group under “Sprites”.

Use the following settings for the rest of the arguments:

undefined

You can change the X and Y values if you want to position the hearts differently.

If you run the game, you will see the number of hearts on-screen that you specified in the max_hearts variable:

undefined

The player still goes down with one hit, so let’s make it so the hearts go down on each hit.

Hurt Knockback

When the player touches an enemy, it should go into a “hurt” state and get knocked back. It should also lose a heart.

Let’s do the following:

  1. Go to obj_player and open its collision event with obj_enemy_parent.

  2. This event runs when the player touches an enemy. Currently it stops the player, changes its color to red and sets Alarm 0, which restarts the room after some frames.

  3. You will find an “Assign Variable” action here which sets the player’s move_speed to 0, so that it can’t move.

  4. We don’t need this action anymore, so click on the X button to delete it.

undefined

Let’s now apply knockback movement to the player and reduce the hearts value.

  1. Make sure that all the new actions are attached to the “If Variable” action, just like the existing actions in this event.

  2. We’ll add the following actions to create a knockback effect:

    1. Set Point Direction”: This will be used to set the player’s direction towards the colliding enemy.

    2. Set Direction Variable”: This will be used to reverse the direction so the player ends up facing away from the enemy.

    3. Set Speed”: This will set the speed of the player so it moves in the given direction.

  3. Finally, we’ll add the “Assign Variable” action to reduce the hearts value by adding -1 to it.

undefined

Now whenever the player comes in contact with an enemy, it will receive a knockback and its hearts value will go down. However, this event calls Alarm 0 which currently restarts the game, so we need to make sure that that only happens when the player is fully defeated.

Defeating the Player

Let's now take care of what happens when the player runs out of all hearts.

Defeated Object

We’ll create a new object for the player’s “defeated” state. Let’s do the following:undefined

  1. Under "Objects" -> "Game", create a new object called obj_player_defeated.


  2. Assign the spr_player_defeated sprite to it, which can be found in the “Player” group under “Sprites”.


  3. Add the “Animation End” event to this object (this event can be found under “Other”).


  4. In the event, add the “Restart Room” action to restart the room when the animation ends.


undefined

We now need to create an instance of this object, which will play the defeat animation and then automatically restart the room.

Transition to Defeat

We’ll now handle stopping the player after its knockback, and restarting the game if the hearts are all gone.

  1. Open the player’s Alarm 0 event and remove any existing actions so that it’s empty.

    1. It used to restart the room after being hit but now it should only do that if the player has run out of hearts.

  2. Add the “Set Speed” action to reset the speed back to 0. This will stop the player’s knockback.

  3. Add an “If Variable” condition to check if the hearts variable is greater than 0, meaning that the player still has some hearts remaining.

  4. If that is true, set the instance’s colour back to white by attaching the “Set Instance Colour” action to it.

  5. Add an “Else” action which will run when the player has run out of hearts.

  6. Attach “Create Instance” to “Else” to create an instance of obj_player_defeated, and then destroy the player instance itself.

Here is what your event should look like now:

undefined

This will effectively “transition” the player into its defeated state once the hearts value is at 0.

If you run the game now and run into an enemy, you will see the player getting knocked back and losing a heart. Once all hearts are gone, the defeat animation plays and the level restarts.

undefined

Summary

Our baddie now has a great defeat animation thanks to Sequences, we have a brand new enemy that flies in circles and spits acid at the player and a solid health and knockback system for the player. Our game’s on the next level already!

Here are some of the things we learned in this part:

  • You can assign path assets to Path Layers in a room, and assign the path asset to an instance for use in its events

  • You can create your own animations in Sequences, and play them using the "Create Sequence" action

  • You can use Alarms to perform actions at a later time, and to run repeated actions (like shooting projectiles)

  • You can use "If Collision Shape" to check if an object is intersecting with a shape

  • You can create particle trails in the Step event

Enemies have gotten too much attention though! In the next part, we’ll work on player upgrades and power ups, and also give it a rechargeable energy shield!

undefined

-> Continue to Part 5

-> Go back to index page