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.
1. Add a new Sprite object in the layout.
2. Import the Enemies\Snail sprite strip.
3. Delete the first empty animation frame.
4. Set the animation to looping, speed 15.
5. Set the origin to the base of the snail image, and apply it to the whole animation.
6. Crop all the frames (shift + crop).
7. Close the animation editor and rename the object to 'SnailEnemy'.
8. Place it on a ground tile as shown.
Now we want to implement the following logic:
- If the player runs in to the snail from the side, they flash and get hurt.
- If the player jumps on top of the snail, the snail is killed.
To make the player flash, select the player and add the Flash behavior. Remember to select the actual player, 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 -> SnailEnemy
This event runs when we collide with the SnailEnemy 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 snail accidentally being killed if we fall past it off a ledge, say. Right-click the 'Is falling' condition and select 'Add another condition'. Remember, all conditions must be met for the event to run. Add the condition:
PlayerBox -> Compare Y -> Less than, SnailEnemy.Y
The Y axis increases downwards, so if the player's Y co-ordinate is lower than the snail's, they are above it.
In this event, add the action:
SnailEnemy -> Destroy
We can also make the player bounce off it by adding another action:
PlayerBox -> Set vector Y -> -700
'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 700 is a little less than the jump strength of 1100. So this will make the player bounce off as if they did a weak jump.
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 Add -> Else. 'Else' is a special condition that runs if the previous event did not run. So this event will run if we collided with the snail 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
Player -> Flash -> (leave default values and click Done)
Remember the Flash behavior is in the Player 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 'poof' sprite on the SnailEnemy when it is destroyed, using the SnailEnemy'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 snail enemy move back and forth across the platform.
Moving the enemy
The main problem with moving the snail is how to detect when it's 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 snail when it touches them.
We can also use the Platform behavior again for the snail. 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 SnailEnemy 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 SnailEnemy - we want to automatically control it. We can do this by setting its Default controls property to No, then using the Simulate control action. Since snails are also pretty slow, 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 load in an opaque rectangle. Call it EdgeMarker. Size the object to about 40 x 40 and set its Initial visibility to Invisible so we don't see it. Place one at each end of the snail's platform like so: (don't forget you can create a new instance by control+dragging it)
The snail 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 snails, 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 "action" instance variable 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 SnailEnemy object. In the properties bar, click Add / Edit under 'Instance variables'.
A dialog listing all instance variables for the object appears. Click the 'add' icon to add a new one. Set the name to action, the type to text, 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 action is "right", simulate the platform movement holding the 'right' key to move the snail right.
- if action is "left", simulate the platform movement holding the 'left' arrow key to move the snail left.
- if the snail hits the EdgeMarker, flip its action (if "left" set it to "right"; if "right" set it to "left").
We can set up the movement with the following two events:
Event: SnailEnemy -> Compare instance variable -> action equal to "right" (use double quotes here to indicate text)
Action: SnailEnemy -> 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 SnailEnemy -> Set mirrored in the "left" event, and SnailEnemy -> Set not mirrored in the "right" event. You should finish with this:
Now to flip the snail's direction on the edges:
Event: SnailEnemy -> On collision with another object -> EdgeMarker
Subevent: SnailEnemy -> Compare instance variable -> action equal to "right"
Action: SnailEnemy -> Set value -> action to "left"
Action: SnailEnemy -> Set value -> action 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 snail 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 snails 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!