如何为游戏开发构建 AI Harness
How to engineer an AI harness for game development
文章系统讲解如何把游戏设计为可被 AI 智能体(如 Claude Code)直接理解与操作的 Harness。核心做法包括固定随机性与确定性状态、把一切抽象为单一 Thing 类型、Command Pattern、外部化数据管理,以及把 Simulation 独立为 DLL 让引擎与逻辑解耦;配合 odiff 做截图差异比对、samply 做文本化性能分析,AI 可以在不编译、秒级循环中验证改动。
给出把游戏变成 AI 可操作 Harness 的完整架构方法,对想用智能体迭代游戏逻辑的开发者有直接借鉴价值。

我在 9/12/26 的Indie Rodeo in Calgary 上做了一个 45 分钟的演讲,题目是 “Your Game Is A Harness”。在演讲之后与许多人交流之后,我觉得把演讲中讲到的想法以更广泛的形式分享出来,让不仅限于参会者的更多人也能享受,是一件好事。
另外,由于我在这个博客上经常谈论 AI,人们就以为我写作时也用 AI。但事实并非如此!下面所有内容都是 100% 由人撰写的,我基本上只参考了我演讲时用的幻灯片来写这篇文章(这不是一份文字转录)。尽管我非常喜欢用 AI 来编写代码和工具,但我同样乐在 “用老办法” 写作,尤其是因为我没有任何理由去试图让这个流程中的任何环节变得更高效。
好了,这些都说完了,下面我把我做过的演讲首次“博客化”呈现给你。请欣赏!
Introduction
[I give a small introduction to myself and my own work, that I’m omitting here, as you’re on my website]
我想聊聊在游戏开发中使用 LLM 的事。不是那种在 Twitter 上看起来很酷的一次性 demo,而是真正的开发。也就是每天工作八小时、一周五天等等那种。
在正式开始之前,我觉得有必要先来谈谈对 LLM 的基本认知。至少先讲一些基础知识,这样后面我讲的内容才更容易理解。我清楚,对很多人来说这部分只是复习,所以如果你只想直接看开发循环相关的内容,请跳到下一节。
我使用 LLM 来做开发的旅程,始于我做的一个情绪板网站,名字叫 mood.site。那时候还 “agentic” 这个概念都还没出现,但我当时想测试一下,看那时的 ChatGPT 是不是真的能写出代码。整个过程基本上就是我一通盲目地把代码复制粘贴到 ChatGPT 的网页界面里。过程很糟糕,但结果却让我惊喜。我把网站做出来了!它真的能跑!我仍然需要自己核实相当多的内容,但这足以让我彻底转变看法。如果当时我无法使用 LLM,我根本不可能在差不多的时间内把那个网站做出来(大概花了 2-3 周的晚上,加起来差不多 20 个小时)。换成现在的话,我很可能还在吭哧吭哧地做第一版。
从那以后,mood 就成了我折腾各种 LLM 相关东西的一块试验田(同时也成了一个还不错的独立项目!),还启发我启动了许多其他使用类似技术栈的项目。此外,LLM 也变得更强了,尤其是 Claude Code 以及后来的那些 Claude 模型。就我自己的感受而言,在基于网页的技术栈方面,现在几乎没有什么事情是我觉得 LLM 做不了 的。
由此很容易得出 LLM 能做一切的结论,毕竟软件里有很大一部分都是网页软件。但每当我尝试用 LLM 来做某个非网页的游戏东西 时,我都会得到一个非常强烈的感受:

“LLMs are Bad At Games"
每次我尝试用LLM来做游戏相关的事情,它就会失败。也许这是我自己的偏见。我做了近15年的游戏程序员,我认为我了解其中的原理。我能一眼就嗅出 ""糟糕"的游戏代码。但在Svelte网页应用方面,我明显了解得少得多,并且倾向于认为LLM生成的任何代码都是 ""好的"。也许它实际上是垃圾。但确实有理由相信Svelte和web技术栈的东西是高质量的,部分原因是网上有大量源代码公开的高质量网页项目。相比之下,对于游戏来说,我猜训练数据要少几个数量级。
不仅如此,游戏中有大量东西不是代码。游戏不仅仅是把静态图片和文字放到布局里。游戏中有大量东西是不容易用代码表示的。而且除此之外,还有所有网页相同的文本布局内容,但这只是游戏需要处理的100个其他系统中的一个子集。
这是很多人一直在用Three.js做游戏的部分原因——他们可以轻松地把资产表示为简单的方块状基本体,并跳过游戏通常需要的大量资产生成/整理工作和处理。因此,很多这类东西很粗糙,因为它们除了LLM的中位水平之外,缺乏任何真正的作者个性。
不过,也许他们说得有道理。

一次性Three.JS工作流(以及LLM擅长构建网页应用)的一个关键思想是一切都是代码。通过把东西作为代码,LLM就能非常容易地对它进行推理、做出修改等。

然而对于传统游戏来说,情况并非如此。或者说,至少不是全部情况。游戏显然有代码,但还有很多其他东西不是代码。这意味着游戏中有一些关键组件对LLM来说实际上是不透明的。如果你的游戏依赖于对这些不透明内容进行有效的操控和变换,那么LLM在尝试处理你的游戏时就会崩溃。

我认为很多游戏开发社区可能不久前尝试过LLM,然后直接撞上了这个问题。这个 ""LLM很烂/没用/等等"的观点(这里有巨大免责声明,只是在谈能力本身,而不是潜在的毁灭社会的含义)来自一头撞上这些局限性。

也就是说,从结构上讲,LLM不擅长阅读游戏。至少在其默认状态下是这样。
LLM基础
让我们更详细地讨论一下原因,以及我们能做些什么。
对任何LLM服务的任何调用/提示都可以分解为三个基本组件:系统提示、助手提示和用户提示:
系统提示:在服务的所有用户之间共享的通用文本。这通常描述LLM本身的基础信息,比如 ""你是Opus 5,一个由Anthropic创建的智能大语言模型……"。这并不是说它们不重要(它们确实重要),而是说它们与我们的用例不是非常相关。你可以在这里看到一些已发布(以及被泄露的)模型的系统提示的例子。
Assistant prompt(助手提示): 这其实是一段辅助性的提示部分,用于引导AI理解自己要回应什么。你基本可以把它看作模型针对你提示的基本 “交互”向量,代表模型基于某些输入对用户的“回应”。通常你不需要特别留意这一部分。
User Prompt(用户提示):这才是真正的主体内容。你输入到任何AI工具中的内容都会进入这里。任何提交的聊天、代码等都会被放进这个部分。当人们谈论“上下文”时,他们大多是在说“提示中用户(和助手)部分存储了多少 token”。
也许很容易觉得每次聊天都是上述内容的“全新”版本,但实际发生的情况是:当你在一个会话中与模型交互时(“会话”的边界取决于你使用的服务——比如可以是关闭前的单个 Claude Code 会话,也可以是网页托管模型侧边栏中可访问的“聊天”等),你之前发送给模型的每一条消息,都会在你每次发送新消息时被全部重新提交一次。这有点像以前操场上的一个游戏:在你这一轮,你必须把其他所有人的台词再说一遍,然后再加上你自己的内容,供下一个人继续接下去。

Context and Context Pollution(上下文与上下文污染)
当你一次又一次地这样做时,你就在你正在使用的会话中逐渐累积起了“上下文”。

这本身没什么问题,但这里需要注意的一个关键点是:你实际上只能处理上下文中的“最新”内容(也就是你提交的新消息)。之前消息中的内容会被一遍又一遍地反复提交给模型。因此,你要确保上下文中的内容是“好的”,否则你可能会陷入糟糕的境况:由于之前“不好的”内容不断影响新的工作,你的上下文会逐渐被污染。

因此,管理上下文是与 LLM 协作时的关键所在,正是在这些限制的背景下,一项名为“context engineering”(上下文工程)的新工程实践应运而生。上下文工程探讨的是“如何把最有价值的信息放进上下文,同时尽可能减少污染”。
这里有一篇很棒的博客文章,主题是关于“长上下文的各种失效方式”,对这个问题做了更深入的探讨,不过我在这里先重点强调一下:

上下文工程本身是一个非常大的主题,超出了本次演讲/博客的范围,但我们会借用其中的一些原则,来探讨如何帮助 LLM 更好地处理游戏相关的工作。
换句话说,让 LLM 处理好游戏是一个上下文问题,而不是与游戏或 LLM 本身“是怎样的”(或“不是怎样的”)有什么本质联系。

不过这里有一个复杂的因素:游戏是很奇怪的。或者至少说:我们没有任何真正现成的范式来构建一款游戏。我们有游戏引擎,但实际上几乎没有任何标准化的工具可用,部分原因是每一款游戏彼此之间的需求都不尽相同。

这是游戏所独有的(我认为),但它在其他技术领域也表现得足够明显,于是出现了一个全新概念,目的是让智能体能够有效地 “配合”你的工作成果:Harness。
关于 Harness 理念的一些说明
Harness 的大致理念是:它是一些预设的行为模式,智能体在收到某些明确(或大致明确)的预期输入时按这些模式采取行动。你可能已经在使用像 Claude Code 这样的 harness,但它们可以被增强、扩展、精炼、抽象化等等,以更好地支持你具体想完成的工作。最近这个领域也出现了LOT量级的动作,许多 harness 正被构建出来,用于开发各种各样的东西:

它们都指向的关键问题是: “我如何更好地理解我正在构建的是什么,并在开发过程中对其进行引导”。Harness 可以整合诸如与浏览器迭代、遥测、性能分析,甚至完整的SDLC工作,如创建工单、编写文档等等。
然而,这些东西没有一个能很好地与你的游戏进行对话。你的游戏有点像一个黑盒,而对LLM来说,大部分可见的内容只有写好的代码,正如我们之前所说,这其实并不够。我们知道 LLM 已经在处理代码方面很擅长,所以并不是说某个 harness 本身就能解决我们一开始遇到的“游戏不仅仅是代码”这个问题。
这里真正需要发生的事情是,我们能把游戏里的所有东西拿出来,并尝试把它转化成 LLM 能够处理的形式。我们需要撬开你游戏的黑盒,让 LLM 能够更直接地访问其中的更多内容。


在演讲版本中,我有一张非常有趣的幻灯片,说这里的箭头才是这场演讲的真正内容。这个笑话在博客形式下效果差得多,但我还是把它留在这里以作纪念:


那么让我们来谈谈我们能做些什么。
让我们把游戏变得对 AI 智能体可读
下面有一些非常具体、可操作的事情,你可以为你的游戏做,让它更易被 LLM 理解并具备可操作性。
在开始之前,作为一个小插曲,也值得注意的是,这些技巧中有许多对游戏开发总体上也是有益的。它们配合 AI 用会被大幅强化,但仅做其中任何一项,也很可能让你开发自己的游戏时整体上变得更好。
固定随机性
贯穿整个演讲/文章的一个主线主题是确定性的概念。基本上就是指你能在多大程度上精确地复现游戏中的某个特定状态。总体目标是让它非常容易、且非常精准。如果到了制作末期才去做这件事,会非常困难,并且很可能需要对游戏进行根本性的最后一刻大改。
而相反地,如果你在写下第一行代码之前就假设有这一约束,并将其作为目标坚持下去,你将为更富成效的收尾阶段铺平道路,避免与熵增和意大利面式代码缠斗。
起步其实很简单。在游戏开始时,创建一个单一的有种子随机源。确保对任何非确定性事物的调用都使用这个随机源。这意味着你的游戏能够以确定性的方式重建游戏中的几乎任何状态。如果你的代码在 30 个地方调用 Random.Float 或类似的函数来决定某些玩法行为,你就知道第 30 次调用每次都会是相同的。很简单。
可截图性
为什么要可复现的状态?原因有很多,但以智能体为例,想象你在多次迭代尝试纠正某些问题。重要的是智能体能够一次又一次地从零开始达到相同的状态,而不需要反复经历状态复制失败。
而一旦你到达那里,你怎么知道呢?你如何评估这些更改?
首先,截图!大多数引擎都有一些内置的截图API或功能:

我还建议添加轻松切换UI图层、玩法图层等开/关的能力,这样你可以减少分散LLM注意力的视觉杂乱(即,如果你要修复某些UI样式问题,不要把玩法放在UI下面)。
在智能体更改前后对游戏进行前后对比截图,是让智能体理解发生了什么变化的好方法。
……真的是这样吗?

当你对一次更改拍两张截图(前后)时,LLM,即使它是多模态的(即具有视觉能力),也很可能非常难以理解变化是什么,更难以理解它所做的更改是否让它更接近自己的最终目标。
这种来自图像数据的噪声本身就是一种上下文腐化:天真地把截图扔给模型会模糊它们自己对进度的感觉,让它们和你都感到困惑。
一个视觉例子可以帮上忙:想象你拍下了这次大会主页的截图:

你让模型 “把骷髅变成海盗”。假设(对文本 LLM 来说,不太可能)这可以作为对图像的注释(Google Stitch可以这样做),你的图像将和你的标签一起传给模型提供商,但同时还有所有其他你没有要处理的内容(下面绿色的部分)。

这意味着绿色中的所有内容都类似于 “上下文污染”。它是LLM正在处理的数据,加上你请求的更改,但它不只是思考你请求的内容,而是在思考整个图像。
你需要让LLM只思考需要更改的内容。你如何判断什么已经改变了?
图像对比!

如果你拍两张图像并 “对比”它们,就像你对代码更改所做的那样,你可以轻松看到什么发生了变化。结合巧妙地使用裁剪,你可以让LLM用一个定量的度量来理解,更改后的某些输出与你提供的输入/目标图像有多接近。
幸运的是,你不必自己做这件事。有一个非常好的免费命令行图像对比工具叫做 odiff,正好做这件事。

给定两张不同的图像,odiff 会找出两者之间的 “差异”,并可以将该差异输出为一张 “对比”图像:

odiff 也能给出像 "85% 相似度"这样的量化差异值,LLM 很擅长阅读这种值,并可以将其作为每一轮智能体操作中评估的稳定基线,致力于缩小差异/提高相似度。
换句话说,在你工作的过程中,这些智能体截图 → 进度差异检查会成为针对你任何任务的持续智能体评估的基础:

然而,这显然不是全部。游戏远不止是其视觉元素。 "进度"在这里会分解为它自己的小型内部循环:

这个内部循环很可能是智能体花费大部分时间的地方,它会独立进行多轮操作,然后再回到截图比较/差异(假设你的更改具有某种视觉影响)。
这里的目标是能够使每次更改都是达到预期结果所需的最小更改量,并且能够快速验证这些更改。你想要一个极其稳定的、 "无聊的"基线,可以在近乎隔离的环境中进行评估,以确保建立在其之上的任何东西都不会产生广泛的技术债务(也就是意大利面式代码)。让我们更多地谈谈确定性状态。

确定性状态
上述图表中的其余项目在某种程度上也涉及确定性状态,但我想谈谈实现它的具体策略和技术,因为它们对其他一切都是必不可少的。
有一个简单的问题可以问,以了解/知道你距离确定性状态有多近:在你的游戏中保存和加载一个精确状态有多容易?换句话说,状态复现最重要的部分是拥有一个从一开始就高效保存和加载你的游戏的策略。
保存/加载通常是业余开发者会推迟到游戏最后才做的事情,但正如许多更有经验的开发者会告诉你的,这是一个严重的错误。很容易把保存/加载视为可以在游戏开发过程中的某个时间点添加的 "功能",但如果在游戏的最初就加以利用,并与开发同步编程,保存和加载可以成为一种开发超能力,拥有几乎数不清的好处。
让我们来谈谈其中一种策略,以及为什么它能让所有开发都变得更加容易。

我们将在这里稍微涉足一下计算机科学风格的编程理论。大多数花费大量时间研究面向对象类型架构用于游戏的人会发现,在他们的项目接近尾声时,上面这样的陈述 "树就是狗"是真的。他们查看他们的代码,以及事物在游戏中的实际行为,并意识到,这两个看似截然不同的、具有各自OOP层次结构的对象,归根结底,从代码的角度来看,实际上或多或少是同一种 "东西"。
一棵树是一个具有网格和碰撞器的对象。一只狗是一个具有网格、碰撞器并可以移动的对象。 "移动"本身并不是 "狗"所特有的东西(游戏中的其他东西也可能移动),因此一棵树和一只狗基本上只是具有某种游戏表示的组件的组合。从代码的角度来看,它们实际上是相同的。
起初这可能会让人觉得违反直觉,因为它感觉是错的,但这里蕴含着一个让人茅塞顿开的洞见——当你牢牢把握住它时,编写游戏程序就会变得容易得多,也不再是一场不断与抽象之墙作斗争的消耗战,那种抽象之墙会在你试图在游戏中维护一套强 OOP 风格的对象模型时浮现出来。
以 Thing(事物)为中心的开发
相反,如果你接受我提出的这个想法并付诸实践,你就可以从游戏一开始便假定一切其实都是同一种东西(甚至可以直接使用 Thing 类型)。如果用代码来定义 Thing,你会得到类似下面这样的内容:

你会看到,一个 Thing 上有一些属性类型的字段,再加上一个由 isAlive 标志所体现的、关于“存活状态”的统一概念。除此之外,thing 的“类型”只是一个枚举值,具体的实现代码可以根据这个类型来执行相应的动作。
而事实证明,AI非常喜欢这种设计。

采用这种架构,对象模型就有了一个唯一真相之源。如果你有 100 个类、对应 100 种不同类型的对象,AI很可能需要理解所有这些类(以及它们之间的关系)才能采取行动。这会塞满它的上下文,也让思考如何契合这套模式变得一团糟——我(AI)究竟应该继承某个类?把某个类的类型改造一下以更好地表达这个新事物?还是新建一个类?很难说得清!
当只有单一对象类型时,就没有任何歧义。AI每次都能准确知道该在哪里做出修改,每一次都是如此。
不仅如此,(回到存读档的话题)单一对象类型也让存读档变得极其简单:

当你只有一种对象类型时,反序列化/序列化就是轻而易举的事情——只有一种类型,就只有一种写入路径和一种读取路径。如果有多种不同的类型(以及潜在的继承树),你很可能会陷入各种诡异的反序列化竞争条件路径中,需要以奇怪的方式把对象和子对象树串联起来,还要去啃诸如“多态反序列化”这样的概念。这些东西当然值得了解,并在真正需要的时候加以使用,但对于游戏而言,这里可是“龙潭虎穴”。
于是,一个游戏就只是一个巨大的 Thing 列表。存盘就是沿着同一条路径把这些 Thing 全部存下来,读档则是沿同一条路径反向恢复。任何需要做的类型转换,都可以在反序列化出来的枚举值上完成。非常舒服!
我也绝非第一个谈论这件事的人,所以如果你想了解更多,我强烈推荐你去听几期 Wookash podcast,其中他与《Dreams》的几位主创聊到了与之非常相似的一套系统:
借助这样一套系统,即时复现状态变得轻而易举,你也能绕开存读档中的一切难题。这里面还有更多的细节,我也推荐你去听上面那期播客以获取更多内容,但其核心理念是一样的——把所有东西都抽象为单一的对象类型。状态本身也就可以由同一套系统轻松捕获。你完全可以在每个结构上都加一个 Turn Number 字段,再加一个类型为 GameManager 的 Type。让这个对象真正包揽一切。而且不会的,它并不像你可能担心的那样耗费内存。
可寻址的状态
一旦你拥有这样一套系统,游戏的其他部分也会随之变得更容易。

如果你只有一种类型的对象,捕获状态变化就非常容易,因为游戏中任何变化大概都针对这单一对象类型。你可能想直接修改这些值,但采用这种 "胖结构体"方法,你可以非常轻松地实现所谓的 "命令模式",从而获得更多好处:

命令模式的核心思想是:你捕获对象自身的变化时,不是把它当作一个松散的函数调用,而是将其作为一个(理想情况下可序列化的)对象。这些命令随后会传递给某个处理类,由它来直接修改对象本身,而不是由你亲自修改。这种间接性乍一看可能又会让人觉得奇怪,但请耐心听我说。
想象一下,你有一个起始状态。玩家执行一些会改变状态的操作,这些操作以命令的形式呈现。这会导致某个最终状态,而这个状态里出现了一个 bug。
假设现在你想尝试复现这个 bug。一种做法是,你尝试与玩家沟通,弄清楚他们做了什么,然后在你的游戏副本里试着做同样的事情。
或者,你可以拿起始状态,然后 "回放"那些序列化的命令,这些命令精确地捕获了玩家所做的操作。值得注意的是,这个过程并不需要你亲自玩这个游戏,也不需要编写专门的函数调用来改变状态:你只需要回放这些命令。甚至不必是你亲自操作,AI 代理也可以回放这些命令,因为它会持续评估自己的修改对状态的影响。

再进一步说,你可能会注意到,这些命令其实也不一定非要从游戏内部编写。你可以在文本编辑器中轻松串联起一系列命令(或者由 AI 生成),然后让它们在游戏运行实例上回放,完全不需要编译!

命令为 AI(也为你)提供了一个类型化的接口来与你的游戏交互,这实际上使它变得 "可被操控",而且无需每一次修改或与游戏的交互尝试都依赖专门的处理逻辑或反射机制(以及随之而来的编译)。AI 喜欢这种做法!
再说说无需编译的修改:你越能让你的游戏做到无需编译就能进行修改,效果就越好。这不仅仅是为了炫技,尤其是对智能体循环而言——它越是能够在不等待任何编译时间的情况下进行测试,就意味着它能更快地行动,并在更紧凑的循环中完成修改与验证。
关于数据管理
这正好与我个人特别在意的一点不谋而合:数据管理。让我们回到胖结构体的例子:

我所说的 "数据",指的是你的游戏用来描述事物如何存在的静态运行数据。在上面的例子里,我会把 Type、attackDamage、walkSpeed 和 boatSpeed 都视作 "数据"。这些是对象保持不变的特性。其他东西可以引用它们、基于它们工作,玩家也许永远不会直接看到这些数字,但它们描述了某些 Things 在游戏中存在的基线形态。
如果你在为游戏制作东西,你可能会直接写出大量的结构体,根据你所知道的属性来设置它们的属性。这些更改随后会被编译进游戏。如果你想更改某物的基础行走速度,你需要要么进行更改并重新编译,要么编写一些函数绑定来分配该值,并且可以从外部调用(想想控制台命令)。
所有这些选项都会给AI智能体带来干扰。特别是在你进行编译的场景中,你把测试基本数值更改的工作放在了编译之后。
更好的做法是将所有数据放在游戏的外部,而不是用代码来表示。分配这些数据只是反序列化数据、应用到正确的对象上的问题,然后就完成了!所以更改数据只是在你选择的数据源中进行更改的问题,在游戏中设置一些基本的反序列化,并将其与对象创建结合起来。完成了!这也允许你(或智能体或其他团队成员等)能够通过构建版来迭代你的游戏!他们甚至不一定需要编辑器访问权限。

对于Cantata来说,这正是我们的工作流程。团队中的所有非程序员(约 7 人)都拿到了游戏构建版和我们自定义的数据编辑器 Depot。这个工作流程非常有效,我现在正在将 Depot 转变为一个完整的产品:

团队成员在 Depot 中进行更改,游戏构建版处理反序列化后的更改,这样他们就可以在一个循环中工作,而不需要我重新生成新的构建版。Depot 是我在这里的首选工具,但其他团队使用 Google 表格或他们自己的自定义工具。
AI智能体可以被教会使用你自己的工具,然后可以像其他团队成员一样,自行测试数据更改,而不需要不断重建游戏。你能把智能体循环做得越紧凑,就越好!这一切都是为了消除循环瓶颈。

这引出了另一个核心想法。这有点像是个不归路,所以请耐心听我说。
独立模拟 / 黑盒
在开发游戏时,无论是在引擎中还是使用一些自定义工具独立开发,通常游戏本身的逻辑会与视觉状态转换、UI 调用等混杂在一起。这并不是任何人的错,但作为默认模型是有道理的:某个动作发生,你更新 UI,也许更改一些视觉效果,等等。
然而,把所有这些放在一起,特别是在引擎中,你开始意识到,要开始尝试测试任何东西或重现某人的错误,你需要让整个游戏运行起来并执行它的任务。模型必须加载,声音要设置,场景要加载,等等。你的游戏的模拟需要拥有 "整个游戏"。当事情开始出错时,就很难区分什么出了问题以及为什么:是模拟吗?模型吗?场景设计吗?
这里更好的工作方式是,你的游戏的模拟位于引擎实际操作逻辑的外部。所谓外部,我是指字面意义上的,作为它自己的 DLL/库。游戏消费模拟及其结果,但本身 "拥有"模拟。
相反,引擎通过 "命令"与模拟通信,这与 Web 编程中客户端和服务器的工作方式并无不同。

这些命令可以(也应该!)采用我们之前介绍的命令的形式:

当引擎将命令传递给模拟核心时,模拟会以更新后的状态传回引擎作为响应。然后引擎会解读这些更新后的状态(或与上一次接收到的状态进行差异对比),并据此作出行动。
这听起来有点抽象,所以下面是一个更具体的例子:
想象一下你的游戏中有什么东西在移动。这种移动可能涉及几个不同的方面——动画、模型、音效,以及移动开始、持续、停止的相关逻辑,也许还包括对象移动时视野等中间更新。然而,移动从根本上说 “就是”某个东西从 A 点移动到 B 点以响应玩家的输入。
这个想法是引擎为某个 Thing(还记得胖结构吧!)发送一个移动请求,而模拟独立地判断这是否是一个有效的移动目标(引擎也可以在提交命令之前专门进行过滤)。然后模拟会解析这个移动,并向引擎返回一个更新后的状态对象,显示该对象确实已经移动了。引擎可以通过与之前的状态进行差异对比来理解移动的发生,或者采用其他过于复杂、无法在此详述的方式(例如记录缓冲命令,并在模拟接受它们时执行,等等)。但关键在于引擎是 “笨”的——它只是根据从模拟获得的信息行动,并不会质疑。模拟是真理之源:如果模拟说某个东西移动了,引擎就会把对象移动过去,不问任何问题。
这种程度的间接性可能看起来过于抽象,但当你将模拟核心与引擎分离时,这意味着任何东西都可以向你的核心发送命令:

这里最直接的巨大收益是网络编程变得FAR容易得多——因为你的游戏设置成只消费状态/差异,所以它们实际来自哪里并不重要。它们可以来自网络、LLM,或者任何东西!
还有更简单的用例。游戏开发中的测试常常有些棘手,但我认为这很大程度上是因为状态/逻辑/数据通常紧密耦合在一起,导致测试游戏非常困难,因为它需要大量的脚手架。
然而,利用这样的架构可以让你轻松测试游戏的模拟,根本不需要引擎。你可以使用纯 C# 项目、标准 C# 工具快速完成测试,不需要任何引擎来运行你的代码。AI喜欢这样!

这个想法通常被称为 “黑盒游戏架构”(Blackbox Game Architecture)。它之前已有过一些版本,但几年前 Brian Cronin 作为 Roguelike Celebration 的一部分所做的演讲,详细介绍了他是如何为 Monster Train 利用这一技术的:
我还与 Solium Infernum 背后的团队进行了交流,作为 Game Data Podcast 的一部分,他们描述了一种类似的技术:
当你利用这样的东西时,它就变成了一种游戏开发超能力。你可以真正开始把你的游戏作为一个与玩家实际体验抽象化的技术对象来使用。你可以直接在模拟本身上运行模拟、假设、测试等等,并且能够绕开所有那些通常困扰着不使用此类架构策略的相关工作的状态问题。

回到我们上面的图表,我们已经走到了飞轮的接近末尾。我们设置了一些确定性输入参数,可以通过结构化方式修改该状态以避免编译,然后就可以在真空中验证我们自己的更改。
影响评估
然而,我们如何真正知道那些更改是否按我们想要的方式影响了实际游戏?这就是 "影响评估"的概念。

我将在这里专注于一个相对狭窄的选项集,但还有更多工作室可以部署的更具针对性的工具。测试之类的东西属于这里的一般领域,但也包括运行固定的玩法场景来确定平衡影响等等。这些都很重要,但都是游戏特定的,因此很难在这篇文章的范围内直接提取出来。
然而,所有游戏都有一个共同点,那就是对性能的担忧。传统上,性能分析一直是一项相对手动的工作,需要开发人员使用分析工具,包括内置在 Unity/Unreal 等编辑器中的工具,或通过IDE工具如 dotMemory:

这些工具是为人类使用而设计的——它们期望你深入到调用栈和图表中去寻找你正在寻找的瓶颈。也许并不令人惊讶的是,事实证明AI并不真的喜欢使用这样的工具:它们对智能体来说不清晰(不过值得一提的是 Unity 确实正在做大量工作来让分析器更好地被智能体读取!)。
AI真正喜欢的是处理文本(大量的文本),所以能够将原始捕获转储为文本的分析工具真的有助于加速AI迭代。这里有几个选项,但我最喜欢的(免费的!)工具是samply:

Samply 可以轻松连接到自己的项目,并将捕获输出转储到控制台。一个AI智能体知道如何阅读 samply 源代码和文档来帮助自己评估这里的更改,因此捕获和迭代循环可以有效地自主运行。AI喜欢这个!
落到实处
随着我们将所有这些工具和技术结合在一起,这样的东西就出现了(这只是其中一个版本!):

在我这里勾勒的面向数据、二进制隔离、无需编译的开发版本中,一个智能体回合可以在秒内完成整个完整循环。它使迭代显著快于基线的朴素方法,并且在每一步都帮助AI更好地理解你的游戏以及你正在尝试做的事情。因为整个循环是高效快速的,所以它可以对每个更改运行,而不仅仅在 "重要"时才保存。
从更宏观的视角重新审视,你可以看到一个游戏驱动的测试工具是什么样的:

剧透:就是你的游戏!任何游戏都能够做到这一切,这意味着任何游戏都可以轻松成为自己的测试工具。或者,正如我们一开始所说的:

感谢阅读!
后记
在演讲之后再写这篇文章很有意思。我仍然坚持我说过的很多内容,但目前正在交付一个项目中,我也没有使用这些方法。我在这里针对的规模其实是更大的项目。我正在交付的东西非常小,所以在这方面确实不需要做太多关于工具方面的工作。
不过,当我开始着手一个正在酝酿中的更大游戏时,这些内容中的很多确实相关,我对所有这些工具非常感激。
如果你将上述策略应用到你自己的游戏中,我很乐意听听你的经历以及哪些有效、哪些无效!随着我继续制作游戏和工具,我确实在这个领域思考了很多,所以很兴奋能听到其他人在做什么。
再次感谢阅读,下次再聊!
发布于 2026 年 10 月 10 日。
来源:Hacker News · kylekukshtel.com