The PCG Game Board

Ryan Watkins

December 2015

In this article by Ryan Watkins, author of the book Procedural Content Generation for Unity Game Development, explains the core functionality of the PCG Game Board. The goal is to have tiles laid out in the direction the player character walks. We designed our algorithm in such a way that the Game Board will expand forever.

(For more resources related to this topic, see here.)

We connected our scripts so that when the player moves, the Player class will update the player position and send it to the Game Manager. The Game Manager will then call a method in the Board Manager to update the Game Board and pass along the player's position and direction. We now need to write the code that will update the Game Board based on the player position.

Let's start by adding the function that will update the Game Board in the Board Manager. Open up BoardManager.cs for editing. The following code snippet shows the function that needs to be added:

77 public void addToBoard (int horizontal, int vertical) {
78  if (horizontal == 1) {
79    //Check if tiles exist
80   int x = (int)Player.position.x;
81    int sightX = x + 2;
82    for (x += 1; x <= sightX; x++) {
83      int y = (int)Player.position.y;
84      int sightY = y + 1;
85      for (y -= 1; y <= sightY; y++) {
86        addTiles(new Vector2 (x, y));
87      }
88    }
89  } 
90  else if (horizontal == -1) {
91    int x = (int)Player.position.x;
92    int sightX = x - 2;
93    for (x -= 1; x >= sightX; x--) {
94      int y = (int)Player.position.y;
95      int sightY = y + 1;
96      for (y -= 1; y <= sightY; y++) {
97        addTiles(new Vector2 (x, y));
98      }
99    }
100}
101else if (vertical == 1) {
102  int y = (int)Player.position.y;
103  int sightY = y + 2;
104  for (y += 1; y <= sightY; y++) {
105    int x = (int)Player.position.x;
106    int sightX = x + 1;
107    for (x -= 1; x <= sightX; x++) {
108      addTiles(new Vector2 (x, y));
109    }
110  }
111}
112else if (vertical == -1) {
113  int y = (int)Player.position.y;
114  int sightY = y - 2;
115  for (y -= 1; y >= sightY; y--) {
116    int x = (int)Player.position.x;
117    int sightX = x + 1;
118    for (x -= 1; x <= sightX; x++) {
119      addTiles(new Vector2 (x, y));
120    }
121  }
122}
123}

This function is a switch driven by direction. The base code is set-up to return only a one-directional value at a time. This means our player character can only move one direction at a time. Either the player moves horizontally in the positive or negative x direction forcing the vertical direction to return 0, or vice versa along the y direction. Let's take a closer look at the code:

  • Line 77: addToBoard is a public function returning void. This will be our entry point from the Game Manager class. From the Game Manager, we pass the player direction to this function as arguments.
  • Line 78: This is our first switch point. If horizontal equals 1, then we know vertical is 0. This corresponds to the player moving to the right on screen.
  • Line 80-85: We are using a for loop nested within a for loop to iterate over the player's line of sight. Remember, the line of sight is the six tile spaces directly in front of the player's movement direction. The line of sight makes up a 2 x 3 grid.
  • Line 86: For each tile space we iterate over, we will call the addTiles method and pass in the Vector2 produced by our for loops. addTiles does not exist yet but we will be writing it next.
  • Line 90-122: The rest of the function is simply a variation of lines 78-86. If the player did not move to the right, then we check the other directions and set up the line of sight for that direction.

Next, we will complete our expanding Game Board functionality by writing the addTiles function used in the addToBoard function you just wrote. The main objective of this function is to check our dictionary for the line of sight tiles, and if they are not there, then we add them. The following code snippet shows this function as part of the BoardManager class:

61 private void addTiles(Vector2 tileToAdd) {
62  if (!gridPositions.ContainsKey (tileToAdd)) {
63    gridPositions.Add (tileToAdd, tileToAdd);
64    GameObject toInstantiate = 
65    floorTiles [Random.Range (0, floorTiles.Length)];
66    GameObject instance = 
67      Instantiate (toInstantiate, new Vector3 (tileToAdd.x, 
68      tileToAdd.y, 0f), Quaternion.identity) as GameObject;
69
70    instance.transform.SetParent (boardHolder);
71  }
72 }

This code should seem familiar. We do similar calls in the BoardSetup function of the Board Manager class. Line 62 is the main difference. Here, we check the Dictionary for the tile before we proceed. If the tile is in the Dictionary, we return out of the function. This prevents us from overwriting tiles that have already been placed in the game.

Procedural Content Generation for Unity Game Development

PCG Game Board

You can now return to the Unity Editor and test the new functionality. Press the Play button to try it out. As per our algorithm design, whenever the player moves, more tiles are revealed in that direction.

This Game Board isn't very interesting, though. We can walk in a single direction forever with no opposition. This terrain would also make it very easy to run away from enemies. We should add some wall tiles for obstacles.

Let's return to editing the BoardManager.cs file. We are going to add onto our addTiles function by putting in a condition that adds WallTiles to newly placed Floor Tiles. The following code snippet shows the code addition:

29 public GameObject[] wallTiles;
...
62 private void addTiles(Vector2 tileToAdd) {
63  if (!gridPositions.ContainsKey (tileToAdd)) {
64    gridPositions.Add (tileToAdd, tileToAdd);
65    GameObject toInstantiate = 
66    floorTiles [Random.Range (0, floorTiles.Length)];
67    GameObject instance = 
68    Instantiate (toInstantiate, new Vector3 (tileToAdd.x, 
69    tileToAdd.y, 0f), Quaternion.identity) as GameObject;
70
71    instance.transform.SetParent (boardHolder);
72      
73      //Choose at random a wall tile to lay
74    if (Random.Range (0, 3) == 1) {
75      toInstantiate = 
76      wallTiles[Random.Range (0,wallTiles.Length)];
77      instance = 
78      Instantiate (toInstantiate, new Vector3 (tileToAdd.x, 
79      tileToAdd.y, 0f), Quaternion.identity) as GameObject;
80    instance.transform.SetParent (boardHolder);
81    }
82  }
83 }

Code Snip 3-9

Let's take a look at what we added in the preceding code snippet:

  • Line 29: Like the Floor Tiles, we are going to add an array of to hold our Wall Tile prefabs.
  • Line 62-68: This is our original addTiles function.
  • Line 70: This conditional uses random numbers to create a probability. We randomly choose a number between 0 and 2. If the number is 1, then we add a Wall Tile to the newly created Floor Tile. This is a 1 in 3 or 33 percent chance that a Wall Tile is added.
  • Line 71-74: We instantiate the Wall Tiles as we do the Floor Tiles.

So, if we return to the Unity Editor and play the game, we get some errors. This is because we added the array for the Wall Tiles, but it is currently empty. You will need to add the Wall Tiles to the GameManager prefab the same way you did the Floor Tiles.

In the BoardManager script component of the GameManager prefab:

  • Set the Size under Wall Tiles to 8 and press Enter
  • Drag and drop Wall1-Wall8 into the newly created Element0-Element7 under Wall Tiles

Finally, the Game Board is fully functional! Press the Play button to test it out. The Game Board with expand as the player explores. Every time you play the game you will experience a different Game Board.

Procedural Content Generation for Unity Game Development

PCG Game Board plus Wall Tiles

With the addition of high frequency wall spawning, there are plenty of obstacles. These walls will also make it more difficult to run from enemies. The PCG nature of the Game Board makes for a unique play of the game every time.

Summary

Our rogue-like game is coming along. You completed your first PCG feature from design to development. There is still plenty more to do though. Take a look at what you covered in this article:

  • You learned about and analyzed a few different data structures
  • You designed an algorithm that would expand the Game Board as the player explores
  • You set up the scene so we could implement our PCG algorithm by adding Layers and a player-tracking camera feature
  • You implemented our algorithm design and created a procedurally generated game world.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Procedural Content Generation for Unity Game Development

Explore Title