一:前言
前段时间在论坛看文档,偶然看到有个列表优化相关的文档 【muzzik教程】50行代码,教你优化列表draw call!【网格、横、纵 全包揽】,感觉实现思路不错,而且还很简单,虽然我们项目的列表优化已经用了【节点复用】+【分层渲染】的方式去优化,但是秉承着能优则优的原则,还是决定去实现一下。
二:实现原理解析
1:监听列表的滚动,监听列表子节点的变化
2:进行碰撞检测,对未与容器节点发生碰撞的节点,透明度设置为0,碰撞则设置为1
三:扩展
1:因为原有的实现使用的是子节点,没有使用包围盒,可能会存在当创建子节点使用不规范的时候(如子节点的大小为0,但是子子节点却有大小,导致的计算错误的问题),所以增加一种包围盒模式
四:优化
1:原有的列表优化实现虽然简单,但是还是有几处思路是可以继续优化的,下面就让我们来探讨这几处可以优化的点
(1):将主矩阵的包围盒计算放到整个循环之前,将计算结果当参数传递进去,这样可以有效避免循环体内部的计算量,避免重复无意义工作
(2)至于原文下面这段话,为了防止滑动过快导致渲染跟不上,从而导致提前激活的实现其实也是可以进行优化的。
优化思路大致是:增加对列表【滑动开始/滑动结束】的监听,从而判断列表是否处于滑动状态,如果处于滑动状态,则扩充检测范围,如果不处于滑动状态,则不扩充检测范围
五:附带开箱即用源码
/***
* 列表的drawcall减少方案
* 1:对滑动进行监听
* 2:对超出可视区域的节点透明度设置为0
*/
export default class ScrollOpacityList
{
//滚动列表的持有对象
private m_stScrollView: cc.ScrollView;
//是否使用安全模式
private m_bSafe: boolean;
//列表是否处于滚动状态
private m_bIsScrolling: boolean;
//检测的子节点的容器节点(当前节点必须在scrollview所在节点下,child所在节点上,默认为scrollview的content节点)
private m_stChildParent: cc.Node;
//当前列表的检测模式
private m_stType: eScrollOpacityType;
/**
* 初始化节点
* @param scrollView 滚动列表组件
* @param childParent 如果不选择滚动列表的content,则可自定义滚动列表内的某一节点为content
* @param eType 子节点模式
*/
public Init(scrollView: cc.ScrollView,childParent?: cc.Node,eType?: eScrollOpacityType)
{
this.m_stType = eType ? eType : eScrollOpacityType.ChildSize;
this.m_stChildParent = childParent;
this.m_stScrollView = scrollView;
this.UpdateOpacity();
scrollView.node.on("scrolling",this.OnScrolling,this);
scrollView.node.on("scroll-began",this.OnScrollBegan,this);
scrollView.node.on("scroll-ended",this.OnScrollEnded,this);
scrollView.content.on(cc.Node.EventType.CHILD_REMOVED,this.OnChildRemove,this);
scrollView.content.on(cc.Node.EventType.CHILD_REORDER,this.OnChildReOrder,this);
}
private OnScrolling(): void
{
this.m_bSafe = true;
this.m_bIsScrolling = true;
this.UpdateOpacity();
}
private OnScrollBegan(): void
{
this.m_bSafe = true;
this.m_bIsScrolling = true;
this.UpdateOpacity();
}
private OnScrollEnded(): void
{
this.m_bSafe = false;
this.m_bIsScrolling = false;
this.UpdateOpacity();
}
private OnChildRemove(): void
{
this.m_bSafe = false;
this.UpdateOpacity();
}
private OnChildReOrder(): void
{
this.m_bSafe = false;
this.UpdateOpacity();
}
//滑动的时候非精准隐藏
//非滑动状态下精准隐藏
/* ***************自定义事件*************** */
private UpdateOpacity(): void
{
if(!this.m_stScrollView)
{
return;
}
let rect1_o = this.GetBoundBoxToWorld(this.m_stScrollView.content.parent,false);
//滚动的时候将范围放大有利于展示所有有利信息
if(this.m_bIsScrolling || this.m_bSafe)
{
rect1_o.width += rect1_o.width * 0.5;
rect1_o.height += rect1_o.height * 0.5;
rect1_o.x -= rect1_o.width * 0.25;
rect1_o.y -= rect1_o.height * 0.25;
}
let parent: cc.Node = this.m_stChildParent ? this.m_stChildParent : this.m_stScrollView.content;
parent.children.forEach(v1_o =>
{
v1_o.opacity = this.CheckCollision(rect1_o,v1_o) ? 255 : 0;
});
}
/* ***************功能函数*************** */
/**获取在世界坐标系下的节点包围盒(不包含自身激活的子节点范围) */
private GetBoundBoxToWorld(node_o_: any,bScrollList: boolean): cc.Rect
{
let w_n: number = node_o_._contentSize.width;
let h_n: number = node_o_._contentSize.height;
let rect_o: cc.Rect;
//为什么scrollList固定0.5,创建的时候是以中心点为起始点,大小为原始大小,导致会往左上偏移
if(bScrollList)
{
rect_o = cc.rect(
-0.5 * w_n,
-0.5 * h_n,
w_n,
h_n
);
}
else
{
rect_o = cc.rect(
-node_o_._anchorPoint.x * w_n,
-node_o_._anchorPoint.y * h_n,
w_n,
h_n
);
}
node_o_._calculWorldMatrix();
rect_o.transformMat4(rect_o,node_o_._worldMatrix);
return rect_o;
}
/**获取在世界坐标系下的节点包围盒(包含自身激活的子节点范围) */
private GetBoundBoxToWorld2(node_o_: cc.Node): cc.Rect
{
return node_o_.getBoundingBoxToWorld();
}
/**检测碰撞 */
private CheckCollision(rect1_o: cc.Rect,node_o_: cc.Node): boolean
{
let rect2_o: cc.Rect;
switch(this.m_stType)
{
case eScrollOpacityType.BoundBox:
rect2_o = this.GetBoundBoxToWorld2(node_o_);
break;
case eScrollOpacityType.ChildSize:
rect2_o = this.GetBoundBoxToWorld(node_o_,false)
break;
case eScrollOpacityType.ScrollList:
rect2_o = this.GetBoundBoxToWorld(node_o_,true)
break;
}
return rect1_o.intersects(rect2_o);
}
public Destory(): void
{
let scrollView = this.m_stScrollView;
if(scrollView)
{
scrollView.node.on("scrolling",this.OnScrolling,this);
scrollView.node.on("scroll-began",this.OnScrollBegan,this);
scrollView.node.on("scroll-ended",this.OnScrollEnded,this);
scrollView.content.on(cc.Node.EventType.CHILD_REMOVED,this.OnChildRemove,this);
scrollView.content.on(cc.Node.EventType.CHILD_REORDER,this.OnChildReOrder,this);
}
}
}
export enum eScrollOpacityType
{
ScrollList = 1,//该列表是虚拟循环列表(项目设计的时候有一点问题,大家可以忽略)
ChildSize,//正常子节点
BoundBox//当列表的子节点异常,只能计算包围盒
}
至此,整个列表性能已经得到明显提升主要是在以下几个方面
1:列表中循环体内的计算量明显减少
2:列表真正只做到渲染可视区域内的节点
优化效果:
相对原有方案,理论上:
drawcall可以再优化100%
加载图片数量可再优化100%
计算消耗相对原来减少50%
附录:
1.这个优化思路只是讨论在原文档列表优化思路上的进一步优化,并不掺杂和讨论其它优化思路
2.本身代码设计的并不完美,思路可能还有进一步优化的点,欢迎大家留言讨论,如果觉得不错的话就点点赞吧,谢谢各位了,阿里嘎多
最后,欢迎大家关注【数字媒体与游戏开发】公众号,在这里我们可以一起讨论有意思的优化技巧,相互学习,也感谢@muzzik大佬的优化思路