PROJECT-v M5:种子驱动的程序生成关卡
M5 的目标是把 M4 的手工 LevelConfigSO.orderedRooms 升级为可复现、可约束、可验证的程序生成链路。它不是简单随机抽房间,而是把 36 个房间原型、三世界顺序、房间类型强约束、平台可达性、Addressable 加载、难度曲线和 30-run QA gate 接成一条可测试的生成系统。
除注明 docs 仓库的文档外,本文所有工程路径均以 PROJECT-V 项目根目录为基准;docs 路径均以 PROJECT-V-game-docs 根目录为基准。
关联提交和依据
M5 相关提交:
| commit | 用途 |
|---|---|
c3840c7848cc4cab8b5ee17b3ea189e2364e6b52 |
feat(levels): deliver M5 procedural generation,程序生成里程碑实现 |
c6bc730 |
docs(reports): add M5 delivery report,阶段交付报告提交 |
分支状态:
| 分支 | 说明 |
|---|---|
develop |
M5 实施分支 |
origin/develop |
已推送 |
主要参考产物:
| 类型 | 路径 |
|---|---|
| M5 交付报告 | reports/M5_delivery_report.html |
| M5 QA 报告,docs 仓库 | qa/reports/M5_test_report.md |
| 30-run QA 报告,docs 仓库 | qa/reports/M5_30runs_test_report.md |
| M5 程序生成 spec,docs 仓库 | design/feature_specs/M5_procedural.md |
| M5 难度曲线,docs 仓库 | design/difficulty_curves/M5_difficulty_curve.md |
| M5 房间分类,docs 仓库 | design/room_prototypes/M5_room_taxonomy.md |
| EditMode 结果 | qa/results/M5_EditMode_TestResults.xml |
| PlayMode 结果 | qa/results/M5_PlayMode_TestResults.xml |
| 60s Runtime Probe | qa/results/M5_RuntimeProbe_60s.json |
| 30-run 生成日志 | qa/results/M5_30Runs_GenerationLog.json |
M5 的目标和边界
M5 承接 M4 的 Level / Room / Boss / Difficulty 结构,但把“手工排好的 9 房间路线”替换为“seed 驱动的三世界生成 run”。
M5 纳入实现的内容:
-
36 个
RoomPrototype:3 个异世界 × 12 个原型。 -
每个原型对应
RoomConfigSO和房间 prefab。 -
RoomPrototypeRegistry:按世界观、房间类型、房间序号和可达性筛选候选房间。 -
SeedSystem:确定性 RNG、seed 派生、三世界顺序洗牌、run hash。 -
LevelGenerator:生成三世界 run,强制 Start / Boss / Shop / Rest 结构。 -
AddressableRoomLoader:按 room prefab key 异步加载 / 卸载生成房间 prefab。 -
M5RuntimeProbe:60s 性能采样和 30 个派生 seed 的生成验证。 -
M5_ProceduralGeneration.unity测试场景。 -
BuildScript 集成
M5ProjectBuilder.VerifyM5Setup()。
M5 明确不做:
-
不做最终房间美术。房间 tile 已接入三世界占位 sprite,但不是终局视觉。
-
不做正式 OGG 房间切换音频资产。当前完成 SFX hook,并绑定现有 WAV,这项债务留到 M6/M7 补齐。
-
不做玩家体感调参收口。当前难度曲线是工程可通关版本,不等于最终节奏。
-
不做 Roguelite 元进度、存档、纯度 UI,这些是 M6 范围。
这个边界合理。M5 真正要证明的是生成系统不会造出结构性坏关卡:无 Boss、无补给、入口/出口悬空、死路、不可复现路线、Addressable key 缺失和性能劣化。
工程生成入口
M5 的入口是 unity-project/Assets/_Project/Editor/M5ProjectBuilder.cs。
SetupM5() 的流程:
1 | EnsureFolders() |
核心产物:
| 类别 | 路径 |
|---|---|
| 原型注册表 | unity-project/Assets/_Project/Levels/SO/Prototypes/RoomPrototypeRegistry_M5.asset |
| M5 房间配置 | unity-project/Assets/_Project/Levels/SO/M5Rooms/ |
| M5 房间 prefab | unity-project/Assets/_Project/Levels/Prefabs/M5Rooms/ |
| 测试关卡 | unity-project/Assets/_Project/Levels/SO/Levels/Level_M5_Procedural_Test.asset |
| 测试场景 | unity-project/Assets/Scenes/TestScenes/M5_ProceduralGeneration.unity |
| 生成器 | unity-project/Assets/_Project/Levels/Scripts/LevelGenerator.cs |
| 原型模型 | unity-project/Assets/_Project/Levels/Scripts/RoomPrototype.cs |
| 原型注册表 | unity-project/Assets/_Project/Levels/Scripts/RoomPrototypeRegistry.cs |
| Seed 系统 | unity-project/Assets/_Project/Levels/Scripts/SeedSystem.cs |
| Addressable loader | unity-project/Assets/_Project/Levels/Scripts/AddressableRoomLoader.cs |
| Runtime Probe | unity-project/Assets/_Project/Levels/Scripts/M5RuntimeProbe.cs |
VerifyM5Setup() 会先调用 M4ProjectBuilder.VerifyM4Setup(),再检查 M5 自身:
-
registry 数量在 30-45 内,当前为 36。
-
三个世界观各至少 10 个原型,当前各 12。
-
每个世界观覆盖 Start / Combat / Elite / Reward / Shop / Rest / Boss。
-
所有原型可达。
-
Addressable entries 存在。
-
生成器能通过强约束验证。
-
M5_ProceduralGeneration.unity场景包含生成关卡 harness。
这延续了前几阶段的原则:阶段产物必须能被工具重建和校验,而不是靠一次手工编辑器状态维持。
RoomPrototype:生成前先定义可达性
M5 的最关键对象不是 LevelGenerator,而是 RoomPrototype。生成器只能在原型质量足够高时工作,否则随机抽取只会放大坏数据。
RoomPrototype 位于 unity-project/Assets/_Project/Levels/Scripts/RoomPrototype.cs,核心字段包括:
| 字段 | 含义 |
|---|---|
worldview |
Wasteland / Cyberpunk / Biohazard |
roomType |
Start / Combat / Elite / Reward / Shop / Rest / Boss |
shapeType |
Horizontal / Platform / LShape / TShape / Vertical |
minRoomIndex / maxRoomIndex |
允许出现的房间序号范围 |
selectionWeight |
生成权重 |
entryLocalPosition / exitLocalPosition |
入口和出口点 |
platforms |
平台段集合 |
requiresJump / requiresVerticalTraversal |
生成与 QA 标签 |
addressableKey / label |
prefab 加载地址 |
roomTemplate |
对应 RoomConfigSO |
M5 的可达性模型使用 M1 玩家能力作为保守边界:
| 参数 | 值 |
|---|---|
| 最大水平间距 | 2.75 |
| 最大向上台阶 | 2.10 |
| 最大安全下落 | 3.20 |
| 入口 / 出口支撑容差 | 0.35 |
RoomPrototype.ValidateReachability() 的判断方式:
-
检查 room size 和平台段尺寸。
-
找到支撑入口点的平台集合。
-
找到支撑出口点的平台集合。
-
把平台段看成图节点。
-
用
CanTraverse()判断平台间是否可走、可跳或可下落。 -
从入口支撑平台做 BFS,只要能到达任一出口支撑平台就通过。
这个模型比“所有平台都必须互通”更合适。它允许奖励旁支、Boss 安全台、T 形分叉存在,但主路径不能断。
M5 实施中修正过三类平台问题:
-
Platform 房出口高度与出口平台不一致。
-
Platform / LShape / TShape 主路径水平间距超过默认可跳距离。
-
Vertical 房最后一段到出口间距过大。
最终 36 个原型都通过了 EditMode 和 VerifyM5Setup 双重验证。
36 个房间原型
M5 每个异世界有 12 个原型:
| 类型 | 每异世界数量 | 用途 |
|---|---|---|
| Start | 1 |
开局安全房 |
| Combat | 5 |
普通战斗 |
| Elite | 2 |
中后段高压战斗 |
| Reward | 1 |
构筑奖励 |
| Shop | 1 |
中段补给 |
| Rest | 1 |
Boss 前缓冲 |
| Boss | 1 |
关卡终点 |
形状覆盖:
-
Horizontal
-
Platform
-
LShape
-
TShape
-
Vertical
代表性原型:
| 世界观 | 示例 ID | 类型 | 形状 |
|---|---|---|---|
| Wasteland | waste_arrival_ruins |
Start | Horizontal |
| Wasteland | waste_scrap_bridge |
Combat | Platform |
| Wasteland | waste_radio_tower |
Elite | Vertical |
| Cyberpunk | cyber_switchboard_t |
Combat | TShape |
| Cyberpunk | cyber_clockwork_spiral |
Elite | Vertical |
| Cyberpunk | cyber_mechanism_core |
Boss | Horizontal |
| Biohazard | bio_clinic_l_shape |
Combat | LShape |
| Biohazard | bio_spine_climb |
Elite | Vertical |
| Biohazard | bio_maternal_core |
Boss | Horizontal |
RoomPrototypeRegistry.Find() 会按四个条件筛选:
1 | worldview |
这意味着生成器不会在第 2 房抽到只适合后段的 Elite,也不会抽到可达性失败的 prefab。
SeedSystem:复现不是附加功能
M5 的 seed 系统位于 unity-project/Assets/_Project/Levels/Scripts/SeedSystem.cs。
它提供三类能力:
| 方法 | 作用 |
|---|---|
NormalizeSeed() |
处理输入 seed |
Derive() |
用 base seed + salt 派生子 seed |
GenerateWorldviewOrder() |
对三世界顺序做确定性洗牌 |
HashGeneratedRun() |
把生成结果压成可复验 run hash |
随机数本身用 SeededRandom,内部是轻量 xorshift。M5 不需要加密随机数,需要的是低成本、可复现、跨测试稳定。
同一 seed 必须稳定产生:
-
三世界顺序。
-
每个世界的房间数。
-
每个房间的类型计划。
-
每个房间抽到的 prototype。
-
最终 run hash。
EditMode 的 SeedSystemProducesStableRunHashAndVariedRoutes 覆盖了两件事:同 seed 重复生成 hash 一致;30 个派生 seed 中至少有足够多不同 route hash,避免“看似随机,实际总是同一条路线”。
LevelGenerator:强约束下的随机
生成器位于 unity-project/Assets/_Project/Levels/Scripts/LevelGenerator.cs。
GenerateRun() 负责生成一个完整 run:
1 | GenerateWorldviewOrder(seed) |
每个 GeneratedLevel 是 6-12 个房间。BuildRoomPlan() 先写死硬约束,再用 seed 填充可变节点:
1 | room[0] = Start |
关键规则:
-
Boss 固定最后一房。
-
Rest 固定倒数第二房。
-
Shop 固定中段。
-
Elite 最早第 4 房,且不相邻。
-
Reward 按剩余 Combat 位置插入。
-
每个候选房间都必须从
RoomPrototypeRegistry.Find(..., requireReachable: true)获取。
ValidateRun() 和 ValidateLevel() 是 M5 的防线:
| 验证 | 失败条件 |
|---|---|
| run level 数 | 不是 3 个世界观 level |
| level 房间数 | 少于 6 或多于 12 |
| 首房 | 不是 Start |
| 末房 | 不是 Boss |
| 房间数据 | prototype 或 room config 缺失 |
| 可达性 | 任一 room prototype 不可达 |
| Boss | Boss 房不是正好 1 个 |
| Shop | 少于 1 个 |
| Rest | 少于 1 个 |
这就是 M5 的核心工程决策:随机只在强约束内部发生。生成器可以改变路线,但不能改变关卡的最低可玩结构。
难度曲线如何接入生成
M5 没有重写 M4 的难度系统,而是复用 DifficultyScaler.Evaluate()。差异在于 M5 为每个生成房间构造 RoomDifficultyContext。
关键输入:
| 字段 | M5 输入 |
|---|---|
roomIndex |
当前房间序号 |
totalRooms |
当前关房间总数 |
levelOrderIndex |
本 run 中第几个异世界 |
roomType |
Start / Combat / Elite / Reward / Shop / Rest / Boss |
worldview |
当前世界观 |
upgradeScoreRaw |
(levelOrderIndex - 1) * 3 + roomIndex * 0.75,上限 18 |
weaponWorldModCount |
当前关序号 |
uniqueWorldviewCount |
当前关序号 |
crossWorldSynergyCount |
levelOrderIndex - 1 |
purity |
当前原型固定 0.5 |
这使得难度在两个方向上递增:
-
关卡内:越靠后压力越高。
-
关卡间:第 1 / 2 / 3 个异世界逐步加压。
Elite、Boss 高于普通 Combat;Reward、Shop、Rest 不制造战斗压力。M5 当前曲线是工程可通关版本,后续仍需 playtest 调整 Elite 密度、Reward 数量、Rest 位置和 Boss 前压力上限。
AddressableRoomLoader
M5 引入 unity-project/Assets/_Project/Levels/Scripts/AddressableRoomLoader.cs。
它的职责很窄:
1 | LoadRoomAsync(RoomConfigSO, parent) |
测试覆盖 AddressableRoomLoaderCanLoadAndUnloadGeneratedRoomPrefab,确认生成关卡里的 room prefab key 能被加载,加载后 LoadedCount = 1,UnloadAll() 后回到 0。
这一步不是为了炫技,而是为 M5/M6 后续关卡 streaming 做工程接口。即使当前仍是测试场景,也不能让生成系统只停留在 SO 数据层。
M5 Runtime Probe
M5 runtime probe 位于 unity-project/Assets/_Project/Levels/Scripts/M5RuntimeProbe.cs。
它在 Start() 中先跑 30-run 生成批次,再开始性能采样:
1 | RunGenerationBatch() |
60s probe 结果:
1 | { |
这里最重要的不是平均 FPS,而是 probe 同时证明了:
-
30 个派生 seed 全部生成并验证通过。
-
788 个房间原型检查点通过。
-
P95 / P99 仍接近 60 FPS。
-
GC collections 为 0。
-
生成场景内仍能触发闪避、U 技能、Boss move 和房间推进。
-
Addressable loader 已挂入 runtime harness。
QA Gate
M5 验证结果如下:
| 验证项 | 证据 | 结果 |
|---|---|---|
| SetupM5 | ProjectV.EditorTools.M5ProjectBuilder.SetupM5 |
Passed |
| VerifyM5Setup | ProjectV.EditorTools.M5ProjectBuilder.VerifyM5Setup |
Passed |
| 尾随空格清理后 VerifyM5Setup | ProjectV.EditorTools.M5ProjectBuilder.VerifyM5Setup |
Passed |
| EditMode | qa/results/M5_EditMode_TestResults.xml |
31 / 31 passed |
| PlayMode | qa/results/M5_PlayMode_TestResults.xml |
24 / 24 passed |
| Windows Development Build | unity-project/Builds/Windows/PROJECT-V.exe |
Success |
| Runtime Probe 60s | qa/results/M5_RuntimeProbe_60s.json |
Passed |
| 30-run 生成日志 | qa/results/M5_30Runs_GenerationLog.json |
30 / 30 passed |
EditMode 的新增 M5 用例覆盖:
-
PrototypeRegistryContainsThirtySixReachableRoomsAcrossThreeWorldviews -
EveryPrototypeHasReachableEntryExitAndAddressableRoomTemplate -
SeedSystemProducesStableRunHashAndVariedRoutes -
ThirtyGeneratedRunsSatisfyM5RouteConstraints -
M5SceneContainsGeneratedLevelHarness -
GeneratedLevelHasM5VisualAndAudioHooks
PlayMode 的新增 M5 用例覆盖:
-
PlayerSpawnsOnReachableGeneratedPlatform -
GeneratedSceneEntryAndExitPointsHaveGroundSupport -
LevelManagerCanAdvanceThroughAllGeneratedRooms -
AddressableRoomLoaderCanLoadAndUnloadGeneratedRoomPrefab -
ThirtyGeneratedRunsValidateInPlayMode
这组测试把 M5 最容易出事故的地方都压住了:玩家出生悬空、entry/exit 没地面支撑、生成房间不可推进、Addressable key 假存在、30 个 seed 中出现坏路线。
已知风险和限制
M5 已通过核心工程与 QA gate,但仍有明确债务:
-
正式 OGG 房间切换音频资产未生成。当前完成 SFX hook 并绑定现有 WAV,应作为 M6/M7 前音频资产补齐项。
-
当前难度曲线是工程可通关版本,尚未经过真实玩家体感 playtest。
-
程序生成已经能保底结构正确,但“路线是否有趣”仍需要后续内容调参和更多原型。
-
60s probe 是阶段工程验证,不等同于发布前长时段 soak。
这些限制不影响 M5 验收。M5 的完成标准是生成系统可靠、可复现、可加载、可达、可验证,而不是最终内容手感收口。
M6 方向
M5 之后,PROJECT-V 已经具备从房间原型库生成三世界 run 的基础。M6 不应该再重写生成器,而应在这条链路上接 Roguelite 元进度。
合理方向:
-
把生成 seed 暴露到后续 UI,支持复盘和分享。
-
将纯度、升级树、存档与
LevelGenerator的 build context 对接。 -
补齐 OGG 房间切换和关卡完成音频资产。
-
基于真实 playtest 调整 Elite 密度、Reward 数量、Rest 位置和 Boss 前压力上限。
-
将 30-run gate 扩展到更多 seed 分布和更长 runtime window。
M5 的阶段价值在这里:PROJECT-V 现在不只是有手工关卡和 Boss,而是有一套可复现、带可达性约束、可被自动化验证的关卡生成基础。后续的重点可以转向“生成出来的 run 如何变得更有构筑价值和长期可玩性”。