This is a simple yet powerful technique for procedural dungeon map generation. Throughout this guide I’ll show you how you can create dungeons similar to one found in the title “Binding of Issac”
To begin, we’ll want to create a room scene with a (placeholder) sprite and certain properties.
In the room node you want to add a few variables that help navigate when generating your map.
- adjacent_rooms will point to the future rooms that you would like to connect to
- num_of_adjacent_rooms can help you manipulate future dungeon generation if you want to limit the number of connections a specific type of room has or if you want to create a boss or trophy room
- room_type: for future room type generation (I will be posting this in a future guide)
extends Node2D
var adjacent_rooms = {
Vector2(1, 0): null,
Vector2(-1, 0): null,
Vector2(0, 1): null,
Vector2(0, -1): null
}
var num_of_adjacent_rooms = 0
var room_type = null
Create new script for map generation…
extends Node
var room = preload("res://scenes/Room.tscn")
var map_size = 11
var generation_chance = 20
the generate function will enable you to procedurally develop your maps. You begin with initializing your variables, your map, the size, and the first room. while iterating through your dungeon you want to create possible adjacent rooms to stem from your keys.
- When we are iterating through each of the rooms, notice that we are doing this chronologically. This will influence your procedural generation. Maybe you don’t want to constantly create new rooms starting from the origin? Maybe you would like a “longer” dungeon over a “wider” one.
func generate():
var room_map = {}
print(typeof(room_map))
var size = 0
room_map[Vector2(0,0)] = room.instance()
#print("dungeon at the start " , dungeon)
size += 1
while(size < map_size):
for i in room_map.keys():
var direction = int(rand_range(0,4))
match direction:
0:
var new_room_position = i + Vector2(1, 0)
if(!room_map.has(new_room_position)):
room_map[new_room_position] = room.instance()
size += 1
if(room_map.get(i).adjacent_rooms.get(Vector2(1, 0)) == null):
connect_rooms(room_map.get(i), room_map.get(new_room_position), Vector2(1, 0))
1:
var new_room_position = i + Vector2(-1, 0)
if(!room_map.has(new_room_position)):
room_map[new_room_position] = room.instance()
size += 1
if(room_map.get(i).adjacent_rooms.get(Vector2(-1, 0)) == null):
connect_rooms(room_map.get(i), room_map.get(new_room_position), Vector2(-1, 0))
2:
var new_room_position = i + Vector2(0, 1)
if(!room_map.has(new_room_position)):
room_map[new_room_position] = room.instance()
size += 1
if(room_map.get(i).adjacent_rooms.get(Vector2(0, 1)) == null):
connect_rooms(room_map.get(i), room_map.get(new_room_position), Vector2(0, 1))
3:
var new_room_position = i + Vector2(0, -1)
if(!room_map.has(new_room_position)):
room_map[new_room_position] = room.instance()
size += 1
if(room_map.get(i).adjacent_rooms.get(Vector2(0, -1)) == null):
connect_rooms(room_map.get(i), room_map.get(new_room_position), Vector2(0, -1))
return room_map
Use a “connect_rooms” function to keep track of all of the node connections
func connect_rooms(prev, next, dir):
prev.adjacent_rooms[dir] = next
next.adjacent_rooms[-dir] = prev
prev.num_of_adjacent_rooms += 1
next.num_of_adjacent_rooms += 1
Keep in mind that you can manipulate the code above in a bunch of ways to gain different outcomes.
- experiment with branching probabilities
- increase or decrease the amount of nodes created at a time
- replace the sprites with tilemaps
- set if statements to generate specific types of maps.
- remove specific nodes for greater complexity
- The possibilities are literally endless