1extends Node
2
3# --- Configuration ---
4
5@export var grid_width: int = 100
6@export var grid_height: int = 100
7@export var max_rooms: int = 20
8@export var min_room_size: Vector2i = Vector2i(5, 5)
9@export var max_room_size: Vector2i = Vector2i(15, 15)
10@export var max_corridor_attempts: int = 500 # To prevent infinite loops
11
12# Tile types (example)
13const TILE_WALL = 0
14const TILE_FLOOR = 1
15const TILE_CORRIDOR = 2
16
17var map_grid: Array = []
18var rooms: Array = [] # Stores Rect2 for each room
19
20# --- Room Class (for convenience) ---
21
22class Room:
23var rect: Rect2
24
25func _init(r: Rect2):
26rect = r
27
28func intersects(other_room: Room) -> bool:
29return rect.intersects(other_room.rect)
30
31func get_center() -> Vector2:
32return rect.position + rect.size / 2.0
33
34# --- Map Initialization ---
35
36func initialize_map():
37map_grid.resize(grid_height)
38for y in range(grid_height):
39map_grid[y] = Array()
40map_grid[y].resize(grid_width)
41for x in range(grid_width):
42map_grid[y][x] = TILE_WALL # Initialize entire map as walls
43
44# --- Room Placement ---
45
46func place_rooms():
47rooms.clear()
48for i in range(max_rooms):
49var room_w = randi_range(min_room_size.x, max_room_size.x)
50var room_h = randi_range(min_room_size.y, max_room_size.y)
51var room_pos_x = randi_range(1, grid_width - room_w - 1) # Ensure room is within bounds, not touching edges
52var room_pos_y = randi_range(1, grid_height - room_h - 1)
53var new_rect = Rect2(room_pos_x, room_pos_y, room_w, room_h)
54var new_room = Room.new(new_rect)
55
56var overlaps = false
57for existing_room in rooms:
58# Add a buffer around rooms to prevent corridors from touching walls directly
59if new_room.rect.grow(1).intersects(existing_room.rect.grow(1)):
60overlaps = true
61break
62
63if not overlaps:
64rooms.append(new_room)
65carve_room(new_room)
66
67# Carves a room into the map grid
68func carve_room(room: Room):
69var r = room.rect
70for y in range(int(r.position.y), int(r.position.y + r.size.y)):
71for x in range(int(r.position.x), int(r.position.x + r.size.x)):
72# Ensure we don't go out of bounds (should be handled by placement, but good safety)
73if y >= 0 and y < grid_height and x >= 0 and x < grid_width:
74map_grid[y][x] = TILE_FLOOR
75
76# --- Corridor Generation ---
77
78func connect_rooms():
79if rooms.size() < 2:
80return # Need at least two rooms to connect
81
82# Connect each room to the next one in the list sequentially
83# For more complex dungeons, consider connecting to a random room or using MST
84for i in range(rooms.size() - 1):
85var room1 = rooms[i]
86var room2 = rooms[i+1]
87create_corridor(room1.get_center(), room2.get_center())
88
89# Optional: Connect the last room back to the first for a loop, or to a random room
90# create_corridor(rooms.back().get_center(), rooms[0].get_center())
91
92# Creates a corridor between two points using a simple L-shaped path
93func create_corridor(start: Vector2, end: Vector2):
94var current_pos = Vector2i(start.x, start.y)
95var target_pos = Vector2i(end.x, end.y)
96
97var attempts = 0
98while current_pos != target_pos and attempts < max_corridor_attempts:
99var dx = target_pos.x - current_pos.x
100var dy = target_pos.y - current_pos.y
101
102# Decide whether to move horizontally or vertically first
103# Prioritize moving towards the target's axis with the larger difference
104if abs(dx) > abs(dy):
105if dx > 0:
106current_pos.x += 1
107elif dx < 0:
108current_pos.x -= 1
109else:
110if dy > 0:
111current_pos.y += 1
112elif dy < 0:
113current_pos.y -= 1
114
115# Ensure we don't carve out of bounds
116if current_pos.x >= 0 and current_pos.x < grid_width and current_pos.y >= 0 and current_pos.y < grid_height:
117# Only carve if it's not already a floor (avoids overwriting room floors unnecessarily)
118if map_grid[current_pos.y][current_pos.x] == TILE_WALL:
119map_grid[current_pos.y][current_pos.x] = TILE_CORRIDOR
120else:
121# If we hit a boundary, stop this corridor attempt
122printerr("Corridor carving hit boundary. Stopping.")
123break
124attempts += 1
125
126# Ensure the end point is also carved as floor
127if target_pos.x >= 0 and target_pos.x < grid_width and target_pos.y >= 0 and target_pos.y < grid_height:
128map_grid[target_pos.y][target_pos.x] = TILE_FLOOR
129
130# --- Generation Function ---
131
132func generate_dungeon() -> Array:
133initialize_map()
134place_rooms()
135connect_rooms()
136return map_grid
137
138# --- Example Usage ---
139
140func _ready():
141var dungeon_data = generate_dungeon()
142# Now you can use dungeon_data to render your dungeon
143# For example, print a simple text representation:
144print("Generated Dungeon (W: %d, H: %d):" % [grid_width, grid_height])
145for y in range(grid_height):
146var row_str = ""
147for x in range(grid_width):
148match map_grid[y][x]:
149TILE_WALL:
150row_str += "#"
151TILE_FLOOR:
152row_str += "."
153TILE_CORRIDOR:
154row_str += "-"
155print(row_str)