兄弟们,我做 Cocos 项目这么多年,最怕听到的一句话就是“这个低端机怎么跑不动了”。上个月接手的这个中度休闲项目,在骁龙 625 这个级别的机器上,战斗场景平均帧率只有 9.8,Draw Call 峰值冲到 320,点开排行榜面板能卡三秒。这次我没有像以前那样一头扎进 Profiler 里手动排查,而是把整个性能优化流程交给了 AI Agent 跑了一遍——从性能数据采集、热点函数定位,到代码改写和回归验证,全部走了一条自动化闭环。最终的结果是平均帧率从 9.8 涨到 58.9,刚好是 6 倍,Draw Call 从 320 降到 45,内存降了 150MB 左右,包体和启动时间也跟着缩了不少。
这篇文章适合谁看?如果你在用 Cocos Creator 做手游,被低端安卓机上的卡顿、掉帧、内存膨胀折腾得焦头烂额,同时又好奇 AI Agent 到底能帮程序员干多少活,那这次实战记录应该能给你一些直接能抄作业的参考。我会把整个优化链路、核心代码改动、坑点排查都摊开来讲,不玄学,不吹牛。
1. 项目背景与瓶颈定位:为什么非得上 AI Agent
1.1 项目基本情况与性能基线
项目本身是一款中度休闲游戏,核心玩法是实时战斗加角色养成,外层还挂了排行、聊天、活动大厅这些常见 UI 模块。团队接手的时候,代码已经迭代了两年多,战斗系统、UI 部分、网络模块混杂在一起,光 Prefab 就有一千多个。这种项目普遍有个特点:功能是齐的,但性能属于“能跑就算成功”的状态。
我在真机上先跑了一遍基线测试,固定使用一台骁龙 625、2GB RAM 的测试机,场景选在同一关卡的同一段战斗流程。结果非常难看:
- 平均帧率:9.8 fps
- Draw Call 峰值:320
- 每分钟卡顿次数:13 次
- 内存峰值:420MB 左右(已经逼近系统杀进程的边缘)
- 包体大小:128MB
- 冷启动时间:6.4 秒
为什么这么差?说白了就是历史遗留问题太多。特效资源没做图集,Label 大量使用动态字体,战斗中的子弹和飘字不停 instantiate 和 destroy,局部刷新数据时动不动就整张表 JSON.stringify 一遍,Shader 还是照搬 PC 端的写法。这些问题单个看都不致命,叠在一起就是灾难。
1.2 传统优化流程的困境
说实话,这些问题如果靠人肉去查,也不是不能解决。我以前做过很多次类似的项目优化,标准流程就是开 Profiler、定位热点、改代码、找人回归。但这次的情况特殊,有几个现实问题:
第一,改动面太大。十个以上的性能热点分散在战斗、UI、存储、网络四条线里,如果全靠人工逐个优化,至少需要两到三周,而且上线窗口不等人。
第二,开发和维护这些模块的人有的已经离职,代码注释约等于没有。改一行代码,可能要顺着调用链翻十几个文件,才能确定不会影响业务逻辑。
第三,性能优化是典型的多轮迭代工作,改完一版要重新采集数据、重新对比、再改下一版。人工反复做这种“采集-分析-修改-验证”的循环,效率非常低。
所以我做了一个决定:把优化过程中大量机械、重复、可量化的环节抽出来,交给 AI Agent 去跑。我保留最终审核和拍板的角色,Agent 负责干活。
1.3 AI Agent 要解决的三个问题
先说清楚,我这里说的 AI Agent 不是简单开个 ChatGPT 窗口一问一答,而是一个能调用工具、能读代码、能改文件、能执行构建和测试的自动化执行体。它的核心能力可以拆成三块:
- 感知:读取 Profiler 数据、扫描项目代码、识别耗时函数和异常调用模式。
- 决策:基于内置的 Cocos 最佳实践知识库,判断哪些节点值得优化、用什么方案优化、风险有多高。
- 执行:生成代码补丁、执行 git diff、触发构建脚本、跑回归测试、对比性能基线。
这三块能力正好对应我在这个项目里踩到的核心痛点:热点定位太慢、优化方案靠经验、回归验证太繁琐。AI Agent 不是替你“创造”优化方案,而是把老师傅脑子里的“看到这个模式就改这里”固化成了可重复执行的流程。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. AI Agent 工作流设计与核心实现
2.1 Agent 的整体架构与任务拆解
如果把这个 Agent 看成一个小团队,那它的工作流大概长这样:采集数据 → 静态扫描 → 热点分析 → 生成优化方案 → 人工审核 → 自动改码 → 构建验证 → 对比基线。只要性能不达标,就循环回到热点分析,继续下一轮。
实现这个流程的时候,我没有选择市面上某个现成的“AI 程序员”产品,而是自己组装了一套。原因很简单:Cocos Creator 项目的优化非常吃项目上下文,通用 Agent 不了解你的图集规则、材质规范、打包配置,给出来的建议往往飘在空中。
技术选型上,底层用的是大语言模型,负责理解和生成代码;上层包了一层任务调度器,每一步的输入输出都是结构化数据。比如性能数据统一用 JSON 格式传给模型,优化建议要求模型按固定模板返回,包含问题文件、问题行号、问题类型、修复方案、风险等级这五个字段。这样后续代码解析、diff 生成、人工审核都很方便。
2.2 性能数据采集与静态代码扫描
第一步是喂数据。Cocos Creator 有自带的 Profiler,真机模式下可以导出每帧的 CPU 耗时、渲染耗时、内存曲线。这些数据原始格式很乱,不可能直接丢给模型,我做了一层归一化处理,把所有指标整理成字段清晰的热点列表。
与此同时,我还写了一套静态扫描脚本,用 AST 遍历项目里所有 TypeScript 脚本,统计出高频问题模式。常见模式包括:每帧调用 getComponent、战斗循环里频繁 instantiate 和 destroy、动画帧事件中执行复杂逻辑、热更新表每次变化就 JSON.stringify 整个对象、Shader 使用大量高精度浮点计算等等。
这轮扫描本身不依赖 AI,更多是规则引擎的功劳。但扫描结果喂给 Agent 后,模型会结合项目上下文给出优先级排序。比如同样是 getComponent,用在一个每帧更新的伤害飘字上,和用在一个只在初始化时执行一次的界面节点上,风险等级完全不同。Agent 能读懂上下文,这是规则扫描做不到的。
2.3 优化建议生成与自动改写
拿到热力数据后,Agent 会针对每个热点生成一份结构化的优化提案。我要求它必须回答“为什么耗”“改哪里”“怎么改”“会不会影响业务”这四个问题。比如对于战斗飘字频繁创建的问题,它给出的提案是:改成对象池缓存,并附上 NodePool 的具体改造代码。
自动改写阶段,Agent 会生成一个标准的 diff 文件,我再通过代码评审工具查看。所有改动默认不直接合入主分支,而是先提交到 feature 分支,由我做最终确认。这里我必须强调一点:自动改写越猛,越要建立人工防线。模型再聪明,也可能对业务逻辑产生误判,我后面会专门讲这个坑。
2.4 自动验证与回归循环
优化代码改完之后,Agent 会自动触发 Cocos Creator 的命令行构建流程,打出一个新的测试包,装到真机上,再自动跑一遍和优化前完全相同的固定场景。跑完之后自动对比帧率、Draw Call、内存这些关键指标,然后生成一份前后对比报告。
如果优化结果达标,就进入下一个热点;如果不达标,Agent 会根据失败原因重新调整方案,进入下一轮迭代。整套流程跑下来,我真正需要动手的地方只有两处:第一,在热点分析阶段决定哪些任务交给 Agent;第二,在代码合入之前做 review。其余的时间,我都在喝茶、盯报告、偶尔骂一骂没素质的历史代码。
3. 六大性能优化点逐一拆解与实操记录
3.1 Draw Call 与合批:先砍大头
优化第一步,先处理最痛的问题。战斗场景里,角色、怪物、特效、飘字各自用着零散的贴图资源,导致 GPU 状态切换极其频繁,Draw Call 峰值直接冲到 320。对移动端来说,这个数字基本等于宣判卡死。
Agent 扫描素材目录后给出的方案很直接:先把散图整理成图集。Cocos Creator 的图集机制可以把多张小图打包到一张大纹理里,渲染时就能走合批,降低状态切换。我用编辑器自带的自动图集功能重跑了一遍资源,同一个战斗场景的图集数量从两百多张压缩到二十几张,Draw Call 立刻降到一百以内。
接下来是静态合批和动态合批的配合使用。静态合批适合那些不动的场景物件,比如地图、建筑;动态合批适合频繁移动的小物件,但要注意顶点数限制,合批对象太多反而会增加 CPU 开销。最终这轮优化后 Draw Call 峰值降到了 45 左右,战斗场景肉眼可见地顺了。
3.2 Label 与渐变 Shader:移动端别再这么写
这个项目里 UI 界面大量使用了 Label 渐变效果,而渐变又绑定着一段高开销 Shader。移动端 GPU 和 PC 完全不在一个量级,我见过不少团队直接把 PC 端 Shader 原封不动搬过来,最后在低端机上被打得毫无还手之力。
Agent 给出的建议分两步:第一步,Label 优先使用 BMFont 位图字体,或者将动态字体文本用 CPU 预先渲染成纹理,避免每帧重建文本网格;第二步,对渐变 Shader 做移动端适配,把精度从 highp 降到 mediump,减少纹理采样次数,把渐变参数提前通过 uniform 传入,而不是在片段着色器里做昂贵计算。
改造后的渐变 Shader 核心片段大概是这种感觉:
glsl复制precision mediump float;
varying vec2 v_uv;
uniform vec4 u_startColor;
uniform vec4 u_endColor;
uniform float u_gradientY;
void main() {
float t = clamp((v_uv.y - u_gradientY) / (1.0 - u_gradientY), 0.0, 1.0);
vec4 color = mix(u_startColor, u_endColor, t);
gl_FragColor = color * v_color;
}
这里的核心思路是:把渐变计算从逐像素的复杂纹理采样,变成简单的线性插值。实测之后,UI 界面的渲染耗时降了 40%。这里有一个坑:mediump 精度在一些老芯片上会出现明显色带,尤其是暗色渐变背景。所以,轮到这个优化点时,我专门安排了一个目标真机截图对比步骤,确认色带问题可接受才放行。
3.3 对象池与内存抖动:卡顿的头号元凶
我把这个项目里最“放肆”的代码翻了个底朝天。战斗场景里的子弹、飘字、捡拾物、特效节点,全部是动态创建和销毁,一分钟能产生上千个临时对象。每次创建和销毁,都在给 JavaScript 引擎的垃圾回收器制造压力,卡顿就是这么来的。
Agent 的建议不用动脑都知道:上对象池。但真正写代码的时候,细节决定成败。NodePool 用起来很简单:
typescript复制import { NodePool, instantiate, Prefab } from 'cc';
export class ObjectPool {
private pool: NodePool = new NodePool();
private prefab: Prefab;
constructor(prefab: Prefab, initSize: number = 10) {
this.prefab = prefab;
for (let i = 0; i < initSize; i++) {
const node = instantiate(this.prefab);
node.active = false;
this.pool.put(node);
}
}
public get(): Node {
const node = this.pool.size() > 0 ? this.pool.get() : instantiate(this.prefab);
node.active = true;
return node;
}
public put(node: Node): void {
node.active = false;
this.pool.put(node);
}
}
这里的关键是回收时要把节点上的状态彻底重置,否则就会出现复用后数据残留的诡异 bug。后面第 5 部分我会专门展开讲这个坑。用了对象池之后,滚屏过程中的内存分配基本抹平,GC 次数直接降了 70% 左右,长帧也少了很多。
3.4 JSON.stringify:看似不起眼,实则大坑
这个热点藏得很深,是 AI 静态扫描扫出来的。项目里有一个排行榜模块,每隔一段时间就会把整个排行榜列表对象 JSON.stringify 之后发到服务端做校验。排行榜列表最长上百条,每条带昵称、头像、段位、战斗力一堆字段,序列化一次要花不少时间。如果操作频繁,对 CPU 的消耗是实打实的。
Agent 给的方案是加“脏标记”,只在数据真正变化时才序列化,同时还把发送模式从全量改成增量。进一步优化时,我把一些结构固定的字段直接用手写拼接替代 JSON.stringify,省掉建对象、遍历字段、生成字符串这一整套开销。
typescript复制// 优化前,每次都全量序列化
socket.send(JSON.stringify(rankList));
// 优化后,加脏标记 + 增量字段
if (rankList.isDirty) {
socket.send(buildRankPayload(rankList));
rankList.isDirty = false;
}
这个模块优化完之后,CPU 占用降了差不多 8 到 10 个百分点。不要小看这个数字,对于低端机来说,10% 的 CPU 余量可能就决定了能不能保住 30 帧。
3.5 动画帧事件与逻辑耗时
动画帧事件是另一个容易被忽视的性能杀手。项目里一些角色的攻击动画,每一帧都挂着一个帧事件,用来调用战斗逻辑。大家写的时候只顾着“能触发”,完全没想过这些逻辑里居然还藏着 find、getComponent 这种运行时开销极大的调用。
Agent 把战斗动画相关的帧事件调用树拉出来分析之后,发现问题不仅是每帧查节点,一些回调甚至直接操作别的 UI 节点,导致渲染树更新频繁。优化方式比较简单粗暴:把帧事件里的复杂逻辑迁到统一的战斗逻辑管理器里,动画只是发一个“播放到第 X 帧”的信号;把频繁查找的节点引用提前在初始化时缓存到成员变量里。
这个改动不涉及架构级重构,收益却很可观。战斗场景的整体逻辑耗时下降了大概 15%,之前帧事件导致的随机 spike 也基本消失了。
3.6 打包 APK 与混淆:从构建侧再抠一刀
性能优化不能只看运行时,构建产物体积和启动效率同样会影响用户体验。这个项目打出的 APK 有 128MB,一方面是因为资源没压缩,另一方面是代码和依赖太大。
在打包环节,我们启用了 Cocos Creator 构建流程里的压缩纹理选项,自动为不同 GPU 平台生成 ASTC/ETC2 纹理,同时把不太常用的玩法子系统拆成 Asset Bundle 按需加载,而不是一股脑打进首包。代码侧开启混淆压缩,剔除了大量调试日志和未使用的分支逻辑。
这里要提醒一句:混淆一定要留好白名单。项目里有些模块依赖字符串反射来调用方法,混淆后方法名被重写,轻则功能异常,重则崩溃。Agent 会扫描代码引用关系,自动生成一份基础 keep 规则,但最终确认还是需要懂业务的人看一眼。这轮之后,包体降到了 92MB,冷启动时间从 6.4 秒降到了 3.2 秒左右。
4. 性能结果对比与复现方法
4.1 量化对比:真机 6 倍是怎么算出来的
为了不让“6 倍”变成拍脑袋,我把优化前后的数据放在同一条件下对比,测试机、测试场景、测试时长完全一致。最终结果如下:
| 关键指标 | 优化前 | 优化后 | 变化幅度 |
|---|---|---|---|
| 平均帧率 | 9.8 fps | 58.9 fps | 约 6 倍提升 |
| Draw Call 峰值 | 320 | 45 | 下降约 86% |
| 每分钟卡顿次数 | 13 次 | 约 1 次 | 下降约 92% |
| 内存峰值 | 420MB | 268MB | 下降约 36% |
| APK 包体 | 128MB | 92MB | 下降约 28% |
| 冷启动时间 | 6.4 秒 | 3.2 秒 | 下降约 50% |
平均帧率那条,严格说是 9.8 提升到了 58.9,按倍率算是整整 6 倍。加上卡顿次数、Draw Call、内存这些维度,实际流畅度的提升体感比数字更夸张。排行榜面板从“点开卡三秒”变成了“点开就弹”,这对我个人来说才是最有成就感的时刻。
4.2 测试方法与工具选择
性能优化最怕“你改了,但我没感觉”,所以测试方法必须可复现。我固定用同一台低端安卓机,在同一段 1 分钟的战斗回放里跑测试,操作路径也保持一致。测试工具重点推荐几个:
- Cocos Creator 内置 Profiler:定位 CPU 耗时、渲染耗时最方便。
- PerfDog:看帧率曲线和 Jank 卡顿次数很直观。
- Tracy:想深入分析原生层优化时非常好用,能看到 C++ 层面的调用栈。
- 自定义埋点:在游戏关键逻辑里打点,统计具体模块耗时,比看整体曲线更精准。
测试场景一定要固定。如果每次测试走的战斗流程不一样,帧率数据根本没有可比性。我甚至把测试用的账号、角色、装备都锁定了,避免战斗复杂度差异干扰结果。
4.3 稳定性回归与灰度发布
性能提上去了,功能不能丢。这轮优化合入之前,我安排了一轮全量回归测试,重点覆盖战斗、UI、排行榜、聊天这些涉及高频改动的模块。同时用三台不同档位的机器一起跑,除了低端测试机,还加了一台中端机、一台高端机,防止“只盯着低端机优化,结果高端机反而表现下降”这种诡异情况发生。
线上发布节奏上,我先做了一个小范围灰度,观察了好几天崩溃率和帧率上报数据。确认没有异常后才逐步放量。别怕慢,性能优化这种东西,线上翻车一次,代价远大于多等几天。
5. 常见问题与排查技巧实录
5.1 Agent 改完代码渲染异常怎么办
我第一次让 Agent 批量替换 Shader 精度时,差点把整个战斗场景搞成了黑屏。原因是部分 Shader 里用到了 v_color,从 highp 降到 mediump 后,颜色插值在某些 GPU 驱动上出现了偏差,部分特效直接变成一团黑。
排查思路是二分定位:把 Agent 生成的 diff 分为三组,分别回滚、单独测试,最终定位到 Shader 精度改动。解决方案也不是全部回退,而是对需要高精度的个别 Shader 单独保留 highp,其他走低精度。经验就是,自动生成的代码再漂亮,也必须在目标机型上做视觉验证,不能只看帧率数字。
5.2 对象池复用导致数据残留
对象池这个坑,我猜做过 UI 优化的兄弟都踩过。优化完战斗飘字之后,某天测试发现飘字偶尔会显示上一次的伤害数值,或者字体颜色不对。原因很简单:节点被回收到对象池后,控件上的文本、颜色、缩放这些属性没有重置,下次复用时全带出来了。
解决方案是定义统一的 reset() 方法,在 put 回池之前强制刷新所有动态属性。这里我想提醒一句:处理回收逻辑时,一定要比创建逻辑更谨慎,因为你面对的不只是当前状态,还有“上一个使用者”留下的状态。
5.3 低端机与高端机结果不一致
优化完同一套代码,低端机帧率提升明显,高端机却偶尔会出现掉帧。我一开始很困惑,后来才发现是高端机对某些 Shader 精度不敏感,反而因为用上了更高分辨率的纹理,内存占用变大,触发了一些奇怪的性能波动。
针对这种情况,我调整了策略:优化决策以低端机数据为主,但高端机也建立基线,防止出现负优化。同时纹理压缩策略要按机型动态区分,不能一刀切。最终在我的项目里,中高端机型直接使用 ASTC 纹理,老机型走 ETC2,效果稳定了很多。
5.4 Agent 生成代码的边界问题
用 Agent 优化很爽,但不是所有代码都适合让它碰。我给自己定了几条边界,分享出来供参考:
- 涉及支付、账号、安全和归档格式的模块,一律人工修改,Agent 只做分析不做建议。
- 网络协议字段顺序和类型,禁止自动改动,避免前后端版本不兼容。
- 所有 Agent 生成的代码必须走 review,且至少要有两个懂业务的人确认。
- 高风险改动要有开关,线上出问题能一键回滚。
这不是不信任 AI,而是工程上的底线。Agent 最大的价值是帮你把重复劳动做掉,而不是替你做技术决策。
6. 最后说点实在的
6.1 我把这套流程固化成了模板
项目上线稳定后,我把整个优化流程整理成了一套可复用的模板,包括性能数据采集脚本、静态扫描规则、Agent 提示词模板、回归测试清单。以后再接新项目,我不需要从零开始,直接在模板上跑一遍,就能快速摸清项目现状,定位到主要性能风险点。
我现在的工作模式跟以前完全不一样了。以前是“拿着 Profiler 一帧一帧看,看到凌晨三点”,现在是“Agent 把热点列表排好,我看一眼决定哪些值得处理”。这个转变不是偷懒,而是把有限的时间花在真正需要人脑判断的地方。
6.2 给想试水的朋友几点提醒
如果你也想在自己项目里复制这套玩法,我给你几个比较实在的提醒:
第一,先手动跑通一次完整流程,再来谈自动化。你得知道每个环节预期输出是什么,否则 Agent 跑歪了你都发现不了。
第二,不要一上来就把所有代码交给 Agent 改。先选一个影响面小、收益明确的模块试点,比如战斗飘字、列表滚动,建立信心之后再扩大范围。
第三,性能数据必须可持续对比。我把每次构建产物的帧率、内存、Draw Call 都存入一个独立平台,这样每次改动都能看到趋势变化,而不是凭感觉说“好像流畅了一点”。
最后再说一句得罪人的话:AI Agent 确实能把性能优化效率提升好几倍,但前提是你自己得先懂性能优化。工具越强,用工具的人越要有判断力。这个项目和这次 6 倍的提升,不是 AI 替我完成了优化,而是 AI 帮我少熬了无数个夜。这就是我最大的感受。
