# Procedural Generation in Godot - Part 8: Dungeons (part 3)

###### Tags: godotgamedevtutorialprocgen

In this series, we’ll explore the applications of procedural generation to game development. While we’ll be using Godot 3.0 as our platform, much of the concepts and algorithms related to this subject are universal, and you can apply them to whatever platform you may be working on.

In this part, we’ll wrap up the procedurally generated dungeon by making it into a TileMap that we can explore.

You can watch a video version of this lesson here:

# Generating a TileMap

Now that we’ve generated the rooms and connected them with a path, there’s one more step to having a dungeon to explore. We will use Godot’s TileMap node to create the walls. In a nutshell, we’ll fill the entire map with walls, carve out the locations of our rooms, and then carve paths between them according to our minumum spanning tree.

The tileset we’ll be using is a simple one with two tiles:

Grass tile: Stone tile:

Add a `TileMap` node to the Main scene and set its Cell/Size to `(32, 32)`. Add the `res://assets/tiles.tres` TileSet resource to the Tile Set property (you can find this in the project download at the end of this document).

Since we still have the visualization running of our room generation, we’ll manually trigger building the TileMap by pressing `<Tab>`. Add this to `_input()`:

``````if event.is_action_pressed('ui_focus_next'):
make_map()``````

And then we’ll define the `make_map()` function:

``````func make_map():
# Creates a TileMap from the generated rooms & path
# find_start_room()
# find_end_room()
Map.clear()

# Fill TileMap with walls and carve out empty spaces
var full_rect = Rect2()
for room in \$Rooms.get_children():
var r = Rect2(room.position-room.size,
room.get_node("CollisionShape2D").shape.extents*2)
full_rect = full_rect.merge(r)
var topleft = Map.world_to_map(full_rect.position)
var bottomright = Map.world_to_map(full_rect.end)
for x in range(topleft.x, bottomright.x):
for y in range(topleft.y, bottomright.y):
Map.set_cell(x, y, 1)

# Carve rooms and corridors
var corridors = []  # One corridor per connection
for room in \$Rooms.get_children():
var s = (room.size / tile_size).floor()
var pos = Map.world_to_map(room.position)
var ul = (room.position/tile_size).floor() - s
for x in range(2, s.x * 2-1):
for y in range(2, s.y * 2-1):
Map.set_cell(ul.x+x, ul.y+y, 0)

# Carve corridors
var p = path.get_closest_point(Vector3(room.position.x,
room.position.y, 0))
for conn in path.get_point_connections(p):
if not conn in corridors:
var start = Map.world_to_map(Vector2(path.get_point_position(p).x, path.get_point_position(p).y))
var end = Map.world_to_map(Vector2(path.get_point_position(conn).x, path.get_point_position(conn).y))
carve_path(start, end)
corridors.append(p)``````

In the first part, we’re finding the rectangle that encloses the entire map, using the `Rect2` method `merge()`. Then we fill the entire TileMap with stone tiles.

Note: For very large and/or sparse maps, this may take a bit of time. In those cases you may prefer to start with a pre-generated “solid” map and only do the “carving” for your created rooms.

The next step is to carve the rooms and corridors. We loop through the rooms and convert their coordinates to map space (`world_to_map()`). Note we’re carving out the room a bit smaller than the room’s actual size. This is so that adjacent rooms will still have walls between them, rather than merging into a single large room.

## Carving corridors

To make the corridors, we check the mst `path` and carve a path for each connection the current room has. So that we don’t carve the same path twice, we keep a list (`corridors`) of the ones we’ve already done.

Finally, we need to call the `carve_path()` function to actually make the connection:

``````func carve_path(pos1, pos2):
# Carves a path between two points
var x_diff = sign(pos2.x - pos1.x)
var y_diff = sign(pos2.y - pos1.y)
if x_diff == 0: x_diff = pow(-1.0, randi() % 2)
if y_diff == 0: y_diff = pow(-1.0, randi() % 2)
# Carve either x/y or y/x
var x_y = pos1
var y_x = pos2
if (randi() % 2) > 0:
x_y = pos2
y_x = pos1
for x in range(pos1.x, pos2.x, x_diff):
Map.set_cell(x, x_y.y, 0)
Map.set_cell(x, x_y.y+y_diff, 0)  # widen the corridor
for y in range(pos1.y, pos2.y, y_diff):
Map.set_cell(y_x.x, y, 0)
Map.set_cell(y_x.x+x_diff, y, 0)  # widen the corridor``````

There are a few important things going on in this function. First, we calculate the direction of the `x` and `y` difference in the start and end positions. If it’s `0`, we pick a random direction. This is because we’re going to carve corridors that are 2 tiles wide.

Second, we need to randomize which of two options we choose when carving the path:

# Adding a Player

As the final step, we’re going to add a player-controlled character that can walk around the map. `Character.tscn` and `Character.gd` are a generic top-down KinematicBody2D character with an attached camera, which we’ll instance when the dungeon is complete.

Add the following new variables to the main script:

``````var Player = preload("res://Character.tscn")

var start_room = null
var end_room = null
var play_mode = false
var player = null``````

Uncomment `find_start_room()` and `find_end_room()` from the start of `make_map()` and add the following functions:

``````func find_start_room():
var min_x = INF
for room in \$Rooms.get_children():
if room.position.x < min_x:
start_room = room
min_x = room.position.x

func find_end_room():
var max_x = -INF
for room in \$Rooms.get_children():
if room.position.x > max_x:
end_room = room
max_x = room.position.x``````

These functions pick the leftmost room as the `start_room` and the rightmost as the `end_room`. By making them separate functions, you can change the criteria as you choose.

As with the map generation, we’re going to manually choose “play mode” by pressing a key (`<esc>` in this case). Make the following changes to `_input()`:

``````func _input(event):
# Spacebar restarts and creates a new set of rooms
if event.is_action_pressed('ui_select'):
if play_mode:
player.queue_free()
play_mode = false
Map.clear()
for n in \$Rooms.get_children():
n.queue_free()
path = null
start_room = null
end_room = null
make_rooms()
# Tab generates the TileMap
if event.is_action_pressed('ui_focus_next'):
make_map()
# Esc spawns a player to explore the map
if event.is_action_pressed('ui_cancel'):
player = Player.instance()
add_child(player)
player.position = start_room.position
play_mode = true``````

# Conclusion

That wraps up our randomly generated dungeon tutorial. We used the physics engine to distribute the rooms, learned how to use a minimum spanning tree to connect them together, and turned the whole thing into a TileMap that can be used in a game.

Obviously this dungeon is very plain and uninteresting. Decorating the dungeon and populating it with treasure, monsters, and other features is beyond the scope of this demo (but might make a great future one!).

Please comment below with your questions and suggestions.