This is part 4 in a set of tutorials for using RogueSharp and MonoGame to create a basic Roguelike game. It is meant to serve as an introduction to the RogueSharp library.
If you missed part 3 in the tutorial you can find it here: https://roguesharp.wordpress.com/2014/05/30/tutorial-3-roguelike-map-exploration-using-roguesharp-and-monogame/
Goal
In this tutorial we will be adding an aggressive enemy to the game that will hunt the player down. This enemy is a Hound straight out the Baskervilles and it will seek out the player no matter how far away the player is. We will also be adding a debugging state to the game, that when invoked will show us as developers more details about what is going on within the game. These details would normally not be available to a regular player but will assist us with developing the game.
Adding Sprites
We’ll need to add two sprites to our game for this tutorial. First we’ll add the aggressive enemy hound that will be hunting the player. Second we’ll add a solid white 64×64 pixel sprite that we’ll tint different colors and overlay on the map to show paths when debugging. Trust me, it will make more sense once we get there.
Remember to add sprites you copy the above image (or use your own) into the Content folder of the game. Be sure to set the Properties – Copy to Output Directory to “Copy if newer“.
Game State
Next we are going to add a new class file. Click on the game project and press “Shift + Alt + C“. This is a shortcut for adding a new class. You can also right click and add it if you prefer.
Name the class Global.cs and add the following code to it.
namespace ExampleGame { public enum GameStates { None = 0, PlayerTurn = 1, EnemyTurn = 2, Debugging = 3 } public class Global { public static GameStates GameState { get; set; } } }
I’m actually not happy to add this class to our code. We now have a globally accessible game state and have defined a few potential states which will be useful to us, but this isn’t a great way to go about it. You’ll see that because of this choice our code will be littered with “if” statements comparing the current game state to another state before doing logic. Basically this is not a solution that will scale well as our game gets larger. I only choose it with reluctance for ease of understanding and simplicity.
I’d recommend reading Programming Game AI by Example, written by Mat Buckland for some better examples. The chapter on finite state machines is partially available online here http://www.ai-junkie.com/architecture/state_driven/tut_state1.html
Debugging Mode
Now we’ll go ahead and use our new Global.GameState to determine if we are in debugging mode or not. In debugging mode we’ll remove the fog of war so that we can see the entire map. As a key to switch too and from debugging mode we’ll use the spacebar.
Start by adding an “else if” block to the Update( … ) method in Game1.cs just below the existing check for exiting the game.
if ( _inputState.IsExitGame( PlayerIndex.One ) ) { Exit(); } // New code to handle switching modes when spacebar is pressed else if ( _inputState.IsSpace( PlayerIndex.One ) ) { if ( Global.GameState == GameStates.PlayerTurn ) { Global.GameState = GameStates.Debugging; } else if ( Global.GameState == GameStates.Debugging ) { Global.GameState = GameStates.PlayerTurn; } }
Next we’ll update the Draw( … ) method in Game1.cs by changing the first two if blocks as follows:
if ( !cell.IsExplored && Global.GameState != GameStates.Debugging ) { continue; } Color tint = Color.White; if ( !cell.IsInFov && Global.GameState != GameStates.Debugging ) { tint = Color.Gray; }
The changes are on lines 1 and 6 where we check the game state and only skip drawing Cells or tint Cells if we are _not_ currently in Debugging mode.
Now if we run the game and press the spacebar we should see something like this
Introducing an Enemy
It’s time to unleash the hounds! Before we can make our first enemy, we’ll need a class to keep track of it.
Aggressive Enemy Class
Add a new class file by clicking on the game project and press “Shift + Alt + C“. Name the class AggressiveEnemy.cs and add the following code to it.
namespace ExampleGame { public class AggressiveEnemy { public int X { get; set; } public int Y { get; set; } public float Scale { get; set; } public Texture2D Sprite { get; set; } public void Draw( SpriteBatch spriteBatch ) { float multiplier = Scale * Sprite.Width; spriteBatch.Draw( Sprite, new Vector2( X * multiplier, Y * multiplier ), null, null, null, 0.0f, new Vector2( Scale, Scale ), Color.White, SpriteEffects.None, 0.5f ); } } }
This code should look familiar from the Player.cs class. We use it again here to keep track of the position of the enemy and to be able to draw it to the screen. Because the code appears in two classes, we should really refactor it, but for now I’ll leave it using what I like to call copy and paste inheritance. 🙂
Drawing the Enemy in an Empty Cell
Add a private field to the top of the Game1 class
private AggressiveEnemy _aggressiveEnemy;
Add the following code to the LoadContent() method to setup our enemy.
startingCell = GetRandomEmptyCell(); _aggressiveEnemy = new AggressiveEnemy() { X = startingCell.X, Y = startingCell.Y, Scale = 0.25f, Sprite = Content.Load<Texture2D>( "Hound" ) };
Line 1 – Notice that we reuse the GetRandomEmptyCell() method to find a starting cell for the enemy. There are a couple of possible bugs here that we’ll talk about later.
Line 7 – We are going to give this enemy the “Hound” texture that added to the project earlier.
The last thing we need to do to draw the enemy is to update our Draw( … ) method.
_aggressiveEnemy.Draw( spriteBatch );
If we run the game now we should see something like this:
Fixing Bugs
There are at least two bugs right now. First the way that we implemented GetRandomEmptyCell() previously has a bug if we call it more than once. The bug is that we are initializing a new DotNetRandom() within the method. Because we do not provide a parameter when we construct it, it will use the current system time (ticks) to seed the pseudo-random number generator. Two pseudo-random number generators initialized with the same seed will always generate the same set of numbers in the same order. This is problematic because if we called GetRandomEmptyCell() twice within the same tick, we would get the same numbers generated.
One way to overcome this is to move the initialization of our DotNetRandom() to our Global class and then have GetRandomEmptyCell() use that.
public class Global { public static readonly IRandom Random = new DotNetRandom(); public static GameStates GameState { get; set; } }
Line 3 – The only line that we changed. Add the DotNetRandom initalization to our global class and reference this whenever we need a random number generator.
private Cell GetRandomEmptyCell() { while ( true ) { int x = Global.Random.Next( 49 ); int y = Global.Random.Next( 29 ); if ( _map.IsWalkable( x, y ) ) { return _map.GetCell( x, y ); } } }
Lines 5 and 6 – Don’t forget to update GetRandomEmptyCell() to use the static Global Random now.
The second bug that you might have noticed is that we draw Hound even if it isn’t currently in the field-of-view for the player. We can fix this by updating our Draw( … ) method in Game1.cs by wrapping an “if” statement around our drawing code for the aggressive enemy.
if ( Global.GameState == GameStates.Debugging || _map.IsInFov( _aggressiveEnemy.X, _aggressiveEnemy.Y ) ) { _aggressiveEnemy.Draw( spriteBatch ); }
If you run the game now you’ll see that the Hound will only be draw when he is in the player’s Field-of-View.
Pathfinding
As usual we had to do a lot of setup to get to the important part of the tutorial. Now we are going to look at RogueSharp’s pathfinding capability and have the Hound start chasing the Player.
Finding a Path to the Player
We can start off by adding a new class that will be able to handle finding and drawing a path to the player. Add a new class file by clicking on the game project and press “Shift + Alt + C“. Name the class PathToPlayer.cs and add the following code to it.
public class PathToPlayer { private readonly Player _player; private readonly IMap _map; private readonly Texture2D _sprite; private readonly PathFinder _pathFinder; private IEnumerable<Cell> _cells; public PathToPlayer( Player player, IMap map, Texture2D sprite ) { _player = player; _map = map; _sprite = sprite; _pathFinder = new PathFinder( map ); } public Cell FirstCell { get { return _cells.First(); } } public void CreateFrom( int x, int y ) { _cells = _pathFinder.ShortestPath( _map.GetCell( x, y ), _map.GetCell( _player.X, _player.Y ) ); } public void Draw( SpriteBatch spriteBatch ) { if ( _cells != null && Global.GameState == GameStates.Debugging ) { foreach ( Cell cell in _cells ) { if ( cell != null ) { float scale = .25f; float multiplier = .25f * _sprite.Width; spriteBatch.Draw( _sprite, new Vector2( cell.X * multiplier, cell.Y * multiplier ), null, null, null, 0.0f, new Vector2( scale, scale ), Color.Blue * .2f, SpriteEffects.None, 0.6f ); } } } } }
Line 6 – Instantiate a new PathFinder object which comes from RogueSharp.
Line 7 – IEnumerable<Cell> will hold each Cell in the path from a location to the Player.
Line 9 – The constructor takes a Player, IMap, and Texture2D as parameters. The Player and IMap are both necessary for finding a path through the Map to the Player. The Texture2D is used to draw the path.
Lines 16 to 21 – The FirstCell property will return the starting Cell in the path from a location to the Player. We will use this later to find the first step the Hound should take to advance towards the Player
Line 23 – The CreateFrom() method takes an X and Y coordinate as parameters and calculates the shortest path to the Player.
Lines 25 and 26 – The ShortestPath( … ) method on the pathfinder object takes two Cells as parameters. The first Cell is the source of the path, or where it starts. The second parameter is the destination Cell or end of the path. In this case the destination is always where the Player is, and the source is provided as parameters to this method.
Line 28 – The Draw method will be used to show the path on the screen.
Line 30 – We only want to draw the path when we are in Debugging mode.
Line 39 – Remember we are using a pure white texture for the Path. We’ll tint it to Color.Blue and multiply that by .2f to make it slightly transparent.
Updating the Enemy
Next we need to add an instance of our new PathToPlayer class to our AggressiveEnemy class.
public class AggressiveEnemy { private readonly PathToPlayer _path; public int X { get; set; } public int Y { get; set; } public float Scale { get; set; } public Texture2D Sprite { get; set; } public AggressiveEnemy( PathToPlayer path ) { _path = path; } public void Draw( SpriteBatch spriteBatch ) { float multiplier = Scale * Sprite.Width; spriteBatch.Draw( Sprite, new Vector2( X * multiplier, Y * multiplier ), null, null, null, 0.0f, new Vector2( Scale, Scale ), Color.White, SpriteEffects.None, 0.5f ); _path.Draw( spriteBatch ); } }
Line 3 – Add an instance of PathToPlayer
Line 8 through 11 – Add a constructor which takes a PathToPlayer as a parameter and sets the private field.
Line 17 – After we draw the enemy, draw the path by calling the Draw( … ) method on our PathToPlayer instance.
Since we changed the signature of the constructor for AggressiveEnemy we need to update our LoadContent() method in Game1.cs by changing the line “_aggressiveEnemy = new AggressiveEnemy();” to the following
var pathFromAggressiveEnemy = new PathToPlayer( _player, _map, Content.Load<Texture2D>( "White" ) ); pathFromAggressiveEnemy.CreateFrom( startingCell.X, startingCell.Y ); _aggressiveEnemy = new AggressiveEnemy( pathFromAggressiveEnemy )
Line 1 – Instantiate a new PathToPlayer and pass in the Player, Map and White texture that we added at the beginning of this tutorial.
Line 2 – Call the CreateFrom( … ) method on our PathToPlayer using the Cell where the Hound started as the source of the path.
Line 3 – Pass in our PathToPlayer instance to the new constructor for AggressiveEnemy.
If you run the game and hit the Spacebar to switch to Debugging mode you should see something like this:
Following the Path
Well now that we have a path, the final task left to us is to have the Hound actually follow it and hunt down our player.
First we’ll add an Update() method to our AggressiveEnemy class
public void Update() { _path.CreateFrom( X, Y ); X = _path.FirstCell.X; Y = _path.FirstCell.Y; }
Line 3 – Create a path from the Hound’s current X and Y location to the Player by calling CreateFrom( … )
Line 4 and 5 – Set the X and Y location of the Hound to the first Cell in the path we calculated by using the FirstCell property.
Last we add more code to our Update( … ) method in Game1.cs to handle switching between the Player and the Enemy turn. Place the following code in the “else” block overwriting the code that is already there.
if ( Global.GameState == GameStates.PlayerTurn && _player.HandleInput( _inputState, _map ) ) { UpdatePlayerFieldOfView(); Global.GameState = GameStates.EnemyTurn; } if ( Global.GameState == GameStates.EnemyTurn ) { _aggressiveEnemy.Update(); Global.GameState = GameStates.PlayerTurn; }
Lines 1 through 6 – If it is the Player’s turn wait for them to move. Then update the field-of-view and change the GameState to the Enemy’s turn.
Lines 7 through 11 – If it is the Enemy’s turn call the Update() method on our aggressive enemy Hound. This will make the Hound move one step toward the player. Then switch the state back to the Player’s turn
If we run our Game now we should be ruthlessly hunted down by this fiendish Hound!
Extra Fun
Thank you for being patient and making it this far. I expected to have this tutorial out a couple of days ago but it took longer than I expected. Until I get the next tutorial out try some of these challenges:
- Try making a cowardly enemy that flees from the player
- Make a system for handling attacks so the player and enemies can fight each other
- Think about ways to make a loot system
- Refactor anything that you don’t like
I had never really thought about explicitly giving the game a game state in order to control the flow of turns. I had always just built the control flow into the player’s action directly (which was horribly sloppy). I really like this idea of state-driven design. Very nice.
Pingback: Tutorial 5 – Creating a 2D Camera with Pan and Zoom in MonoGame | Creating a Roguelike Game in C#
Error CS0266 Cannot implicitly convert type ‘RogueSharp.Path’ to ‘System.Collections.Generic.IEnumerable’. An explicit conversion exists (are you missing a cast?) ExampleGame c:\users\peter\documents\visual studio 2015\Projects\ExampleGame\ExampleGame\PathToPlayer.cs 36
I dont know what to do
Hello and thank you for visiting. You’re likely experiencing this issue because you’re using version 3.0 pre-release of RogueSharp and the tutorials were written with version 2. I have a branch that addresses the issue here -> https://bitbucket.org/FaronBracy/roguesharpmonogamesamples/branch/RogueSharpV3PreUpdate
Also, you can find more information about this problem on the Reddit post here -> https://www.reddit.com/r/roguelikedev/comments/3r8bac/hello_and_question_regarding_roguesharp/
Thanks for the extra effort! I am using RogueSharp 3.0 – not the pre-release. I found that the project file was already up-to-date. I used most of the code changes. If you are interested, I will provide details. Thanks again for a ‘most excellent’ tutorial.
Hey Faron, the Pathfinder can’t find a path between an enemy and a player for me. I think it’s because I set the ‘isWalkable’ flag to false for each Cell that contains a character (enemy or player), and the pathfinder requires that the End Cell should be walkable. Is there any way you could add an option to ignore the last cell? (or any tip on how to change it myself?).
Cheers and thanks for a great library!
Hi David,
I probably won’t cut a new version of RogueSharp just for that feature but I will definitely consider adding it along with other features in a future release. With that said there are a few options available besides modifying the source.
One option is you could make your own function for finding a path between a source and destination cell which wraps the normal RogueSharp PathFinder. First it could set the Source and Destination cell’s `IsWalkable` to `true`. Then it would find the a path using the PathFinder as normal. After finding the path set the Source and Destination cells `IsWalkable` back to their original values. Not super elegant but it will work and is probably the simplest thing that you can do. Here is an example of that on line 27 -> http://bit.ly/1LCTKZV
Another option is to have your Map just be your level without any characters. The floor tiles are walkable regardless of if they have a character on them or not. Before pathfinding Clone that map using the built in Clone command. Then on the new map iterate through your characters and set the Cell’s IsWalkable to false where there is a character. Skip the source and destination cells. You can also skip cells according to other rules, like maybe monsters can pass through each other. Use that cloned map for your pathfinding. You can discard it when done.
A third option is to use a GoalMap that takes a Map without all your creatures. Then for each creature that you don’t want to be able to pass through, add them as an Obstacle which is a feature built in to the GoalMaps. Then call FindPath on your GoalMap.
If you do decide to update the source code the PathFinder class is here -> http://bit.ly/1RsqGRz
If you get it working well and open a pull request I can include your code in the next release.
Hope one of these will work for you!
Take care and good luck,
Faron
Hey Faron!
Such an easy solution.. and it works! This is my ‘GetPath’ function:
private void UpdatePathToPointOfInterest()
{
Cell origin = currentCell;
bool destinyWalkable = dungeonMap.IsWalkable(pointOfInterest.X, pointOfInterest.Y);
dungeonMap.SetIsWalkable(origin.X, origin.Y, true);
dungeonMap.SetIsWalkable(pointOfInterest.X, pointOfInterest.Y, true);
currentPath = new PathFinder(dungeonManager.map).ShortestPath (origin, pointOfInterest);
dungeonMap.SetIsWalkable(origin.X, origin.Y, false);
dungeonMap.SetIsWalkable(pointOfInterest.X, pointOfInterest.Y, destinyWalkable);
}
I don’t think this solution deserves a Pull Request, but I can add the feature into the Pathfinder class and send it to you if you want.
Cheers,
– David L.
Glad that it worked for you!
Thank you for the tutorial! For a beginner it has been rather interesting.
1. Worth pointing out if you are using RogueSharp 4.0, 4.1 or 4.2 (compared to 3.0) the path will start from the mob so my code ended up looking like this:
public static class Extensions
{
public static Path Skip(this Path path, int count)
{
if (path.Length <= count) { return new Path(path.Steps.Skip(path.Length – 1)); }
return new Path(path.Steps.Skip(count));
}
}
public void Skip(int count)
{
_path = _path.Skip(count);
}
public void CreateFrom(int x, int y)
{
_path = _pathFinder.ShortestPath(
_map.GetCell(x, y),
_map.GetCell(_player.X, _player.Y)
).Skip(1);
}
What is the reason behind this change?
2. Currently debugging mode will draw the tile that the enemy is standing on as their next move after the enemy turn, basically being 1 turn behind. While not a problem it is inconsistent to the path drawn on turn 0. I ended up removing the Start/FirstCell/CurrentStep in Update. Something like this:
// "_path" renamed to "_pathToPlayer"
_pathToPlayer.CreateFrom(X, Y);
X = _pathToPlayer.FirstCell.X;
Y = _pathToPlayer.FirstCell.Y;
if (Global.DEBUG_NEXT_TURN_PATHS)
{
_pathToPlayer.Skip(1);
}
I don't quite understand why we need both CurrentStep and Start properties in Path class if we recalculate the Path every turn anyway, am I missing a case where the enemy loses LoS and should follow the last path? Even then I don't see a reason to store previous cells. Could you also explain the purpose of StepBackward() method and when it should be used?
Hello Karl,
Thank you for visiting, you have a lot of questions here and bring up some good points.
First, it looks like you worked through some issues on your own, but for anyone else reading this if you want to follow the tutorials exactly, it’s important to note that these Monogame + RogueSharp tutorials were written for v2.0 over 6 years ago. If you want to use v3.x, v4.x, or v5.x it will require changes as you pointed out. I did make some changes to help others update to v3 in this branch – https://github.com/FaronBracy/RogueSharpMonoGameSamples/tree/RogueSharpV3PreUpdate but have not kept up with subsequent releases.
As for why we store properties in the Path that aren’t used in the tutorial, it’s because the pathfinding is meant to be used by more than just this small tutorial. For example RogueSharp can also be used can also be used for tactics style games (think X-com or Final Fantasy Tactics). In a turn in those style games, you can move multiple cells in one turn. I also didn’t want to make assumptions for even Roguelikes that a player or monster can only move one cell at a time. Even the example you gave where an enemy loses LoS and should follow the last path could be useful though not implemented in this small tutorial.
As for how StepBackward() is used, one example I’ve seen is it used on Fire Slime type creatures that leave a trail of fire behind them as they travel multiple cells in one turn. Sure, it’s just a convenience method, and there are plenty of ways to accomplish the same thing and the StepBackward() method could be removed from the Path class. My advice would be don’t expect to use every method that is provided in RogueSharp. Don’t assume that just because the tutorial isn’t using something, that it isn’t useful to anyone. One of the benefits of being open source is that if something is really bothering you, you can make a fork and change it or remove it. If you add or remove something that you think would be useful to a broader audience, you can even open a pull request and maybe the change will make it into the next official version.
Be well and have fun with programming,
Faron