程序化关卡生成:彻底拆解一款四人合作射击游戏

修订说明(2026-09-30):本文记录 2026-06-28 的阶段工作。早期代理方案、样本测量和原版算法证据已重新区分,未完成事项按当时验证范围理解。后续进展见高度图还原与POI、Stamp 和植被岩石布局。

本篇 —— 拆解这款游戏怎么做这套程序化关卡(不碰 UE;另有一篇讲在 UE 复刻)。

标注约定(全文一以贯之):

– 实测:能从解包数据直接验证,标「通过结构解包可以看到」。

– 推断:从数据现象反推、无直接证据,标「从数据现象推断」。

– 黑盒:涉及运行时算法或网络通信、既未逆向也未抓包,标「不下结论」。


引子

这是一款四人合作的俯视角射击游戏。玩家每次出击,降落到的星球地图几乎不会重复 —— 地形起伏、敌方据点、刷怪分布,每一局都不一样。但同一局里,四名玩家看到的却是完全相同的一张图。

“每局都不同”与”四人完全一致”放在一起,指向一种典型的程序化生成思路:地图不是美术一张张手工摆出来的,而是算法在每局开始时按规则现”生成”出来的。从数据现象看,种子确实参与了这个过程:后面会看到,同一个模板下的星球,彼此只差一颗随机种子(第三章详述)。至于这个过程是否为完全确定性生成(给定相同输入必得相同结果),以及几台机器如何算出完全相同的一张图 —— 这属于运行时算法,既未逆向也未抓包,本文不下结论。

但这里要先划清证据边界。本文的结论几乎都来自对游戏数据文件的解包,并没有监听网络通信。因此:能从解包直接验证的 → 标「通过结构解包可以看到」;从数据现象反推的 → 标「从数据现象推断」;凡涉及运行时通信的细节 —— 那颗种子由谁产生、由对局房主还是后端服务器下发、网络里到底传了什么 —— 没有抓包,无法下结论,文中不当作事实。(这款游戏的对局是 P2P 联机、通常有一名玩家当房主,但也存在中心化的战役后端,所以这事并非一望而知。)

带着这条边界,开始拆解。


一、整体:一张关卡的三段

要把这套系统讲清楚,先建立整体框架:一张关卡的”一生”分三段时间 —— 生产、启动、运行时组装。

生产,发生在制作期。 美术与策划做的不是一张张地图,而是一套可复用的预制资产(单位、预制件、场景块)与一套生成规则。这些资产连同规则的字段都打包进游戏,可被解包看到。这一段的产物里只有”原料”:资产带块内局部坐标,却不含任何成品地图、不含任何据点的世界坐标。

启动,发生在每局开始的瞬间。 一颗种子进来,决定这一局,并做两件可解析程度不同的事:其一是选定配方 —— 用哪个星球模板、哪套 region、哪些规则,这一层在数据里可见(第三章会看到,同一模板下的星球在静态配置层仅以种子等少数字段区分);其二是解算布局 —— 把配方与种子关联到每个据点的最终世界坐标,这一步的算法编入程序、尚未逆向。所以启动这一段,一半可控、一半是黑盒。

运行时组装,发生在关卡载入时。 按解算出的布局,把预制资产放置到对应世界坐标上,拼成这一局的关卡;每个据点的世界坐标,在此刻才确定并实例化。

到这里关卡才真正成形 —— 而成形的这一刻,正是解包能力的边界。

这一局生成的关卡布局(选用了哪些场景块、各自的世界坐标)不写入任何文件。 解包数据里只有”原料”与”配方”,找不到任何一张成品地图。可以确定它是运行时产生的;但它如何产生、由谁求解、四端如何达成一致,既未逆向算法、也未做网络检测,无法下结论。一个最基本的问题就已无法回答:四端看到同一张图,究竟是帧同步(lockstep) —— 各端以相同种子与算法独立求解,还是状态同步 —— 某端求解后将结果复制给其余端?两种架构都能解释”四端一致”,且都不在静态数据里留下世界坐标,缺网络检测便无从判定。唯一能确定的,是那个现象本身:四名玩家最终看到完全相同的一张图。

所以全篇主线可以概括为:制作侧透明,运行时成品是黑盒。 制作期产出的内容 —— 做了哪些资产、附加了什么任务与 AI 组件、规则有哪些档位 —— 几乎都可解析;但”最终拼成什么样”在运行时产生、不落文件。后续各章都会回到这条边界:可解包者详述,黑盒者只标”运行时产生、不下结论”。第七章会把这些边界合并,阐述为”数据分层墙”。

ch1 整体 · 三段时间线

二、资产层级:从单位到资源包

关卡的内容,组织成可复用的预制资产。但要先分清:前三级是”生产的内容”,第四级(package)是”保存的打包”,两者不是一回事。

三级内容资产:unit → prefab → level

  • unit(单位) 是最小粒度,约 4100 种。它不是”美术模型”,而是一个 ECS 实体:外观(模型 / 材质 / 贴图)加一组组件,组件决定它在玩法里是什么 —— 挂 AiSpawner 是刷怪点(虫洞)、挂 Behavior / Faction 是敌方单位、挂 Interactable / Objective 是任务终端、挂 Health / Deposit 是可破坏物或资源点;什么玩法组件都不挂的,才是纯装饰。
  • prefab(预制件) 是一组 unit 封装成的可复用模块,582 种,平均实例化约 55 次,支持嵌套(实测 358 处嵌套引用、无环)。成块复用的内容才封装为 prefab,零散单位直接放置。
  • level(场景块) 是一段预制场景,1845 块,是”混合容器”:prefab 与 unit 都能直接放进去 —— 混合(prefab+unit)1028 块最多,纯 unit 701 块次之。

三者是组装关系:小到大,逐级拼成更完整的场景内容。

package:保存的打包,不是第四级内容

容易误解的是 package(资源包):它看起来像第四级,但和前三级根本不是一回事。前三级是美术 / 策划生产的实际内容;package 只是把生产好的 level 按类型打包保存的方式,本身不含任何新内容。1105 个 package 把场景块按阵营 / 类型分组(机械 384 / 虫 230 / 超 205 / 光 65…),一个 block 还能归属多个包(平均约 2.5)。它的用处:可确认的是确定打包分类(实测);至于是否参与运行时按需加载,从静态配置无法判断。而是否参与生成期选块 —— 整个配置层未找到任何对具体 package 的直接引用,因此它更像一个非生成参与层(打包维度),而非选块逻辑的一环。这道”索引存了、内容对象留运行时”的缝,第七章会归并成同一道墙。

简言之,内容资产是”生产、组装”的,package 是”保存、打包”的,两者不应混为一谈。

ch2 资产层级 · 三级内容 + package 打包层

复用是这套设计的根基

582 种 prefab 在场景块中累计实例化数万次,最频繁的 prefab 分布于 200+ 个场景块,最频繁的 unit 被放置数千次。美术维护一套规范资产、运行时反复实例化,降低重复制作成本、保证全局风格统一 —— 这是程序化生成”四两拨千斤”的第一层。


三、生成:星球与地形

星球与地形不是”做”出来的,是”算”出来的。

星球 = 模板 + 种子

384 颗真实星球,背后只有 25 个模板。同一模板下的星球,生成配置完全相同,仅以名字、资源点、种子区分 —— 在静态配置层,同模板下的星球只体现为名称、资源点与种子的差异(planet_data 425 实体全部导出)。从数据看,种子参与了生成,但它在运行时的实际作用范围、是否为唯一驱动变量,无法从静态数据确认,本文不下结论。

每个 planet_setup 还把内容轴一次性绑定:1 个 setup = 1 scenario + 1 scatter + 1 环境群系,同 setup 内低 / 高地配置完全一致(26 个 setup 逐行核对、无一不一致)。region 有 29 种;星球数据里 region 分低地 / 高地两个结构,但 425 颗实测低地 / 高地取值完全一致 —— 理论上有 29×29 种组合,但从当前数据看,该维度在星球实例中只表现为单一取值,未观察到组合扩展。道路则在环境层预设 5 条样条(1 条基础路 + 4 条阵营路),全部环境共用同一套。

3a 星球 = 模板 + 种子

地形:形 / 皮 / 物

当时检查的几何类型没有直接资源引用(STR=0),只能说明这些记录的字段。后续已确认自然 Stamp 与 Location 通过其他资源层级引用静态高度资产,不能将地形概括为完全无资产输入。

所以美术只进”皮”和”物”;地形的”形”是程序按种子算出的,不是美术制作的。

地形的三类输入:几何、材质与物件

地形系统的组件

按”规则 → 几何 → 形变 → 放置”四层看:区域规则层(GenerationRegionSettings / SubRegion)定参数底板;几何层(Voronoi / 噪声)算出形状;形变层(DisplacementComponent)在基础地形上挖坑 / 隆起 / 压平;放置层(Stamp / LocationStampInfo)把地貌建筑盖上去。这套组件的类型、字节、字段、枚举几乎全解包;但实例真值里,只有 DisplacementComponent 一类成功导出(632B,实测半径 15 / 45 / 33 / 6 等)。其余配方层(GenerationRegionSettings / Stamp / Location)只有结构、实例未序列化 —— 这一点留到第七章。

3c 地形系统组件

四、任务

任务不是一张固定列表,而是”候选池 + 规则 + 标签”。

任务怎么编排

候选池决定”哪些任务能进哪些块”:环境 / 阵营圈定资源包类,再到候选 level 与 objective(1026 对 → 去重 203 个 objective、339 个 level、81 个 family、12 种 task_shape)。这个 1026→203 的收敛说明一件事:任务不是”一块地图绑定一个任务”,而是 203 个目标在 339 个块上多对多匹配 —— 是一张组合型任务图,不是关卡绑定的固定列表。而”一个块能承载哪些任务”,是块自带的 —— 每个 level 的 Objective range 在数据里圈住”块内哪些对象是任务实体”(165 个 range 组、覆盖 2085 个槽)。一局的任务多按”核心 + 支撑 + 投递”三槽组合(摧毁 / 上传 + 轨道炮 / SAM + 油桶 / 逃生舱)。放置规格由 LevelGenerationLocation 定:地点类型(主目标 / 副目标 / 撤离 / 营地)、默认半径(营地约 90、撤离约 130);但实例 0 条 —— 这一局放几个、放在哪、坐标多少,在运行时。

4a 任务编排

任务怎么完成

一个任务的完成是”执行蓝图”:Objective(205 卡)→ 每卡 8 个阶段槽 → 每个激活的阶段是一种 Stage Type(19 种);实际激活平均 1.2 个、75% 是单阶段。”8 个槽却平均只激活 1.2 个”不是浪费,而是一套留足扩展位、按需填用的模板:绝大多数任务一步即成,少数多阶段任务复用同一套槽位结构,无需为长任务另起一套数据。最能体现”积木化”的是终端:同一套终端组件,靠配置签名拼出 11 种玩法(上传 / 钻取 / 雷达 / SAM / ICBM…) —— 不是 11 套代码,是 1 套组件 × 不同配置。失败判定靠目标单位的”摧毁即失败”标记(FailOnDestroyed_Always,55 个),战备需求如 Hellbomb、RaiseFlag。

4b 任务执行

五、刷怪

刷怪是三层架构:静态预设的刷怪器 + 运行时调压的导演 + 预设的敌人队伍。

架构与三阵营

静态 spawner(66)预设在哪刷(虫洞 / 工厂 / 传送门),HiveMind 导演(38)按阵营 / 难度调压,Squad(17)是预设的敌人队伍组合。三阵营机制各不同:虫族靠预制虫洞(SpawnLane 左 / 中 / 右 481 / 482 / 483),机械靠工厂(门 708)加运输机空投,光能靠传送门召唤(传送点 1850 / 1851,独有主动压力 pressure 初值 300 / 600)。据点守备规模由 GuardForce 定(阵营 × 倍率,虫 2× / 机 1.5×)。

5a 刷怪 · 三层架构 + 三阵营

刷什么 / 怎么刷 / 何时刷

按三层精度看边界:

  • 刷什么(模板) 解出大半 —— 遭遇波 Squad 成员表(虫族 6 波具体到只数)、Encounter 池、本地车道;但据点守备的成员表仍黑盒。
  • 本地怎么刷(时序) 这次新解出 —— start / stop 信号、车道、时序元组(初始延迟 / 波间隔 / 单位间隔 / 冷却,实测如 40 / 40 / 4 / 0.2)、计数;本地刷怪器”隔多久、几个一波”的框架已清。
  • 全局何时 / 多少(调度) 仍黑盒 —— HiveMind 的触发时机、波次预算、运行时调用链,全在运行时内存、不落盘。

所以从已解包的结构看,本地刷怪的时序与分层规则已基本清晰;但更上层的全局调度机制,仍未在静态数据中出现,仍属运行时黑盒。

5b 刷怪 · 刷什么 / 怎么刷 / 何时刷

六、世界

星球的”世界感”是参数配置 + 手工摆放 + 程序撒点的组合。

环境氛围

天气是 EnvironmentalEffectType,15 种(雪暴 / 雨暴 / 沙暴 / 离子风暴 / 浓雾…),按 region 限定 —— 沙漠星球不会下雪暴。天空是整套数据里解得最透的一块:35 组 × 42 参数,每个参数都分昼 / 昏 / 夜三时段(Rayleigh / Mie 散射、雾、云色、地表湿度、星亮),一颗星球的”光”就是这 42 个旋钮配出来的。散布是 scatter(21 个分类键、非清单)加 StampGroup(密度 / 间距 / 概率)驱动的程序撒点;它给的是”怎么撒”的规则,不是”撒了哪些”的清单,具体撒点算法在运行时。

6a 环境氛围 · 天气 / 天空 / 散布

掩体与道路

掩体分三层,只有一层是游戏真语义:A 是游戏真实 cover(*_cover 套件 + CoverInfo 组件 + AI 按高度分级的 task_cover_125cm),手工摆 + AI 运行时识别;B 是大型实心物(岩石 / 箱体,功能能掩护但非游戏标签);C 是装饰。cover 不是程序撒的 —— 这点和植被正相反。道路是一套全局统一的 5 槽样条(基础路 + 四阵营路,碰撞宽 15 / 网格宽 20),全部环境共用、按阵营选用。

6b 掩体与道路

七、边界:数据分层墙

全篇反复遇到的边界,可以归结为同一道墙。

数据分两层:可序列化层(索引 / 分类 / 规则 / 配方结构 / 默认模板)打包进 datalibrary、能解包;内容对象层(运行时实例 / 成品布局 / 世界坐标 / 选定结果)按 64 位 hash 打包资源加运行时内存,结构性拿不到。这道墙在静态数据中未出现任何实例序列化结果(只有索引与结构);对此更合理的解释是存在”运行时对象与静态配置的分离”,而非加密或隐藏机制 —— “谁用哪份配方”(索引)存了,”配方的实际内容对象”留运行时。但其具体实现方式、以及是否刻意设计成这样分层,无法从静态数据判断。

四条独立调查指向同一道墙:① region / 地形(结构有、实例 type-hash 扫 14 库 0 命中)② 刷怪(遭遇波加本地时序有、据点守备加全局调度无)③ 任务(候选池加执行蓝图有、实际选哪几个加坐标无)④ 任务放置(默认模板有、实例 0 条)。

为什么有的拿得到?PTR 分档:0-PTR 是可序列化配方(结构可落盘),有-PTR 是运行时对象图(拿不到)。最有力的证据是 DisplacementComponent —— 它 0-PTR 且已成功导出真值,证明 0-PTR 配方结构上完全能落盘;其余 0-PTR 类型只是没序列化实例(不是解不出,是没存)。

ch7 边界 · 数据分层墙

八、总结

回到开头那句:这款游戏没有”静态存储的完整成品地图”。 一张关卡的组成部分(预制资产、生成规则、星球配方)都在文件里,但拼合后的成品 —— 选了哪些块、刷了哪些怪、每个据点落在哪 —— 不落盘,运行时按一颗种子实时算出。制作侧的资产、规则、配方、默认模板几乎完全可解包;但”某一局最终拼成什么样”是运行时产生的。

这套设计的好处清晰:美术做一套预制资产反复复用(省成本)、只发种子不发整图(省带宽)、加配方即可扩充内容(易扩展)。而四端如何看到完全相同的一张图 —— 是确定性生成各端独立求解、状态同步由某端下发,还是两者的混合 —— 未逆向算法、未做网络检测,本文无法确认。它把”做关卡”变成了”做一套生成关卡的系统”。

那么 —— 看懂了这款游戏怎么做,能不能照着在 UE 里复刻一套?这由另一篇回答。


*全文数值定义与统计范围已逐条核实;凡标”黑盒 / 运行时”处,均为未逆向算法或未做网络检测,不作无据推断。*

《程序化关卡生成:彻底拆解一款四人合作射击游戏》有1条评论

发表评论

了解 AI Native Game Development 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读