AI 写一段 MOD 脚本只要几秒钟,但这段脚本要在游戏里真的跑起来,先得回答一串它自己并不知道的问题:这个角色的数据表在哪个文件里,招募状态有没有现成字段,队伍人数上限由谁检查,存档里已经存在的同名 NPC 该怎么处理。所以“一句话生成 MOD”从来不是一次生成,而是一条流水线。下面按顺序拆开,看每一步产出什么,又会在哪里出错。
第一步:把自然语言压成 Mod Specification
玩家说:“把酒馆里的铁匠变成可以招募的队友,战斗时用锤子,别太强。”这句话里至少藏着四个决定:目标是哪个铁匠(游戏里可能有三个同名 NPC)、“队友”对应游戏里的哪套系统、“锤子”是现有武器还是新武器、“别太强”到底是多强。规格阶段的产物是一份结构化文档,即 MOD 规格(Mod Specification):目标实体 ID、要新增或修改的字段、依赖的游戏系统、数值范围、验收条件。它应该是 JSON 或 YAML,而不是一段自由文字。
这一步最常见的失败是过度自信地猜。指令有歧义时,好的做法是反问一次,或者把猜测写进规格并标成“假设”,交给玩家确认。后面所有步骤都建立在规格之上,规格错了,后面越顺利越糟糕。
第二步:脚本与资产生成
规格会被拆成三类任务:脚本(招募对话、加入队伍、战斗行为)、角色数据(属性表、技能、装备槽、对话文本)、资源(头像、图标,必要时还有贴图和模型)。AI 目前已经能生成贴图、3D 模型和脚本,也能把引擎 API 用自然语言解释清楚,这一步本身并不是瓶颈。
真正的失败点是接口幻觉:模型写出一个看起来很合理、游戏里却不存在的函数,比如它以为叫 party.add_member(),而真实接口叫 Roster.Enlist。缓解办法是不让模型凭记忆写,而是把当前版本的 API 文档、示例 MOD 和数据模式(Schema)检索出来放进上下文,生成后马上做静态检查:函数是否存在、参数类型是否匹配、字段是否符合模式。资源方面的典型问题更琐碎,比如头像分辨率不对,或者贴图放进了游戏根本不会去读的目录。
第三步:兼容性检查
到这里为止,生成出来的东西还没有碰过真实游戏。兼容性检查(Compatibility Check)把它和游戏本体、已安装的其他 MOD 对照:依赖的游戏版本对不对,有没有覆盖别的 MOD 已经改过的记录,有没有新增会被存档拒绝加载的字段。这部分的细节,可以看多个 MOD 同时改一个角色时怎样自动发现冲突,也是MOD 装进去会不会把游戏弄坏那个问题的核心。这里只强调一点:检查必须产出可读的报告,而不是一个通过或不通过的布尔值,因为下一步的修复要靠报告里的定位信息。
第四步:运行时测试
把 MOD 放进沙盒里的游戏实例,用脚本化的场景去跑:加载存档、走进酒馆、触发对话、招募、进入战斗、存档再读档。产物是日志、断言结果,以及必要的截图或状态快照。
这一步能抓到静态检查抓不到的问题:脚本在读档后丢了状态,招募之后队伍界面出现空白头像,铁匠离开之后原本依赖他的锻造任务被卡死。运行时测试的代价是慢,所以通常分层:先跑几秒钟的冒烟测试,通过了才跑完整流程。
第五步:错误修复与打包
测试失败以后,把报错堆栈、日志、规格和出错的脚本一起交给修复环节。修复不是重新生成,而是带着失败信息做最小改动:改一行调用、补一个空值判断、把字段名换成模式里存在的写法。循环要设上限,比如三轮,仍不通过就退回给玩家,并附上失败步骤和原因,而不是无限重试。还要防一个陷阱:模型可能通过“把出错的功能删掉”让测试变绿,所以测试断言必须覆盖规格里的验收条件,而不只是“没有报错”。
全部通过以后才是打包(Mod Package):清单文件(名称、版本、依赖的游戏版本和其他 MOD)、脚本、数据、资源,再加一份变更说明和卸载方式。金沙娱乐的设计思路是把清单文件当作一等公民,因为后续的冲突检测和版本更新后的自动修复都要读它。
把失败点放在一张表里
| 阶段 | 产物 | 典型失败 | 对策 |
|---|---|---|---|
| 规格 | 结构化 MOD 规格 | 误解意图、目标实体选错 | 歧义反问,假设显式标注 |
| 生成 | 脚本、数据、资源 | 接口幻觉、字段不合模式 | 检索当前版本文档,生成后静态检查 |
| 兼容检查 | 可读的冲突报告 | 覆盖他人修改、版本不符 | 字段级比对,报告带定位 |
| 运行测试 | 日志与状态快照 | 读档丢状态、任务被卡死 | 脚本化场景,冒烟加完整两层 |
| 修复与打包 | MOD 包与清单 | 删功能换绿灯、卸载残留 | 断言对齐验收条件,限制轮次 |
怎么衡量这条流水线好不好
不要只看“生成成功率”,那个数字最容易好看。更有信息量的是四个指标:规格确认率(有多少指令在生成前被澄清过)、首轮通过率(第一次生成就通过全部检查的比例)、平均修复轮次,以及退回给玩家的比例。如果首轮通过率很低却依赖修复兜底,说明规格或上下文检索出了问题,该回头补的是前两步,而不是把修复循环调得更长。