ScrollView循环列表+分帧加载+分层渲染实现

前言:scrollview是一个很常用的组件,如排行榜,背包,关卡选择页面等等。如果里面的item简单数量少还好,一旦复杂而且数量多,那么drawcall就会非常高。然后就搞了这个循环+分帧+分层滑动面板


原理

循环列表:只创建视图范围内行数+1的数量item,当item滑动到超出视图顶部时,将item移动到底部。反之,当item滑动到超出视图底部时,将item移动到顶部。循环利用超出视图的item,能极大优化item数量很多而引发的性能问题。

分帧加载:当item比较复杂时,一次性创建N个item可能会造成短暂的卡顿,在创建item前获取当前时间,创建item后获取当前时间,创建后减去创建前所获取的时间,就是创建item所需时间,如果所需时间大于某个值,就先不要创建,弄个计时器,延迟一会再创建,延迟期间先执行其他游戏逻辑

分层渲染:因为合批是按照深度遍历合批的,导致虽然item样式一样,但却没法合批。创建item时,把所有子节点分离出来与item同一父节点,然后通过设置zIndex改变层级从而合批**(注:因为label如果用系统字,并且CacheMode为None时,是无法合批的,所以如果检测是默认字体并且Cache Mode为None时,会将CacheMode改成BitMap或者Char才能合批,如果item里的label只有数字考虑用char,如果带各种中文,用BitMap)**。


优缺点

优点:大幅度降低drawcall,加上只实例化视图范围内+1的行数,对于item数量极大的来说,能有效降低开销。
缺点:因为和item是同一父节点,item子节点的active属性用来同步父节点的active属性了,所以不能使用active属性来修改item子节点的显隐,用了会出问题。如果要改item子节点的显隐,请用opacity。



如上图,左下角信息drawcall占1个,空的滑动面板占4个(不知道为啥一个空mask占用4个drawcall,据说是统计策略问题),item背景1个,label1个,共7drawcall。
数据200个,实际上item只实例化35个,无论数据多少个,item始终只实例化视图窗口数量+1行的数量。

注:目前的组件只是简单的功能实现,常规的竖向布局。如果item有非常复杂的功能,例如比例大小变化之类的,横向的,目前这个组件都不支持。

附demoScrollView.zip (205.0 KB)
引擎版本:2.2.2

14赞

谢谢楼主的分享。然后说下我的看法,说的不好的地方见谅。
分帧跟循环都不错。
分层这块,如果只是改变zindex,那大可创建的时候就放在一起,而不必用代码去控制。分层管理的麻烦处在于item还要交互,各个节点都是随时可能会更改,显隐的,全用item当父节点,管理麻烦。
然后就是字体缓存模式了,实际的背包系统中,我们要展示的不光数量,还有各种道具的名字,这文字之多,用BITMAP,内存会很吓人。如果单单的只是图片跟数字,所有图片跟数字打个图集1个,左下角信息1个,总共2个就够了。

之前论坛也有过这类方式优化drawcall的讨论
看下来感觉和你描述的一样

总的来说因为代码的原因,gui上的界面的描述与实际情况已经不符了。

有些时候看起来的笨点的实现可能要更好点,实现效果相同,代码可读性也更高

其实分帧+循环就够用的。
显隐可以用opacity控制啊。本来想同步active属性,但引擎发出警告不让重写。
如果交互不复杂就加个分层,如果有点击放大缩小那种就不好同步了。代码里注释掉this._separateRender的调用就可以把分层逻辑去掉了,只剩下分帧+循环逻辑,可以加标记灵活控制。。
bitmap如果使用名字过多的话确实不好。。用ttf或者打字图集,也是可以分层合批的
我这边项目的滑动面板没有太复杂,用分层dc超低。原本普通200+dc,循环后降到60,分层后降到15,实际item只用了3dc。
另外不是很理解你说的创建时候放在一起。:joy:

是的,论坛上有很多优化drawcall的讨论。其实我也是综合了他们的技术点,普遍有分帧+循环逻辑,然后我就试试加上分层,现在发现分层后子节点状态同步是个坑。。

因为active要改变的属性很多,比如pos,而opacity不会去打断他的渲染树。

我只是想照抄active的代码,然后在后面补抛一个事件而已:joy:,像位置大小那些都有事件抛出,active和opacity却没有。

未分层渲染

分层渲染

drawcall降低还是比较明显的…70多降到20多…而且有几百个Item也是20多

其实还有一套终极的理论方案,cocos的label支持char模式,看了引擎代码,char模式其实就相当于可以生成动态的bmfont功能,我的理论想法就是将整个UI贴图以及label的贴图合在一张图上,用char模式去动态生成之后的文字(这里要考虑很多问题)。让文本和UI的贴图全在一张合图上面。能达到一个界面一个drawcall。

分层的节点依然可以统一管理,简单点来说你可以创建一个管理控件然后加入索引就可以了,然后再代码里面将节点渲染分层,逻辑部分直接用管理控件就可以了,很简单的逻辑,而且分层对于性能的提升要优于循环,至于分帧根本没必要,加入tableview的逻辑之后分帧太鸡肋了

基本用上scrollview的场景都要在item上挂逻辑,你这方式直接抛弃item上可能存在的逻辑处理了

1赞

mask!!!

有3.X的版本吗?

3.x版本已经有人搞了啊,插件商店有,叫啥98k合批的,可以去看看

这个是旧版本的了,现在有新的实现方式,请前往

大佬 请问ttf是怎么合批的呢 我里连续的ttf也没有合批

看Label组件,有个Cache Mode,None是无法合批的,其他的看文档上的Label组件,有介绍用途

嗯嗯 感谢

这边动态的新增是如何设置呢?分页加载这样