今日速览

  1. Godot 内部用的是 PCG 家族 伪随机数:同一 seed → 同一串随机数,这是”可复现关卡”的地基。
  2. 全局 randi() 够用但不可控;RandomNumberGenerator 实例 有自己的 seed 和 state,适合”每个关卡一条独立随机流”,还能回滚到之前的状态(做撤销/回放/联机同步)。
  3. FastNoiseLite 是内置噪声资源(Perlin/Cellular/Value 等),输出大致在 [-1, 1],用 fractal_octaves 叠层就能出「大山脉 + 小碎石」的自然感。
  4. 程序化生成真正难的 不是生成,是验收:先写”这个关卡合不合格”的检查函数,再让 AI 改生成器。
  5. 给玩家一个 可复制的种子号(seed = "abc".hash()),Bug 就能被玩家一键复现,这对 Steam 上的差评防御极其重要。

核心学习内容

一、先把”随机”关进笼子:种子即关卡 ID

新手最常见的写法是到处 randi_range(),结果关卡坏了根本没法重跑。正确做法是一个关卡一个 RNG 实例:

var rng := RandomNumberGenerator.new()

func generate_level(seed_value: int) -> void:
    rng.seed = seed_value          # 设定种子 = 重置状态
    var width := rng.randi_range(40, 60)
    var room_count := rng.randi_range(6, 12)
    # 之后所有随机都走 rng,而不是全局 randi()

好处有三:① 日志里存一行 seed=884812,玩家报 Bug 你本地就能 1:1 复现;② 每个系统(掉落、敌人、装饰)用不同种子,互不干扰;③ rng.state 可存可还原,将来做”重开同一张图”或联机同步都省事。

二、房间拼接:BSP 太麻烦,先做”撒房间 + 连走廊”

对独立开发者,BSP 递归切分容易写出重叠和死角,性价比低。更稳的四步法:

  1. 撒房间:在网格里随机生成 N 个矩形,尺寸带上下限(如 6×6 ~ 12×9);
  2. 去重叠:新房间和已有房间做 Rect2.intersects() 检测,撞了就重试(最多重试 30 次,失败就少放一间,绝不死循环);
  3. 连走廊:按房间中心点排序,依次用 L 型走廊把第 i 间连到第 i+1 间——保证全图连通,不会出现”孤岛房间”;
  4. 贴图/放内容:走廊和房间写进 TileMapLayer,再按”离起点距离”决定敌人强度和宝箱品质。

这种做法的关键优势是可控:房间数量、大小、连通性都是你能调的参数,而不是”看起来随机但玩起来很糟”。

三、噪声:给地表一层”自然感”

FastNoiseLite 用来做地形高低、生物群系、洞穴分布。实用参数组合:

  • noise_type = TYPE_PERLIN(平滑)或 TYPE_CELLULAR(洞穴/岛屿感);
  • frequency:0.01 ~ 0.05 起步,太大就变成噪点雪花;
  • fractal_octaves = 4 + fractal_gain = 0.5:叠出”大结构 + 小细节”;
  • 判定阈值别用 0.5 硬切,用两段阈值(如 >0.3 草地、>0.6 岩石)才有过渡带。

记得 noise.seed = seed_value,噪声也是可复现的。

四、验收清单:让 AI 帮你写”关卡体检”

生成完必跑一遍体检,不合格就换种子重生成(for attempt in 20:)。检查项建议:

  • 起点能否走到终点(BFS 洪水填充,检查可达格子数 ≥ 房间总面积 80%);
  • 是否有房间/宝箱落在不可达区域;
  • 敌人出生点是否离玩家出生点过近(至少 > 8 格);
  • 最短路长度是否在目标区间(太短=无聊,太长=劝退)。

把这几条写成函数,你就有了一把”程序化生成的裁判尺”,也是给 DeepSeek 提需求时最有效的约束条件。

🎯 DeepSeek 提示词

提示词 1:生成可复现的 2D 房间拼接关卡生成器

适用场景:给 Godot 4 项目加一个 Roguelike / 地牢关卡生成器,要求可复现、不死循环。

你是一名 Godot 4.x(GDScript 2.0)资深游戏程序员。
任务:写一个 2D 程序化关卡生成器,作为独立脚本 `LevelGenerator.gd`(继承 Node2D)。

需求:
1. 用 RandomNumberGenerator 实例管理随机流,提供 `generate(seed_value: int) -> LevelData`,
   相同 seed 必须生成完全相同的关卡。
2. 生成 6~12 个不重叠矩形房间,每间 6x6~12x9 格;用 Rect2.intersects 检测重叠,
   每个房间最多重试 30 次,失败则减少房间数,禁止 while 死循环。
3. 按房间中心点排序后用 L 型走廊依次连接第 i 与第 i+1 间房间,保证全图连通。
4. 输出 LevelData(自定义 Resource 或 Dictionary),包含:rooms(Rect2i 数组)、
   corridors(Vector2i 数组)、player_spawn(Vector2i)、exit(Vector2i)、
   enemy_spawns(Vector2i 数组,距离 player_spawn 至少 8 格)。
5. 提供 `paint(tilemap_layer: TileMapLayer)`,把地板/墙写进 TileMapLayer,
   墙只画在与地板相邻的格子上。
6. 附带 `is_valid(level: LevelData) -> bool`:用 BFS 洪水填充检查 exit 可达、
   可达格数 >= 房间总面积 80%、最短路长度在 20~120 格之间。

输出格式:
- 先给 5~10 行设计说明(数据结构与算法取舍);
- 再给完整可运行代码,函数带中文注释;
- 最后列出 3 个我能调的 @export 参数(房间数、房间尺寸、走廊宽度)及推荐值。

约束:不要使用全局 randi();不要引入第三方插件;代码必须能在 Godot 4.3+ 直接跑。

提示词 2:用 FastNoiseLite 做地形与洞穴

适用场景:给地图铺地表/生物群系,或做洞穴、岛屿类地形。

你是 Godot 4 程序化地形专家。目标:用内置 FastNoiseLite 生成一张 128x128 的 2D 地形网格。

要求:
1. 创建两个 FastNoiseLite:一个 Perlin(生物群系/高度),一个 Cellular(洞穴)。
   两者 seed 都来自同一个关卡种子,保证可复现。
2. 参数建议:frequency 0.02、fractal_octaves 4、fractal_gain 0.5;
   生物群系用双阈值分级(水 / 草地 / 岩石 / 雪),不要用单阈值硬切。
3. 输出 `Array[Array[int]]` 的 tile id 网格 + 一个 `to_tilemap(tilemap_layer)` 方法;
   相邻群系边界做一次平滑(把周围 8 格里出现次数最多的 tile 覆盖中间格)。
4. 提供 `@export` 参数:noise_frequency、cave_threshold、biome_bias,并写清每个参数
   调大调小分别会发生什么。

输出:GDScript 代码 + 一段"参数怎么调"的说明表(Markdown 表格,3 行以上)。
禁止:不要用 Image/贴图生成,只输出 tile id 数据。

提示词 3:程序化生成的 Debug 与体检提示词

适用场景:生成结果”能跑但不好玩”、出现孤岛房间/卡死/闪退时。

你是 Godot 4 代码审查员。下面是我的程序化关卡生成脚本(见附代码)。
请按以下顺序检查并给出可执行的修改补丁:

1. 确定性:是否混用了全局 randi()/randf()?是否有跨函数的隐式状态导致同 seed 结果不同?
2. 终止性:所有循环是否有硬上限?重叠重试、走廊连接是否可能死循环?
3. 连通性:是否存在无法从起点到达的房间或出口?给出 BFS 检查代码。
4. 性能:单次生成耗时是否可能超过 16ms(导致卡帧)?是否应改为分帧/线程生成?
5. 可玩性:房间尺寸与数量是否会导致"空旷"或"拥挤"?给出 3 组推荐参数组合。

输出格式:
- 按「问题 → 影响 → 具体改法(含代码 diff)」三段式,逐条列出;
- 每个问题标注严重级别(致命/严重/建议);
- 最后给一份"生成质量自检清单",我可以直接存成 TODO。

约束:只审查我给的范围,不要重写整个文件;改动尽量小于 50 行。

学习心得

程序化生成是”用 AI 做 Steam 游戏”里最容易被低估的一块:它让一个人也能做出 20 小时的内容量,但也会把”设计问题”放大 20 倍——随机出来的关卡不好玩,玩家不会骂生成器,只会骂游戏。所以正确的顺序是:先把”什么算好关卡”写成检查函数,再让 DeepSeek 去实现生成器。给 AI 的输入不是”帮我写个随机地牢”,而是”满足这 6 条验收标准的生成器”,产出可用率会高一个数量级。另外一定要把种子暴露在 UI 上:既方便自己 debug,也让玩家能分享”神种子”,这本身就是免费的社区话题。

明日关注

  • 把 LevelData 存成 Resource 并用 ResourceSaver 落地,方便手改单张关卡做对比测试;
  • 试 @export_tool_button + @tool,在编辑器里一键重新生成并预览关卡;
  • 给生成器加”难度曲线”参数:按层数提升敌人密度与房间规模,看看是否比纯随机更有节奏。

本文由自动化学习流水线每日生成,坚持即学即记。