procedural-gen

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Procedural generation

程序化生成

Generate levels, terrain, and loot from compact rules and a seed. The throughline of good procgen is determinism: a single seed reproduces the same world, so bugs are repeatable and players can share seeds. This skill owns the core algorithms — noise, seeded RNG, dungeon layout, weighted tables; genres like
roguelike
and
survival-crafting
consume it.
通过简洁的规则和种子生成关卡、地形与战利品。优质程序化生成的核心是确定性:单个种子可重现相同的世界,因此Bug可复现,玩家也能共享种子。本技能涵盖核心算法——噪声、种子式RNG、地牢布局、加权表;
roguelike
(类Rogue)和
survival-crafting
(生存建造)等类型游戏会用到这些算法。

When to use

使用场景

  • Use to generate maps, dungeons, terrain heightmaps, item drops, or any content you do not want to author by hand.
  • Use when results must be reproducible from a seed (debugging, daily challenges, shareable worlds).
  • Use to pick weighted random outcomes (loot rarity, spawn tables).
When not to use: for the engine's tile API to paint the result, use
godot-tilemap
or
unity-tilemap-2d
. For routing AI through the generated map, use
game-ai
. For carefully hand-paced levels, use
level-design
— procgen and authored design are complementary, not interchangeable.
  • 用于生成地图、地牢、地形高度图、物品掉落,或任何不想手动制作的内容。
  • 当结果需要通过种子重现时使用(调试、每日挑战、可共享世界)。
  • 用于选择带权重的随机结果(战利品稀有度、刷新表)。
不适用场景:若要使用引擎的瓦片API来绘制结果,请使用
godot-tilemap
unity-tilemap-2d
。若要让AI在生成的地图中寻路,请使用
game-ai
。对于精心设计的手动关卡,请使用
level-design
——程序化生成与人工设计相辅相成,而非互相替代。

Core workflow

核心工作流程

  1. Own your randomness. Create one seeded RNG instance and pass it everywhere. Never call the global/static random in generation code — it makes results irreproducible and order-dependent.
  2. Pick the technique for the content. Continuous terrain/heightmaps → noise. Discrete rooms/corridors → space partitioning or agent-based carving. Outcomes with rarities → weighted tables.
  3. Generate into a plain data grid/array first, decoupled from rendering. Generation fills
    int[][]
    or a dict; a separate pass draws it.
  4. Validate before shipping the result to the player. Is every room reachable? Is the spawn safe? Is there a path to the exit? Reject or repair layouts that fail; do not hand the player a broken map.
  5. Tune with the seed fixed so each parameter change is visible in isolation, then sweep seeds to check the distribution, not just one lucky map.
  1. 掌控随机性。创建一个带种子的RNG实例,并在所有需要的地方传递它。绝对不要在生成代码中调用全局/静态随机函数——这会导致结果不可重现,且依赖调用顺序。
  2. 为内容选择合适的技术。连续地形/高度图→噪声算法。离散房间/走廊→空间划分或基于智能体的雕刻。带稀有度的结果→加权表。
  3. 先生成纯数据网格/数组,与渲染解耦。生成过程填充
    int[][]
    或字典;单独通过另一步骤绘制内容。
  4. 在将结果交付给玩家前进行验证。每个房间都可到达吗?出生点安全吗?有通往出口的路径吗?拒绝或修复不合格的布局;不要给玩家提供有问题的地图。
  5. 固定种子进行调优,这样每个参数的变化都能单独观察到,然后遍历多个种子检查分布情况,而不只是依赖一张“幸运”地图。

Patterns

模式

1. Seeded, deterministic RNG (the foundation)

1. 带种子的确定性RNG(基础)

python
import random
rng = random.Random(seed)        # a dedicated instance — NOT the global random.*
room_count = rng.randint(5, 12)  # same seed -> same sequence, every run
python
import random
rng = random.Random(seed)        # 专用实例——不要使用全局random.*
room_count = rng.randint(5, 12)  # 相同种子→相同序列,每次运行结果一致

RIGHT: thread
rng
through every function that makes a choice.

正确做法:将
rng
传递给所有需要做选择的函数。

WRONG: calling random.randint(...) (global state) — order-dependent, unseedable.

错误做法:调用random.randint(...)(全局状态)——依赖调用顺序,无法通过种子控制。


Engine equivalents: Godot `var rng = RandomNumberGenerator.new(); rng.seed = s`;
Unity `var rng = new System.Random(seed)` (or `UnityEngine.Random.InitState`).
Store the seed in the save file so a world can be regenerated.

引擎等效实现:Godot中`var rng = RandomNumberGenerator.new(); rng.seed = s`;Unity中`var rng = new System.Random(seed)`(或`UnityEngine.Random.InitState`)。将种子存储在存档文件中,以便世界可以重新生成。

2. Fractal (fBm) noise for heightmaps

2. 用于高度图的分形(fBm)噪声

python
undefined
python
undefined

Sum several octaves: each higher octave has higher frequency, lower amplitude.

叠加多个八度:每个更高的八度具有更高的频率、更低的振幅。

def fbm(noise, x, y, octaves=5, lacunarity=2.0, gain=0.5): total, amp, freq, norm = 0.0, 1.0, 1.0, 0.0 for _ in range(octaves): total += amp * noise(x * freq, y * freq) # noise() returns ~0..1 norm += amp # track total amplitude amp *= gain # each octave contributes less freq *= lacunarity # ...at a higher frequency return total / norm # normalize back into 0..1
def fbm(noise, x, y, octaves=5, lacunarity=2.0, gain=0.5): total, amp, freq, norm = 0.0, 1.0, 1.0, 0.0 for _ in range(octaves): total += amp * noise(x * freq, y * freq) # noise()返回~0..1 norm += amp # 跟踪总振幅 amp *= gain # 每个八度的贡献减少 freq *= lacunarity # ...频率更高 return total / norm # 归一化回0..1范围

Redistribute to carve flat valleys / sharpen peaks: higher exp -> more lowland.

重新分配值以雕刻平坦山谷/锐化山峰:指数越高→低地越多。

elevation = pow(fbm(noise, nx, ny), 2.2)

Use a real noise library (`FastNoiseLite`, `opensimplex`,
`Unity.Mathematics.noise`, or `Mathf.PerlinNoise`) — do not implement gradient
noise yourself. Seed **elevation and moisture with different seeds** so a
biome lookup over both fields isn't perfectly correlated. Full biome lookup and
island shaping are in `references/noise.md`.
elevation = pow(fbm(noise, nx, ny), 2.2)

使用真实的噪声库(`FastNoiseLite`、`opensimplex`、`Unity.Mathematics.noise`或`Mathf.PerlinNoise`)——不要自己实现梯度噪声。为**海拔和湿度使用不同的种子**,这样基于这两个字段的生物群查找不会完全相关。完整的生物群查找和岛屿塑造方法在`references/noise.md`中。

3. Weighted loot table (rarity-correct selection)

3. 加权战利品表(符合稀有度的选择)

python
undefined
python
undefined

Roll proportional to weight: common drops far more often than legendary.

根据权重随机选择:普通掉落的频率远高于传奇物品。

def weighted_pick(rng, table): # table: list of (item, weight) total = sum(w for _, w in table) roll = rng.uniform(0, total) # a point on the cumulative line upto = 0.0 for item, w in table: upto += w if roll < upto: # first bucket the roll falls into return item return table[-1][0] # float-safety fallback
loot = weighted_pick(rng, [("common", 70), ("rare", 25), ("legendary", 5)])

Weights need not sum to 100 — they are relative. To prevent bad streaks, use a
"pity"/bag system (see `references/dungeon-generation.md` notes on distributions).
def weighted_pick(rng, table): # table: (物品, 权重)的列表 total = sum(w for _, w in table) roll = rng.uniform(0, total) # 累积线上的一个点 upto = 0.0 for item, w in table: upto += w if roll < upto: # 骰子落入的第一个区间 return item return table[-1][0] # 浮点数安全回退

权重不需要总和为100——它们是相对值。为了避免连续坏结果,可以使用“保底”/背包系统(详见`references/dungeon-generation.md`中关于分布的说明)。

4. Rooms-and-corridors dungeon (sketch)

4. 房间+走廊地牢(草图)

python
undefined
python
undefined

1. Place non-overlapping rooms; 2. connect them; 3. carve into the grid.

1. 放置不重叠的房间;2. 连接它们;3. 在网格中雕刻。

rooms = [] for _ in range(attempts): r = Rect(rng.randint(1, W-w-1), rng.randint(1, H-h-1), w, h) if not any(r.intersects(o.expand(1)) for o in rooms): # keep a 1-tile gap rooms.append(r) for a, b in zip(rooms, rooms[1:]): # connect each room to the next carve_l_corridor(grid, a.center, b.center, rng) # horizontal then vertical

The complete generator (BSP partitioning, L-corridors, reachability check, and
random-walk caves) is in `references/dungeon-generation.md`.
rooms = [] for _ in range(attempts): r = Rect(rng.randint(1, W-w-1), rng.randint(1, H-h-1), w, h) if not any(r.intersects(o.expand(1)) for o in rooms): # 保持1格间距 rooms.append(r) for a, b in zip(rooms, rooms[1:]): # 将每个房间与下一个连接 carve_l_corridor(grid, a.center, b.center, rng) # 先水平后垂直

完整的生成器(BSP划分、L型走廊、可达性检查、随机游走洞穴)在`references/dungeon-generation.md`中。

Pitfalls

常见陷阱

  • Using the global RNG inside generation makes worlds unreproducible and breaks the moment call order changes. Always pass a seeded instance.
  • Correlated noise fields: sampling elevation and moisture from the same seed/offset produces biomes that line up in bands. Offset or reseed each field.
  • Octave artifacts: adding octaves without renormalizing pushes values out of
    0..1
    ; divide by the summed amplitude (and beware library output ranges — some return
    -1..1
    , some
    0..1
    ).
  • No connectivity check: rooms or caves can end up isolated. Flood-fill from the spawn and discard/reconnect unreachable regions before play.
  • Unbounded placement loops: "keep trying until N rooms fit" can spin forever on a small grid. Cap attempts and accept fewer rooms.
  • Seeding once globally, then relying on frame timing: any non-deterministic input (time, physics, hash randomization) leaking into generation destroys reproducibility.
  • 在生成中使用全局RNG会导致世界不可重现,且调用顺序改变时会出错。始终传递带种子的实例。
  • 相关噪声场:从相同种子/偏移量采样海拔和湿度会导致生物群呈带状排列。为每个字段设置偏移量或重新设置种子。
  • 八度伪影:叠加八度而不归一化会使值超出
    0..1
    范围;需除以总振幅(注意库的输出范围——有些返回
    -1..1
    ,有些返回
    0..1
    )。
  • 无连通性检查:房间或洞穴可能会被孤立。从出生点进行洪水填充,在游戏开始前丢弃/重新连接不可达区域。
  • 无限制放置循环:“不断尝试直到放置N个房间”在小网格上可能无限循环。限制尝试次数,接受更少的房间。
  • 全局单次播种,然后依赖帧计时:任何非确定性输入(时间、物理、哈希随机化)渗入生成过程都会破坏可重现性。

References

参考资料

  • references/noise.md
    — octaves/lacunarity/gain, redistribution, island shaping, two-axis biome lookup, blue-noise object scatter.
  • references/dungeon-generation.md
    — BSP, rooms+corridors, random-walk caves, cellular-automata smoothing, connectivity validation, distribution/pity tables.
  • references/noise.md
    ——八度/ lacunarity/增益、重分配、岛屿塑造、双轴生物群查找、蓝噪声对象散布。
  • references/dungeon-generation.md
    ——BSP、房间+走廊、随机游走洞穴、元胞自动机平滑、连通性验证、分布/保底表。

Related skills

相关技能

  • godot-tilemap
    ,
    unity-tilemap-2d
    — paint the generated grid into the engine.
  • game-ai
    — pathfinding over the generated graph.
  • level-design
    — pacing and hand-authored structure that procgen complements.
  • roguelike
    ,
    survival-crafting
    — genres that compose this skill.
  • godot-tilemap
    unity-tilemap-2d
    ——将生成的网格绘制到引擎中。
  • game-ai
    ——在生成的图上寻路。
  • level-design
    ——程序化生成所补充的节奏控制和人工设计结构。
  • roguelike
    survival-crafting
    ——使用本技能的游戏类型。