
上个月我在做一个微信小游戏《清单之外》玩法很轻每天从“清单之外”的任务池里随机抽三条小任务完成一条就攒一个徽章本质上是个收集养成向的休闲游戏。开发方式有点特殊——核心逻辑几乎全部交给AI来写我主要负责场景搭建、资源制作还有最后真机适配。项目做到一半时单元测试跑下来全是绿的我以为这波稳了结果往Cocos Creator里一挂启动直接黑屏控制台刷了几行报错连首帧画面都没出来。当时我整个人是懵的AI写的逻辑层明明是通的测试也全过了怎么一进引擎就炸后来花了好几个晚上排查才发现“全绿”这件事本身就是个陷阱。这篇文章就是把这次完整的踩坑过程复盘出来聊聊为什么单元测试通过和游戏能跑之间隔着好几道坎也把AI辅助开发实际工作中的那张“安全边界”划清楚。如果你准备用AI来写小游戏代码尤其是最终要发微信小游戏的这篇应该能帮你省掉不少我交过的学费。1. 项目复盘游戏本身、技术选型和“全绿”是怎么来的1.1 《清单之外》的玩法定位与技术选型《清单之外》的核心玩法一句话就能说清玩家每天进入游戏系统会自动刷新三条“清单之外的挑战”完成对应的动作就能获得徽章碎片集齐一套可以点亮一张主题卡。这个玩法的好处是代码量不大但需要处理的逻辑不少——任务池抽选、每日重置、完成判定、徽章进度、存档读取还有一套NPC对话触发的随机事件。技术选型上没有太多犹豫直接用Cocos Creator做客户端TypeScript写逻辑最终构建成微信小游戏。选择Cocos主要是因为它的2D工作流成熟发布微信小游戏是一键流程社区里适配资料也多。微信小游戏本身就是一个特殊的JavaScript运行环境很多浏览器API不存在比如window、document、localStorage这些常规Web习惯在实际运行时都可能缺失或行为不同。这一点决定了后续的坑都是从这里冒出来的。项目一开始我就打算用AI辅助写代码理由是任务池算法、日期判断、进度管理这类逻辑边界清楚、规则明确AI生成起来效率很高。实际也确实如此数据模型、随机抽选、每日零点重置等模块AI只花了几分钟就给了我一套像模像样的实现还顺手配了单元测试。1.2 AI到底帮我写了哪几个模块这次AI承担开发的模块大概有四块任务数据模型任务类型定义、奖励数值、稀有度权重。每日任务池抽选算法根据日期种子决定当天任务保证同一天不同设备读到的任务一致。完成任务判定逻辑根据用户的操作类型和参数校验是否匹配任务要求。徽章进度与存储封装进度变化、徽章解锁条件判断、存档的读写接口。这些模块的共同特点是它们都不直接依赖引擎或硬件能力大部分是纯数据逻辑用普通的TypeScript对象和函数就能表达。AI写的代码在结构和可读性上让我比较满意测试用例覆盖了边界情况跑起来也是一片绿。我当时还特意看了覆盖率报告核心逻辑的语句覆盖率到了九成以上。现在回头看问题恰恰出在这个“九成覆盖率”上——它覆盖的是代码逻辑不是运行环境。章节跑在Node环境里测试时用的存储、时间、随机数都是mock出来的而真实游戏跑在Cocos的组件化环境里面对的API完全不是同一套。这是我前期完全没有意识到的一件事。1.3 “测试全绿”是怎么跑出来的测试框架用的是Vitest跑在Node环境里。AI生成的测试文件大致长这样import { describe, it, expect, vi } from vitest; import { pickDailyTasks } from ./taskPool; import { loadProgress, saveProgress } from ./storage; vi.mock(./storage, () { const store new Map(); return { loadProgress: vi.fn(() JSON.parse(store.get(progress) || null)), saveProgress: vi.fn((data) store.set(progress, JSON.stringify(data))) }; }); describe(taskPool, () { it(同一天返回相同任务, () { const a pickDailyTasks(2025-04-01); const b pickDailyTasks(2025-04-01); expect(a.map(t t.id)).toEqual(b.map(t t.id)); }); });注意第三行的vi.mock(./storage)——它把存储模块整个换成了内存Map。在测试环境里这没问题逻辑对就行。但真实环境里sotrage模块调用的是Cocos的sys.localStorage再往平台层看是微信的wx.setStorageSync。AI在Node环境里测试mock掉了所有环境差异可它不知道真实运行时那些API能不能用。这就是“测试全绿”最大的欺骗性绿得越彻底越容易让人忘记环境真实性的缺失。这个教训值好几晚的调试点。2. 全绿背后藏着四个致命差异2.1 第一刀生命周期和启动时序Cocos Creator的脚本不是一进场景就把所有代码从上到下执行一遍。每个挂到节点上的组件都要等场景加载完成后依次触发onLoad、start、update这些生命周期回调。很多AI生成的代码常常在模块顶层直接写“访问引擎API”的初始化逻辑比如// 这种顶层直接拿着引擎能力初始化的写法是重灾区 const storage wx.getStorageSync(dailyTasks);这段代码如果出现在普通网页环境里没问题但出现在Cocos工程里脚本文件在导入阶段就可能执行此时场景还没就绪节点还没创建甚至平台能力还没有完全注入。于是报错五花八门Cannot read property getStorageSync of undefined、wx is not defined最惨的是没有报错只是黑屏没有任何反应。正确做法是把所有依赖引擎能力的操作都挪到onLoad之后让引擎完成初始化再执行自己的逻辑。比如读取存档这件事我就改成在组件的onLoad里通过sys.localStorage.getItem去拉取。而AI生成的纯逻辑代码比如任务抽选函数则保持纯粹的函数式只接收参数、返回结果不碰任何全局对象。这种“逻辑与引擎解耦”的写法后来成了我重构的主线。2.2 第二刀渲染方式根本不是同一套另一个问题的爆发点是渲染。AI在生成“画任务卡片”“画进度环”这类代码时非常自然地使用了Canvas 2D那套APIconst canvas document.createElement(canvas); const ctx canvas.getContext(2d); ctx.fillStyle #f00; ctx.fillRect(0, 0, 100, 40);在小游戏环境里没有document没有createElement。微信小游戏要求你直接操作底层Canvas而且即便拿到了Canvas上下文Cocos Creator的渲染机制也完全不走裸Canvas。Cocos有自己的节点、组件、渲染管线你要画一个进度环正确思路是给节点挂上Graphics组件然后调用circle、fill这些方法import { _decorator, Component, Graphics } from cc; ccclass(RingView) export class RingView extends Component { start() { const g this.getComponent(Graphics)!; g.lineWidth 10; g.circle(0, 0, 60); g.stroke(); } }也就是说AI的“画图”和Cocos的“画图”根本是两个宇宙。我后来总结出一个判断标准凡代码里出现canvas、ctx、document这三个词就要拉响红色警报。这类代码放到Cocos里即使勉强运行也会因为渲染体系和引擎冲突而出现绘制结果错乱、节点坐标对不上、显示层级莫名其妙等问题。2.3 第三刀坐标系和屏幕适配坐标系的坑更隐蔽。微信小游戏的Canvas坐标系和浏览器一样原点在左上角x轴向右y轴向下而Cocos Creator的坐标系原点在屏幕中心y轴向上。感受一下这个区别想让卡片出现在屏幕右上角Canvas思路x取screenWidth-100y取100Cocos思路x取visibleSize.width/2-100y取visibleSize.height/2-100而且节点锚点不同最终位置还会受影响。AI不懂这个它按浏览器坐标系的习惯去写最直接的后果就是小游戏在低端机不同分辨率下部分元素直接跑到屏幕外面。尤其是适配微信小游戏的fitHeight、fitWidth设置和设计分辨率之间还有一层换算关系。这些坐标算法和设计稿配置AI是完全无从推断的它没有项目上下文自然不可能替你算对。这块最终的修复方案是把坐标换算统一收敛在一个工具类里节点摆放一律使用Cocos的坐标设计分辨率锁定为固定的横屏尺寸再配合微信小游戏适配模式。所有坐标相关的计算不放逻辑层放表现层。2.4 第四刀存储和平台能力依赖存储模块是这次唯一一个AI还原生封装过、但实际还是炸了的模块。AI写的存储封装用的是localStorageexport function saveProgress(data: Progress) { localStorage.setItem(progress, JSON.stringify(data)); } export function loadProgress(): Progress | null { const raw localStorage.getItem(progress); return raw ? JSON.parse(raw) : null; }浏览器里这是对的但微信小游戏环境没有localStorage。在Cocos里跨平台的封装是sys.localStorage发布到微信平台后引擎会把它桥接到wx.setStorageSync。所以正确的写法是import { sys } from cc; const KEY clear_list_progress; export function saveProgress(data: Progress) { sys.localStorage.setItem(KEY, JSON.stringify(data)); } export function loadProgress(): Progress | null { const raw sys.localStorage.getItem(KEY); return raw ? JSON.parse(raw) : null; }这个差异如果不改构建出来的小游戏包在微信开发者工具里会直接抛异常。更麻烦的是如果AI在多个地方分散使用localStorage改起来非常费劲。我当时是把所有存储相关调用全部搜索出来集中替换成统一的存储管理模块再跑一遍真机。下面这张表是我这次踩完坑后整理出来的环境差异对照维度纯逻辑层测试环境Cocos引擎真实环境存储mock出来的Map或localStoragesys.localStorage/wx.*存储桥渲染不涉及或DOM mock节点 组件 Graphics/渲染器坐标系无屏幕概念屏幕中心原点y轴向上生命周期同步执行无生命周期onLoad - start - update全局对象Node环境可安全使用window/document不可用仅wx前缀异步资源测试里直接返回结果需要加载回调或Promise机制这张表背后是一句话逻辑正确只是起点环境适配才是能不能跑起来的关键。3. 从黑屏到定位根因的完整排查实录3.1 先说现象三种失败其实完全不同开始排查前先把“进Cocos失败”说清楚失败的形式有不同含义。我这次遇到的是黑屏但控制台有没有报错、有没有白屏、有没有闪退对应的排查方向都不一样。黑屏有报错说明引擎已经启动但脚本在执行过程中挂了错误信息能直接指出问题在哪一行。黑屏无报错多个信号被吞掉或者脚本根本没有被加载优先查构建配置、资源路径、插件脚本顺序。白屏场景加载出来了但没有渲染任何内容最常见是摄像机方向不对或者节点不可见。闪退往往是小游戏内存不足或API调用异常需要看微信开发者工具的性能面板和日志归档。我遇到的是小黑屏报错属于最好定位的那种。控制台给出的报错是localStorage is not defined定位到storage.ts第17行。那一刻我反而松了一口气——至少引擎是活的能指引修。真正难搞的是那些“没有任何报错但就是没画面的黑屏”下面会讲我们如何用日志定位。3.2 加日志和二分法定位一步步逼近问题排查决断不能靠猜。我当时采取的策略是在引擎最小可行路径上插入大量日志。具体来说我给主场景的第一个节点的onLoad加了日志给脚本入口文件的顶层也加了日志然后在start里也加了日志。过程跑起来后哪段日志没打印就能立刻锁定断点。举例如果onLoad日志都没打出来说明这个组件脚本没有被正确关联到节点上或者脚本编译时就因语法错误、运行时异常中断了。如果onLoad打了、start没打说明onLoad里有异常后面所有内容都不会执行。这套二分法特别适合定位AI生成代码接入引擎时的初始化问题。我当时还用了另一个很笨但有效的方法——逐步注释。把非核心模块全部注释只保留一个挂空节点的场景确认能渲染出一个纯色背景再逐步放开一个一个模块每放开一个就跑一遍预览。跑到哪个模块时黑屏根因就在哪个模块。3.3 揪出三个真正的根因经过二分法排查后锁定最后三个表现各异的根因挨个说明第一是存储模块的localStorage调用。只要进入start阶段读取进度就会触发异常整个组件的启动流程终止。这个修复最简单换成sys.localStorage即可。第二个是任务池模块在模块顶层执行了随机数初始化。AI代码在顶层直接调用了一个依赖Date.now()的种子生成逻辑。这本身没问题但它在模块加载时就执行并且干扰了Cocos的脚本加载顺序导致部分依赖该随机数的场景组件在onLoad时拿到的数据是过期或空值的。解决办法是把初始化动作移到逻辑管理器里显式调用。第三是UI绘制代码试图调用原生Canvas API直接绘制任务卡片。那一整段绘制逻辑在真机黑屏中占了大头因为它在onLoad里直接创建Canvas上下文但微信小游戏环境中这种调用可能返回null之后的绘制全失败画面就没渲染了。最终把绘制代码全部改成Cocos的Graphics组件和Sprite节点。3.4 修复后的回归测试全绿但仍然不行怎么办修复完这三个问题之后我重新在微信开发者工具里跑了预览画面出来了但还是有一个小问题——任务徽章的位置偏移。这个问题的起因正是前面说的坐标系差异。UI节点没按Cocos坐标调整个别元素跑到屏幕边缘外。我又统一改了坐标换算工具类把任务卡片的锚点和位置换算成屏幕中心坐标系这才真正全部跑通。回归测试我做了两层第一层是原有Vitest测试继续保持全绿因为改动没有破坏纯逻辑第二层是真机验收在微信开发者工具和两台真机上各跑一遍完整流程确认任务刷新、完成、徽章收集三个核心链路正常没有卡死或白屏。这里我想强调一句测试全绿之后最需要做的是在真正目标平台上跑一遍最小闭环。哪怕只是从启动到看到第一帧画面也要尽早跑。如果这一步晚了修复成本会指数级上升。我这次是到集成阶段才跑等于把各种环境问题堆到了一起排查效率很低。4. AI辅助开发这趟水到底有多深4.1 AI代码的三大典型命门这次项目让我把AI代码的问题总结成了三类命门以后遇到同类情况可以直接按图索骥。第一类是“浏览器假设”。AI的预训练语料里绝大部分是网页或Node代码生成时默认存在window、document、localStorage这些对象。在小游戏引擎里这些对象经常不存在或行为不同。所以拿到AI代码的第一件事就是全局搜索这三个词一旦出现就要问自己它到底在什么环境里才能跑第二类是“引擎瞎猜”。AI对Cocos Creator的API并不是完全无知但它会混用旧版本API、推荐不存在的组件甚至把2.x和3.x的用法揉在一起。比如有的代码里同时出现cc.Node和ccclass一看就是模型在撒谎。凡涉及引擎具体API必须对照当前版本的官方文档。第三类是“空有结构没有边界”。AI很喜欢把所有逻辑写在一个大文件里动不动就上百行。实际项目里更要命的是它会把“取数据”和“画界面”混在一起让后续替换存储、替换渲染层都变得异常痛苦。这次踩坑后我强制要求AI代码按“纯逻辑层”和“引擎适配层”分离前者不允许import任何cc模块或平台API后者才是负责调用引擎能力的薄薄一层。4.2 单元测试的盲区比想象中大写完测试全绿很容易让人膨胀但必须承认单元测试验证的是“函数输入输出对不对”不是“游戏在设备上能不能用”。这次项目的测试覆盖了任务抽选、进度计算、徽章解锁条件它们都是无状态的纯函数所以测试通过很正常。但真正出错的部分——存储模块的localStorage、Canvas的先后顺序、坐标系的换算——恰恰全都没有被真实测试覆盖。如果你想用自动化测试覆盖环境问题正确做法不是指望Node环境里mock而是在小游戏开发者工具里跑集成测试或者至少增加一个“启动冒烟测试”脚本加载后能否成功执行onLoad获取到第一帧渲染结果。哪怕只是日志断言也比几千行单元测试更有用。另一个关键点是AI生成的测试用例往往会“测试AI自己写的代码”容易出现“对着特征写测试”的问题。也就是说测试验证的是AI代码的当前行为而不是业务需求本来的约束。比如每日任务应该是“同一天相同”但如果AI某天写成了“同一种子相同”测试可能还是会过因为它就是照当前实现生成的期望值。最好是自己先梳理几条核心业务规则让测试对准规则而不是对准实现。4.3 正确的AI辅助姿势先生存后完善经过这次教训我现在的AI辅助开发流程已经改成“先生存后完善”的六步先把最小可运行闭环跑通从启动到第一帧有画面哪怕只有背景色。这一步先不要贪多。让AI只写纯逻辑模块数据模型、任务抽选、进度计算、存储接口的抽象定义都可以让它来但不要让它碰任何渲染代码。人工负责“引擎适配层”Cocos的生命周期、节点操作、坐标系、渲染组件这些由我来写或者让AI写完我逐行审。环境隔离任何读取存储、读取时间、生成随机数的地方都通过接口注入而不是直接在模块内部调用全局API。尽早集成尽早真机每完成一小块就丢进Cocos预览里跑一遍能快速暴露环境差异。测试分两层逻辑层交给Vitest运行层靠真机冒烟和日志验证。这套流程下来AI依然能显著提效但判断风险的地方由我控制不会再出现“全绿但进Cocos黑屏”这种返工了。4.4 我现在的工作流和工具组合结合这次经验目前我用下来的比较顺手的组合是需求拆解先把玩法拆成“数据规则”和“界面流程”两张清单。AI工具辅助生成数据层、工具函数、测试用例。Cocos Creator负责场景和UI组装图形资源用现成的或简单程序化生成。代码用一般性的格式化工具统一风格避免AI代码不同文件风格差异过大。真机验证现在固定至少两台设备一台低端安卓、一台iOS性能和兼容性问题尽早暴露。另外我强烈建议把项目中所有“非Cocos标准API”的调用都收拢到一个platform.ts文件里统一适配。比如存储、震动反馈、分享、系统时间读取都通过这个文件暴露接口。这样即便AI生成的逻辑里偶尔偷用平台API也能在编译期快速发现不至于散落各处改到崩溃。5. 修复后的效果和几条保命建议5.1 修复后实际跑起来是什么样修复完之后《清单之外》在微信开发者工具里完成了从启动到收集徽章的全流程验证。启动耗时降到可接受范围没有再出现黑屏或空白页。每天零点之后进入游戏会正确按日期种子刷新三条任务完成任务后点确认徽章碎片1集齐后点亮卡片触发一次简单的弹窗反馈。真机上跑了两台旧设备也没有再出现闪退和位置偏移。比较有意思的是整个问题修复过程中AI生成的纯逻辑代码一行没改。任务抽选函数、完成判定、进度计算依旧是原有实现。这也说明AI在“规则明确的计算”上确实好用只是引擎这一层不是它的主场。5.2 给准备入坑的人几条保命建议如果你也想用AI辅助写小游戏我的建议很简单但每条都是踩完坑换来的第一拿到AI代码先搜document、window、localStorage、canvas这四个词发现就标记风险。第二任何涉及引擎生命周期、屏幕坐标、场景节点的代码都必须人工过一遍。第三测试不是终点真机跑通才是。第一帧画面出来的越早后面返工越少。第四积累一套本项目的“环境差异速查表”类似上面那张表格粘在项目仓库的README里方便随时核对。第五如果AI代码出现你完全不理解的部分宁可重写也不要盲信。理解不了的东西放进项目里就是埋雷。5.3 后续还能怎么扩展这次项目跑顺之后我已经在规划一些扩展方向了。比如给每日任务增加玩家自选模板把任务池改成动态内容配置不用改代码就能运营活动再比如把进度存档接上云存储支持跨设备续玩。这些功能里AI依然可以在我给清楚规则的情况下帮我生成数据层和业务层但引擎适配的部分依旧走人工评审。说实话这次“测试全绿、进Cocos失败”的教训对我来说很值。它让我真正理解了“可测试”和“可运行”之间的差距。AI写代码的能力毋庸置疑它帮我把大量繁琐的数据计算和边界判断工作做了速度和覆盖面都是手写没法比的。但它不会知道你用的引擎API长什么样也不会替你判断运行环境支持哪些对象。真正成熟的工作流不是把所有工作丢给AI而分清楚哪一层适合交给它哪一层必须由人来兜底。最后留一个小技巧任何从网页或Node环境的代码搬进Cocos的第一步先把代码里所有“全局对象”找出来凡涉及window/document/localStorage的一律改成引擎提供的接口或你自己的适配层。这个动作做好至少能跳过这次复盘里80%的坑。