Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
The Top Down settings for entities provides an easy way to implement top-down movement for a character. It can be extended to support any input device, multiple sets of movement variables, and animation.
The CurrentMovement property controls the values used by the top down entity in response to input values. This value can be changed according to various conditions in your game such as:
Collision with different terrain (such as walking through mud)
Responding to power-ups (such as collecting a power-up which increases speed)
Responding to special moves or input which changes the character's movement variables (such as holding down a run button)
Movement values can be defined in Glue or code. If your game has a limited set of movement values, these can be defined in Glue. To define a movement value in Glue:
Select the entity
Click the Entity Input Movement tab
Verify that your entity is using the Top Down option for Input Movement Type
Click the Add Control Values button
Modify the newly-added movement values as necessary
The Top Down tab displays all movement values for the selected entity.
You can assign the current movement values in code through the CurrentMovement propety. For example, the following code assigns the movement to FastMovement or Default depending on the state of an Xbox360GamePad:
This tutorial covers how to create enemy movement in a top down game with direct control over the enemy's input device. Compared to the Enemy Pathfinding tutorial, this tutorial shows how to directly control the input device manually. This approach is considered low level and is not recommended for beginners. If your game includes enemies which can pathfind through a map then the Enemy Pathfinding tutorial is recommended.
This project tutorial assumes a Top Down Standard starting point (using the wizard), but the steps here can be used to add an Enemy entity to any project. We assume that the game already has levels.
We'll be creating a new Enemy entity for this tutorial. To do this:
Click the Quick Actions tab
Click the Add Entity button
Enter the name Enemy
Check AxisAlignedRectangle
Check Top-Down for the Input Movement Type
Leave the rest of the defaults and click OK
We will also change the color of the enemy rectangle to 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
To add an enemy to Level1:
Expand the Screens folder, then expand Level1's Objects folder
Select the EnemyList
Click the Quick Actions tab
Click Add a new Enemy to Enemy List
Modify the X and Y values for the new enemy so it is inside of the level boundaries by changing X to 160 and Y to -160
So far our Enemy instance is an entity with no behavior - it simply stays in the same spot when the game runs. First, we'll mark it as using Top-Down movement:
Select the Enemy entity
Click the Entity Input Movement tab
Set Input Movement Type to Top-Down
Set Input Device to None (Can Assign in Code)
Now our Enemy has the behavior of top-down movement, but it is not using an input device for movement. Custom input can be set by defining an input device class which inherits from FlatRedBall.Input.InputDeviceBase. To do this:
Open the project in Visual Studio
Create a new class called EnemyInput. Mine is in an Input folder.
The EnemyInput class needs to inhert from the FlatRedBall.Input.InputDeviceBase class which provides virtual methods to control how the input device behaves. Although the InputDeviceBase class offers many virtual methods, the only two that the top-down movement logic uses are:
GetDefault2DInputX
GetDefault2DInputY
We can override these to return values between -1 and 1. In this case we'll return constant values to test the functionality, as shown in the following code snippet.
Next we need to assign the EnemyInput on the Enemy. To do this:
Open the Entities/Enemy.cs file in Visual Studio
Modify the CustomInitialize as shown in the following snippet:
Now we can run the game and see the enemy move down to the right.
This tutorial primarily shows how to create an EnemyInput class which can be used to control your enemy. So far the device returns constant values for GetDefault2DInputX and GetDefault2DInputY. A real game would use logic to determine what to return, such as the position of the Player, or how far along the enemy has moved along a patrol path.
At this point the rest of the implementation is up to you depending on your needs.
This tutorial provides information on how to change the input device used by the entity. By default the entity uses the first Xbox game pad if one is available, otherwise the entity uses the keyboard. Sometimes this functionality is not desired, such as if the game supports multiple players or AI-controlled entities.
The Top Down entity code interacts with the IInputDevice interface, which provides a standard set of values for controlling game objects. Both the Xbox360GamePad and Keyboard implement this interface, so if the default implementation for this interface meets your game's needs, you can use these objects as-is. Otherwise, your game can implement its own IInputDevice to change how the top down entity is controlled. First we'll look at how to customize the input to use different keyboard keys.
As mentioned earlier, our Player entity defaults to using an Xbox gamepad if one exists. If not, it uses the keyboard. Even though we won't customize this code, we can see its implementation by looking in Player.Generated.cs and searching for InitializeInput.
Ultimately, these calls make their way to calling CustomInitializeTopDownInput. Since this is a partial method, we need to add it ourselves to our Player if we want to customize the input. To do this:
Go to Player.cs
Add the following code to the Player.cs file
The player will now move around with the IJKL keys (rather than the default WASD). Note that this will also override input even if you have an Xbox gamepad plugged in. If this is not desired, we can change the input if it's using the keyboard. This is an example of how to keep the input device, but only change the movement code conditionally:
This code could contain further updates to change desired input based on other input devices such as Xbox360GamePad.
The code in the section above modifies which keys are used from the keyboard. It could be expanded to also handle other input devices like Xbox gamepads. However, instead of changing which keys are used when using the keyboard, you may want to change which input device is being used by your player. This is typically done in the GameScreen. This allows the GameScreen to initialize input, especially if your game allows the player to pick which input device to use. For example, to force the player to always use an Xbox gamepad, modify the following code in the GameScreen's CustomInitialize:
If your game followed the previous tutorials, then your GameScreen has a PlayerList with a single Player named Player1. In this setup, the GameScreen will always have at least 1 player, but can have more. A quick way to support multiplayer without any UI is to detect the number of Xbox gamepads plugged in. If more than 1 is plugged in, we can create additional players very easily. The following code will create one player per Xbox gamepad plugged in. Notice that we only create additional players if we have two or more gamepads since the first player is automatically created.
To see more than one player you must have at least two gamepads plugged in to your computer. Also, keep in mind that the players will initially overlap so you must move one to see both.
This tutorial sets up an entity with Top Down controls. It provides a default implementation which requires no code. Later tutorials show how to interact with this plugin using code.
The FRB Editor provides support for top-down entities through the Entity Input Movement tab. Any entity can be created as a Top Down entity; however, the most common setup is to have a Player entity which uses top down controls.
Empty projects can use the Project Setup Wizard to create a top down player entity. Existing games can add top down controls to new or existing entities with a few clicks. This tutorial shows you how to do both.
The simplest way to set your project up is to use the new project wizard. FlatRedBall automatically launches the wizard when creating a new project.
To create a top-down project, select the Standard Top Down button.
Now your game should be set up with a fully-functional top-down entity. You can verify this by clicking on the Player entity and then clicking on the Entity Input Movement tab. The Player should be marked as having Top-Down as its Input Movement Type.
This section will explain how to manually add a GameScreen and Top-Down entity. You do not need to follow this section if you have used the wizard as shown in the previous step.
Select the Quick Actions tab
Click the Add Screen button
Click OK to the default GameScreen name (all games should have a single GameScreen)
To add a Player entity:
Click the Add Entity button
Name the entity Player
Check:
Circle under Collisions
Top-Down under Input Movement Type
Leave the rest of the defaults and click OK
If you already have an entity created, you can make it a Top Down entity:
Select the entity
Click the Entity Input Movement tab
Click the Top-Down option
By default your GameScreen should have a list of Players (it was an option earlier when creating the Player entity). We recommend always creating a list of Players even if you intend to only have one player. This standard appears throughout FlatRedBall's documentation and can make moving from one project to another easier.
If you did not add a PlayerList earlier by keeping the Include lists in GameScreen, or if you created your GameScreen after your Player, you can manually add a PlayerList by following these steps:
Verify Player is selected
Click the Quick Actions tab
Click the Add Player List to GameScreen button
You will also need a Player instance in the list. To do this, drag+drop the Player onto the GameScreen and it will be added to the Player list.
Now that the entity is marked as a Top Down entity and now that we have an instance of the entity in the GameScreen, we can run the game and see the player move. By default the entity uses a gamepad if one is connected. Otherwise, the entity will use WASD keys on the keyboard.
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
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.
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.
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:
Finally, we can add the following code to GameScreen to create EnemyBase instances when clicking with the mouse.
We can now click to place EnemyBase instances. Each EnemyBase instance follows the player according to pathfinding along the WalkingNodeNetwork and the position of the Player.
Notice that the EnemyBase moves fairly quickly - the same speed as the Player. You may want to reduce the EnemyBase's movement speed to make it easier to test pathfinding.
So far we can create an EnemyBase by clicking in the game. The enemy base instance follows the player using the WalkingNodeNetwork. We have lots of options for customizing the way the enemy follows the player.
First, you may nave noticed that the enemy follows the player on a path by only moving horizontally or vertically. The reason for this is because our WalkingNodeNetwork is set up to only have four links per node: up, down, left, and right. we can include diagonal nodes to allow the enemy to also move diagonally.
Some games, such as the Legend of Zelda on the NES and the 2D Final Fantasy games do not allow the player, NPC, or enemies to move diagonally. If this limitation matches your game, then you can keep this default setup.
However, if your game requires diagonal movement, you can enable it in the TileNodeNetwork properties for the WalkingNodeNetwork.
Now our EnemyBase instance can travel diagonally.
You may have noticed that at times the EnemyBase backtracks - in other words at times the enemy seems to walk backwards rather than following its path. This can happen when the path is re-evaluated. The new path requires the enemy to move backwards slightly to move to its next node.
To help explain this problem we can visualize a simple path that an enemy may take. It's important to remember that each node in the TileNodeNetwork is always placed at the center of the tile. The following image shows an example path. The blue circles represent the node positions, the red circle represents the enemy, and the purple lines represent the path the Enemy takes.
Notice that the enemy moves directly towards the first node diagonally. This happens regardless of where the enemy is located - by default the very first segment in the path connects the enemy to the path. In this case the movement seems to be what we might expect, but in some situations the enemy may be positioned such that its first movement requires it to backtrack, as shown in the following image:
Notice that in this case the closest node to the enemy is to the right of the enemy. Therefore, the first movement segment has the enemy moving to the right before it resumes moving to the left.
We can solve this by removing the first segment of the path, but only if the path has more than one point. If there is exactly one point in the path, then we should not remove it since the enemy is near its final target.
We can modify our code as shown in the following EnemyBase.CustomActivity:
Note that while this approach does solve the backtracking problem, it can at times cause the enemy to move diagonally to the next node in a way which may result in getting stuck behind collision.
The reason we discuss this solution is not necessarily to suggest that this is the best solution for backtracking for production games, but rather to show that the TopDownAiInput object allows you to manipulate its Path at any time. You can perform more advanced logic to determine if backtracking will occur, and to remove, add, or modify existing nodes to correct for this problem according to your game's specific needs.
At this point our enemies can pathfind but they do not respect the level's solid collision. If we enable collision we will notice that this introduces a number of problems.
To enable collision between EnemyBase and the leve's SolidCollision:
Expand GameScreen's Objects folder
Drag+drop EnemyBaseList onto SolidCollision
Verify that the newly-created CollisionRelationship is using BounceCollision
Change the elasticity to 0 so that the EnemyBase doesn't actually bounce off the wall. By setting it to 0 the wall absorbs the EnemyBase's velocity.
If we run our game now we'll notice that the enemy gets stuck when trying to follow the player along any collision.
This is happening because the collision on our enemy is too big - it has a circle with a radius of 16. This prevents it from getting close enough to tiles to properly pathfind. We can fix some of the problems by changing the EnemyBase's CircleInstance radius to a smaller value. A value of 8 allows the EnemyBase to touch the center of a tile, so we will change the value to 8.
This change helps pathfinding, but it doesn't solve all of the problems. If your game is using diagonal movement, you may notice that the enemy slows down when colliding against corners. We can force the enemy to pathfind around a house. Notice the enemy slows down when moving around corners.
This is occurring because we have enabled 8-way pathfinding, and this includes corner pathfinding. We can set our node network visibility to true to see that the node networks cut corners, in other words diagonal paths connect nodes which result in the EnemyBase attempting to push against a corner collision.
We can disable corner cutting through an option in the TileNodeNetwork Properties tab:
Now our tile node network still supports diagonal movement, but it will not attempt to move diagonally around corners as shown in this screen shot:
So far we have made modifications to the EnemyBase entity and the WalkingNodeNetwork to address problems with collision, but one issue still remains - our enemy movement still feels unnatural. We can solve this problem by implementing line-of-sight pathfinding. This form of pathfinding initially uses the TileNodeNetwork to determine which path it should take, but it then eliminates nodes along the path if a more direct path is available. This allows an entity to move in any direction and it can make pathfinding feel more natural.
Of course, keep in mind that your game may intentionally have pathfinding strictly along a node network so whether you use line of sight pathfinding can be a stylistic choice.
To enable line of sight pathfinding, the TopDownAiInput must have two additional pieces of information:
The width of the collision. This is needed because line of sight pathfinding must check if a direct path exists to the target and also if following that direct path results in bumping against solid collision.
The solid collision to avoid
We can enable line of sight pathfinding by modifying the InitializePathfinding
method as shown in the following code:
Notice that we use CircleInstance.Radius*2
to determine the width of the entity. If using a different shape, or multiple shapes, your code needs to account for this. Also, keep in mind to also add any offsets if your shape is not centered on the X or Y. Finally, you may need to increase this value if your collision shapes can adjust during runtime - such as being modified by AnimationChains.
Since this method now requires passing in a TileShapeCollection, we need to modify GameScreen.PrepareEnemyPathfinding to pass in the SolidCollision as shown in the following code:
Now our enemy follows the player directly if there is a line of sight. If not, the enemy performs line of sight checks to skip nodes which are not necessary. Notice that when navigating tight spaces the enemy follows the node network, but when navigating open spaces the enemy moves more directly towards its target.
This tutorial has shown how to create an enemy that performs pathfinding towards a player. This pathfinding can be customized to address common problems, to enable or disable diagonal movement, and to use line of sight pathfinding for more natural movement.