众所周知,游戏中的背包是drawcall的灾难地区,主要原因是背包物品数值和不同状态的图片打断了合批渲染。已知cocos creator的最新版能让不同颜色和不同Type的Sprite也合批渲染。对于我们项目已经不好再升级引擎,BMPfont的使用也感觉不是很好。我们游戏的背包一打开就是200以上的drawcall,这么高的drawcall在微信小游戏上虽然不会导致卡顿,但是长时间打开背包游戏发烫会比较严重,一时也不知道如何优化。
某次参加cocos creator的沙龙,panda老师提了一个解决方案:将不同渲染的阶段的节点放在不同的content上,也就是说一个物品的详细信息节点比如背景和和物品数量就分别放在2个content上,这2个content随着ScrollView的滑动同步滑动。
这种方案实现起来比较复杂。
最近看到cocos creator公众号的一篇文章:SLG《乱世王者》深度优化方案里面提到了ui的batch渲染,就是将同层级的ui放在一起渲染,详情见文章。
具体到cocos creator如何实现:
主要是修改引擎的render-flow.js
1、增加新的渲染阶段
2、实现_childrenBatchRender
let childrens = [];
let cqueue = [];
_proto._childrenBatchRender = function(node) {
// 排序逻辑
node._renderFlag &= ~CHILDREN;
node._renderFlag |= CHILDREN_BATCH_RENDER;
let cullingMask = _cullingMask;
let parentOpacity = _walker.parentOpacity;
_walker.parentOpacity *= (node._opacity / 255);
let worldTransformFlag = _walker.worldMatDirty ? WORLD_TRANSFORM : 0;
let worldOpacityFlag = _walker.parentOpacityDirty ? COLOR : 0;
let config = node.config;
childrens.length = 0;
cqueue.length = 0;
Array.prototype.push.apply(cqueue, node._children);
let ch = null;
while(cqueue.length > 0) {
for(let i = 0, len = cqueue.length; i< len; i++) {
ch = cqueue.shift();
if(ch.active) {
childrens.push(ch);
Array.prototype.push.apply(cqueue, ch._children);
}
}
}
childrens.sort((ch1, ch2) => {
return config[ch1._name] - config[ch2._name];
});
let children = childrens;
// 下面是_children里面拷贝过来的,除了标识的代码块要加外,尽量不要改。
for (let i = 0, l = children.length; i < l; i++) {
let c = children[i];
//>>>>>下面这个一定要有
if(c.childrenCount > 0) {
c._renderFlag &= ~CHILDREN;
}
//<<<<<
if (!c._activeInHierarchy) continue;
_cullingMask = c._cullingMask = c.groupIndex === 0 ? cullingMask : 1 << c.groupIndex;
c._renderFlag |= worldTransformFlag | worldOpacityFlag;
// TODO: Maybe has better way to implement cascade opacity
c._color.a = c._opacity * _walker.parentOpacity;
flows[c._renderFlag]._func(c);
//>>>>>下面这个一定要有
if(c.childrenCount > 0) {
c._renderFlag |= CHILDREN;
}
//<<<<<
c._color.a = 255;
}
_walker.parentOpacity = parentOpacity;
this._next._func(node);
_cullingMask = cullingMask;
}
3、使用方法
创建config配置每个节点对应的层级,这个config是我们自己项目背包物品item的节点层级,key就是节点名称,value就是对应的节点层级。
let config = {
everydaySignItem : 0,
iconItem : 1,
item_bg : 2,
item_check : 3,
equip_base_0 : 4,
icon : 5,
iconColor : 6,
special_effect : 7,
image01_1 : 8,
num_bg : 12,
lable_num : 13,
starLayout : 9,
X5 : 10,
X4 : 10,
X3 : 10,
X2 : 10,
X1 : 10,
levellayout : 9,
_level$0 : 10,
bangding_icon : 10,
vipNode : 11,
EverydaySinIconMask : 20,
_sinedMask : 21,
k2 : 23,
k1 : 23,
_bq : 22,
bqLbl : 23,
_sinDay$0 : 25
}
设置渲染状态标识
let content = this._listView.$ScrollView.content;
content._renderFlag |= cc.RenderFlow.FLAG_CHILDREN_BATCH_RENDER;
content.config = config;
这样就能将同层级的Sprite放在一起进行渲染。
自动计算节点的层级代码
function deapSearchConfig(node, h, config) {
config[node.name] = h;
if(node.childrenCount > 0){
for(let i = 0; i < node.childrenCount; i++) {
deapSearchConfig(node.children[i], h + 1, config);
}
}
}
假设有个item是要显示在ScrollView里面的,获取到这个item的预制体prefab,按照如下方式使用可获得config的内容,避免手动填写config。当然这种方式生成的config并不一定是最优的,可以自己手动再进行调整。
let config = {};
let h = 0;
let itemNode = itemPrefab.data; // 预制体的data属性里面包含了节点的详细信息
deapSearchConfig(itemNode, h, config);
cc.log(config);
注意:
- 如果ui结构复杂,请大家谨慎使用。最好用WebGL Inspectort调试看看
- 一定要用
gulp build-html5
重新生成下引擎 - 节点中不要有富文本,富文本的节点会生成一个name为null的节点,导致节点排序不是理想情况。
- 我这里使用的是2.0.5版本的引擎源码,如果是其他版本的引擎最好仿照自己当前引擎
render-flow.js
的_children
方法去改写_childrenBatchRender
。 - 如果使用的是2.0.10版本的引擎,
_childrenBatchRender
里面一定要有EventManager._updateRenderOrder(c, ++_renderQueueIndex);
,不然按钮事件可能会捕获不到。 - 节点内部不能有相同名称的节点。因为排序的时候是根据名称获取节点对应的层级,如果有相同名称的节点,可能会出现层级对应不上的问题。
bug修复
- 修复因为将子节点的CHILDREN渲染阶段清理,导致复用节点的时候渲染不到