AI 写的 Unity 代码能编译、进游戏却坏了,是为什么?

John Van Scoyoc 在 2026 年 10 月的 devlog 里列出 AI 写的三类 Unity bug:编造 API、已销毁对象上的假 null、改名丢调校值的序列化字段。其中两项有 Unity 6 文档支撑。本文是检查清单,不是单一项目复盘。

AI 写的 Unity 代码能编译、进游戏却坏了,是为什么?
本篇导航5 个章节

AI 说做完了,代码能编译,bug 却在进游戏后才出现。这就是 AI 辅助 Unity 开发特有的失败形态:编译器抓住难堪的错误,却放行了安静的那些。三项检查能抓住其中大部分。

Devlog 自带的对比图:左边是 AI 写的 null 条件代码,右边是 Unity 空检查规则。这是作者 devlog 配图,不是 Unity 项目截图。

快速信息

  • 来源:John Van Scoyoc 在 itch.io 的 devlog(2026 年 10 月 16 日与 10 月 25 日),以及 Unity 6 官方文档:UnityEngine.Object(6000.5)与 FormerlySerializedAsAttribute(6000.6)。

  • 定性:有文档例证的开发者观察,不是一次单一项目复盘。本文没有点名任何游戏或团队说它们上线了这些 bug。

  • 利益说明:devlog 作者同时在售一套围绕这些规则的 Unity AI Agent Kit。下面每条规则凡有官方文档的,都已对照文档核对。

  • 响亮的一项:编造 API。按 devlog 说法,GameObject.FindByTag、Rigidbody.AddVelocity、transform.GetChildByName 都不存在。编译器会抓住它们。

  • 静默的两项:已销毁对象上的假 null,以及改名后悄悄回到默认值的序列化字段。

  • 版本说明:2026 年 10 月 28 日对照 Unity 6 文档(6000.x)核对,未声称某个具体小补丁号。

  • 最后核实:2026年10月28日。

先说响亮的一项:AI 编造的 API

10 月 16 日那篇 devlog 举了三个编造名字作例子:GameObject.FindByTag、Rigidbody.AddVelocity、transform.GetChildByName。Devlog 直说它们不存在,给的修复是一条规则而不是代码补丁:告诉 agent 只用有文档的 API,拿不准就说拿不准、不要猜。这类失败只是烦人而不是危险,因为它在你能看到的地方打断构建。真正的危险在它周围的工作流:如果你不编译就接受 AI 输出,编造名字会躺在你以为已完成的文件里;如果你不查文档就粘贴同一助手给的修复,可能用一个编造名字换另一个。

静默失败反过来:能编译,游戏一直跑,直到恰好那个错误的时刻。第一项是假 null。UnityEngine.Object 的官方文档解释,某个实例的原生对应物在托管对象被垃圾回收前被销毁时,该实例可能处于 detached 状态。在这种状态下,对象通过 Unity 自定义的相等运算符比较时仍等于 null,但它并不是真正的 null:用 Object.ReferenceEquals 对 null 比较返回 false,而且该对象不能用来调用方法或访问属性。同一文档页还说明,null 条件运算符和 null 合并运算符不受 Unity 对象支持,因为它们无法被重写以同样对待 detached 对象,只有在确认被检查对象绝不会 detached 时才安全使用。

第二项静默失败:一次改名重置了你的调校

10 月 25 日那篇 devlog 完整给出了改名例子。AI 被要求清理脚本,把序列化的 speed 字段改名为 moveSpeed。Unity 按字段名存储 Inspector 值,所以 devlog 说,所有把 speed 调到 7.5 的 prefab 和场景都会悄悄回到默认值 5。没有任何报错,开发者几天后才发现,游戏手感不对了。7.5 变 5 的数字请当作作者例子,不要当作 Unity 文档结论;文档确认的是机制与修复。FormerlySerializedAsAttribute 文档用一句话说明用途:用这个属性改名而不丢失序列化值。它是仅编辑器属性,接受旧字段名,文档例子把 hitpoints 改名为 health 同时保留数值。公开变量默认序列化,私有变量需要 SerializeField 属性。

同一篇 devlog 还提到了第三种静默模式——订阅事件后不取消订阅,但本文范围只限标题里的三项检查,那个模式留待以后。贯穿三项的共同教训是检查顺序:编译错误由编译器免费帮你检查;静默 bug 只有你主动去找才会被检查,所以下面的清单按静默程度排序:假 null 第一、改名第二、编造名字第三,因为第三项至少会出声。

能复用的部分

下面的步骤都不是所谓 devlog 作者针对某款已上线游戏的修复,因为没有点名任何已上线游戏。它们是依据文档与 devlog 例子推导出的检查规则,写出来是为了让你周一早上就能用在自己的项目上,而不需要碰别人的代码。

周一早上照着做

  • 把 AI 写的所有 diff 搜一遍,找 UnityEngine.Object 引用上的 null 条件、null 合并与 is null 检查,换成 Unity 的相等检查,例如 if (target != null) 或 if (target)。

  • 先编译,再把 AI 输出里每个不熟悉的 API 名字对照文档查一遍再信。编造名字是规则失败,不是笔误:加一条规则,要求 agent 拿不准就说拿不准、不要猜。

  • 每次改名序列化字段都加上带旧名的 FormerlySerializedAs,并引入 UnityEngine.Serialization 命名空间。清理式改名也不例外。

  • 改名后打开一个调过值的 prefab 或场景,确认调校值还在。通过编译却重置了数值的改名,正是要抓的静默失败。

  • 把三条规则写进助手每次请求都会读的规则文件,例如 Cursor 规则文件、CLAUDE.md 或 AGENTS.md。每次对话重新提醒,正是 devlog 警告的失败模式。

  • 把编译通过当作测试的开始,而不是完成。在 play mode 里走一遍对象销毁路径,因为假 null 真正触发的地方就在那里。