


一个月前,我给自己定了个目标:做一款能上线、能靠广告活下来的小游戏。不组队、不融资、不用现成游戏引擎——但也不"纯手写":全程和一个 AI 结对,我负责方向和验收,AI 负责设计、实现、测试和排坑。
现在这款游戏——《猫咪守卫队》(合成 + 守城塔防,微信)已经完成开发进入审核流程。这篇文章是完整复盘:AI 结对到底怎么工作、坑长什么样、怎么被 AI 写的自动化测试救回来的。全文约 5000 字,建议收藏。
项目起点是 xx网站 的一个免费卡通猫防御素材包:角色立绘、UI 套件、场景图,以及——最关键的——32 套 Spine 骨骼动画(JSON Atlas 格式)。
拿到素材第一件事不是写代码,是让 AI 通读素材清单,先把设计文档全部吐出来:玩法循环、15 级进化链的数值公式(伤害 1.35^lv 成长这类)、关卡波次结构、广告变现节点,七份文档文档化之后才动工。这个顺序后面被证明极其正确:数值公式定稿后,波次、Boss、经济的迭代全是在表格层面推演,不用反复改代码试。
技术选型时 AI 给了我三个选项的分析:
选项一:Cocos Creator。 我真装了。但玩法是纯 2D、无物理需求、UI 自己画,引擎运行时对小游戏 4MB 主包限制来说是笔不小的"税"。而且小游戏的坑大多在平台适配层(微信/抖音 API 差异、真机怪癖),这层不管用什么引擎都得自己趟一遍。
选项二:Laya / Egret 等老牌 H5 引擎。 生态偏老,TS 支持和工具链不如自己搭的顺手。
选项三:自研 TS + Canvas。 渲染层只需要:图片、圆角矩形、文字、渐变、粒子——Canvas 2D 原生全覆盖。缺的不是渲染能力,是工程化。
最终落自研:
Sys 接口(canvas / 触摸 / 存储 / 音频 / 广告 / 震动),三个实现 WebSys / WxSys / TtSys,业务代码零平台分支最终三端总包 2.76MB,无分包——Spine 运行时 + 全部素材 + 游戏逻辑,远低于微信 4MB 主包红线。
先讲清楚工作流,后面所有章节都是它的注脚。一个月下来,稳定成型的分工是:
| 我(人类) | AI |
|---|---|
| 提需求、给素材、定产品方向 | 写设计文档、架构、全部业务代码 |
| 真机测试,把截图和报错丢回去 | 定位根因、改代码、跑四套测试回归 |
| 体验判断:"Boss 太强了""18 波太长" | 数值推演、实现改动、全绿后提交 |
| 我自己也直接改代码(UI/波次大改过几百行) | 改完后 AI 负责更新测试坐标、补回归用例 |
几个关键心得:
素材包里的 Spine 动画是最大的卖点(猫咪的待机/攻击/受击全是骨骼驱动),也是最大的坑源。接入 @esotericsoftware/spine-canvas@4.3 后,画面上所有角色纹丝不动——攻击照常出伤害,但模型像一尊雕塑。
我把症状描述给 AI(伤害在跳、模型不动),它排查出是三个问题叠加:
坑 1:TextureAtlas 构造 API 变了。 网上教程(包括官方旧文档)的写法是把图片和文本一起传给构造函数,4.3 版本实际是构造函数只收文本,图片要逐页手动绑定:
ts
const atlas = new spine.TextureAtlas(atlasText);
for (const page of atlas.pages) {
const img = await loadImage(page.name);
page.setTexture(new spine.CanvasTexture(img));
}
这个 API 变更没有任何运行时报错,就是白屏/不动。
坑 2:每帧更新顺序不能错。 正确顺序是:
ts
state.update(dt);
state.apply(skeleton); // 把动画姿态写到骨骼
skeleton.update(dt);
skeleton.updateWorldTransform(spine.Physics.update);
漏掉 state.apply(skeleton),skeleton 永远保持初始姿态——这是"伤害在跳、模型不动"的直接原因。
坑 3:最隐蔽的一个——加载流程漏调了初始化。 早期 AI 用批量补丁改过一次 app.ts,补丁脚本的断言失败了,但被 try/catch 静默吞掉,导致 SpineCache.loadAll() 压根没执行。这个坑后来成了我们结对流程的一条铁律:
批量补丁的断言失败必须让整个流程报错,而不是静默跳过。 否则你修的是 A,坏的是 B,而日志里一片绿色。
附带一坑:怎么证明"动画在动"? 最初 AI 读 bone.pose.worldX 做活性检测,发现永远是 0——因为渲染走的是 appliedPose,不在 bone.pose 上。最后改成连拍两帧像素对比:同一位置截图两次,差异像素超过阈值才算"动了"。土,但真实。
不同体型单位的贴地问题,用 skeleton.getBounds() 算包围盒,做脚底对齐 + 高度归一化,保证 Lv1 小猫到 Lv15 巨猫都踩在同一地平线上。
模拟器一切正常,真机全是惊喜。我把截图和报错一张张丢给 AI,它总结出一条小游戏开发铁律:永远不要用"应该"来猜测平台行为,拿不到的数据就返回 null,绝不编造。
现象 1:画面旋转 90°。 真机上整个游戏横过来了,拖动点击全部错位。根因:微信小游戏启动早期 jsbridge 未就绪时,getWindowInfo 返回的是竖屏尺寸,代码用错误尺寸建了横版画布。修复分三刀:
systemInfo 获取失败返回 null,绝不 fallback 猜一个尺寸现象 2:jsbridge not ready 直接崩。 启动期调用系统 API,微信侧桥还没注入。同上,等。
现象 3:document.getElementById is not a function。 为浏览器调试写的 DOM 垫片垫得不全,真机直接抛异常。最终方案:五重判定 isRealBrowser(),浏览器专属代码严格隔离到 WebSys。
现象 4:导入开发者工具就报"未找到 game.json"。 小游戏根目录和仓库根目录不是一层,放一个 project.config.json 指向 miniprogramRoot: dist/wechat/ 即可;纯资源分包必须有 game.js——最后评估包体足够小,直接取消分包。
小游戏主包 < 4MB 是硬红线,最终三端总包 2.76MB:
画质取向:移除了自动降质逻辑,强制 imageSmoothingQuality = 'high',低端机宁可掉帧也不糊——产品决策我拍板,实现 AI 落地。
人机结对最大的风险不是 AI 写不出功能,是改 A 坏 B 而不自知。我让 AI 在开发早期就搭了四套测试:
1. 无头整局模拟:Node 直接实例化 Battle,快进模拟整局,断言波次推进、合成规则、胜负星级。跑一遍 3 秒,覆盖 90% 逻辑回归。
2. 像素冒烟测试:Playwright + 无头 Chrome,连拍两帧像素对比(证明动画在动)+ 素材加载检查 + 运行时错误捕获。
3. 微信环境无头模拟:mock 完整 wx API,覆盖横竖屏切换、jsbridge 延迟、systemInfo 为 null 等真机场景。旋转 90° 的回归用例就住在这里。
4. 18 项全流程交互:从签到、训练、进关、召唤、拖拽合成、技能释放、暂停退出到重新进关,全自动点一遍。
这套体系的价值在下一章的数值迭代里,有现场证据。
上线前玩家反馈:一局 18 波太长了。我的决策:按难度分级——难度一 12 波、难度二 14、难度三 16、难度四 18(难度五 20)。
AI 的第一版实现看起来很"工程正确":波次结构从硬编码 18 改成 CHAPTER_WAVE_COUNTS + actBounds() 统一推导(三幕均分,幕末守门 Boss、倒数第二波怪潮),再加一刀切的密度补偿——每波敌人预算 ×(18/波数),防止短局经济缩水。
跑测试,教学关第 1 波直接团灭。 12 波局的第一波敌人从 3 只变成 5 只,新手两只猫,墙被啃穿;第 12 关同样第 1 波就输。
AI 自己的测试抓住了 AI 自己的 bug——这就是安全网的意义。修正后的形状是渐进补偿:开场与 18 波经典局完全一致,随进度逐步加密,12 波局末段约 1.5 倍:
ts
const dens = 18 / waveCount;
const densW = 1 + (dens - 1) * Math.pow(w / (waveCount - 1), 0.6);
const budget = Math.max(2, Math.round(3 + (ramp * densW)));
教学关回绿,末波 Boss 强度对齐。这次迭代还修掉一批历史硬编码(路线选择触发点、路线时长写死 waveIdx + 6、HUD 幕数 floor(wn/6)),也暴露了交互测试的时序脆弱:30 秒采样点恰好落在三选一弹窗上,暂停按钮点了个寂寞——改成轮询等回 playing 再操作后彻底稳定。
没有这张测试网,这个改动会带着"教学关必输"的 bug 直接上线——而且无论写代码的是人还是 AI,结局一样。
标题 Q 版化(逐字弹跳、金橙渐变、猫爪点缀)时,我们沉淀了一套双保险验证法,也是"AI 验收 AI"最有意思的部分:
getImageData 比对颜色值(猫爪主垫 #fff3e0 是否出现在指定坐标)——证明代码真的把图形画到了画布上为什么要两道?因为有次像素采样全对,但图形太小太淡,视觉模型根本没把它识别成装饰——"画了"和"看见了"是两回事。后来猫爪挡字,我人眼反馈"挡住了",AI 挪完再用两道验收收口。
另一个跨平台教训:装饰猫爪最初用 emoji(🐾)实现,无头浏览器里根本不渲染。改为纯代码绘制(贝塞尔画爱心肉垫 + 三趾 + 描边)后三端表现完全一致。emoji 属于文案,不属于画面。
国内上线小游戏,备案材料本身就是个工程:
回看这一个月,我的结论:
游戏叫 《猫咪守卫队》 :拖拽合成 15 级进化、五路守城、肉鸽三选一、七个技能 Boss、每日 Boss 与无尽模式,完全免费无内购。微信搜索即玩(上线后替换为准确入口)。
想了解某个模块更深的展开(Spine 接入完整代码 / wx mock 设计 / 音频合成参数 / 人机结对的提示词技巧),点赞过百我拆专文。