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.
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 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 creates an Enemy entity which is used in the remainder of the tutorials. This enemy is similar to the Player entity - it has collision and uses Platformer physics, but it does not use input from a keyboard or gamepad - instead its movement is controlled purely in code.
To create the Enemy entity:
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
You can optionally change the color of your Enemy if you would like by selecting the newly-created AxisAlignedRectangle and setting its color to Red.
Entities such as Enemy are usually added directly to levels such as Level1. Note that it is possible to add Entities in more ways including through Tiled and directly in code, but we will be adding an instance directly in the FlatRedBall Editor.
Note that the EnemyList object is defined in GameScreen, but it is also accessible in all levels, such as Level1.
The EnemyList must be accessible in both screens. It is used in GameScreen to create collision relationships, which should always be the same across all levels. It is used in each level to define which enemies should appear in a particular level. This setup is enabled by the EnemyList having its ExposedInDerived property set to true, which is set up by default from when we created our Enemy entity.
To add an enemy to Level 1, drag+drop the Enemy entity onto the Level1 Screen.
The newly created entity is automatically added to Level1's EnemyList.
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. By doing this in the GameScreen, this new Collision Relationship will apply to all levels, including Level1.
Glue automatically sets the Collision Physics in the new Collision Relationship 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.
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 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.
Next the Enemy should be positioned so it is standing on the ground. You can position this through trial-and-error by running the game and adjusting the position of the entity, but the fastest way to accomplish this is in Live Edit mode. For information about setting up Live Edit, see the page.