ADDING AN ENEMY
Hopefully you're familiar with the process of adding sprites and animations now. So for conciseness the steps in full won't be repeated. Let's add a new sprite for an enemy.
- Add a new Sprite object in the layout.
- Add frames from the files enemyWalking_1.png and enemyWalking_2.png from PNG\Enemies file from the Abstract Platformer pack.
- Set the animation to looping.
- Set the origin to the base of the enemy image, and apply it to the whole animation.
- Close the animation editor and rename the object to 'Enemy'.
- Place it on a ground tile as shown.
Now we want to implement the following logic:
- If the player runs in to the enemy from the side, they flash and get hurt.
- If the player jumps on top of the enemy, the enemy is killed.
To make the player flash, select the PlayerAnim object and add the Flash behavior. Remember to select the actual PlayerAnim, not the PlayerBox object (since it is the visible player we want to flash). We'll use the Flash action from this behavior in a moment.
Switch to the event sheet view, and add a new event:
PlayerBox -> On collision with another object -> Enemy
This event runs when we collide with the Enemy from any angle. We can then use sub-events to test whether the player is jumping on top or running in from the side. Let's first test if the player is above.
Add a sub-event to the collision event:
PlayerBox -> Is falling
We should also test the player is actually above the enemy. This can prevent the Enemy accidentally being killed if we fall past it off a ledge, say. Right-click the 'Is falling' condition and select . Remember, all conditions must be met for the event to run. Add the condition:
PlayerBox -> Compare Y -> Less than, Enemy.Y
The Y axis increases downwards, so if the player's Y co-ordinate is lower than the enemy's, they are above it.
In this event, add the action:
Enemy -> Destroy
We can also make the player bounce off it by adding another action:
PlayerBox -> Set vector Y -> -550
'Set vector Y' basically just sets the vertical speed of the platform movement; setting it to a negative value sets it upwards (again, the Y axis increases downwards), and 550 is a little less than the default jump strength of 650. So this will make the player bounce off as if they did a weak jump.
Once done, the event should look something like this :
We're not quite done: right-click the margin of the 'Is falling' event (the space just to the left of the PlayerBox icon) and select . 'Else' is a special condition that runs if the previous event did not run.
So this event will run if we collided with the enemy but we weren't jumping on top of it - we ran in to it from the side, say. In this event we want the player to be hurt. Add the action
PlayerAnim -> Flash -> (leave default values and click Done)
Remember the Flash behavior is in the PlayerAnim object, not PlayerBox.
OK, so the player will never die, they'll just flash. But we've got the detection set up of whether they jumped on top or ran in to the side. This tutorial won't cover all the bells and whistles, but hopefully you can see where to create kill effects (try creating a place holder sprite on the Enemy when it is destroyed, using the Enemy's 'Spawn object' action) and where to take off health (in the event that makes the player flash - you can learn about health using instance variables in the top-down shooter tutorial which you might want to look at afterwards).
Let's make the enemy move back and forth across the platform.
Moving the enemy
The main problem with moving the Enemy is how to detect when it has reached the edge of a platform. The easiest way to do this is with invisible 'edge' markers. These are just invisible sprites that flip the direction of the enemy when it touches them.
We can also use the Platform behavior again for the Enemy. This is convenient because:
- it can get it moving left and right
- it will go up and down slopes just like the player can
- it will fall off ledges if you want it to
- if you want to make a jumping enemy, you can make the enemy automatically jump too using the 'simulate control' action.
Add the Platform behavior to the Enemy sprite. Since we are not using complicated animations on this object, we can get away with using the platform behavior directly on the object without an invisible 'box' object. Note if you make a different platform game with enemies with complicated animations, you should use the same box technique we used on the player.
We don't want the player to control the Enemy - we want to automatically control it. We can do this by unchecking the checkbox for Default controls, then using the Simulate control action. Let's have a pretty slow enemy, set the Max speed to 50 and its Acceleration and Deceleration to 100!
We'll also need our 'Edge' markers. Add a new Sprite object and just fill it with a solid color in the Animations editor. Call it EdgeMarker. Size the object to about 40 x 40 and uncheck its Initial visibility so we don't see it. Place one at each end of the enemy's platform like so: (don't forget you can create a new instance by control+dragging it)
The enemy also needs to know which way it is currently moving - either left or right. We can do this with instance variables. These are simply numbers or text stored in each instance of the object. If we have multiple enemies, they each store their instance variables separately. This allows them to have unique values for their health, current direction, and so on. A simple technique to control enemies automatically is to create an instance variable named state which holds its current state. For example, it could be "run away", "chase player" or "idle". In this case we only need "left" and "right", but it's useful to set it up the same way.
Select the Enemy object. In the properties bar, click Instance variables on the line Add / Edit under 'Instance variables'.
A dialog listing all instance variables for the object appears. Click Add new instance variable to add a new one. Set the name to state, the type to String, and the initial value to right (for moving right).
Click OK and close the instance variables dialog. Switch to the event sheet.
We want to implement the following logic:
- if state is "right", simulate the platform movement holding the 'right' key to move the enemy right.
- if state is "left", simulate the platform movement holding the 'left' arrow key to move the enmy left.
- if the enemy hits the EdgeMarker, flip its state (if "left" set it to "right"; if "right" set it to "left").
We can set up the movement with the following two events:
Event: Enemy -> Compare instance variable -> state equal to "right" (use double quotes here to indicate text)
Action: Enemy -> Simulate control -> Right
It should be straightforward to do the same for left. We also want the image to mirror left and right like we did for the player. So add Enemy -> Set mirrored in the "left" event, and Enemy -> Set not mirrored in the "right" event. You should finish with this:
Now to flip the enemy's direction on the edges:
Event: Enemy -> On collision with another object -> EdgeMarker
Subevent: Enemy -> Compare instance variable -> action equal to "right"
Action: Enemy -> Set value -> state to "left"
Action: Enemy -> Set value -> state to "right"
It's important to use Else here, because events are run from top-to-bottom. If instead of 'else' we said 'action equal to "left"', notice the previous event would have just set it to that. So it'd just set it right back again, having no overall effect. By using 'else', we prevent the second event running if the first was true.
Run the project. Notice the enemy is moving back and forth on its platform. This makes it a bit harder to jump on! This is a very rudimentary "AI" system, but hopefully you can imagine that you could create more intelligent enemies by controlling the movement with more events, possibly even allowing them to fall off edges, or using other markers to trigger a jump to make it look like the enemy knew to jump up on to a platform.
Try creating a platform with two enemies on it. Notice they control themselves individually, since they each have their own individual action instance variable holding their current state. Hopefully you can begin to see how important instance variables are for controlling instances independently - they don't all have to be doing exactly the same thing as each other!
Let's have some fun and add some pickups and score to our game.