Procedural Level Generation in Games using a Cellular Automaton: Part 2

A tutorial on procedural level generation using a cellular automaton to create cave-like levels in games. By Kim Pedersen.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Handling treasure chests and exit

The collision handling between the knight and cave is in place, but you still need to create the code for handling the collisions between the knight and the treasure chests, as well as define what happens when the knight reaches the exit.

You'll handle this collisions in a similar way to how you handled collisions with the walls. The only major difference is the exit and treasure chests should not block the knight from walking through them.

Your strategy is to determine the cell type underneath the center point of the knight. If the cell type is either a CaveCellTypeExit or CaveCellTypeTreasure, then you'll handle the collisions by taking an appropriate action.

Add the following code to the update: method in MyScene.m just after the comment // Insert code to detect if player reached exit or found treasure here:

CaveCell *cell = [self.cave caveCellFromGridCoordinate:
  [self.cave gridCoordinateForPosition:self.player.position]];

switch (cell.type) {
  case CaveCellTypeExit:
    NSLog(@"Found the exit");
    break;
    
  case CaveCellTypeTreasure:
    NSLog(@"Found treasure");
    break;
    
  default:
    break;
}

First, it determines the type of cell underneath the player. Depending on the cell type it writes a different log statement to the console.

Build and run the project again, and move the knight over the exit and/or treasure cells to see that these collisions are detected.

Now that you know the collision logic works as expected, add the code to handle the collisions. First, add this method to MyScene.m to resolve the collision with the exit:

- (void)resolveExit
{
  // Disable the joystick to ensure the player cannot move around after reaching the exit
  self.isExitingLevel = YES;
  
  // Create actions to play the sound file for reaching the exit and add a block to transition
  // to the next cave
  SKAction *soundAction = [SKAction playSoundFileNamed:@"fanfare.mp3" waitForCompletion:NO];
  SKAction *blockAction = [SKAction runBlock:^{
    [self.view presentScene:[[MyScene alloc] initWithSize:self.size] transition:[SKTransition
      doorsCloseVerticalWithDuration:0.5f]];
  }];
  SKAction *exitAnimAction = [SKAction sequence:@[[SKAction group:@[soundAction]], blockAction]];
  
  // Run the action sequence
  [self.player runAction:exitAnimAction];
}

This is basic Sprite Kit code that plays a sound and presents a new scene when the knight emerges from the cave. The first line is important: it sets isExitingLevel to YES, which update: checks to disable the knight's movement.

Go back to update: and replace the line NSLog(@"Found the exit"); with the following:

[self resolveExit];

Do a build and run, and lead your knight to the exit. Once you pass through it, you'll hear a victorious fanfare and a new random cave will generate.

Epic Loot!

The only thing left to do is to add the code to resolve collisions with treasure chests. Add this final method to MyScene.m:

- (void)resolveTreasureInCell:(CaveCell *)cell
{
  // Make this cell into a floor
  cell.type = CaveCellTypeFloor;
  
  // Calculate the position of the cell within the cave
  CGPoint cellPosition = CGPointMake(
    cell.coordinate.x * self.cave.tileSize.width + self.cave.tileSize.width / 2,
    (cell.coordinate.y * self.cave.tileSize.height + self.cave.tileSize.height / 2));
  
  // Get the node at the point of this cell
  SKNode *node = [self.cave nodeAtPoint:cellPosition];
  
  if (node) {
    // Get the treasure child node
    if ([node.name isEqualToString:@"TREASURE"]) {
      node = node.parent;
    }
    
    // Remove the treasure child sprite node
    [node removeAllChildren];
    
    // Play a sound effect for picking up the treasure
    [node runAction:[SKAction playSoundFileNamed:@"treasure.wav" waitForCompletion:NO]];
  }
}

First, the cell converts to a floor cell, which prevents future collisions with this cell. Then it calculates the position of the cell within the cave, which gets the treasure chest sprite node from the cell. The treasure sprite is removed and the game plays a sound file to signal the knight found the treasure.

Finally, change update: in MyScene.m by replacing the line NSLog(@"Found treasure"); with the following:

[self resolveTreasureInCell:cell];

Build and run. Take the knight on an adventure in the cave and collect as much treasure you can before emerging into the next level of the cave.

Where to Go From Here?

Congratulations! Now that you're at the end of this tutorial, you should have a good understanding of how to implement cellular automaton to create cave-like levels for games.

You also know a thing or two about how to overcome some of the challenges of using a cellular automaton. If you want to see a copy of the game in its entirety, download the final project. It has all the code from the above tutorial.

To take your learnings a step further, experiment with your cave generation code and find ways to improve it.

For instance, you can combine the A* path-finding with removing very small disconnected caverns. This, combined with finding a cell in the main cavern nearby the disconnected cavern, will make the path-finding a lot less destructive. If you want to preserve more of the original structure of the cavern, this is will help.

Also, try to make the tiles in the cave a bit more random, and you can start with some of the extra textures included in the texture atlas. Our book iOS Games by Tutorials has some great examples on how to do this, so be sure to pick up a copy to catapult your skills to the next level.

If you have any comments or suggestions related to this tutorial, please join the discussion below.

Kim Pedersen

Contributors

Kim Pedersen

Author

Over 300 content creators. Join our team.