2016-10-14

In the last article, we started setting up combat, gave our Player a health bar, and gave our enemies the ability to attack the Player. This was a good start, but our enemies still can't move towards the Player to attack, or walk around the Battlefield. Obviously, this isn't going to cut it, so let's take some time to build up our AI a bit more.

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

Movement

The first thing we'll do is give our Enemy the ability to move towards a target destination. To set this up, we'll need a FindTarget event, which determines where the Enemy needs to go, and a Move event which tells the Enemy to start moving there.

This will be similar to how we move our camera since we will use TargetX and TargetY variables as we did there. You can see where these events will be placed in the Step event if you look at the PositionFront and PositionBehind cases.

First, we'll add our target variables.

In the OBJ_Enemy object, open the code for the Create Event.

Add the following code to the end of the event:

Next, we will add the FindTarget event. For now, this event will find a random position near the Player, and set the Enemy's target to that position. Once we know the Move event works, we can implement more sophisticated targeting with a switch statement.

In the Enemy object, choose Add Event > Other > User Defined > User 4.

Add the Action Control > Code > Execute Code.

Add the following code:

As I said, this code chooses a random position near the Player, in this case within a 10 pixel radius around them, and sets the Enemy's target position to that point. It also uses an if statement to check whether the Player is still in AttackRange of the target position, or whether the distance between the TargetY and the Player's y is greater than the LayerSize. This way, if the Player hasn't moved since the last target position was found, the target position will not get changed for no reason.

Now we need to make the movement code.

In the Enemy object, choose Add Event > Other > User Defined > User 5.

Add the Action Control > Code > Execute Code.

Add the following code:

This code checks how close the Enemy is to their target position. If the distance to the target is further than their move distance, the Enemy moves towards the target position; otherwise, they stop.

Before we can test this in-game, we need to add these events to the Step event in the locations I highlighted earlier.

In the Enemy's Step event, replace the //Find Target position comments with the following code:

Then replace the //Move there comments with the following code:

Now when you go in-game, the enemies should start moving towards you the moment you enter the BattleRegion.

The movement itself seems to work well, aside from the fact that there's no animation, but you may notice something strange when you attack the Enemies. If you attack the Enemy while they are moving, they do not stop moving while they are in the Hit state. This is because the move_towards_position script changes the object's built-in speed variable (notice the lowercase s), which moves them forward at a constant rate until we tell the speed to change. So even after the Enemy is hit, their speed continues moving them, since we haven't told them to stop.

This issue is the same reason we have to manually set the speed to 0 when they reach their target. If we didn't tell them to stop, they would never stop on their own. We can fix this by modifying the Step event so that their Hit case sets their speed to 0.

In OBJ_Enemy, go to the Step Event.

In the code, find the Hit case for the switch statement, and replace it with this code:

Now when you go in-game and attack Enemies, they will not move during their Hit animation.

Adding Basic Animation

We're definitely off to a good start, but there are still some obvious issues with the movement. First and foremost, the Enemies do not animate at all—they just sort of slide around the battlefield towards the Player. This doesn't look very good, so let's go into the Animate event we made in the last tutorial to improve it.

In OBJ_Enemy, go into User Defined 3.

Replace the existing "PositionFront" and "PositionBehind" case in the switch statement with this code:

In this new code, the first thing we do is set the image_xscale based on the direction the Enemy is moving in. So if they are moving left, they will face left, and if they're moving right, they'll face right. Then, if their speed does not equal 0, it sets their animation to the walking animation, and if it does, it sets their animation to idle.

It should now look much better when you test it in-game.

PositionFront vs. PositionBehind

At this point, you may be thinking "Why do we need PositionFront and PositionBehind if they do all the same things anyway?" Well if you look at our current Enemy movement, you'll see there's a lot of room for improvement.

Having two separate states allows us to have each enemy approach from different sides, rather than having all the Enemies attack from the same location. This will make battles more challenging, and tougher to escape from.

To implement this, we need to modify the FindTarget code so that it takes the state into account when choosing the target position. Rather than adding an if statement or a switch statement, we'll do this using the SideMod variable.

SideMod is actually a variable we've had in place from the very beginning. If you look back at the Create code, and the switch statement in the Enemy's Step event, you'll see that we've already started using SideMod.

SideMod will act as a modifier that will offset the Enemy's target position, relative to the Player's position. So if SideMod is positive, the Enemy's target will be PlayerX + position range, and if it is negative the Enemy's target will be PlayerX - position range. This way, both states can use the same targeting code, but end up with different results.

To see this in action, let's edit the Find Target event.

In OBJ_Enemy, go to User Defined 4.

Replace the if statement with the following code:

This code improves on the original code in a few important ways. For the targeting itself, the targeting code now includes a 60 pixel buffer between the Player and the Enemy, to prevent the Enemy from moving to stand on top of the Player. On top of that, the targeting code now uses SideMod to determine which side of the Player the Enemy should be on. If SideMod is positive, the Enemy's position would be PlayerX + PositionRange, and if it's negative, it would be PlayerX - PositionRange.

We also made some changes to the if statement. The if statement now checks if the Enemy is too close to the Player, as well as too far, to maintain the buffer distance we added. The if statement also now checks whether the Enemy is on the wrong side of the Player in case the Player moved. So if the Enemy's State is PositionFront, and the Player moves so that the Enemy ends up behind the Player, they will find a new target position.

If you go in-game now, the Enemies should behave in a much more interesting way when they try to surround the Player.

Facing the Player

That was a pretty big improvement, but there are still some issues. One of the clearest ones is obvious if you let the Enemies surround you a few times, and it can be seen in the image above. Essentially, Enemies do not adjust the direction they're facing relative to the position of the Player. So if the Enemy is facing left, and the Player is to the right of them, any attacks they use will go in the wrong direction. This is a pretty simple issue to fix, though.

In OBJ_Enemy, go to User Defined 3.

In the code, replace the PositionFront/PositionBehind case with the following code:

With the original code, the Enemy would always face in the direction of their Target position. With the new code, the Enemy faces towards their target position when they are far away, and begins facing towards the Player once they are within a short distance of their target. This ensures that once the Enemy is in attacking range, they will start facing the Player instead of the target position itself.

You should now see a major improvement when you go in-game.

The Basic Queueing State

Things should be starting to come together, but we still have one nagging problem. Right now, our Enemies do not change their behavior based on the number of Enemies attacking, or where other Enemies are standing.

This makes it easy for Enemies to surround the Player and overwhelm them. The Player can generally manage three to four Enemies as we have now, but anything more than that will quickly get out of hand. To resolve these issues, we're going to add a new state called Queueing.

The Queueing state will act as a temporary state for the Enemy while they're waiting for an opening to attack. While the Enemy is Queueing, they will stay relatively close to the Player, but they will not get close enough to actually attack until one of the attacking enemies dies, or switches to a different state. If you take a look at the video below, you can see something like this in action:

In the video above, pay close attention to the behavior of the Enemies before they begin attacking the Player. As you can see, the Enemies do not all approach the Player until the Player has killed or knocked back the Enemy they are currently fighting. This is the behavior we want to emulate in our game.

You can see a general idea of how our new State will work in the State diagram below:

To make this system work, we need to have a queue, or list, of enemies that are currently attacking. Then, when an Enemy gets within a certain range of the Player, they can look at the list and see how many Enemies are on it. If the list has already maxed out, they will switch to the Queueing state, and wait their turn. If the list is not full, they will add themselves to the list of attackers, and continue approaching the Player.

So the first thing we'll need is the queue itself. I am going to use a ds_list and add it to the Player object.

If you’ve never used a ds_list object before, you can learn about it here. To put it simply, though, a ds_list is a more flexible version of an array that gives you more built-in functions like Shuffle, Sort, and Insert, and it can make certain operations a bit easier.

With OBJ_Player, go to the Create event.

Add the following code to the end of the create event code:

Now we need to make sure that Enemies are referencing this list when they get ready to attack. To do this, we will add an if statement at the end of the Move event that checks whether the enemy list is full when they are within range to add themselves. If it is, they will switch states, and if it's not, they will add themselves to the list.

With OBJ_Enemy, go to User Defined 5.

Add the following code to the end of the Move Event:

This if statement checks three things before adding an Enemy to the list. First, it checks whether the Enemy is less than 200 pixels away. Then it checks to make sure the list has less than two Enemies in it already. Finally, it checks to make sure that they are not already on the list. If all three of those things are true, the Enemy gets added to the list.

At this point, we also need to go into the State change event and make sure that an Enemy will switch to the Queuing State if they cannot attack.

With OBJ_Enemy, go to User Defined 0.

Add the following case to the switch statement:

This if statement checks the same things as the one we added to the previous event, except that it does check the distance, since they are already in the PositionFront/PositionBehind state. If the if statement is true, it sets the Enemy's state to "Queueing", and sets their speed to 0.

Now try going in-game, and see what happens when you get the attention of all three Enemies. It should start out the same way as before, but after a few seconds, one of the Enemies should stop approaching the Player. This is because they've switched to the Queueing state, and are no longer being told to move towards their target position.

Animating the Queuing State

This is a good start, but there are some glaring issues we should address. First of all, if you look at the Enemy that switched to the Queueing state, you'll notice that their walk animation will continue playing even after they stop moving.

This issue occurs because the Queuing state doesn't have any animation code of its own. So whatever animation it was running when the Queuing state began is the animation it will stay in until the state changes to one that does trigger the animation code.

We can fix this by adding animation code for our new state, and by adding the Queueing state to the switch statement in the Step event.

In OBJ_Enemy, go to User Defined 3.

Go to the code event, and add this case to the switch statement:

Aside from one small change, this animation code is exactly the same as the code we have for PositionFront and PositionBehind. The difference is that this code causes the Enemy to start looking at the player from a further distance than the other two states. This makes it clearer that the Enemy is focusing on the Player, and is not just moving around randomly.

Finally, we need to add a Queueing case to the switch statement in the Step event so that we can make sure the animation code gets used.

In OBJ_Enemy, go to the Step event.

Go to the code event, and add this case to the Switch statement.

All this code does is run the animate event every step.

Now if you go in-game, the Enemy should stop completely the moment that they switch to the Queueing state.

Resuming the Attack

The next issue we have will come up if you try killing either of the attacking Enemies. No matter what happens, the Queuing Enemy never switches back to PositionFront or PositionBehind states. Even if you kill both of the attacking Enemies, and begin attacking the remaining Queuing Enemy, they will continue to do nothing.

This problem is caused by several factors. First, Enemies never get removed from the list of attackers, even when they die. So even after all the Enemies are killed, the Queuing Enemy will never see an opening to start attacking. On top of that, even if the Enemies did get removed from the list, we don't have any code in the change state event to deal with the Queuing state. Let's tackle these issues one at a time.

First let's start removing Enemies from the attacker list after they die. We can do this with a Destroy event that runs the removal code when the Enemy is killed.

Go to the OBJ_Enemy object.

Choose Add Event > Destroy.

Choose Control > Execute Code.

Add the following code to the code event:

This code checks to see if the Enemy is in the list of attackers. If they are, it removes them from the list.

The second thing we have to do is get the Enemy looking for openings. We can do this by adding code for the Queueing state into the Change State event. The code we're going to add will look at the list of attackers, and determine how many Enemies are on it. If the list is not full, the Enemy will add themselves to the list, and switch to either "PositionFront" or "PositionBehind", depending on where they are relative to the Player.

In OBJ_Enemy, go to User Defined 0.

Go to the code event, and add this case to the switch statement:

This code does exactly what I said above. If the Enemy list is not full, the Enemy will add themselves, and choose the best state based on their position.

Now if you go in-game you should see the Queueing enemy behaving correctly.

Separating the Enemies

While our Queuing State is just about finished for now, there's one last thing we need to do. If you play the game for a little while, you may notice something like this happen when multiple Enemies are trying to attack you:

The problem here is that both enemies randomly chose the PositionBehind state, and ended up with very similar Target positions, so they're now basically on top of each other. This makes it hard for the Player to see how many enemies they are fighting, and it makes it easier for the Player to try and escape or just kill both Enemies simultaneously. Ideally, one Enemy would approach from the left, and one would approach from the right.

To solve this problem, we are going to add one more piece of code to our Change State event.

With OBJ_Enemy, go to User Defined 0.

Add the following code to the beginning of the PostionFront, PositionBehind case.

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

This code does two things. First, it checks whether there are any other Enemies that are located at/near the target destination. If there are, it switches from PositionFront to PositionBehind, or vice versa.

The important thing to notice about this check is that the if statement also verifies that the instance it is finding is not itself. This is because the script we're using, instance_position(), does not have the ability to exclude itself from the results.

Make sure that this code comes before the code that checks whether the Enemy should be Queueing.

Now if you go in-game to test it, you should no longer see multiple Enemies attacking from the same side.

While our Queueing state is still a little incomplete, and our Enemy AI needs a few more adjustments, I think we should switch gears for the rest of this article, and focus on something a bit more exciting.

Combo Attacks

Combo attacks are a staple of every classic action game. Stringing together multiple attacks, and combining attacks in unique ways, keeps combat interesting through many levels. So, before we start adjusting the AI anymore, let’s build a combo system.

Before we can implement any combos, there are two primary combo types you should understand: AB combos and A+B combos.

AB combos are combos which occur when the player uses two or more attacks in a specific order, within a limited amount of time. A light punch, then a heavy punch in quick succession would be an AB combo.

A+B combos are combos where the player uses two different attacks simultaneously. A light punch and a heavy punch at the same time would be an A+B combo.

These can also be combined into a third combo type, AA+B, where an A+B combo is used as one of the steps in an AB combo. A light punch, then a simultaneous light punch and heavy punch would be an example of this third type.

We’re going start by making an A+B combo.

Making the New Attack

To get our combo working, we’ll need a third attack for the player. This attack will be the Uppercut, and it will need two new sprites, the attack sprite, and the hitbox sprite. Add two new sprites to the game, as laid out below:

Sprite Name

Images

Origin

SPR_PlayerUppercut

PlayerUppercut1.png, PlayerUppercut2.png, PlayerUppercut3.png, PlayerUppercut4.png, PlayerUppercut5.png

X = 45, Y = 128

SPR_PlayerUppercut_Hitbox

PlayerUppercutHitBox1.png, PlayerUppercutHitBox2.png, PlayerUppercutHitBox3.png, PlayerUppercutHitBox4.png, PlayerUppercutHitBox5.png

X = 45, Y = 128

Let's also import a new sound for use with the Uppercut attack.

Right-click the Sounds folder, and choose Create Sound.

Set the Name to SND_Uppercut.

Import the sound file HeavyPunch2.wav from the project’s assets.

Set the Sample Rate to 48000.

Set the Bit Rate to 320.

Press Ok to save your sound.

Next, we need to make the new Attack object. Follow these steps to make the Uppercut Attack Object:

Right-click on the Objects folder and choose Create Object.

Name the object ATK_Uppercut.

Set the object’s sprite to SPR_PlayerUppercut_Hitbox.

Set the parent for the object to OBJ_ATK.

Uncheck the Visible checkbox.

Click Add Event > Create.

Add the Action Control > Code > Execute Code.

Add the following code:

Right now, the only difference between this attack and the strong attack is that the uppercut stuns the enemy for slightly longer. Eventually, though, this attack will be made more unique with knockback effects.

You can close this object.

Modifying How We Attack

Now that we have an attack for our combo to trigger, we need to start detecting when the combo is used. To do this, we’re going to modify the press <any key > event we made in the first tutorial.

In case you don’t remember, the current code tests to see if you’re pressing any of the basic attack buttons, or any of the strong attack buttons, and then sets your AttackType accordingly. Then, if you’re on the ground, it calls User Event 2 to execute the actual attack.

Right now we’re only detecting one button press at a time, but we need to detect multiple buttons to handle A+B combos. We could do this by making another if that says “if you press one of the strong attack buttons, AND one of the basic attack buttons, use uppercut”, but that would quickly get out of hand. As we added more attacks, these checks would become more complex, harder to read at a glance, and we would have a number of repeat checks for buttons used in multiple combos.

So, instead of checking for the button combinations themselves, every button that’s pressed will be added to a string of text. Then we’ll have an if statement at the bottom which says, “If the string says 'Up + Basic Attack', use option A, and if it says 'Up + Strong Attack', use option B, etc." This way, we can check each button once, and the code will be much easier to read.

To do this, we’ll need to make a couple of changes. First, we need to add a string which will store our list of buttons. We’re going to call this variable ButtonCombo.

Go to OBJ_Player and open the press <any key> event.

Add this line of code to the very beginning of the event:

Next, in the first if, change the code to this:

And change the code within the second if to this:

Finally, add this code before the if statement which checks whether the player is on the ground:

Your code should now look like this:

Overall this code is basically the same as what we had before. The difference is, instead of having the if statements set the AttackType directly, all they do is append a unique string for each button that's pressed to the end of the ButtonCombo string. Then, the AttackType is set based on the final ButtonCombo string. By doing it this way, the final ButtonCombo string that gets evaluated is a combination of all the buttons that are being pressed, and the attack is based on that, rather than it being just a static attack type based on which individual button is pressed.

We also have a string_delete statement before the if statement that sets the AttackType. This statement deletes the + sign from the beginning of the ButtonCombo string to improve the readability.

Making the A+B Combo

With this new system, if you press multiple buttons that the event checks for, all of them will show up in the string. So all you have to do to add an A+B combo is add a new else/if block which checks for the new attack to the end of the if statement that determines the AttackType. For example, add the following code to the end of if/else that sets the AttackType to implement an A+B combo for the Uppercut:

Your code should now look like this:

Before we can actually execute the Uppercut, though, we also need to go into User Defined 2 and add this block of code to the end of the if statement that checks what the Player's AttackType is set to.

Your code should now look like this:

If you did everything right, you can go into the game, and use the Uppercut by hitting the Basic Punch and Strong Punch buttons at the same time.

Making an AB Combo

Unlike A+B combos, AB combos require us to know the history of attacks the Player used, rather than just the most recent one.

To keep track of which moves have already been used, we will create what I call the Command List. The Command List will be a ds_list object that keeps track of the last X ButtonCombo strings. Every time we use a move, all we have to do is look at the most recent commands in the list, and check if they match any of our combos. This is similar to what we do now, only we’ll be looking at the past couple ButtonCombo strings, instead of just the most recent.

So before we can set up our new Combo, we need to create a new Attack object. Import the following assets for the new attack:

Sprite Name

Images

Origin

SPR_PlayerTriplePunch

PlayerTriplePunch1.png, PlayerTriplePunch2.png, PlayerTriplePunch3.png, PlayerTriplePunch4.png, PlayerTriplePunch5.png

X = 46, Y = 129

SPR_PlayerTriplePunch_HitBox

PlayerTriplePunchHitBox1.png, PlayerTriplePunchHitBox2.png, PlayerTriplePunchHitBox3.png, PlayerTriplePunchHitBox4.png, PlayerTriplePunchHitBox5.png

X = 46, Y = 129

Let's also import a new sound for use with the new combo.

Right-click the Sounds folder, and choose Create Sound.

Set the Name to SND_TriplePunch.

Import the sound file LightPunch2.wav from the project’s assets.

Set the Sample Rate to 48000.

Set the Bit Rate to 320.

Press Ok to save your sound.

Finally, we need to make the new Attack object. This new attack will be called the triple Punch:

Right-click on the Objects folder and choose Create Object.

Name the object ATK_TriplePunch.

Set the object’s sprite to SPR_PlayerTriplePunch_Hitbox.

Set the parent for the object to OBJ_ATK.

Uncheck the Visible checkbox.

Click Add Event > Create.

Add the Action Control > Code > Execute Code.

Add the following code:

So this attack will be slightly stronger than a standard light attack, and stun for a longer period of time.

Now that we have our new attack, we can start modifying the attack code to implement the Command List. To start, we need to add the CommandList object.

With OBJ_Player, go into the Create Event.

Add the following code to the end of the event:

That’s all we need to do to create our list.

Next we need to integrate the list into our combat system.

With OBJ_Player, go to the press <any key> event.

Replace the string_delete line, and the if/else if statement which sets the AttackType, with the following code:

This code is very similar to what we had before. First, it adds the ButtonCombo string to the CommandList. Then, since I’m limiting my max combo length to seven commands, it checks to make sure that the command list is less than or equal to seven items long, and deletes any extra items on the list. Finally, the if statement looks at the most recent item in the list the same way it looked at ButtonCombo, and determines the AttackType.

If you test your game now, it should work exactly the way it did before we added the CommandList.

Keep in mind that you can use any number you want for the max combo length. I chose seven because it seemed like a good choice to me, but if you want your game to have 10, 20, or even 100 action combos, you can do that, and the code will work fine. Personally, I suggest you increase it as needed when you make new combos, rather than trying to anticipate what you’ll need in advance.

Now all we have to do to make an AB combo is create a check that looks at multiple items in the list, instead of just one. So let’s say I want to add my triple punch and make it so that you need to do three LightPunches in a row to execute it. To make that work, you would need to append this code block to the beginning of the if/else if statement that sets the AttackType:

When adding this check to your game, make sure you put it at the beginning of the if/else if statement. If you don't, and you leave it at the end, one or both of the individual button checks will evaluate to true before it gets to the AB combo check, and the combo will never succeed. So your modified if/else if should look like this:

So remember, when you’re making new AB combos, the more buttons that are needed to execute the combo, the earlier it should be in the if/else if statement. If you put an AB combo at the bottom, it will almost never get executed.

Also remember that ButtonCombo strings are added to the list at the end. This means that the most recent command to be used is the last item in the list, not the first. So if the combo was LAtk, SAtk, LAtk would be in position Size-2, and SAtk would be in position Size-1.

Finally, we need to go back into User Defined 2 and add the statement to create the Triple Punch. Go into User Defined 2, and add this block of code to the end of the if statement that checks what the Player's AttackType is set to.

Now if you go in-game and use three Light Punches in a row, you should successfully execute a Triple Punch.

Clearing the Command List

Finally, we need to clear our Command List if the player takes too long to continue the combo. Right now, as long as you use three basic punches in a row, you will execute a triple punch. Even if there is a multi-second pause between each punch, it won't make any difference. This greatly reduces the skill required to execute powerful combos, so we need to add a system which resets the CommandList after a certain period. We’ll use an Alarm to accomplish this.

To create the Alarm, follow these steps:

In OBJ_Player, go to Add Event > Alarm > Alarm 0.

Add the Action Control > Code > Execute Code.

Add the following code:

All this code does is clear the CommandList completely when the Alarm goes off.

Next, we need to go to the press <any key> event, and add code to trigger this alarm after the code which adds the ButtonCombo string to the CommandList, but before the While statement. You can see exactly where it should go in the picture below:

Add the following code to that location in the press <any key> event:

This code starts the alarm and sets it to 10 steps, or game cycles. If 10 steps pass before the player presses a new button, the CommandList clears; otherwise, the combo can continue.

If you go in-game and test the combo now, you’ll see that it only works if you press the buttons in a relatively quick succession.

Increasing the number of steps will make comboing easier, and decreasing it will make comboing harder. I think 10 is a good choice, but you need to find the right level of challenge for your game. Your combos may be very complex, so you may decide you want to give the player more time to finish them. If you really want to decrease the time, you can do that as well, but your game will become substantially more challenging for the average player with each step you subtract.

Creating Pickups

The final thing we'll look at in this article is Pickups. No brawler or platformer is complete without power-ups and health pickups. When the player is in a dire situation there is nothing they love more than to get that random food pickup, or to stumble upon a secret power-up. Creating a basic pickup is actually very easy, but before we can do that, we'll need to import a sprite to use for our pickup.

This cheery sprite will be used for the parent pickup object and the food pickup we'll create later on.

Sprite Name

Images

Origin Position

SPR_Food_Cherry

FoodCherry.png

X = 13, Y = 47

Let's also import a new sound for when the Player gets the food pickup.

Right-click the Sounds folder, and choose Create Sound.

Set the Name to SND_FoodPickup.

Import the sound file FoodPickup.wav from the project’s assets.

Set the Sample Rate to 48000.

Set the Bit Rate to 320.

Press Ok to save your sound.

Next we will make our Pickup parent object.

Right-click the Objects folder and choose Insert Object.

Name the new object OBJ_Pickup.

Set the object’s sprite to SPR_Food_Cherry.

Use Add Event > Create.

Under the Control tab, add an Execute Code action.

Add the following code:

This code sets the pickup’s depth in the game world the same way that we set the Player and Enemy depth previously. It also adds a variable to store the sound that will be made when we collect food, the same way we have a variable to store the hit and miss sounds of attacks.

Next, we’ll add a collision event so the Player can actually get the pickup in-game.

With OBJ_Pickup, use Add Event > Collision > OBJ_Player.

Under the Control tab, add an Execute Code action.

Add the following code:

This if checks whether the pickup and the player are close enough to interact in almost the same way that we check for collisions with attack objects. If they are, the pickup runs User Event 0 and destroys itself. User Event 0 is where we’ll put the code that applies whatever effect the pickup has. By putting the code for the pickup's effect into a separate event, we can easily modify the effect of any new pickups we add later, without having to modify the collision code.

Now we just need to add some placeholder code for User Event 0, and the pickup should work perfectly.

Use Add Event > Other > User Event 0.

Under the Control tab, add an Execute Code action.

Add the following code:

This code is purely a placeholder since the base pickup doesn’t actually do anything.

If you place the pickup in your level and go in-game, you should see it disappear, and hear the pickup audio, when you collide with it.

While we could move on to the first actual pickup right away, let’s give the base pickup a shadow just like we did for the Player and Enemy. This way it fits well into the existing visual style of the game.

Use Add Event > Draw.

Under the Control tab, add an Execute Code action.

Add the following code:

This code does the same thing as our shadow code for other objects. First, it sets the alpha to .6 and sets the color to dark gray, then it draws an ellipse for the shadow. After that, it draws the pickup’s actual sprite.

Making the Food Pickup

Now that our basic pick-up works, let’s make the food version.

Right-click the Objects folder and choose Insert Object.

Name the new object PKP_Food.

Set the object’s sprite to SPR_Food_Cherry.

Set the Parent to OBJ_Pickup.

Use Add Event > Create.

Under the Control tab, add an Execute Code action.

Add the following code:

This code inherits the properties of the base class to set its depth, and it creates a HealAmount variable which determines the strength of the pickup’s healing ability.

Now we’ll go into User Event 0, and add the code that actually heals the Player when they get the food.

Use Add Event > Other > User Event 0.

Under the Control tab, add an Execute Code action.

Add the following code:

All this does is add the HealAmount to the Player’s HP.

Before you test this out, make sure you delete the original pickup in the level and replace it with the food object.

Now go in-game to test the Food Pickup, and you should see something interesting. While the pickup does heal the player, it actually pushes his health past the edge of the health bar. This happens because we didn’t do anything to prevent the Player’s CurrentHP from exceeding their MaxHP.

Go back to User Event 0 in the food pickup, and replace the existing line of code with the following code:

The difference between this version and the original version of the code is that this one uses a min statement to set the Player's health to the lower of the two values. So if CurrentHp+HealAmount is less than their MaxHP, it will set their CurrentHP to that; otherwise, it will set it to their MaxHP, and prevent it from going over that value.

To test this new version out, you should wait until the Player has taken damage to use the pickup. Try letting yourself get hurt by the enemies before getting the food, and seeing what happens. You should see that no matter how little damage you've taken, or what you set the HealAmount to, your HP never goes beyond the Maximum.

Making the Cherry Pickup

Finally, let’s turn the PKP_Food object into a parent class as well, by making a Cherry pickup. This way, you can have as many different food pickups as you want, by continuing to extend the base class with new objects.

Right-click the Objects folder and choose Insert Object.

Name the new object PKP_FOOD_Cherry.

Set the object’s sprite to SPR_Food_Cherry.

Set the Parent to PKP_Food.

Use Add Event > Create.

Under the Control tab, add an Execute Code action.

Add the following code:

Now you can replace the generic food pickup with the cherry we just created, and it should work perfectly in-game.

Conclusion

With that complete, we are going to stop for the day. Make sure you return for the next article, where we will finalize the Enemy and Player movement, and implement a few new features.

In the meantime, don't hesitate to ask me any questions or leave any comments in the feed below.

Show more