Tutorial 4 – Roguelike Pathfinding using RogueSharp and MonoGame

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.

Aggressive Hound

Aggressive Hound

White Texture

White Texture

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“.

The code so far

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

The code so far

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

Debugging Mode

Debugging Mode

The code so far

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. 🙂

The code so far

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:

Hound Bug

Hound Bug

The code so far

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 code so far

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.

The code so far

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.

The code so far

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:

Path To Player

Path To Player

The code so far

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!

Unleash The Hound

Unleash The Hound

The last commit

The final code for tutorial 4

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
Advertisements

9 thoughts on “Tutorial 4 – Roguelike Pathfinding using RogueSharp and MonoGame

  1. Joe Byall

    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.

    Reply
  2. Pingback: Tutorial 5 – Creating a 2D Camera with Pan and Zoom in MonoGame | Creating a Roguelike Game in C#

  3. Peter

    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

    Reply
    1. Faron Bracy Post author

      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/

      Reply
      1. Leonard

        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.

  4. David León (@xDavidLeon)

    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!

    Reply
    1. Faron Bracy Post author

      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

      Reply
      1. David

        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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s