借助 Codex 独立做游戏 · 开发复盘

从“能跑”到“能重来”
我用 Codex 做第一波战斗时修掉的四个问题

正常流程能跑,不等于这个功能已经可靠。

上一篇结束时,《Riftkeeper》只有一条从主界面进入战斗模块的入口。

当时我给下一阶段定下的目标是:一波怪物、一种武器、一次购买和放置、怪物沿路径移动、武器造成伤害,最后产生胜利或失败并打开结算界面。

从代码进度看,这条最小战斗链路已经基本搭起来了。配置读取、战斗规则、ECS、动态战场、商店、放置合成、实时 HUD 和结算逻辑都有了对应实现。但这次真正值得记录的,并不是增加了多少功能,而是 Codex 写出的代码进入异常路径后,连续暴露出的四类问题。

● 纯规则覆盖了正常输入,却没有真正关住异常边界;

● ECS 对象池复用后,旧会话可能误操作新会话;

● 进入战斗由多段异步操作组成,任何一步失败都需要回滚;

● ECS 已经发布状态时,MVVM 界面可能还没有完成订阅。

01. 规则写出来了,但输入边界没有真正关住

最先完成的是一些不依赖 Cocos 的纯战斗规则,包括基地受伤、阶段切换、怪物路径、购买合成和胜负判断。

这些代码看起来并不复杂,但补测试时很快发现,第一版主要覆盖了“数据完全正确”的情况。怪物沿格点移动时,还要处理路径引用不存在的格点、重复格点、一帧跨过多个节点、路径末尾循环,以及速度、时间或坐标出现负数、无穷大和 NaN。

正常路径之外,还要验证重复格点、缺失引用、循环和异常输入。

阶段切换也有一个隐蔽问题。最初使用普通对象保存允许的阶段,如果直接查询对象属性,像 toString 这样的原型链字段可能被误认为合法阶段。

解决方法:先定义输入域和不变量

最后增加了自有属性判断,并用测试遍历所有允许和禁止的阶段组合。配置表进入战斗前先验证引用关系,领域层再保证每次状态变化都是完整、原子的。

不能只让 Codex 根据示例写算法,还要让它回答:
合法范围是什么?哪些引用必须存在?哪些状态绝不能发生?

02. ECS 对象池复用,让旧会话碰到了新会话

为了减少重复创建,战斗实体和组件会被对象池复用。第一局结束后,同一个组件对象可能在下一局重新初始化。

问题出在事件退订上。旧界面关闭时保存着一个退订函数,如果新战斗已经复用了原来的组件并注册了新监听器,旧退订函数就可能删除新会话的监听器。

对象可以复用,但旧生命周期不能继续操作新会话。

解决方法:把所有权写进运行句柄

每个运行句柄都要确认自己是否仍然拥有当前组件。只有句柄还在运行、组件仍属于当前会话时,退订操作才能修改监听器集合。

同时,停止和销毁被改成幂等操作:重复停止不会重复释放;表现层释放报错时,实体仍会在 finally 中销毁;组件重置时清空命令、事件、监听器和缓存状态;初始化中途失败时,已经取得的实体必须归还。

旧会话订阅 → 旧会话销毁 → 组件被复用 → 新会话订阅 → 旧退订函数延迟执行

单独测试一局时,这个问题几乎看不出来,只有按完整复用顺序验证才会暴露。

03. 进入战斗不是一串异步调用,而是一笔事务

战斗入口包含很多步骤:加载 bundle、读取配置、取得 Prefab、加载场景、挂载 BattleStage、创建表现层、创建 ECS、建立 Session,最后替换主界面并打开 BattleUI。

第一版主要考虑了成功顺序,却没有完整描述每一步失败后应该撤销什么。如果 BattleStage 已经挂载,但 ECS 创建失败;或者 BattleUI 打开失败,而 bundle、场景和节点都已经存在,下一次点击开始战斗时就可能遇到重复节点、残留资源或“会话已经存在”。

只回滚本次已经取得的资源,保留安全起点,并允许重新进入。

解决方法:为每次进入维护一份资源账本

每次进入都记录本次真正取得的资源。任何步骤失败后,按生命周期反向清理:

停止 Session → 解除订阅 → 关闭 UI → 释放表现层 → 移除战斗舞台 → 销毁 ECS → 释放 bundle

清理过程中即使某一步再次报错,也会继续释放其他已经取得的资源,最后再把原始错误和清理错误一起报告。

测试也不再只验证一次成功进入,而是让流程在不同阶段故意失败,确认只释放已取得资源、主界面仍然可用、同一个流程可以重试、重复退出不会清理两遍,并发退出也不会误删后来创建的新会话。

04. ECS 已经发布状态,MVVM 界面却还没订阅

战斗 ECS 可能先计算出第一份 HUD 状态,BattleUI 随后才创建并注册监听器。如果把 HUD 当成普通事件处理,晚注册的界面只能等待下一次状态变化,无法得到当前完整状态。

这不是字段绑定写错了,而是把“状态”和“事件”混在了一起。事件只代表发生过什么,不应该回放;状态代表现在是什么,新的订阅者必须立即得到一份当前快照。

状态需要向晚订阅者回放当前快照,事件只向前传递。

解决方法:由状态源缓存并回放最新快照

修复后,战斗状态源会缓存最新 HUD 快照。BattleUI 注册后立刻收到当前状态,后续只有状态真正变化时才再次发布。

如果首次状态回放时界面回调抛出异常,刚登记的监听器也会立即移除,不能留下一个调用方拿不到退订函数的失效监听器。这个约束后来被补进开发规则:状态订阅必须支持首次快照;事件订阅不回放历史;会话结束和对象池复用时必须清理缓存与监听器。

05. 这次我怎样要求 Codex 检查代码

经过这轮返工,我会把下面这段要求加入后续提示词:

不要只验证成功路径。请按资源所有权和状态生命周期检查流程,列出每一步可能失败的位置,以及失败后的回滚顺序。至少覆盖重复进入、并发退出、退出后重进、对象池复用、晚订阅、回调异常和初始化中途失败。完成后分别报告聚焦测试、全量测试和未执行的 Editor、设备及端到端验证。

它不会让 Codex 少写代码,但能让检查重点从“功能有没有出现”,转向“状态是否真的可控”。

06. 当前验证结果

战斗相关测试 70 项

70 项通过 · 0 项失败

● 应用 TypeScript 检查通过;

● git diff --check 通过;

● 完整测试共 133 项,通过 122 项,失败 11 项。

完整测试不能写成“全部通过”。现有失败主要集中在旧 Loading 美术文件缺失、历史绝对路径、一个过期的 runLoadingStartup 测试调用,以及未纳入仓库的 .vscode/settings.json。

这些失败目前没有指向本轮战斗代码,但仍然是项目需要继续清理的技术债。更重要的是,本次没有执行 Cocos Creator 预览、真机运行和完整端到端操作。

这次最准确的结论

第一波战斗已经形成了代码与自动化测试层面的纵向切片,但还不能称为已经验证完成的可玩版本。AI 可以很快补出正常流程,真正决定代码是否可靠的,是人有没有继续追问异常状态、资源所有权和验证边界。

下一步

下一步,我不会急着增加更多武器和波次,而是先把这条链路放进 Cocos Creator 中实际运行:

主界面进入战斗 → 购买并放置武器 → 完成一波战斗 → 打开结算 → 退出并重新进入

只有这条链路在真实运行环境中也能重复完成,“第一波战斗”才算真正交付。

如果你也在用 AI 做游戏,欢迎留言说说你遇到过哪些“正常流程之外”的问题。

Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务,一站式满足开发者需求,帮助企业提升开发效率并创新开发模式。

更多推荐