This set of tutorials covers how to create enemy movement in a platformer game. Enemies in many platformers move horizontally until hitting a wall or the edge of a platform, then turn around.
This tutorial creates an Enemy entity which uses Platformer physics, but is fully controlled by logic rather than input devices like the keyboard.
This tutorial will create the Enemy entity which we'll use in the remainder of the tutorials. This enemy will be similar to a Player entity - it has collision and will use Platformer physics, but it will not use input from a keyboard or gamepad - instead its movement will be controlled purely in code.
We'll be creating an Enemy entity ourselves since it wasn't automatically created for us by the Glue Wizard. To do this:
Click the Quick Actions tab
Click the Add Entity button
Enter the name Enemy
Check AxisAlignedRectangle
Check Platformer for the Input Movement Type
Leave the rest of the defaults and click OK
We will also change the color of the enemy rectangle so we can tell it apart from the Player:
Expand the Enemy Object folder
Select AxisAlignedRectangleInstance
Select the Variables tab
Change Width to 16
Change Height to 16
Change Color to Red
Normally entities like Enemies are added through Tiled files, as shown in the breaking blocks tutorial. To keep the tutorial shorter, we will be directly adding Enemy instances through Glue. We could add enemies to GameScreen, but it's more common for each level to specify its own enemies, so we'll be adding the enemy instance to level1. First we will modify the EnemyList in GameScreen (which was automatically added as one of the default options when we created our Enemy entity) so it can be accessed in the levels. To do this:
Expand GameScreen Objects folder
Select EnemyList
Click the Properties tab
Set ExposedInDerived to True
Now that this is true, the EnemyList appears in all of the Level screens (which are derived from the GameScreen), and we can add instances to these lists.
To add an enemy to Level 1, drag+drop the Enemy entity onto the Level1 Screen.
We also need to modify the Enemy so it is positioned inside of the solid boundary of our game screen. To do this:
Select the newly-created Enemy1 in Level1
Select the Variables tab
Change X to 160
Change Y to -160
Now we have a fully-functional enemy, but it falls through the solid collision since we haven't yet set up an EnemyList vs SolidCollision relationship. To do this:
Expand GameScreen Objects folder
Drag+drop the EnemyList onto SolidCollision. Notice that we are doing this in the GameScreen rather than Level1 because we want all enemies to collide with the SolidCollision regardless of level.
Glue automatically sets the Collision Physics to Platformer Solid Collision since the Enemy entity is marked as a Platformer.
Now we have an Enemy entity and an instance of this Enemy in Level1. This instance collides with the game's SolidCollision and has full support for platformer physics. Currently both the Player and Enemy are controlled by the keyboard (or gamepad if one is plugged in). We will remove this input control from the Enemy and replace it with logic-based movement in the next tutorial.
We’ll begin this tutorial with an empty project. Mine will be called EnemyMovement.
The Glue Wizard can help us get a quick project set up. We will leave most of the options to their default, but we will change the following:
Change What kind of control will the player have? to Platformer
Uncheck Add Sprite to Player Entity unless you intend to add graphics to your Player entity. This tutorial will not cover how to add graphics to the Player entity.
Note that we will not be explicitly working with the Player entity in this game, but we still create one since most games need one eventually.
Change Number of levels to create to 1. We will only have one level in this game
Now our project is ready to go as a fully functional platformer. We can begin adding the Enemy entity in the next tutorial.
We will add this logic to our Enemies in this tutorial.
Currently Enemies have a single collision shape called AxisAlignedRectangleInstance.
This shape acts as the Enemy's body. It can be used to keep the enemy from moving through the ground, walls, and ceiling (the SolidCollision TileShapeCollection). Currently this shape does not provide enough information to detect ledges. From the point of view of the AxisAlignedRectangleInstance, this first situation...
... provides the same information as this second situation...
Both situations are identical in the collision event - they both result in collision and both have the same RepositionDirection. To detect if the Enemy is near the edge of a platform, we can use a new collision rectangle (displayed in green). If this green rectangle is not colliding with SolidCollision, then the player is near a ledge. For example, in the following diagram the Enemy is near the edge of a platform on the right side:
If the player is not near a ledge, then the green rectangle will collide with solid collision.
Since we want to detect ledges on both the left and right side, we need to have one new rectangle on each side.
When we define these rectangles, we need to remember a few things:
These rectangles should not be used for solid collision - they should be excluded from the ICollidable interface
These rectangles should be smaller than the size of our tiles (16x16) so they do not reach across gaps or down pits
These rectangles will be used to apply logic when not colliding with the SolidCollision. Normally we perform logic when a collision occurs, so we will need to write custom code to handle this situation.
As mentioned above, we'll need to create two AxisAlignedRectangle instances in our Enemy entity - one for left edge collision and one for right edge collision. To create the left edge collision rectangle:
Select the Enemy entity
Select the Quick Actions tab and click the Add Object to Enemy button
Select the type AxisAlignedRectangle
Enter the name LeftEdgeCollision
Click OK
With the LeftEdgeCollision object selected, click on the Variables tab
Change X to -12
Change Y to -12
Change Width to 8
Change Height to 8
To help visualize, change the Color to Green
Repeat the steps above to create RightEdgeCollision, but change the X value to a positive 12 so it sits on the right side of the Enemy.
Now our enemy has two extra rectangles which it can use for detecting edges, but these rectangles are being used for solid collision. To fix this:
Select the LeftEdgeCollision
Click on the Properties tab
Change IncludeInICollidable to False so that the rectangle is not used in default collision relationships (like EnemyListVsSolidCollision in GameScreen)
Repeat the steps above for the RightEdgeCollision and the Enemy will no longer use these two rectangles for solid collision.
Normally when performing logic related to collision, we do so inside a collision relationship event. Detecting an edge is different because we need to respond to a situation when there is no collision. Therefore, we will be writing code which happens every frame inside of our GameScreen CustomActivity. To detect if the enemy should turn around, modify the GameScreen.cs file so that its CustomActivity matches the following code snippet:
This code shows that we can check collisions directly in code at any time, rather than relying purely on CollisionRelationships. Notice that we only perform this logic if the Enemy is on the ground. Otherwise, enemies would alternate directions very quickly when falling. If using the default level, this logic won't do anything since there are no raised platforms. We can modify Level1Map to add a few platforms. Feel free to adjust it however you like, as long as you have platforms with edges which we can use to test this logic.
We should also adjust the Enemy1 starting position so it starts on a ledge instead of falling from the top of the level. Change the X and Y values on Enemy1 in Level1 so that it starts in the right spot. This may take a little trial-and-error. Once you have the Enemy on a platform, you may notice that it still runs off the edge. The reason for this is because the Enemy moves very quickly and it takes time to slow down when changing directions.
To do this:
Select the Enemy entity
Click the Entity Input Movement tab
Change the Max Speed to 100
Change the Slow Down Time to 0.03
These changes enable the enemy to turn around without falling off of the platform. It's important to note that with enough Max Speed or a large enough Slow Down Time will result in an enemy sliding off of an edge.
Now our enemy has functionality for turning around based on wall collision and edge detection. The two approaches shown here could also be used to turn around when Enemies collide with other Enemies (also using reposition values), but we'll leave this and other situations to individuals who are interested.
This tutorial shows how to replace the default keyboard (and gamepad) input logic with movement controlled only in code.
First we'll remove the Input Device from the Enemy entity:
Select the Enemy entity
Select the Enemy Input Movement tab
Check None under Input Device
Now the Enemy will not move in response to keyboard or gamepad input. Note that if we do not assign an InputDevice in code, the game crashes so we must do so before running the game.
Next we will create our own custom InputDevice which will control the movement of the enemy. The reason we are creating this is because Platformer entities expect to receive commands from an input device like a keyboard. We can create a class which simulates input commands without actually having physical input. To create this class:
Open the project in Visual Studio
Create a new class called EnemyInput. I will place mine in an Input folder
Modify the code so that the EnemyInput class inherits from FlatRedBall.Input.InputDeviceBase
This class provides many virtual methods which we can override to customize the behavior of our enemy. For example, we can override the method for GetHorizontalValue to control which way the Enemy is walking, as shown in the following code snippet:
To use EnemyInput, we can modify the CustomInitialize method in the Enemy.cs class to assign its InputDevice. The Enemy.cs file can be found in the Entities folder in the Visual Studio Solution Explorer.
To use EnemyInput as the Enemy's InputDevice, modify the Enemy CustomInitialize method as shown in the following code snippet:
Now if we run the game, the Enemy automatically walks to the right and ignores keyboard and gamepad input.
This tutorial does not include adding jumping to the enemy behavior. If your game needs jumping, you can add this by including a primary input override in the EnemyInput class. Of course, you would only want to press the jump button under some condition. The following code snippet shows how this could be accomplished:
Our EnemyInput object can be expanded to support any type of input - we just need to have the GetHorizontalValue function return a value between 0 and 1. Note that we are only using horizontal movement for this tutorial, but we could also have the Enemy jump by implementing the GetPrimaryActionPressed method, which controls whether the jump button is down.
For this tutorial we need access to a value to indicate whether the Enemy should move to the left or right. We will ignore values inbetween -1 (left) and +1 (right), but a full game may support enemies which may stand still or move at various speeds. We'll create a new enum value and expose a property in EnemyInput so that it can be controlled externally. To do this, modify the EnemyInput class as shown in the following code snippet:
Many platformer games include enemies which turn around when colliding with other objects such as walls, platform edges, and other enemies. We will implement wall collision here since it is the simplest scenario to control enemies turning around. First we will add an event whenever Enemies collide with SolidCollision:
Expand the GameScreen -> Objects -> Collision Relationships item in Glue
Select EnemyListVsSolidCollision
Select the Collision tab
Click the Add Event button\
Click OK to accept the defaults
Glue will add an event to GameScreen.Event.cs which we can modify to adjust the EnemyInput DesiredDirection, as shown in the following snippet.
The code above is assigns the EnemyInput DesiredDirection according to the enemy's AxisAlignedRectangleInstance.LastMoveCollisionReposition. We'll take a deeper look at how this property works in the next section.
The collision relationship between Enemies and SolidCollision prevents Enemy instances from overlapping the SolidCollision TileShapeCollection. Whenever an Enemy overlaps one of the rectangles in the SolidCollision TileShapeCollection, the Enemy must be repositioned so it does not overlap the solid collision. For example, consider a falling Enemy (white). Initially the enemy may be falling but not overlapping any solid collision (red).
As the Enemy continues to fall, it eventually overlaps the solid collision.
When this happens, the Enemy must be repositioned up to resolve the overlapping collision.
In this case, the RepositionDirection would have a positive Y value. For example, it may be (0,1,0). Similarly, whenever an Enemy collides with a wall in the SolidCollision TileShapeCollection, the Enemy is repositioned horizontally, so the X value of the RepositionDirection is non-zero, as shown in the following diagram:
In the diagram above, the RepositionDirection has a negative X value (points to the left) so we know that the wall is to the right of the Enemy. If the RepositionDirection has a positive X value, then the wall is to the left of the Enemy.
Now that we have this logic in place, our Enemy will automatically walk until colliding with a wall, then it turns around and walks in the other direction.
The next tutorial will enable Enemies to turn around when reaching the end of a platform.
Some games include enemies which turn around when reaching a platform edge. For example, in Super Mario World, the red Koopa enemies (turtles) walk until they reach an edge, then turn around.