Procedural Dungeon Generation Part3

During the Part2, we updated the dungeonMap to have all coordinates in the same room share the same numerical value. However, all rooms are unconnected and that would make a pretty boring dungeon. We’ll correct that!

Add corridors
The goal of this step is to add corridor and make sure that all rooms are connected together and are all accessible. In the following image, you can see an updated version of the previously created dungeon with corridors. Each room now have a corridor starting from a random position in it(C#_Start) going to another random position in a random room/corridor(C#_End).

PDG Step3

Procedural Dungeon Generation – Step 3

Here is the whole CreateDungeonHalls() method that update the dungeonMap with added corridors. The code below will be split in 3 part below and explained individually.

// Create a hall starting from each room, connecting to another room or corridor
static private int[,] CreateDungeonHalls(int[,] _dungeonMap, int _sizeX,int _sizeZ, int nbrRoom)
{
	int _x1; // x coordinate of the starting position
	int _z1; // z coordinate of the starting position
	int _x2; // x coordinate of the ending position
	int _z2; // z coordinate of the ending position

	// Start a corridor from each room
	for(int _curRoomNbr = 1; _curRoomNbr <= nbrRoom; _curRoomNbr++)
	{
		int nbrRoomsTry = 0; // Counter is used to avoid looping forever if there is a bug in the program.
		int _nbrRoomsTryMax = 5000;

		// Find a random coordinate in the room with number _curRoomNbr
		_x1 = Random.Range (1, _sizeX-1);
		_z1 = Random.Range (1, _sizeZ-1);

		// Find a random position with the current room number
		while(_dungeonMap[_x1,_z1] != _curRoomNbr && nbrRoomsTry < _nbrRoomsTryMax)
		{
			_x1 = Random.Range (1, _sizeX-1);
			_z1 = Random.Range (1, _sizeZ-1);
			nbrRoomsTry++;
		}
		nbrRoomsTry = 0;

		// Find a random coordinate in any room/corridor that isn't the first room
		_x2 = Random.Range (1, _sizeX-1);
		_z2 = Random.Range (1, _sizeZ-1);

		// Find a random position in a different room or corridor created from a previous room
		while((_dungeonMap[_x2,_z2] == 0 || _dungeonMap[_x2,_z2] == _curRoomNbr) && nbrRoomsTry < _nbrRoomsTryMax)
		{
			_x2 = Random.Range (1, _sizeX-1);
			_z2 = Random.Range (1, _sizeZ-1);
			nbrRoomsTry++;
		}

		// Difference between both coordinates on each axis. This is used to find direction of corridor to create
		int _diffX = _x2 - _x1;
		int _diffZ = _z2 - _z1;

		int _xDirection = 1; // Coefficient used to loop in the right direction on x axis(-1 or 1)
		int _zDirection = 1; // Coefficient used to loop in the right direction on z axis(-1 or 1)

		// Evaluate direction of the corridor on the X and Z axis
		if(_diffX != 0)
		{
			_xDirection = _diffX/Mathf.Abs(_diffX); // 1 = Left to Right (->) ... -1 =  Right to Left (<-)
		}
		else
		{
			_xDirection = 0; // No horizontal corridor if they are aligned on X axis
		}

		if(_diffZ != 0)
		{
			_zDirection = _diffZ/Mathf.Abs(_diffZ); // 1 = Top to Bottom( \/ ) ... -1 = Bottom to Top ( /\ )
		}
		else
		{
			_zDirection = 0; // No vertical corridor if they are aligned on Z axis
		}

		// randomize corridor portion and height
		int _hallWidth  = Random.Range (4,10);
		int _hallHeight = Random.Range (4,10);

		//Create vertical part of the hall
		for(int i = _x1; i != _x1 + _xDirection*_hallWidth; i += _xDirection)
		{
			for(int j = _z1; j != _z2; j += _zDirection)
			{
				if(i >= 0 && i < _sizeX && j >= 0 && j < +_sizeZ) // Make sure that the index is within the map range
				{
					if(_dungeonMap[i,j] == 0) // Only modify empty position, not rooms position
					{
						_dungeonMap[i,j] = -1; // Write corridor as -1 in the dungeonMap
					}
				}
			}
		}

		//Create horizontal portion of the hall
		for(int i = _x1; i != _x2; i += _xDirection)
		{
			for(int j = _z2; j != _z2 + _zDirection*_hallHeight; j += _zDirection)
			{
				if(i >= 0 && i < _sizeX && j >= 0 && j < +_sizeZ) // Make sure that the index is within the map range
				{
					if(_dungeonMap[i,j] == 0) // Only modify empty position, not rooms position
					{
						_dungeonMap[i,j] = -1; // Write corridor as -1 in the dungeonMap
					}
				}
			}
		}
	}
	return _dungeonMap; // The _dungeonMap contains 0 for non-room, -1 for corridor and N for rooms
}

¸
First part of the method (line 10-38) simply find a random position to start a corridor from each room and an ending position in another room or in a previously added corridor.

// Start a corridor from each room
	for(int _curRoomNbr = 1; _curRoomNbr <= nbrRoom; _curRoomNbr++)
	{
		int nbrRoomsTry = 0; // Counter is used to avoid looping forever if there is a bug in the program.
		int _nbrRoomsTryMax = 5000;

		// Find a random coordinate in the room with number _curRoomNbr
		_x1 = Random.Range (1, _sizeX-1);
		_z1 = Random.Range (1, _sizeZ-1);

		// Find a random position with the current room number
		while(_dungeonMap[_x1,_z1] != _curRoomNbr && nbrRoomsTry < _nbrRoomsTryMax)
		{
			_x1 = Random.Range (1, _sizeX-1);
			_z1 = Random.Range (1, _sizeZ-1);
			nbrRoomsTry++;
		}
		nbrRoomsTry = 0;

		// Find a random coordinate in any room/corridor that isn't the first room
		_x2 = Random.Range (1, _sizeX-1);
		_z2 = Random.Range (1, _sizeZ-1);

		// Find a random position in a different room or corridor created from a previous room
		while((_dungeonMap[_x2,_z2] == 0 || _dungeonMap[_x2,_z2] == _curRoomNbr) && nbrRoomsTry < _nbrRoomsTryMax)
		{
			_x2 = Random.Range (1, _sizeX-1);
			_z2 = Random.Range (1, _sizeZ-1);
			nbrRoomsTry++;
		}

Second part of the method (line 40-64) calculate the difference between the start/end position on the X and Z axis. It also calculate a coefficient to increment in the good direction while creating the corridor.

		// Difference between both coordinates on each axis. This is used to find direction of corridor to create
		int _diffX = _x2 - _x1;
		int _diffZ = _z2 - _z1;

		int _xDirection = 1; // Coefficient used to loop in the right direction on x axis(-1 or 1)
		int _zDirection = 1; // Coefficient used to loop in the right direction on z axis(-1 or 1)

		// Evaluate direction of the corridor on the X and Z axis
		if(_diffX != 0)
		{
			_xDirection = _diffX/Mathf.Abs(_diffX); // 1 = Left to Right (->) ... -1 =  Right to Left (<-)
		}
		else
		{
			_xDirection = 0; // No horizontal corridor if they are aligned on X axis
		}

		if(_diffZ != 0)
		{
			_zDirection = _diffZ/Mathf.Abs(_diffZ); // 1 = Top to Bottom( \/ ) ... -1 = Bottom to Top ( /\ )
		}
		else
		{
			_zDirection = 0; // No vertical corridor if they are aligned on Z axis
		}

Last part of the method (line 66-101) start by calculating a random width and height for the new corridor. The map is then updated with the vertical portion of the corridor first and the horizontal portion of the corridor second. Corridor are a simple corner for now. It would be possible to add randomness/more corner or connect them in a different way by modifying this part. It would also be possible to customize the corridors width/height depending on the room size/distance between rooms and such.

To create the corridors, we take the previously evaluated direction on the X axis and step toward that direction with the corridor width. This corridor is filled on the Z axis from z1 to z2. Same thing is done with the horizontal part of the corridor.

		// randomize corridor portion and height
		int _hallWidth  = Random.Range (4,10);
		int _hallHeight = Random.Range (4,10);

		//Create vertical part of the hall
		for(int i = _x1; i != _x1 + _xDirection*_hallWidth; i += _xDirection)
		{
			for(int j = _z1; j != _z2; j += _zDirection)
			{
				if(i >= 0 && i < _sizeX && j >= 0 && j < +_sizeZ) // Make sure that the index is within the map range
				{
					if(_dungeonMap[i,j] == 0) // Only modify empty position, not rooms position
					{
						_dungeonMap[i,j] = -1; // Write corridor as -1 in the dungeonMap
					}
				}
			}
		}

		//Create horizontal portion of the hall
		for(int i = _x1; i != _x2; i += _xDirection)
		{
			for(int j = _z2; j != _z2 + _zDirection*_hallHeight; j += _zDirection)
			{
				if(i >= 0 && i < _sizeX && j >= 0 && j < +_sizeZ) // Make sure that the index is within the map range
				{
					if(_dungeonMap[i,j] == 0) // Only modify empty position, not rooms position
					{
						_dungeonMap[i,j] = -1; // Write corridor as -1 in the dungeonMap
					}
				}
			}
		}
	}
	return _dungeonMap; // The _dungeonMap contains 0 for non-room, -1 for corridor and N for rooms
}

Now that the map dungeonMap is updated with all the rooms and corridors, we need to find a list of all wall to create and instantiate them in the dungeon scene. This will be the topic of the next 2 posts.

Update : I’m currently updating the way I do part 4 and 5. They are accessible on the Dungeon Grind repository on GitHub but, they can’t be buggy or in an imperfect state. You can however look for inspiration on how to continue. The most important file is DungeonGenerator.cs and DungeonManager.cs can be useful.

Advertisements

One thought on “Procedural Dungeon Generation Part3

  1. Pingback: Dungeon Grind – Procedural Dungeon Generation Tutorial | LearningGeek Blog

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