Files
tekton/scripts/generators/modular_map_generator.gd
T
2026-01-27 03:43:51 +08:00

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)