Entries tagged with 'boulder dash'

Boulder Dash Part 2: Collision Detection

In our previous post we introduced the Firefly and Butterfly sprites and their movement rules around a random map. Now we're going to update the project to include collision detection and a new player sprite.

Refactoring AiTest

To start with though, we're going to do a little refactoring. The ButterflySprite and FireflySprite classes share pretty much the same movement code and will share exactly the same collision code, so we'll merge the common behaviour into a new abstract EnemySprite class which these will inherit from. We'll also add a protected constructor which will allow us to specify the differences between the inherited sprites and their movement rules.

protected EnemySprite(Direction preferredDirection, Direction fallbackDirection)
{
  this.PreferredDirection = preferredDirection;
  this.FallbackDirection = fallbackDirection;
}

protected Direction FallbackDirection { get; set; }

protected Direction PreferredDirection { get; set; }

With that done, we'll change FireflySprite and ButterflySprite to inherit from EnemySprite instead of Sprite, and update the constructors of these classes to call the protected constructor to supply the movement rule differences.

Finally, we'll remove the Move methods from the two sprite classes and instead let EnemySprite implement the movement code.

public ButterflySprite()  : base(Direction.Right, Direction.Left)

 public FireflySprite()  : base(Direction.Left, Direction.Right)

Although I won't go into this here, other refactoring we did was to move the Sprites collection from MainForm into Map. I also added an IsScenery method to the Map class which returns if a tile is considered scenery, for example a piece of solid earth, or a boulder which can't currently move.

A basic load map system was also added. You can still use the "Create Random Map" to generate a mostly empty canvas for the sprites to move around in (clicking with the left button will add a new firefly, with the right a butterfly) or you can load the predefined map.

Collision Detection

Now it's time to implement the actual collision detection. We'll do this by adding a new function to the base Sprite class that will check to see if a the location of any sprite matches a given location.

Note: This implementation assumes that only one sprite can occupy a tile at any one time, which is the case in Boulder Dash.

We're also using LINQ in this function for convenience. If you haven't yet upgraded to Visual Studio 2008/2010 you'll need to replace the call with a manual loop

public bool IsCollision(Point location, out Sprite sprite)
{
  sprite = this.Map.Sprites.SingleOrDefault(s => s.Location == location);

  return sprite != null;
}

I choose to implement the function as a bool to allow it to be easily used in an if statement, but providing an out parameter to return the matching sprite (or null otherwise).

With that done, it's time to update our movement code to also perform the collision detection. The two conditions in the Move method which check if a tile is part of the scenery will be modified to call out new method.

if (!this.Map.IsScenery(tile) && !this.IsCollision(tile.Location, out collision))

With this change, sprites on the map are now aware of each other and when they bump into each other they will automatically turn away.

Collision Actions

Our example project now has collision detection in place for the enemy sprites. Being enemies of the player nothing happens when they bump into each other. If they bump into the player on the other hand...

Time to add a new sprite. The PlayerSprite will be a non functioning sprite masquerading as a player character.

class PlayerSprite : Sprite
{
  public override void Move()
  {
    // Do nothing, this sprite doesn't automatically move
  }

  public override Color Color
  {
    get { return Color.Aquamarine; }
  }
}

In our previous modification the Move method of our EnemySprite implementations we grab the sprite that we are colliding with, but we don't do anything with it. Time to change that.

We'll add a basic enum that will control what happens when a sprite hits another. For this demo, that will either be nothing, or "explode" killing both sprites.

enum CollisionAction
{
  None,
  Explode
}

We're also going to modify the base Sprite class with a new method:

public abstract CollisionAction GetCollisionAction(Sprite collidedWith);

Sprite implementations will override this method and return a CollisionAction based on the sprite they collided with.

The implementation for our new Player class is quite straightforward:

public override CollisionAction GetCollisionAction(Sprite collidedWith)
{
  return CollisionAction.Explode; // Player dies if it touches any other sprites
}

And the one for EnemySprite is almost as easy:

public override CollisionAction GetCollisionAction(Sprite collidedWith)
{
  CollisionAction result;

   if (collidedWith is PlayerSprite)
    result = CollisionAction.Explode; // Kill player
  else
    result = CollisionAction.None; // Do nothing

  return result;
}

Now we have this, we'll update the Move method of our EnemySprite to take care of the action:

// if we collided with a sprite, lets execute the action
if (collision != null && this.GetCollisionAction(collision)== CollisionAction.Explode)
{
  // kill both this sprite and the one we collided with
  this.Map.Sprites.Remove(collision);
  this.Map.Sprites.Remove(this);
}

Note that if the Player could move as well then it too would need collision detection. However, as we only have one class capable of movement we'll add the code just to that for now.

Also note that we had to adjust the original NextMove method in MainForm otherwise it would crash when looping through the sprite list and a removal occurred.

for (int i = _map.Sprites.Count; i > 0; i--)
  _map.Sprites[i - 1].Move();

Sample Project

You can download an updated version of the sample project from the link below.

Downloads:

  • aitest2.zip

    (14.13 KB | 07 July 2010 )

    Sample project for implementing collision detection in the sprites of the Boulder Dash (Boulderdash) arcade game.

Post a Comment | | Trackback specific URL for this entry

Boulder Dash Part 1: Implementing Sprite AI

One of the projects I've had on the backburner for over a year now was a Boulder Dash clone. While I was working on this clone I written a basic game engine using GDI, another using managed DirectX, editing tools, and even a conversion tool for the BDCFF. Everything but the game itself.

After working pretty much nonstop on the Sitemap Creator and WebCopy tools recently, I wanted to take things a bit easy between releases and wanted to resurrect this project.

If you haven't heard of Boulder Dash you're missing out on some classic gaming of yesteryear. Basically, it involved collecting a given number of diamonds in a cave, and there were various enemies (butterflies and fireflies) and game elements (diamonds, boulders, various types of walls, slime, amoeba) which you use to beat each cave. There's lots more than this basic synopsis of course, but it covers the essential elements you will see.

This series of articles will describe some of the design of the game using sample projects to demonstrate the different elements, starting with the AI of the enemies.

In Boulder Dash, enemies don't follow a specific path, nor do they chase you as such. Instead, they are governed by a series of rules.

Firefly Movement Rules

  • if the space to the firefly's left is empty then turn 90 degrees to firefly's left and move one space in this new direction
  • otherwise if the space ahead is empty then move one space forwards
  • otherwise turn to the right, but do not move

This pattern means a firefly can instantly turn left, but takes double the time when turning right.

Butterfly Movement Rules

The butterfly shares the same basic rules as the firefly, the exception being that the directions are reversed. For the butterfly, the preferred turning direction is right rather than left. So the butterfly can instantly turn right, but is slower at moving left.

The sample project

The sample project (available to download from the link below) creates a basic testing environment. A map is randomly generated to which you can add fireflies or butterflies. A directional arrow displays the current facing of the sprites. Each second the sprites will be updated.

In this first article we aren't interested in further topics such as collision detection, we just want to make sure our sprites move according to the rules above.

The basic logic for each sprite is:

  • can I move in my preferred direction?
  • can I move straight ahead?

If the answer to either of these questions is "Yes", then our sprite will move. If "No", then it will turn in the opposite direction to its preferred direction.

In Boulder Dash, each cave (level) is comprised of a grid of tiles, nothing fancy. The player can move up, down, left or right, but not diagonally. All other game elements are constrained in the same way.

The following snippet shows the movement logic for the Firefly:

      // first see if we can move in our preferred direction, left
      tile = this.GetAdjacentTile(this.GetNewDirection(Direction.Left));
      if (!tile.Solid)
      {
        // we can move here, update our position and also set our new direction
        this.Location = tile.Location;
        this.Direction = this.GetNewDirection(Direction.Left);
      }
      else
      {
        // can't move in our preferred direction, so lets try the direction the sprite is facing
        tile = this.GetAdjacentTile(this.Direction);
        if (!tile.Solid)
        {
          // we can move here, update our position, but not the direction
          this.Location = tile.Location;
        }
        else
        {
          // can't move forwards either, so finally lets just turn right
          this.Direction = this.GetNewDirection(Direction.Right);
        }

The above code relies on two helper methods, one to return a new direction based on the current direction, and a second to return an adjacent cell from a given direction.

GetNewDirection

The GetNewDirection method below calculates a new direction based on the current sprites direction and a new facing of either left or right.

    public Direction GetNewDirection(Direction turnDirection)
    {
      Direction result;

      switch (turnDirection)
      {
        case Direction.Left:
          result = this.Direction - 1;
          if (result < Direction.Up)
            result = Direction.Right;
          break;
        case Direction.Right:
          result = this.Direction + 1;
          if (result > Direction.Right)
            result = Direction.Up;
          break;
        default:
          throw new ArgumentException();
      }

      return result;
    }

GetAdjacentTile

The GetAdjacentTile method simply returns the text next to the current sprite in a given direction.

    public Tile GetAdjacentTile(Direction direction)
    {
      Tile result;

      switch (direction)
      {
        case Direction.Up:
          result = this.Map.Tiles[this.Location.X, this.Location.Y - 1];
          break;
        case Direction.Left:
          result = this.Map.Tiles[this.Location.X + 1, this.Location.Y];
          break;
        case Direction.Down:
          result = this.Map.Tiles[this.Location.X, this.Location.Y + 1];
          break;
        case Direction.Right:
          result = this.Map.Tiles[this.Location.X - 1, this.Location.Y];
          break;
        default:
          throw new ArgumentException();
      }

      return result;
    }

Once the sample has gotten a tile, it will check to see if the sprite can move into the tile. For our example, we are just using a bit flag to state if the tile is solid or not, but in future we'll need to add collision detection for all manner of game elements.

If the sprite can move into the first tile into it's preferred direction, it will do this. Otherwise, the movement routine will next check to see if the tile in front of the sprite is solid, and if so again it will move. If neither of the two movements were possible then it will update it's current facing to be the opposite of it's preferred direction. The process will be repeated for each "scan" of the game elements.

Using these rules it is quite easy to setup scenarios where the sprites can "guard" a game element by endless circling it. And just as easily the unwary player will be chased mercilessly if they are unwary.

Please let us know if you'd like to see more of this type of article here on cyotek!

Edit 07/07/2010: Please also see Boulder Dash Part 2: Collision Detection

Downloads:

  • aitest.zip

    (11.27 KB | 19 June 2010 )

    Sample project for implementing the AI of the Butterfly and Firefly sprites of the Boulderdash arcade game.

Post a Comment | | Trackback specific URL for this entry

Recent News

Recent Articles

Most Popular

Tags

Advertisments