关于Cocos引擎的一些思考

关于Cocos引擎的一些思考 性能优化Cocos Creator 性能优化需从渲染、逻辑、资源、内存四大核心维度入手覆盖从开发配置到上线调优的全流程。一、渲染性能优化最核心直接影响帧率1.降低Draw Call在游戏开发中DrawCall 作为一个非常重要的性能指标直接影响游戏的整体性能表现。Draw call 就是 CPU 调用图形 API比如 OpenGL命令 GPU 进行图形绘制。一次 Draw call 就代表一次图形绘制命令由于 Draw call 带来的 CPU 及 GPU 的渲染状态切换 消耗往往需要通过批次合并来降低 Draw call 的调用次数。批次合并的本质就是在一帧的渲染过程中保证连续节点的渲染状态一致将尽可能多的节点数据合并一次性提交从而减少绘图指令的调用次数降低图形 API 调用带来的性能消耗同时也可以避免 GPU 进行频繁的渲染状态切换。1.1 静态合图1在资源层面进行散图合并保证相邻的节点使用的纹理都是同一张贴图因为同一张图集的纹理状态都是一致的所以能够达到渲染批次合并对纹理状态的要求。2合并图集cocos creator提供自动图集功能或者使用第三方打包图集的工具比如TexturePacker 等。划分图集最好可以尊选以下原则 --- 由于不同平台对纹理尺寸有限制最大尺寸最好控制在 2048 2048 以内。 -- 目前的图集解析已经去掉了大小必须是2的N次方限制了 --- 为了避免 UI 界面打开时图集资源过大导致加载缓慢的问题通常将单个 UI 界面所使用的图片资源放入一个文件夹为该文件夹创建自动图集即可保证同一界面使用的纹理图片资源一致。如果静态合图很大但当前场景实际用到的只是其中很小一部分散图则也可能造成*浪费导致游戏加载时间延长和内存占用增多。--- 游戏开发中常常将使用率频繁资源单独提取到一个common目录通用资源作为一张图集资源。3 对与Label组件为了保证所有的 Label 节点使用相同的纹理如果可以设计成BMFont可以做到合批。通常会使用 BMFont 将要使用的 UI 文字提前进行打包并使用引擎的自动图集与散图一起合并进一张大的纹理即可与其他相邻的 Sprite 节点进行批次合并。1.2 动态合图静态合图除了占用空间的问题静态合图的局限性还常常体现在动态文本的渲染过程如 Label 组件在使用系统文本时文本贴图是依据文本内容通过 Canvas 绘制动态生成不能提前进行图集打包。引擎也提供了动态合图的功能。在运行时引擎通过将散图添加到动态图集中来保证节点使用的纹理一致。由于动态图集使用的是默认纹理状态所以只有当散图的纹理状态与动态图集的状态一致才可参与到引擎的动态合图中。比如Label 组件目前提供三种 Cache ModeNONE、BITMAP 和 CHAR。NONE 模式即 Label 的整个文本内容会进行一次绘制并进行提交但是并不参与动态合图。BITMAP 模式即 Label 的整个文本内容会进行一次绘制并加入到动态图集中以便进行批次合并。CHAR 模式即 Label 会将文本内容进行拆分单个字符进行绘制并将字符缓存到一张单独的字符图集中下次遇到相同字符不再重新绘制。目前引擎的动态图集主要有两种一种是为散图及使用 BITMAP 模式的文本提供的动态图集最大数量为 5 张尺寸为 2048 * 2048。另外一种是为使用 CHAR 模式的文本提供的字符图集单个场景只有一张尺寸为 2048 * 2048这两种动态图集在切换场景时会进行清理释放。由于动态图集空间有限因此需要最佳化的利用。动态合图开启Cocos Creator 在 v2.0 中加入了 动态合图的功能它能在项目运行时动态的将贴图合并到一张大贴图中。当渲染一张贴图的时候动态合图系统会自动检测这张贴图是否已经被加入到了动态合图系统如果没有并且此贴图又符合动态合图的条件就会将此贴图合并到动态合图系统生成的大贴图中。贴图限制动态合图系统限制了能够进行合图的贴图大小只有贴图宽高都小于 512 的贴图才可以进入到动态合图系统。用户可以根据需求修改这个限制2 影响合批操作合批动态合批 / 静态合批的核心前提是 “渲染状态完全一致”任何破坏该一致性的操作都会导致合批失败。材质是合批的 “基础通行证”只要两个物体的材质或着色器状态不同100% 无法合批。1 调整节点的透明度opacity)透明度会影响 GPU 的 “混合计算”如 SRC_ALPHA * 源色 DST_ALPHA * 目标色不同透明度需要不同的混合参数无法合批。(2) 调整节点的混合模式BlendMode混合模式定义了 “当前像素如何与已渲染像素混合”不同模式的 GPU 计算逻辑完全不同必须拆批。(3) 遮罩Mask影响Mask 会开启 “模板测试Stencil Test”不同 Mask 的模板值Stencil Value不同GPU 需分开处理裁剪区域。(4) 抗锯齿AntiAlias状态抗锯齿会增加 GPU 的采样计算开关状态不同会导致渲染指令差异无法合批。(5) Z 轴排序或深度测试— 透明物体与不透明物体混合排列透明物体需 “从后往前” 渲染不透明物体 “从前往后”顺序冲突— 部分物体开启 node.zIndex 强制排序破坏合批的默认顺序。合批要求物体按 “同一渲染顺序” 处理Z 轴或 zIndex 差异会导致排序冲突必须拆批以保证渲染正确。二、逻辑代码优化减少 CPU 占用避免主线程阻塞CPU 主要处理游戏逻辑、脚本计算、对象管理优化目标是 减少循环耗时、避免冗余计算、降低 GC垃圾回收频率。1. 减少冗余计算与循环开销1避免帧内重复计算场景中不变的值如屏幕宽高 cc.winSize、物体初始位置缓存到变量而非每帧在 update 中重复获取复杂计算如向量运算、距离判断若结果不变缓存到变量避免每帧重复计算。或者可以建个N帧执行一次。2优化循环逻辑避免在 update 中遍历大量数组如场景中所有敌人若需遍历优先用 for 循环比 forEach 性能高且缓存数组长度// 坏forEach 性能低且每帧获取数组长度update(dt){this.enemyList.forEach(enemy{/* ... */});}// 好for 循环 缓存长度update(dt){constlenthis.enemyList.length;// 缓存长度for(leti0;ilen;i){constenemythis.enemyList[i];/* ... */}}动态数组如 cc.NodePool 管理的对象优先用 “对象池” 复用避免每帧 push/pop 操作数组扩容 / 缩容会触发 GC。2. 降低 GC 频率垃圾回收会暂停主线程导致卡顿1避免频繁创建临时对象临时变量如 cc.Vec2、cc.Color缓存到成员变量而非每帧创建。// 坏每帧创建新 Vec2触发 GCupdate(dt){constdirnewcc.Vec2(this.x,this.y);// 临时对象}// 好缓存到成员变量复用onLoad(){this.tempDirnewcc.Vec2();// 初始化时创建}update(dt){this.tempDir.set(this.x,this.y);// 复用对象不触发 GC}字符串拼接用 cc.js.format 替代 会创建临时字符串cc.js.format 更高效// 坏字符串拼接创建临时对象consttext分数this.score;// 好复用格式化方法consttextcc.js.format(分数%d,this.score);2合理使用对象池NodePool适用场景频繁创建 / 销毁的物体如子弹、敌人、粒子效果用 NodePool 复用对象避免每帧 cc.instantiate创建对象和 node.destroy销毁对象3. 优化定时器与事件1避免滥用 setTimeout/setInterval优先用 Cocos 内置的 this.schedule支持暂停 / 恢复且与节点生命周期绑定节点销毁时自动取消// 好使用 schedule节点销毁时自动停止onLoad(){// 每 0.5 秒执行一次执行 10 次后停止this.schedule((){/* ... */},0.5,9);}// 坏setInterval 需手动清除否则节点销毁后仍执行导致内存泄漏onLoad(){this.timersetInterval((){/* ... */},500);}onDestroy(){clearInterval(this.timer);// 需手动清除容易遗漏}2事件监听及时取消node.on 绑定的事件在 onDestroy 中用 node.off 取消避免内存泄漏如监听全局事件 cc.systemEvent.on节点销毁后仍触发回调。三、资源优化减少包体大小降低加载耗时资源纹理、模型 、音频、动画是包体和内存的主要占用者优化目标是 “小而精”按需加载、压缩体积、复用资源。1. 纹理资源优化占比最高优先处理1 控制纹理分辨率UI 纹理手机端 UI 图标分辨率 不超过 256x256背景图不超过 1024x1024根据屏幕分辨率适配避免过度放大导致模糊。场景纹理地面、建筑纹理分辨率不超过 2048x2048且优先用 “重复纹理”如地砖纹理设置 Wrap Mode 为 Repeat1 张图覆盖大面积。2) 使用 mipmap 优化远处纹理场景中远处可见的纹理如地形在属性检查器勾选 生成 MipmapGPU 会自动加载低分辨率 mip 层降低远处渲染压力近处仍用高分辨率不影响画质。2. 模型与动画优化(1) 简化模型面数移动端模型面数主角模型不超过 5000 面敌人 / 道具不超过 1000 面用 Blender、Maya 等工具减面保留关键结构。(2) 动画资源优化用 Spine 替代帧动画帧动画需大量纹理帧体积大、内存占用高Spine 动画仅存储骨骼数据体积小、播放流畅适合角色动画。3. 按需加载与资源释放(1) 场景分块加载大场景如开放世界按 “区域” 分割为多个子场景进入区域时用 cc.director.loadSceneAdditive 加载子场景离开时用 cc.director.unloadScene 卸载避免一次性加载所有资源。(2) 资源释放时机场景切换时在 onDestroy 中释放当前场景的资源如 cc.resources.releaseAll() 释放所有已加载资源或按路径释放指定资源。动态资源如临时加载的道具纹理使用后立即调用 cc.resources.release 释放避免内存堆积。