godot-procedural-generation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseProcedural Generation
程序化生成
Seeded algorithms, noise functions, and constraint propagation define replayable content generation.
基于种子的算法、噪声函数和约束传播技术,可实现可复现的内容生成。
Available Scripts
可用脚本
wfc_level_generator.gd
wfc_level_generator.gd
Expert Wave Function Collapse implementation with tile adjacency rules.
基于瓦片邻接规则的专业Wave Function Collapse实现。
NEVER Do in Procedural Generation
程序化生成中的禁忌操作
- NEVER forget to seed RNG — without seed = same dungeon every time. Use
randi()OR expose seed for speedrunning.seed(hash(Time.get_ticks_msec())) - NEVER use in
randf()for multiplayer — Each client calls_ready()at different times = desynced RNG = different dungeons. Use shared seed from server._ready() - NEVER skip validation — Drunkard's walk dungeon with no exit? Playability fail. ALWAYS validate (e.g., A* from start to end) OR regenerate.
- NEVER use noise.get_noise_2d() every frame — Calling noise 10,000x/frame = lag. Pre-generate heightmap in , cache in Array.
_ready() - NEVER use BSP without minimum room size — Infinite splits = 1x1 rooms = crash. Set (e.g., 6x6) to prevent over-subdivision.
min_size - NEVER ignore WFC contradictions — Wave Function Collapse fails when no valid tiles remain. MUST detect contradiction, backtrack OR restart generation.
- NEVER block main thread for large generations — Generating 1000x1000 terrain in = freeze. Use worker thread OR split across frames with
_ready().await
gdscript
func generate_dungeon(width: int, height: int, fill_percent: float = 0.4) -> Array:
var grid := []
for y in height:
var row := []
for x in width:
row.append(1) # 1 = wall
grid.append(row)
# Start in center
var x := width / 2
var y := height / 2
var floor_tiles := 0
var target_floor := int(width * height * fill_percent)
while floor_tiles < target_floor:
if grid[y][x] == 1:
grid[y][x] = 0 # Create floor
floor_tiles += 1
# Random walk
var dir := randi() % 4
match dir:
0: x = clampi(x + 1, 0, width - 1)
1: x = clampi(x - 1, 0, width - 1)
2: y = clampi(y + 1, 0, height - 1)
3: y = clampi(y - 1, 0, height - 1)
return grid- 绝对不要忘记为RNG设置种子 — 未设置种子的会导致每次生成的地牢完全相同。使用
randi(),或者为速通玩法暴露种子参数。seed(hash(Time.get_ticks_msec())) - 绝对不要在多人游戏的中使用
_ready()— 每个客户端调用randf()的时间不同,会导致RNG不同步,进而生成不同的地牢。应使用服务器提供的共享种子。_ready() - 绝对不要跳过验证步骤 — 生成的醉汉游走地牢没有出口?这会导致游戏性失效。务必进行验证(例如用A*算法检查起点到终点的连通性),或者重新生成内容。
- 绝对不要每帧调用— 每帧调用10000次噪声函数会导致卡顿。应在
noise.get_noise_2d()中预先生成高度图,并缓存到数组中。_ready() - 绝对不要在使用BSP树时不设置最小房间尺寸 — 无限分割会产生1x1的房间,甚至导致崩溃。设置(例如6x6)以防止过度分割。
min_size - 绝对不要忽略Wave Function Collapse的矛盾情况 — 当没有有效瓦片可选时,Wave Function Collapse会失败。必须检测矛盾情况,进行回溯或重新开始生成。
- 绝对不要在主线程中执行大规模生成操作 — 在中生成1000x1000的地形会导致游戏冻结。应使用工作线程,或者通过
_ready()将生成任务拆分到多帧执行。await
gdscript
func generate_dungeon(width: int, height: int, fill_percent: float = 0.4) -> Array:
var grid := []
for y in height:
var row := []
for x in width:
row.append(1) # 1 = wall
grid.append(row)
# Start in center
var x := width / 2
var y := height / 2
var floor_tiles := 0
var target_floor := int(width * height * fill_percent)
while floor_tiles < target_floor:
if grid[y][x] == 1:
grid[y][x] = 0 # Create floor
floor_tiles += 1
# Random walk
var dir := randi() % 4
match dir:
0: x = clampi(x + 1, 0, width - 1)
1: x = clampi(x - 1, 0, width - 1)
2: y = clampi(y + 1, 0, height - 1)
3: y = clampi(y - 1, 0, height - 1)
return gridPerlin Noise Terrain
Perlin噪声地形
gdscript
var noise := FastNoiseLite.new()
func generate_terrain(width: int, height: int) -> Array:
noise.seed = randi()
noise.frequency = 0.05
var terrain := []
for y in height:
var row := []
for x in width:
var value := noise.get_noise_2d(x, y)
# Map noise to tile types
var tile: int
if value < -0.2:
tile = 0 # Water
elif value < 0.2:
tile = 1 # Grass
else:
tile = 2 # Mountain
row.append(tile)
terrain.append(row)
return terraingdscript
var noise := FastNoiseLite.new()
func generate_terrain(width: int, height: int) -> Array:
noise.seed = randi()
noise.frequency = 0.05
var terrain := []
for y in height:
var row := []
for x in width:
var value := noise.get_noise_2d(x, y)
# Map noise to tile types
var tile: int
if value < -0.2:
tile = 0 # Water
elif value < 0.2:
tile = 1 # Grass
else:
tile = 2 # Mountain
row.append(tile)
terrain.append(row)
return terrainBSP Rooms
BSP房间
gdscript
class_name BSPRoom
var x: int
var y: int
var width: int
var height: int
var left: BSPRoom = null
var right: BSPRoom = null
func split(min_size: int = 6) -> bool:
if left or right:
return false # Already split
# Choose split direction
var split_horizontal := randf() > 0.5
if width > height and float(width) / float(height) >= 1.25:
split_horizontal = false
elif height > width and float(height) / float(width) >= 1.25:
split_horizontal = true
var max := (height if split_horizontal else width) - min_size
if max <= min_size:
return false # Too small
var split_pos := randi_range(min_size, max)
if split_horizontal:
left = BSPRoom.new()
left.x = x
left.y = y
left.width = width
left.height = split_pos
right = BSPRoom.new()
right.x = x
right.y = y + split_pos
right.width = width
right.height = height - split_pos
else:
left = BSPRoom.new()
left.x = x
left.y = y
left.width = split_pos
left.height = height
right = BSPRoom.new()
right.x = x + split_pos
right.y = y
right.width = width - split_pos
right.height = height
return true
func generate_bsp_dungeon(width: int, height: int, iterations: int = 4) -> Array[BSPRoom]:
var root := BSPRoom.new()
root.x = 0
root.y = 0
root.width = width
root.height = height
var rooms: Array[BSPRoom] = [root]
for i in iterations:
var new_rooms: Array[BSPRoom] = []
for room in rooms:
if room.split():
new_rooms.append(room.left)
new_rooms.append(room.right)
else:
new_rooms.append(room)
rooms = new_rooms
return roomsgdscript
class_name BSPRoom
var x: int
var y: int
var width: int
var height: int
var left: BSPRoom = null
var right: BSPRoom = null
func split(min_size: int = 6) -> bool:
if left or right:
return false # Already split
# Choose split direction
var split_horizontal := randf() > 0.5
if width > height and float(width) / float(height) >= 1.25:
split_horizontal = false
elif height > width and float(height) / float(width) >= 1.25:
split_horizontal = true
var max := (height if split_horizontal else width) - min_size
if max <= min_size:
return false # Too small
var split_pos := randi_range(min_size, max)
if split_horizontal:
left = BSPRoom.new()
left.x = x
left.y = y
left.width = width
left.height = split_pos
right = BSPRoom.new()
right.x = x
right.y = y + split_pos
right.width = width
right.height = height - split_pos
else:
left = BSPRoom.new()
left.x = x
left.y = y
left.width = split_pos
left.height = height
right = BSPRoom.new()
right.x = x + split_pos
right.y = y
right.width = width - split_pos
right.height = height
return true
func generate_bsp_dungeon(width: int, height: int, iterations: int = 4) -> Array[BSPRoom]:
var root := BSPRoom.new()
root.x = 0
root.y = 0
root.width = width
root.height = height
var rooms: Array[BSPRoom] = [root]
for i in iterations:
var new_rooms: Array[BSPRoom] = []
for room in rooms:
if room.split():
new_rooms.append(room.left)
new_rooms.append(room.right)
else:
new_rooms.append(room)
rooms = new_rooms
return roomsRandom Loot
随机战利品
gdscript
func generate_loot(loot_level: int) -> Array[Item]:
var items: Array[Item] = []
var roll_count := randi_range(1, 3)
for i in roll_count:
var rarity := roll_rarity()
var item := get_random_item(rarity, loot_level)
items.append(item)
return items
func roll_rarity() -> String:
var roll := randf()
if roll < 0.6:
return "common"
elif roll < 0.85:
return "uncommon"
elif roll < 0.95:
return "rare"
else:
return "legendary"gdscript
func generate_loot(loot_level: int) -> Array[Item]:
var items: Array[Item] = []
var roll_count := randi_range(1, 3)
for i in roll_count:
var rarity := roll_rarity()
var item := get_random_item(rarity, loot_level)
items.append(item)
return items
func roll_rarity() -> String:
var roll := randf()
if roll < 0.6:
return "common"
elif roll < 0.85:
return "uncommon"
elif roll < 0.95:
return "rare"
else:
return "legendary"Wave Function Collapse
Wave Function Collapse
gdscript
undefinedgdscript
undefinedSimplified WFC for tile patterns
Simplified WFC for tile patterns
Load compatible tile adjacency rules
Load compatible tile adjacency rules
var tile_rules := {
"grass": ["grass", "path", "water_edge"],
"water": ["water", "water_edge"],
"path": ["grass", "path"]
}
func wfc_generate(width: int, height: int) -> Array:
var grid := []
for y in height:
var row := []
for x in width:
row.append(null) # Uncollapsed
grid.append(row)
# Collapse cells until complete
while has_uncollapsed(grid):
var pos := find_lowest_entropy(grid)
collapse_cell(grid, pos)
propagate_constraints(grid, pos)
return gridundefinedvar tile_rules := {
"grass": ["grass", "path", "water_edge"],
"water": ["water", "water_edge"],
"path": ["grass", "path"]
}
func wfc_generate(width: int, height: int) -> Array:
var grid := []
for y in height:
var row := []
for x in width:
row.append(null) # Uncollapsed
grid.append(row)
# Collapse cells until complete
while has_uncollapsed(grid):
var pos := find_lowest_entropy(grid)
collapse_cell(grid, pos)
propagate_constraints(grid, pos)
return gridundefinedBest Practices
最佳实践
- Seeding - Use seeds for reproducibility
- Validation - Ensure playable levels
- Performance - Generate async if needed
- 种子化 - 使用种子确保可复现性
- 验证 - 确保生成的关卡可游玩
- 性能 - 必要时使用异步生成
Reference
参考
- Related: ,
godot-tilemap-masterygodot-resource-data-patterns
- 相关内容:,
godot-tilemap-masterygodot-resource-data-patterns
Related
相关
- Master Skill: godot-master
- 核心技能:godot-master