【前言】
游戏性能优化优化通常都是针对CPU进行处理,目的就是为了加快加载速度,减少发热、闪退、耗电快等问题的出现。
【常用方法】
一、降低DrawCall
奖励DC就是奖励渲染批次,在ccc的渲染流中有一个_batch变量,它负责处理节点的渲染信息。它会检测当前节点和当前渲染流的材质以及相机提出掩码是否完全一致,如果一致则传入当前信息模型,如果不一致则传入新的信息模型。如:精灵和label间隔渲染,不同图集的精灵,都会增加DC。
1、静态合图
这是一个很常用的办法,就是将多个散图拼成一个大图。这样连续多个渲染节点都是使用的这个纹理且其它参数一致的情况下,就会合并渲染批次。同时还能带来的好处就是降低IO,本地则是读取本地文件,网上的话则是减少网络请求次数。
获取合图的途径可通过CCC的自动图集来产生,或者通过TexturePacker手动生成。
2、动态合图
这是CCC提供的一种机制,就是它会在内存中动态生成一张纹理,数量限制是5,大小是2048*2048。满足条件的散图就可以参与动态合图。
开启动态合图。
macro.CLEANUP_IMAGE_CACHE = false;
dynamicAtlasManager.enabled = true;
参与条件可通过cc.dynamicAtlasManager.maxFrameSize设置,默认是512。
参与动态合图会改变原纹理的uv坐标,所以自定义shader的中如果有涉及uv的部分可能会无效。
3、Label
使用Label通常会打断合批。因为默认label是根据输入内容动态生成的一个纹理去显示的。
一个方案是使用位图字体BMFont。它就相当于是静态合图。
如果使用系统字或者TTF字体,则可以使用CCC提供的Label的缓存模式,BITMAP模式是将整个Label生成一个散图,并尝试并入动态合图中。CHAR模式则是将每个字符生成散图,并入一个专属label的动态合图中。
4、列表优化
列表优化最常见的就是虚拟列表,这个就略过不提了。
但是当Item是由各种Sprite、Label穿插的时候,合批会被打断,导致DC是单个ItemDC * 显示的Item数量。
在论坛搜索看到了2篇文章,我觉得挺好的,他们的原理都是希望在content这个列表下遍历Item的时候可以按照指定的顺序去遍历,尽量不打断合批。
方案一:
https://forum.cocos.org/t/ui/80026
Item预制体对应一个config文件描述每个node的优先级。
在render-flow.js中添加一个渲染函数针对列表中的content。
先遍历得到content下的所有子节点,再按照config中设置的优先级进行排序。再执行渲染。
ps:个人觉得不好,每次content渲染都要进行一次排序,config这种硬编码容易出错,导致比较严重的问题。
方案二:
https://forum.cocos.org/t/topic/133526
在list节点上去遍历content的子节点,遍历每个Item的时候,每个node都有对应的层级,根据层级存入对应的数组,最后将二维数组按顺序合并为一维数组。
在render-flow中渲染的时候,让渲染流去遍历这个一维数组。这样就可以保证总DC = 单个Item的DC。
ps:我觉得这个方案比方案一的config要好。还是不错的。
5、图集数组
当一个页面使用多张图集的时候,你希望它们能够进行合批,就可以使用此方案。
自定义材质允许传入多张纹理,建议不超过8。以及参数texture_idx,根据下标在片段着色器中使用对应的纹理。
自定义Assembler和顶点格式,添加一个浮点数用于存储texture_idx。
自定义Sprite,更新assembler 和 传入texture_idx。
写了个简单Demo如下,
参考自:https://forum.cocos.org/t/topic/136349
https://forum.cocos.org/t/topic/95087
// CustomAssembler.ts
//@ts-ignore
let gfx = cc.gfx;
let vfmtCustom = new gfx.VertexFormat([
{ name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
{ name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 },
{ name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true },
{ name: "a_texture_idx", type: gfx.ATTR_TYPE_FLOAT32, num: 1 }
]);
@ccclass
export default class CustomAssembler extends GTSimpleSpriteAssembler2D {
verticesCount = 4;
indicesCount = 6;
uvOffset = 2;
colorOffset: 4;
idxOffset = 5;
floatsPerVert = 6;
// 此部分略去 都是参考自 MovingBGAssembler.ts
...
updateTextureIdx(textureIdx: number) {
let verts = this._renderData.vDatas[0];
let idx: number = this.idxOffset;
for(let i = 0; i < this.verticesCount; ++i) {
verts[idx + i * this.floatsPerVert] = textureIdx;
}
}
}
@ccclass
export default class CustomSprite extends cc.Sprite {
_resetAssembler() {
this.setVertsDirty();
let assembler = this._assembler = new CustomAssembler();
assembler.init(this);
// this.spriteFrame = cc.find("Canvas").getComponent(Main).spf;
assembler.updateTextureIdx(this.getTextureIdx());
//@ts-ignore
this._updateColor(); // may be no need
}
getTextureIdx() {
let texture = this.spriteFrame.getTexture();
let textures = cc.find("Canvas").getComponent(Main).atlas;
//@ts-ignore
this.getMaterial(0).updateHash(99999);
let textureidx = textures.indexOf(texture);
return textureidx;
}
}
6、共享节点
大致看了下,比如一个节点挂载了Sprite组件,显示了一张图片。
这个时候将该节点作为共享节点,生成很多子节点,这些子节点并不挂载Sprite组件。
修改渲染流,当渲染到子节点时,就使用共享节点的渲染数据,对其中的顶点坐标进行一个偏移,使其移动到目标位置。
这样在某种适合使用大量共享节点的场景下,就可以省掉很多的渲染组件和渲染流程。
内存优化
我认为内存的优化主要就是2个方向。
1.资源有合理的管理,需要的资源保留,不需要的资源释放。
2.尽量减少资源的体积,比如没透明像素的图片使用jpg格式,能九宫格的图片使用九宫格。采用一些压缩方式压缩资源的体积。
1.资源的管理
在理想的情况下,内存中应该只有需要的资源。所以对资源的加载和释放要有进行合理的控制。
在CCC中,加载的资源会再cc.assetManager.assets中缓存。每个资源都会有缓存计数。
如果是静态引用的资源,如场景、预制体引用的资源,引用计数会加1,并在销毁之后减1。这由引擎那边进行管理。
如果是通过Bundle动态加载的资源,则需程序员自己进行管理。最初加载进来,引用计数为0。如果节点销毁会进行引用计数检查,为0,引擎会帮你释放掉。但如果你从节点树上移除了,不进行销毁。它就会一直在内存中。比较合理的方案是动态加载的资源,自己手动进行引用计数的管理,当为0的时候,调用assetManger的相关方法进行释放。
2.资源的压缩
优先使用jpg,和九宫格。
采用压缩纹理,安卓端使用ETC,IOS端使用PVR。
这一块还是不甚了解,这是看一些文章大致说的意思是压缩纹理读入内存就可以给GPU使用,且占用内存更小,是推荐使用的。
注:以上内容来自https://www.cnblogs.com/developer-llp/articles/17341519.html
帧动画优化
先使用ccc自带的动画编辑器进行编辑,然后根据anim文件里面的信息,用json文件记录,如果角色动作、方向、帧数,如:
{
"run": [5, 20, 0.033],
"idle": [5, 4, 0.1],
}
然后在代码中用AnimationClip.createWithSpriteFrames在需要时进行动态创建。该优化使用与anim动画节点为精灵动画。节点相对单一时也可根据json配置进行tween动画编辑。
去掉Plist文件
ccc中Plist文件原来会按图片帧打散,一个帧就一个json文件,里面描述了帧的位置偏移等信息。当plist文件太多时,引擎在加载解析plist文件,引擎要把所有的Json文件加载进来。性能消耗就高。
故而就需要去掉Plist文件,只留图片。但在删除plist前,需要将原先plist文件的信息提取到json中。同时在代码中,不在加载cc.SpriteAtlas,直接加载cc.Texture2D,当创建cc.AnimationClip
时,需要从配置中找到cc.SpriteFrame
的纹理信息,然后用:
new cc.SpriteFrame.create([Texture2D ] [rect ] [rotated ] [offset ] [originalSize ] )
动态创建帧,把这个帧传入动画剪裁。
以上内容来自:2D MMO中角色动画的优化总结