206 lines
5.5 KiB
GDScript
206 lines
5.5 KiB
GDScript
extends Node
|
|
class_name ModularMapGenerator
|
|
|
|
# ModularMapGenerator - Procedural generation for Floor 0 (Ground)
|
|
|
|
const GROUND_ITEM = 0
|
|
const WALL_ITEM = 4 # Or Void if using empty cells
|
|
|
|
# Generation Parameters
|
|
var grid_width: int = 14
|
|
var grid_depth: int = 14
|
|
var min_walkable_cells: int = 20 # Minimum for 12 players + space
|
|
var max_walkable_cells: int = 100
|
|
var connectivity_guaranteed: bool = true
|
|
|
|
# Shapes / Strategies
|
|
enum MapShape {
|
|
RECTANGLE,
|
|
ISLANDS,
|
|
MAZE_LIKE,
|
|
ROOMS,
|
|
RANDOM_NOISE
|
|
}
|
|
|
|
func generate_map(gridmap: Node, width: int, depth: int, shape: int = MapShape.ROOMS) -> bool:
|
|
grid_width = width
|
|
grid_depth = depth
|
|
|
|
var grid_data = [] # 2D array [z][x]
|
|
|
|
# Initialize with walls/void
|
|
for z in range(depth):
|
|
var row = []
|
|
for x in range(width):
|
|
row.append(WALL_ITEM)
|
|
grid_data.append(row)
|
|
|
|
# Apply generation strategy
|
|
match shape:
|
|
MapShape.RECTANGLE:
|
|
_generate_rectangle(grid_data)
|
|
MapShape.ISLANDS:
|
|
_generate_islands(grid_data)
|
|
MapShape.ROOMS:
|
|
_generate_rooms(grid_data)
|
|
MapShape.RANDOM_NOISE:
|
|
_generate_noise(grid_data)
|
|
_:
|
|
_generate_rooms(grid_data) # Default
|
|
|
|
# Ensure connectivity
|
|
if connectivity_guaranteed:
|
|
_ensure_connectivity(grid_data)
|
|
|
|
# Ensure minimum walkable count (by growing if needed)
|
|
_ensure_min_walkable(grid_data)
|
|
|
|
# Apply to GridMap
|
|
_apply_to_gridmap(gridmap, grid_data)
|
|
|
|
return true
|
|
|
|
func _generate_rectangle(grid: Array):
|
|
# Simple rectangle with some noise edges?
|
|
for z in range(1, grid_depth - 1):
|
|
for x in range(1, grid_width - 1):
|
|
grid[z][x] = GROUND_ITEM
|
|
|
|
func _generate_noise(grid: Array):
|
|
var rng = RandomNumberGenerator.new()
|
|
rng.randomize()
|
|
for z in range(1, grid_depth - 1):
|
|
for x in range(1, grid_width - 1):
|
|
if rng.randf() > 0.4:
|
|
grid[z][x] = GROUND_ITEM
|
|
|
|
func _generate_rooms(grid: Array):
|
|
# Random walk rooms
|
|
var rng = RandomNumberGenerator.new()
|
|
rng.randomize()
|
|
|
|
var room_count = rng.randi_range(3, 6)
|
|
var rooms = [] # Rects
|
|
|
|
for i in range(room_count):
|
|
var w = rng.randi_range(3, 6)
|
|
var h = rng.randi_range(3, 6)
|
|
var x = rng.randi_range(1, grid_width - w - 1)
|
|
var z = rng.randi_range(1, grid_depth - h - 1)
|
|
|
|
# Carve room
|
|
for rz in range(z, z + h):
|
|
for rx in range(x, x + w):
|
|
grid[rz][rx] = GROUND_ITEM
|
|
|
|
# Connect to previous room
|
|
if i > 0:
|
|
var prev = rooms[i-1]
|
|
_connect_points(grid, Vector2i(prev[0], prev[1]), Vector2i(x, z))
|
|
|
|
rooms.append([x, z, w, h])
|
|
|
|
func _generate_islands(grid: Array):
|
|
# Cellular automata smoothing
|
|
_generate_noise(grid)
|
|
|
|
# Smoothing passes
|
|
for i in range(3):
|
|
var new_grid = grid.duplicate(true)
|
|
for z in range(1, grid_depth - 1):
|
|
for x in range(1, grid_width - 1):
|
|
var neighbors = _count_neighbors(grid, x, z)
|
|
if neighbors > 4:
|
|
new_grid[z][x] = GROUND_ITEM
|
|
elif neighbors < 3:
|
|
new_grid[z][x] = WALL_ITEM
|
|
grid = new_grid
|
|
|
|
func _count_neighbors(grid: Array, x: int, z: int) -> int:
|
|
var count = 0
|
|
for dz in range(-1, 2):
|
|
for dx in range(-1, 2):
|
|
if dz == 0 and dx == 0: continue
|
|
if grid[z + dz][x + dx] == GROUND_ITEM:
|
|
count += 1
|
|
return count
|
|
|
|
func _connect_points(grid: Array, p1: Vector2i, p2: Vector2i):
|
|
var curr = p1
|
|
while curr != p2:
|
|
grid[curr.y][curr.x] = GROUND_ITEM
|
|
if curr.x != p2.x:
|
|
curr.x += 1 if p2.x > curr.x else -1
|
|
elif curr.y != p2.y:
|
|
curr.y += 1 if p2.y > curr.y else -1
|
|
grid[p2.y][p2.x] = GROUND_ITEM
|
|
|
|
func _ensure_connectivity(grid: Array):
|
|
# Flood fill from first walkable
|
|
var start = Vector2i(-1, -1)
|
|
var all_walkable = []
|
|
|
|
for z in range(grid_depth):
|
|
for x in range(grid_width):
|
|
if grid[z][x] == GROUND_ITEM:
|
|
if start == Vector2i(-1, -1):
|
|
start = Vector2i(x, z)
|
|
all_walkable.append(Vector2i(x, z))
|
|
|
|
if start == Vector2i(-1, -1):
|
|
return # Empty map
|
|
|
|
var visited = {}
|
|
var queue = [start]
|
|
visited[start] = true
|
|
var connected_count = 0
|
|
|
|
while not queue.is_empty():
|
|
var curr = queue.pop_front()
|
|
connected_count += 1
|
|
|
|
# Check neighbors
|
|
var dirs = [Vector2i(0, 1), Vector2i(0, -1), Vector2i(1, 0), Vector2i(-1, 0)]
|
|
for d in dirs:
|
|
var next = curr + d
|
|
if next.x >= 0 and next.x < grid_width and next.y >= 0 and next.y < grid_depth:
|
|
if grid[next.y][next.x] == GROUND_ITEM and not visited.has(next):
|
|
visited[next] = true
|
|
queue.append(next)
|
|
|
|
# If disconnected parts, keep largest island or removing others?
|
|
# Better: grow bridges. But for simplicity, let's just use the main island
|
|
# and turn others to walls? Or connect them?
|
|
# For "Rooms" geneation, we explicitly connected them.
|
|
# For "Islands", let's just keep the biggest island or assume automata made big blobs.
|
|
|
|
if connected_count < all_walkable.size():
|
|
# Prune disconnected (simple approach)
|
|
for pos in all_walkable:
|
|
if not visited.has(pos):
|
|
grid[pos.y][pos.x] = WALL_ITEM
|
|
|
|
func _ensure_min_walkable(grid: Array):
|
|
var walkable = []
|
|
for z in range(grid_depth):
|
|
for x in range(grid_width):
|
|
if grid[z][x] == GROUND_ITEM:
|
|
walkable.append(Vector2i(x, z))
|
|
|
|
if walkable.size() < min_walkable_cells:
|
|
# Force create a central room
|
|
var center_x = grid_width / 2
|
|
var center_z = grid_depth / 2
|
|
var radius = int(sqrt(min_walkable_cells)) / 2 + 1
|
|
for z in range(center_z - radius, center_z + radius):
|
|
for x in range(center_x - radius, center_x + radius):
|
|
if x >= 0 and x < grid_width and z >= 0 and z < grid_depth:
|
|
grid[z][x] = GROUND_ITEM
|
|
|
|
func _apply_to_gridmap(gridmap: Node, grid: Array):
|
|
for z in range(grid_depth):
|
|
for x in range(grid_width):
|
|
var item = grid[z][x]
|
|
# Floor 0 logic
|
|
gridmap.set_cell_item(Vector3i(x, 0, z), item)
|