Enemy Pathfinding
Introduction
Enemy pathfinding allows enemies to navigate through complex levels. This can be used to move to a target position (such as a patrol point) or follow an entity (such as enemies chasing a player). This tutorial shows how to create a pathfinding enemy. Specifically, it covers the following topics:
Creating a TileNodeNetwork which defines the walkable parts of a map
Creating an input device which is used to control the enemy so it follows its desired path
Creating line-of-sight pathing for more natural movement
Creating a TileNodeNetwork
First we'll create a TileNodeNetwork. This is defined in our GameScreen, but will use the Map for each level. For a more thorough walkthrough of creating a TileNodeNetwork, see the TileNodeNetwork page.
To create the TileNodeNetwork:
Select the GameScreen
Click the Add Object to GameScreen under Quick Actions, or right-click on GameScreen and select Add Object
Select the TileNodeNetwork type
Enter a name such as WalkingNodeNetwork
Click OK
Next we'll define a tile to use for pathfinding. To do this:
Open any of your levels in Tiled
Select the Tileset that you use for your GameplayLayer. By default this is called TiledIcons
Click the wrench to edit the Tiles
Select a tile that you would like to use for pathfinding
Change the Class for that tile to WalkableTile
Save your tileset (TSX)
Next, select your current level (such as Level1Map) and paint the WalkableTile onto the GameplayLayer. Note that if you would like to be able to walk on areas which already have other tiles, you can create a new layer specifically for Tiles.
Be sure to save both your level and the tileset.
Finally, we can indicate that the WalkableTile should be used to populate the nodes in the Tile:
Select the WalkingNodeNetwork Object under GameScreen
Click the TileNodeNetwork Properties
Select the From Type option
Select Map as the Source TMX File/Object
Select WalkableTile as the Type
Optionally, you may want to also set the Visible variable to true so the TileNodeNetwork shows up in your game.
The game should now show the TileNodeNetwork. Note that you can also make the GameplayLayer invisible in Tiled so that the TileNodeNetwork is easier to see in game.
Notice that the links between nodes are either vertical or horizontal. We will discuss diagonal movement later in the tutorial.
Creating Enemy InputDevice
Now that we have a TileNodeNetwork defined, we can create an input device. Of course, we need to have an Enemy defined. For this tutorial I'll use a simple enemy with the following characteristics:
It is named EnemyBase - this naming convention is used so that the enemy could be used as a base Entity for derived variants. Larger games would likely need multiple enemy types so this sets up to expand easily.
The Enemy has a single Circle collision. More complex games may include multiple types of collision, but it's important to note which collidable object will be used as the solid collision - we'll use this for line-of-sight pathfinding later in this tutorial.
The enemy uses the Top-Down movement types, just like the Player.
The enemy's InputDevice is set to None. As indicated in the FRB Editor, this must be assigned in code or the game will crash.
Next we'll define the InputDevice. You may be familiar with the InputDevice as hardware input which can control the movement of the Player (such as the Keyboard or Xbox360GamePad). Although these are common implementations of the input device (IInputDevice interface), the concept of an InputDevice is something which can be read by an Entity (such as the Enemy) to determine how it should move.
In this case the InputDevice will not be tied to actual hardware. Instead, we will return input values which move the EnemyBase through the map by following the path obtained from the WalkingNodeNetwork.
Fortunately, the TopDownAiInput class is an InputDevice which automatically returns these movement values according to a desired path.
First, we will assign this InputDevice in the EnemyBase's CustomInitialize:
Notice that the topDownAiInput is defined at class scope - this lets us access it in other methods without needing to cast the InputDevice every time. Also, the IsPathVisible and PathColor properties can be used to control the appearance of the path that the EnemyBase is going to take to get to the player. We will enable this to show the path. Also, it may be worth turning off the WalkingNodeNetwork visibility so that the EnemyBase path can be seen more clearly.
Initializing TopDownAiInput
Next, we'll create a method to set up the node network and target player. Add the following to EnemyBase.cs:
For this tutorial we assume a single player which never changes. A multiplayer game may require changing the target based on which player last hit the enemy, proximity, or whether the current target is dead.
Also, note that this method must be called by the GameScreen. We will add the code to call this method later in this tutorial.
Finally, we can set the path to follow the player in CustomActivity.
Notice that this code only updates the path every second. Updating the path is a fairly quick operation, but it can be expensive if the game includes hundreds of EnemyBase instances. We'll check once per second to prepare for a larger game.
Now that our EnemyBase has the code it needs to pathfind to the player, we can add code to the GameScreen to call InitializePathfinding.
EnemyBase instances can be created both before and after GameScreen.CustomInitialize is called, so we must handle both cases.
To handle EnemyBase instances created before CustomInitialize is called (if they have been added directly to a level in the FRB Editor or live edit), we can loop thorugh EnemyBaseList and call InitializePathfinding.
To handle EnemyBaseInstances created after CustomInitialize (if they are added using a factory or variant object such as through a spawner) we can subscribe to the factory method. Note that a game which has derived enemy variants must subscribe to all derived variant factories to handle creation.
Modify the GameScreen.cs to include the following code: