我在 Indie Rodeo in Calgary 于9/12/26做了一场45分钟演讲,题为《Your Game Is A Harness》。会后与多人交流后,我想把想法分享给未参会的人。虽然我常谈AI,但以下文字100%由人写成,只参考当时幻灯片,并非转录;我喜欢用AI写代码和工具,也同样享受老方式写作。这是我第一次把演讲改写成博客:我想谈在游戏开发中使用LLMs,不是推特(Twitter)上那些看起来酷的一次性演示,而是每天工作8小时的真实开发。
正文
我在 2026 年 9 月 12 日卡尔加里的 Indie Rodeo 上做了一场 45 分钟的演讲,题目是“Your Game Is A Harness”。演讲后和不少人聊过,我觉得应该把这些想法更广泛地分享出来,让没参会的人也能受益。
另外,因为我常在博客里聊 AI,很多人以为我用 AI 写文章。并不是!下面内容 100% 由我本人撰写,基本只参考了当时演示的幻灯片(不是演讲转录)。虽然我很喜欢用 AI 写代码和工具,但我也享受用“老办法”写作,尤其没必要非把这个过程变得更高效。
好,闲话到此为止。这是我第一次把演讲改成博客,祝你读得开心。

引言
我想聊聊在游戏开发中使用 LLM——不是那些在 Twitter 上看起来很酷的一次性演示,而是真正的开发:每天八小时、每周五天那种。
在进入正题前,先对齐一下对 LLM 的基本认识。很多人可能已经懂,若只想看开发循环部分,可以跳过往下看。
我开始用 LLM 做开发,是从一个叫 mood.site 的情绪板网站开始的。那时还没有“agentic”任何东西,我只是想测试当时的 ChatGPT 能不能真的写代码。过程基本是我盲目地把代码复制粘贴进 ChatGPT 网页界面,体验很糟,但结果让我惊讶:我把网站做出来了,而且能用。我还是需要自己核对不少东西,但这已经足够让我转变。如果没有 LLM,我不可能在那么短的时间内做出那个网站——大概花了 2 到 3 周的晚上,总共约 20 小时。否则我可能至今还在做第一版。
自那以后,mood 成了我试验 LLM 的试验田(本身也算个小有成绩的项目),也催生了很多使用类似技术栈的项目。而且 LLM 进步很大,尤其是 Claude Code 和后来的 Claude 模型。以我的经验,对于基于 Web 的技术栈,我现在几乎想不出 LLM 有什么做不到的事。
很容易因此说 LLM 什么都能做,毕竟很多软件都是 Web 软件。但每当我试图用 LLM 做一个非 Web 的游戏“东西”时,得到的结论都非常一致:

“LLM 不擅长游戏”
只要我试图用 LLM 做游戏相关的东西,它就会扑街。也许这是我的偏见。我做了近 15 年游戏程序员,觉得自己知道东西是怎么运作的,能一眼闻出“坏”的游戏代码。但说到 Svelte Web 应用,我就明显没那么熟,反而倾向于认为 LLM 产出的代码是“好”的。也许它其实是垃圾。但确实有理由相信 Svelte 和 Web 技术栈的东西质量不错,部分原因是网上有大量高质量的开源 Web 项目可供学习。相比之下,游戏方面我能想象到的训练数据要少几个数量级。
不仅如此,游戏里还有很多“不是代码”的东西。游戏不只是把静态图片和文字塞进布局里。游戏中有大量东西不容易用代码表示。而且在此之上,还有 Web 里同样的文字布局问题,但这只是游戏需要处理的 100 个其他系统中的一个子集。
这也是为什么很多人用 Three.js 做游戏——他们可以把资源表示成普通的方块图元,跳过游戏通常需要的资源生成/规整工作和处理流程。结果,很多这类东西就成了缺乏真正作者声音的“泔水”,只剩下 LLM 的中位水平。
不过,也许他们有道理。

一次性 Three.js 工作流(以及 LLM 擅长构建 Web 应用)的一个关键理念是:一切都是代码。把“东西”变成代码,LLM 就很容易推理、修改等等。

然而传统游戏并非如此,至少不是全部。游戏当然有代码,但还有很多“不是代码”的东西。这意味着游戏的一些关键组件对 LLM 实际上是不透明的。如果你的游戏依赖对这些不透明东西的有效操控和转换,那么 LLM 在试图处理你的游戏时就会扑街。

我认为很多游戏开发者之前试过 LLM,然后一头撞上这些限制。那种“LLM 不行/没用”的情绪(这里只谈能力,不涉及潜在社会性影响)正是来自撞上这些限制。

也就是说,从结构上看,LLM 默认不擅长“读”游戏。
LLM 基础
我们来多聊聊原因,以及能做些什么。
任何对 LLM 服务的调用/提示,基本都由三部分组成:system prompt、assistant prompt、user prompt。
System prompt(系统提示):服务所有用户共享的通用文本,通常描述 LLM 自身基础,比如“你是 Opus 5,由 Anthropic 创建的智能大语言模型……”。这些提示重要,但和我们的用例不太相关。这里可以看到一些已泄露/公开的系统提示示例。
Assistant prompt(助手提示):一个辅助提示段,用来引导 AI 响应什么。可以把它理解为模型与你提示交互的基本“响应”向量,代表模型基于某些输入对用户的“回复”。通常你不用操心这个。
User prompt(用户提示):这才是关键。你在任何 AI 工具里输入的大部分内容都进到这里。任何提交的聊天、代码等都会塞进这个部分。人们说“上下文”时,基本指的是用户(和助手)提示部分里存了多少 token。
可能容易以为每次聊天都是上面这些的“全新”版本,但实际情况是:随着你在某个会话里和模型工作(“会话”边界取决于服务,可以是关闭前的一次 Claude Code 会话,也可以是网页版侧边栏里的一个聊天),你之前发给模型的每条消息,在你每次发新消息时都会被重新提交一遍。这有点像操场上的老游戏:轮到你时,你得先把其他所有人说过的话再说一遍,然后才能加上自己的话,让下一个人继续接着背。

上下文与上下文污染
如此反复,你就积累了当前会话的“上下文”。

这本身没问题,但要注意:你真正能处理的其实只有上下文里的“最新”东西(即你新提交的消息)。之前的消息会一遍又一遍提交给模型。因此,你要确保上下文里的东西是“好”的,否则会陷入退化场景:早期“坏”东西不断影响新工作,导致上下文被污染。

管理上下文于是成了使用 LLM 的关键,面对这些约束,出现了一种新的工程实践,叫“上下文工程”(context engineering)。上下文工程问的是:如何把最好的信息放进上下文,同时尽可能减少污染。
有一篇关于“长上下文如何失败”的好博客 更深入讲了这些,我这里只强调关键部分:

上下文工程本身太大,超出这场演讲/这篇博客范围,但我们会借用它的一些原则,来探索如何让 LLM 更好地处理游戏。
换句话说,让 LLM 和游戏配合好,是一个上下文问题,而不是游戏或 LLM“本质”上如何(或不如何)的内在问题。

不过这里有个复杂因素:游戏很“怪”。或者至少:我们没有真正构建游戏的模板。我们有游戏引擎,但几乎没有任何标准化工具,部分原因是每个游戏都以不同方式体现自己的需求。

这(我认为)尤其为游戏所独有,但它在其他技术领域也足够常见,于是出现了一个新概念,用来让 agent 能有效“处理”你的工作产物:Harness。
Harness 是什么
Harness 的一般思路是:它是一组固定模式的行为,让 agent 针对某种明确(或松散)定义的预期输入去执行。你很可能已经在用某种 harness 了,比如 Claude Code;但它们可以被增强、扩展、提炼、抽象,以更好地支持你要做的具体工作。最近这个领域非常热闹,出现了很多用于开发各种东西的 harness:

这些都在指向同一件事:“我怎样才能更好地理解自己在构建什么,并在开发过程中引导它”。Harness 可以接入浏览器迭代、遥测、性能分析,甚至完整的 SDLC 工作,比如建工单、写文档等。
但没有一个 harness 真正擅长的是:和你的游戏对话。你的游戏像个黑盒,LLM 能看到的基本只有“写成代码的那部分”,而正如前面所说,这不够。我们知道 LLM 已经擅长处理代码,所以 harness 本身并不能解决“游戏不只是代码”这个问题。
真正需要做的是:把游戏里所有的“东西”拿出来,变成 LLM 能处理的东西。我们需要撬开游戏的黑盒,让更多部分能被 LLM 直接寻址。


在演讲版里,我放了一张“很好笑”的幻灯片,说这里的箭头才是这场演讲真正的内容。这个笑话在博客里基本没效果,但我为了纪念还是留着:


那么,我们能做些什么?
让游戏对 AI Agent 可读
下面是一些很具体、可操作的做法,能让你的游戏对 LLM 更可读、更可操作。
顺便说一句,这些建议里有很多对游戏开发本身也有好处。有了 AI 会更强,但即使不做 AI,做这些也通常会让你开发自己的游戏更顺手。
固定随机性
贯穿整场演讲/这篇博客的一个主题是:确定性(determinism)。也就是你能多准确地复现游戏里的某个特定状态。总体目标是:复现要非常容易、非常精确。如果到生产末期才做这件事,会非常难,而且很可能在最后一刻需要对游戏做根本性改动。
相反,如果你在写第一行代码之前就假定这个约束,并把它当目标坚持下去,就是在为高效的后期铺路,不用和熵增、面条代码搏斗。
起步很简单:在游戏开始时,创建一个单一的“带种子”随机源。确保所有非确定性调用都使用这个随机源。这样你的游戏就能确定性地重建几乎任何状态。如果你的代码在 30 个地方调用 Random.Float 之类的东西来决定某些玩法行为,那么第 30 次调用每次都会一样。简单。
可截图性
为什么要可复现状态?原因很多,但对 agent 来说,想象你在反复迭代修正某个东西:agent 必须能从头一次次进入相同状态,而不用不断遭遇状态复现失败。
到达状态后,你怎么知道?怎么评估这些改动?
首先就是截图!大多数引擎都有内置的截图 API 或功能:

我还建议加上轻松开关 UI 层、玩法层等的能力,这样可以减少视觉干扰,让 LLM 不被无关层分散注意力(比如你要修 UI 样式,就别让玩法藏在 UI 下面)。
在 agent 改动前后各截一张图,是让 agent 理解“改了什么”的好办法。
……真的是吗?

当你对改动前后截两张图(Before/After)时,即使 LLM 是多模态的(有视觉能力),它大概率也很难不仅理解“改了什么”,还理解“这次改动是否让它离自己的目标更近”。
图像数据带来的噪声本身就是一种上下文腐化:天真地把截图丢给模型,会混淆它们对进展的判断,让你和它都困惑。
一个视觉例子有助于说明:假设你截了一张这个会议官网主页的图:

你让模型“把骨架变成海盗”。假设(对于纯文本 LLM 来说不太可能)这个修改能作为标注加到图上(Google Stitch 就是这么做的),那么图像会连你的标签一起传给模型供应商,但也会带上你并不想处理的“其他所有东西”(绿色部分)。
这意味着绿色部分全都类似于“上下文污染”。它是 LLM 和处理你的请求时一起处理的数据,但它不只思考你请求改的东西,而是在思考整张图。
你需要让 LLM 只思考需要改的部分。那怎么判断改了什么?
图像 diff!
如果你把两张图像像代码改动那样“diff”一下,就能很容易看清改了什么。再配合巧妙的裁剪,你可以让 LLM 用量化指标理解:改动后的输出离你提供的输入/目标图有多接近。
幸运的是,这事不用你自己做。有一个非常好的免费命令行图像 diff 工具,叫 odiff,做的就是这件事。
给定两张不同的图,odiff 会找出两者的“差异”,并可以把差异输出成一张“diff 图”:
odiff 还能给出一个量化差异值,比如“85% 相似”,LLM 很擅长读这种值,并可以在每一轮 agent 迭代中把它当作稳定的评估基线,努力缩小差异/提高相似度。
换句话说,在你工作过程中,这些“agent 截图 -> 进展 diff 检查”就成了对 agent 持续评估的基础,朝向你的任务目标推进。
但这显然不是全部。游戏远不止视觉元素。这里的“进展”会分解成自己的小内层循环:
这个内层循环很可能就是 agent 花最多时间的地方:它会自己先转好几轮,然后再回到截图对比/diff(假设你的改动有视觉影响)。
这里的目标是:让每次改动都是达成预期结果所需的最小改动,并且这些改动能被快速验证。你想要一个极其稳定、“无聊”的基线,能在近乎隔离的情况下被评估,确保建立在它之上的任何东西都不会产生广泛的技术债(也就是面条代码)。所以我们再来多聊聊确定性状态。
确定性状态
上面图里剩下的项也都多少和确定性状态有关,但我想专门聊聊实现它的策略和技巧,因为这对其它一切都必不可少。
有个简单问题能帮你判断离确定性状态有多近:你能多容易地保存和加载游戏的精确状态?换句话说,状态复现最重要的部分,是从一开始就有高效保存/加载游戏的策略。
保存/加载通常是业余开发者会拖到游戏末期才做的事,但这是大错,很多资深开发者都会告诉你这一点。人们容易把保存/加载当成某个阶段可以加的“功能”,但如果从游戏最开始就利用保存/加载,并与游戏同步编写,它会成为一种开发超能力,好处几乎数不完。
我们来聊一种实现策略,以及为什么它让所有开发都更简单。
这里要稍微绕进 CS 式编程理论。大多数长期用面向对象架构做游戏的人,会在项目后期发现类似上面“A Tree is a Dog”这种说法是真的。他们看着代码和游戏里实际发生的事,会意识到:这两个看似非常不同、有各自 OOP 层级的对象,从代码视角看,归根结底其实是同一种“东西”。
树是一个有网格和碰撞体的对象。狗是一个有网格、碰撞体、还能移动的对象。“移动”并不是狗的内在属性(游戏里别的东西也可能移动),因此树和狗基本上只是一些具备游戏表现形式的组件组合。从代码角度看,它们实际上是一样的。
一开始这可能有违直觉,感觉不对,但这里有个“开悟”式的洞察:一旦你抓住它,游戏编程会变得容易很多,不再是一场对抗抽象之墙的上坡战——那种情况常发生在你试图在游戏里维持强 OOP 对象模型时。
面向 Thing 的开发
相反,如果你接受我这个想法并贯彻下去,可以从游戏一开始就假设:所有东西其实都是同一种东西(甚至可以用 Thing 这个类型)。如果你在代码里定义 Thing,大概会像这样:
你能看到 Thing 上有一些数值型状态,还有通过 isAlive 标志体现的“存活”共享概念。除此之外,Thing 的“类型”只是一个枚举值,实现代码根据类型采取相应行动。
结果证明,AI 非常喜欢这种架构。
有了这种架构,你的对象模型就有单一事实来源。相反,如果你有 100 个类、100 种不同对象,AI 很可能要理解所有这些类及其关系才能行动。这会填满上下文,也让“怎么套用这个模式”变得混乱——我(AI)应该扩展某个类吗?改造类的类型来更好地捕捉这个新东西?还是新建一个类?很难说!
而用单一对象类型,就没有歧义。AI 每次都知道该在哪儿改。
不仅如此,(回到保存/加载)有了单一对象类型,保存和加载会容易得多:
当你只有一种对象类型时,反序列化/序列化就很琐碎——只有一个类型,所以进出都只有一条路。如果有多种不同类型(还可能有继承树),你很可能会陷入奇怪的竞态反序列化路径,需要以奇怪方式链接对象和子对象树,还要对付“多态反序列化”这类概念。了解并在需要时使用它们当然好,但对游戏来说,这里“有龙”。
于是游戏只是一个巨大的 Thing 列表。保存就是把所有这些 Thing 通过一条路径存下来,加载同理,只是反过来。任何你需要的分支判断,都可以在反序列化后的枚举类型值上做。很清爽!
我也远不是第一个聊这个的人,所以如果你想听更多,我强烈推荐 Wookash podcast 的几期节目,他和 Dreams 的一些创造者聊过非常类似的系统:
用这种系统工作,立刻复现状态很容易,你也绕开了保存/加载的所有难点。这里还有更多复杂度,我推荐上面那个演讲作为延伸阅读,但根本思路是一样的——把一切都捕捉为单一对象类型。状态本身也能被同一系统轻松捕捉。没什么能阻止你在每个结构上都加一个 Turn Number 字段,再加一个 GameManager 类型。让这个对象 literally 做所有事。而且,它并不像你以为的那么耗内存。
可寻址状态
一旦有了这种系统,游戏其他部分也会更容易。
如果你只有一种对象类型,就很容易捕捉状态变化,因为游戏里任何改动理论上都指向那个唯一对象类型。你可能想直接改这些值,但用这种“胖结构体”方式,你可以很容易实现所谓“命令模式”,获得更多好处:
命令模式的思路是:把对对象本身的改动不捕捉成松散的函数调用,而是捕捉成一个(最好可序列化的)对象。这些命令随后被传给某个处理类,由它来改对象,而不是你直接改。这层间接一开始也显得奇怪,但先忍一下。
想象你有个初始状态。玩家做了些改变状态的操作,这些操作表现为命令。这导致某个结束状态,bug 出现了。
现在你想复现。一种做法是你去和玩家沟通,搞清楚他们做了什么,然后在你的游戏副本里尝试同样操作。
或者,你可以拿初始状态,“回放”那些序列化后的命令,它们精确捕捉了玩家做过什么。重点在于,这不一定要你真的去玩游戏或写定制函数调用来改状态:你只要回放命令。甚至不一定要你来回放,AI agent 也可以运行它们,在持续评估自己的改动如何影响状态时反复回放。
再进一步,你可能注意到这些命令其实不一定非要在游戏里生成。你可以轻松地在文本编辑器里(或由 AI 生成)把命令串起来,然后在运行中的游戏实例上回放,无需编译!
命令给了 AI(和你)一个类型化的接口来和游戏交互,实际上让游戏变得“可操控”,不需要每次改动或尝试都定制处理逻辑或反射(然后再编译那些改动)。AI 喜欢这点!
再聊聊“免编译改动”:你越能让游戏做到无需编译就能改,就越好。这不只是一枚荣誉徽章,对 agent 循环尤其重要——它能在不需要等任何编译时间的情况下测试,就意味着能更快行动,在更紧的循环里做改动/验证。
关于数据管理
这正好接上我自己的一个执念:数据管理。回到胖结构体例子:
我说“数据”时,指的是游戏用来描述事物“是什么”的静态操作数据。在上面的例子里,我会把 Type、attackDamage、walkSpeed、boatSpeed 都算作“数据”。这些是对象不变的特征。东西可以修改和使用它们,玩家可能永远看不到这些数字,但它们描述了某个 Thing 在游戏里存在的基本形状。
如果你在为游戏做东西,可能会直接写出很多结构体,根据你知道的属性来设置它们。这些改动随后被编译进游戏。如果你想改某个东西的基础行走速度,你要么改完重新编译,要么写个外部可调用的函数绑定来赋值(比如控制台命令)。
所有这些选项都会给 AI agent 带来折腾。尤其要编译的场景下,你等于把“测试基础数值改动”放在了编译之后。
更好的办法是:把所有数据放在游戏外部,而不是用代码表示。赋值这些数据只是反序列化数据、应用到正确对象、完事!所以改数据只是改你选定的数据源,在游戏里做点基础反序列化,再和对象创建结合。完成!这也让你(或 agent,或其他团队成员)能拿着一个 build 来迭代游戏!他们甚至不一定需要编辑器权限。
对 Cantata 来说,这就是我们的实际工作流。团队里所有非程序员(约 7 人)都拿到了游戏构建和我们自研的数据编辑器 Depot。这个工作流非常高效,我现在正把 Depot 变成一个完整产品:
团队成员在 Depot 里改,游戏构建处理反序列化后的改动,于是他们能在一个循环里工作,不需要我不断出新构建。Depot 是我在这里的首选工具,但其他团队会用 Google Sheets,或自己的定制工具。
AI agent 可以学会使用你自己的工具,然后像其他团队成员一样,自己测试数据改动,而不用不断重建游戏。你能把 agent 循环做得越紧,就越好!一切都是为了消除周期瓶颈。
这引出了另一个核心想法。有点绕,忍一下。
独立模拟 / 黑盒
开发游戏时,无论是在引擎里还是用自定义工具,通常游戏逻辑本身会和视觉状态过渡、UI 调用等混在一起。这不是谁的错,作为默认模型也合理:某个动作发生,你更新 UI,可能改点视觉表现,等等。
但把这些都放在一起,尤其在引擎里,你会开始意识到:想要测试任何东西或复现别人的 bug,你必须让“整个游戏”跑起来做它该做的事。模型要加载,声音要设置,场景要加载等等。游戏的模拟必然要求拥有“整个游戏”。当东西开始坏掉,就很难分清到底什么坏了、为什么:是模拟?模型?场景设计?
更好的做法是:游戏的“模拟”坐在引擎实际操作逻辑之外。这里说的“之外”,是字面意义上作为独立的 DLL/库。游戏消费模拟及其结果,但自己并不“拥有”模拟。
相反,引擎通过“命令”和模拟通信,类似 Web 编程里客户端和服务器的工作方式。
这些命令可以(也应该!)采用我们前面介绍的“命令”形式:
引擎把命令输送给模拟核心,模拟把更新后的状态回传给引擎。引擎随后解释更新后的状态(或与上次收到的状态做 diff),并据此行动。
这听起来有点抽象,所以来个更具体的例子:
想象游戏里有东西在移动。这个移动可能涉及几件不同的事——动画、模型、音效,以及移动开始、持续、停止的逻辑,也许还有随着物体移动更新视野等中间更新。然而,移动本质上“是”:某个东西响应玩家输入,从 A 点移动到 B 点。
思路是:引擎为某个 Thing(还记得胖结构体!)发送移动请求,模拟独立判断这是不是一个合法移动目标(引擎也可以在提交命令前专门过滤)。模拟随后解析移动,并回给引擎一个更新后的状态对象,显示该物体确实移动了。引擎可以通过和之前状态做 diff,或其他这里复杂到没法细说的方式(记录缓冲命令并在模拟接受后执行等)来理解移动发生了。关键是引擎是“笨”的——它只按从模拟拿到的东西行动,不质疑。模拟是事实来源:如果模拟说某个东西移动了,引擎就移动它,不多问。
这层间接看起来可能抽象过度,但当模拟核心和引擎分离时,意味着任何东西都能向你的核心发命令:
这里最直接的大赢家是:网络编程变得容易得多——因为你的游戏被设定成只消费状态/diff,那么它们实际来自哪里并不重要。它们可以来自网络、LLM,随便什么!
还有更简单的用例。游戏开发里的测试常常很难搞,但我认为很多原因在于状态/逻辑/数据耦合太紧,导致测试游戏需要大量脚手架。
然而,利用这种架构,你可以轻松测试游戏的模拟,根本不需要引擎。你可以用纯 C# 项目和标准 C# 工具链猛跑测试,不需要任何引擎运行你的代码。AI 喜欢这点!
这个思路广义上叫“Blackbox Game Architecture”(黑盒游戏架构)。它的版本以前就有,但 Brian Cronin 几年前在 Roguelike Celebration 的演讲,技术性地讲了他如何在《Monster Train》里使用这个技巧:
我也在 Game Data Podcast 里和 Solium Infernum 团队聊过,他们描述了类似技巧:
当你利用这种东西,它就成了一种游戏开发超能力。你可以真正把游戏当作一个从玩家体验中抽象出来的技术对象来处理。你可以直接在模拟本身上跑模拟、假设、测试等,绕开那些通常困扰不使用此类架构策略的努力的状态问题。
回到我们上面的图表,我们已经走到了飞轮接近末尾的地方。我们设定了一些确定性输入参数,有办法以结构化方式修改状态、且无需编译,然后能在一个真空里验证自己的改动。
影响评估
但怎么知道这些改动真的按我们想要的方式影响了实际游戏?这就是“影响评估”。
我这里聚焦一组较窄的选项,但工作室可以在这个领域部署更多针对自己游戏的工具。像测试这类东西属于通用范畴,也可以跑固定玩法场景来评估平衡影响等。这些都重要,但都是游戏特定的,在这篇博客范围里很难直接提取。
不过,所有游戏都共有的一项顾虑是性能。传统上,性能分析是相对手工的事,需要开发者用分析工具,比如 Unity/Unreal 内置的,或 dotMemory 这类 IDE 工具:
这些工具是为“人类”消费设计的——它们期望你钻进取调用栈和图表等,去找你关心的瓶颈。或许不出意外,AI 并不真喜欢用这类工具:它们对 agent 来说不可读(不过顺便一提,Unity 正在做很多工作,让 profiler 更能被 agent 读取!)。
AI 真正喜欢的是处理文本(而且越多越好),所以能把原始捕获输出成文本的分析工具,会极大加速 AI 迭代。这里有几个选项,但我越来越喜欢的是(免费的!)工具 samply:
Samply 可以挂到你自己的项目上轻松运行,并把捕获输出打印到控制台。AI agent 知道怎么读 samply 源码和文档,来帮它评估自己的改动,所以捕获和迭代循环能有效自主进行。AI 喜欢这点!
收尾
当我们把所有这些工具和技巧组合起来,就会看到类似下面这样的东西(这只是其中一种版本!):
用我在这里勾勒的数据导向、二进制隔离、无需编译的开发版本,agent 的一轮可以在几秒钟内跑完整个循环。它让迭代显著快于天真的基线做法,并且在每一步都帮 AI 更好地理解你的游戏和你要做的事。因为整个循环高效且快,它可以对每一次改动都跑,而不是只在“重要”时才跑。
把这再放大一点,你就能看到游戏驱动的 harness 长什么样:
剧透:它就是你的游戏!任何游戏都能做到这些,意味着任何游戏都能轻松成为自己的 harness。或者,就像我们开头说的:
谢谢阅读!
后记
写完这篇比起演讲时挺有意思。我坚持自己说过的很多东西,但我正在做一个项目,却完全没用上面任何东西。我想这里针对的规模其实是更大的项目。我正在做的东西很小,所以在 harness 方面我真的不需要做太多。
不过,当我开始做一个正在酝酿的更大游戏时,上面这些肯定非常相关,我也很感激有这些工具。
如果你把上述策略用到自己的游戏里,我很想听听你的经验,什么有用/没用!我(显然)在这个领域想了很多,因为我继续做游戏和工具,所以很期待听到别人在做什么。
再次感谢阅读,下次聊!
评论
讨论气氛务实且带着明显的怀疑,多数参与者承认 LLM 已能显著加速游戏原型和中小项目的编码,但分歧主轴在于:这种加速是否等于解决了游戏开发真正困难的部分。反方强调美术、创意、手感微调和大规模架构仍高度依赖人类,正方则用端到端生成和大量小项目证明门槛已被大幅拉低。
认为 AI 已能把小项目甚至完整游戏端到端做出来
- 不少人在边界清晰或小体量项目里验证了产出:几万行代码能跑出可玩帧率,智能体还能顺手生成关卡编辑、资产转换和平衡性观测等周边工具;有人让模型一次性生成复杂得多的完整游戏也直接成功。(较多人) > 我用 LLM 做了几十个网页小游戏和五个 Swift 游戏,成品好玩且几乎没 bug。(@Kuyawa)
认为编码从来不是游戏开发最难的部分,美术与内容才是瓶颈
- 模型把写代码变简单,但游戏开发的真正难点在美术资产、内容新颖度、手感微调,以及无法用语言传达的艺术判断;AI 很容易产出“平庸的中间值”,却难凭空发明有趣玩法,艺术工具也还没到位。(较多人) > LLM 已经彻底解决游戏开发里的编码部分,但编码从来不是最难的部分,甚至差得很远。(@bob1029)
认为大规模、性能敏感和复杂架构仍会把 AI 卡住
- 小游戏里可行的“按帧复制大量对象”做法,在正经 3D 项目的性能预算下会失效,最终只能彻底重写;这也是 ECS、SOA 等数据导向架构需要另一套验证工具的原因。确定性要求和跨平台一致性维护会变成额外束缚。即使最强模型也会把架构设计进死角,需要人类先用经验定好核心系统,再让 AI 在稳固基础上实现并拒绝破坏性改动。(较多人) > 即使最强模型也会把架构设计进死角,之后虽能靠大量改动爬出来,但远不如人类先想清楚核心系统再让 AI 写代码。(@dudeinhawaii)
认为真正问题不是模型能力,而是不披露使用会让玩家抵触
- 就像写作一样,模型本身并不可怕,隐瞒使用了 LLM 才是威胁;人类读者想看人写的东西,玩家也想玩人做出来的游戏。(少数人) > 就像写作一样,游戏开发最大的威胁其实不是模型本身,而是不披露用了 LLM;人类想玩人类做出来的东西。(@mycocola)
有价值的补充
- 智能体拥有“无限耐心”,可以让它把交互设计文档、机制演示视频或各种提升生活质量的小工具放到后台并行推进;把 DPS、生命值等做成可导出指标时序,会形成智能体特别需要的可观测性,便于检查进度曲线和平衡性。(@theptip)
- 有参与者用“Gell-Mann 失忆症”形容人机协作:在自己有领域专长的地方,能识破模型的错误假设;但在自己不懂的领域,反而更容易盲信它输出的代码。(@transitorykris)
- 一个反直觉的体验是:在简单 Pong 上反复微调时,前沿模型每加一个功能都会弄坏别的地方;但让模型一次性生成一个复杂百倍的游戏时,它却完美跑通、毫无问题,这可能和内容是否落在模型的“分布内”有关。(@andai)
- 有人用 AI 快速迭代图形资产和精灵图,结果不仅加速了生产,还反过来帮助自己成为更好的美术和动画师。(@armcat)
- 有开发者推荐 Godogen 等工具,并已尝试用它生成童话故事的 2D 动画,可作为 LLM 辅助内容生产的另一条路径。(@wslh)
- 小型项目可以做到约四万行代码并跑出不错帧率,但最好把关卡编辑、资产转换等工具链尽量隔离在主代码库之外。(@roger23)