像代码一样热重载游戏设计
大多数引擎发布的是不透明的二进制文件。RakuAI 发布的是可以 diff、可以评审、可以热重载的文本——AI 神经系统就在文件里,随时可以寻址。正是这个选择,让一整个由智能体组成的团队能够和你一起构建。
文件格式的周末。在这个引擎里,AI 是一个运行时原语,而不是附加在旁边的功能。这是我此前一直在阐述的架构主张。这篇文章要讲的是让这个主张变得具体的那个文件格式。
如果你在大多数引擎上做游戏,你交付的产物是一个二进制文件、一个项目包、一个资产数据库,或者三者的某种组合。你所编写的内容存在于一个专有编辑器内部。你交付的东西对你团队已经在用的工具来说是不透明的。想要 diff 两个版本的体验,意味着要把同一个编辑器打开两次,还得祈祷变更日志是诚实的。
我们走了另一条路。一个 RakuAI 体验就是一个 .raku 文件。这个文件就是 JSON。你可以在任何编辑器里打开它。你可以在任何代码评审工具里 diff 两个版本。你可以用模式(schema)校验它。你可以用 git 对它做版本管理。你可以在 PR 里评审它。你可以热重载它。如果你愿意,你甚至可以手写一个体验文件。
这个文件格式并不炫目,但它是承重的。
一个真实的 .raku 文件长什么样
下面是一个真实的游戏定义,为本文做了裁剪:
{
"schema_version": "1.0",
"game": {
"title": "My Space Shooter",
"genre": "space_shooter",
"template": "space_shooter",
"mode": "prototype",
"max_players": 1
},
"ai": {
"dda_enabled": true,
"target_flow_state": 0.7,
"profiler_mode": "active",
"emotional_tracking": true,
"adaptive_music": true
},
"entities": [
{
"type": "player_ship",
"health": 100,
"shield": 80,
"speed": 22.0,
"fire_rate": 0.09
},
{
"type": "enemy_wave",
"count": 8,
"health": 15,
"ai_behavior": "strafe",
"properties": { "enemy_id": "interceptor" }
},
{
"type": "boss",
"health": 500,
"ai_behavior": "boss_pattern"
}
]
}
这就是一个体验的大部分内容了。一个带模式版本的头部。一个包含表层元数据的 game 区块。一个配置运行时 AI 的 ai 区块。一个描述世界中有什么、每个实体如何行为的 entities 数组。
有几点值得单独指出。
ai 区块就是运行时契约
再看一下这部分:
"ai": {
"dda_enabled": true,
"target_flow_state": 0.7,
"profiler_mode": "active",
"emotional_tracking": true,
"adaptive_music": true
}
这里就是文件宣布「AI 神经系统已开启」的地方。不是「让 GPT 帮我写一个关卡」,而是开启——在运行时层面,每一帧,伴随玩家游玩的全过程。
dda_enabled 开启动态难度调整。运行时会分析玩家正在做什么,并即时重塑遭遇战。target_flow_state: 0.7 是运行时所瞄准的难度区间。profiler_mode: "active" 表示 AI 在读取玩家的行为,而不只是采样它。emotional_tracking 和 adaptive_music 是同样的思路应用到其他子系统上。
一个工厂模式的引擎不可能在文件层面拥有这些旋钮,因为没有运行时 AI 可供文件寻址。在我们的引擎里,这些旋钮就是作者告诉神经系统「这应该是一种什么样的体验」的方式。
你可以在 PR 里评审这个区块。你可以在 CI 里对两个数值做 A/B 测试。一个从未见过编辑器的制作人也能读懂这段内容,并就「目标心流状态」意味着什么与工程团队展开对话。文件格式让设计决策变得可见。
逐实体的 AI 行为是一个字符串,而不是一次集成
文件中的每个实体都可以携带一个 ai_behavior 字段:
{
"type": "enemy_wave",
"count": 8,
"ai_behavior": "strafe"
}
"strafe" 不是一次模型调用,而是运行时中一个已注册的行为,由神经系统层驱动。同一个实体可以是 "patrol",可以是 "boss_pattern",也可以是运行时懂得执行的任何其他行为。文件不会导入一个模型,它寻址的是一种能力。
这就是如何将文件格式与特定模型解耦的方法。作者写下意图,运行时决定用哪个模型、哪套权重、哪种确定性兜底方案来实现这个意图。下个季度换掉模型,.raku 文件不需要改动。
这种分离比听上去更重要。我接触过的每一个把某个特定 LLM 硬编码进引擎的团队,都在模型厂商变动时不得不重做整个集成。我们不需要。
为什么是 JSON
这是我展示这个文件格式时被问得最多的问题。为什么是 JSON,而不是一种拥有更友好数学表达式语法、内联脚本和响应式绑定的自定义 DSL?
三个原因。
第一:每个工具都已经会说 JSON。 代码评审、diff 工具、版本控制、代码检查工具(linter)、模式校验器、CI 流水线、静态分析、每个平台上的每个编辑器——我们不需要自己造这些东西,直接免费拿来用就行。
第二:人类阅读 JSON 的能力足够好。 上面那个文件并不好看,但一位资深设计师无需培训就能读懂它。相比之下,一门需要花一周时间学习才能让人参与 PR 评审的 DSL 就差远了。
第三:AI 助手对 JSON 的阅读能力极强。 这一点在 2026 年的分量比三年前重得多。当一位设计师要求 AI 助手「把这个 boss 调得更吓人一点」时,助手可以读取文件、提出一个 diff,人类再决定接受还是拒绝这个 diff。换成自定义 DSL,就得教会每一个助手一套新的语法。
代价是真实存在的。JSON 很啰嗦,没有内联数学表达式、没有注释、没有简写。我们用表达力换取了工具生态的普适性。到目前为止,这笔交易已经多次回本。
这带来了什么
一旦你的体验变成文本,一些事情就变了。
游戏设计的 PR 评审。 一位设计师把 target_flow_state 从 0.7 改成 0.5,这个改动会以一行 diff 的形式出现在一个 pull request 里。工程和设计一起评审这个改动。PR 里的讨论就是「这个体验为什么是这样玩的」的记录。六个月后,当有人问起为什么难度曲线是这个感觉时,答案就在提交日志里。
体验的 CI。 一个 .raku 文件没通过模式校验,构建就会在改动上线前失败。跑你单元测试的那套 CI,也跑你的体验定义。
热重载。 文件在磁盘上发生变化,运行时会注意到,世界随之更新,不需要重启。开发循环缩短到几秒钟。
回滚。 某个改动破坏了 boss 战,回退那次提交,体验就跟着回退了。不需要编辑器,不需要重建资产,不需要一周的往返。
由 AI 撰写。 团队成员用自然语言描述他们想要什么,AI 助手写出 .raku 的 diff,人类评审者批准它。这个 diff 是可审计、可版本化的,并且遵循和其他任何代码改动一样的评审纪律。
这些都不是什么稀奇的能力。它们是每个现代软件团队在代码库其余部分早已习以为常的东西。我们把它们延伸到了游戏设计上。
难的地方在哪
诚实地说说代价。
JSON 没有注释。我们用配套的 .md 文件记录设计意图、用描述性的字段名来弥补,但这仍然是一个真实的摩擦点。
模式(schema)的演进必须小心翼翼。我们曾经从 schema_version: 1.0 升到 2.0,因为世界生成的需求超出了原有的形状。所有已经在外面流通的文件都需要一条迁移路径。这份工作是分内之事,而且并不轻松。
总有一种想不断添加字段的冲动,我们比听上去更用力地在抵制它。每加一个字段,就是运行时要永久兑现的一份契约。我们的倾向是让文件保持精简,把复杂性放进运行时,而不是塞进文件里。
最后一点:基于文本的体验定义,只有在运行时真的能用它们做出有意思的事情时才有意义。文件格式是下游产物,上游是那个更根本的架构决定。如果引擎把 AI 当作内容工厂,文件就只是一份清单。如果引擎把 AI 当作运行时原语,文件就是乐谱。
如果你想从头到尾读一遍这个格式,模式定义在公开文档里,示例文件也随仓库一起发布。打开一个看看,把它当作代码来读,因为它本来就是代码。
两天的文件格式工作已经收工。下个周末回到引擎本体。
编写你的 AI 能读写的体验
RakuAI 把 AI 当作运行时原语,把体验当作代码。打开 .raku 格式,diff 它,热重载它——让你的助手和你一起在真实世界中构建。