NGUI开发优化技巧(下)
NGUI更新开销:NGUI进行自身的数据更新以及生成网格的过程中所产生的开销,目前UI部分的CPU耗时大部分都是这部分的开销;
NGUI的更新开销几乎全部来源于UIPanel.LateUpdate中;一般超过3~4ms,说明这部分更新开销较高;
UI更新的数量和频率通过合理的布局,可以把这部分的开销限制在很多小的范围里面;
在关卡的切换或点击一些UI界面时,可以出现一些较高的峰值;
但在战斗部分要保证UIPanel.LateUpdate在很小的范围内波动,一般在2~3ms;
UIPanel.LateUpdate在进行完全的重建时,才会出现较高的堆内存开销;
NGUI更新的对渲染的影响:
NGUI的更新在渲染时的耗时出现在MeshRenderer.Render,Mesh.DrawVBO和Mesh.CreateVBO有一部分是由NGUI造成的,NGUI中UIPanel的刷新和UI元素的变动导致网格发生了变化或更新,都会使Mesh.Create产生开销;
UIPanel.LateUpdate的总堆内存分配在10~20M之间,是合理的;(10000~20000帧)
UIPanel的堆内存是有UIPanel的刷新引起的,堆内存越高说明UIPanel的刷新频率越高,所涉及的UI元素的数量可能越大;
UILabel的更新开销
1:Font.CacheFontForText
2:Shadow
3:Outline
在手机上,纹理的大小会有所限制,纹理的内容会不停地变动,纹理不停地刷新,会导致
Font.CacheFontForText不停地被触发;
出现文本比较大,首次出现,并且用的是动态字体,就会产生比较高的Font.CacheFontForText函数开销,对于频繁地、反复使用的文本,尽可能地做成静态字体;
开启和不开启Shadow的区别;
开启和不开启Outline8的区别;(比Shadow的性能开销还大)
对于频繁移动的文本,开启Effect效果,对性能开销比较大;
对于动态的文本(会移动或会变颜色),尽可能少用这些特效,对于静态的不影响;(不会每一帧去更新网格)
UISprite的更新开销
(Size越大,平铺地越多,三角面片和网格数越多,造成开销越大)
使用和不使用Tiled模式的区别;
UISprite或文本的UI元素本身在移动或变颜色时,才会真正产生开销;静止时则不会更新,
不产生开销;
UIPanel更新机制
1:更新单个UIDrawCall;
2:更新所有的UIDrawCall;
UIDrawCall:一个UIPanel中可能会有各种各样的UI元素,这些UI元素可能来自于不同的图集,对于来自于不同图集的元素最终渲染时不能合在同一个DrawCall里面,所以一个UIPanel里面会包含很多个UIDrawCall,这些UIDrawCall首先按照UI元素的深度(Depth)进行排序,
然后再根据相邻元素是否来自于同一个图集来进行合并,这里的每一个UIDrawCall都包含了若干个UI元素,这些UI元素来自于同一个图集,且深度值在排序里面是相邻的;
进行Panel更新时,有两种模式:
1:更新单个UIDrawCall:在更新过程中,某个UI元素进行了移动,只会更新自己所在的UI元素里面的网格,不会影响其他的DrawCall;
2:更新所有UIDrawCall:一旦满足某种条件,会直接重建UIPanel里面所有的DrawCall;
并不是把DrawCall降低地越低越好,当一个顶点发生变化时,因为这个顶点和其他的顶点在同一个Mesh上面,所以一个顶点的更新会导致所有Mesh的更新;
如果添加的界面并不是和变动的UI元素在同一个DrawCall里面,那么在更新元素时,并不会产生影响;
总结
对于频繁变动的UI元素,其所在的UIDrawCall面片数越少越好;
更新所有的UIDrawCall:
主要原因:UIPanel中的UIDrawCall发生了变化;(在UIPanel里面添加了DrawCall或是把UIPanel里面原有的DrawCall拆分成多个(动态地添加或删除一些UI元素))
在这一帧内,有很多网格进行了重建,会影响到Mesh.CreateVBO;
动态添加窗口,穿插了NGUI Panel,导致UIPanel的完全重建;
将其中一个UIDrawCall分离到其他的UIPanel中后
不同UIPanel下的UI元素是不能进行合并的;
调整深度,不要打断原有的DrawCall;
动态的UI元素中少放东西,或直接把它独立成一个UIPanel;
更新所有UIDrawCall的条件
1:添加/删除元素时,穿插了其他的UIDrawCall;
2:添加/删除的元素自成一个UIDrawCall;
总结:
1:对于频繁变动的UI元素,其所在的UIDrawCall面片数越少越好;
2:动态添加UI元素时,注意Depth的设置,尽可能合入已有的UIDrawCall;
(在添加UI元素时,这个Depth是包含在现有的DrawCall里面的,并且这个DrawCall的材质和所添加的元素的材质是一致的,这样就不会产生完全的重建)
3:对于需要动态添加UI元素的UIPanel,减少其复杂度,从而避免在更新所有
UIDrawCall时开销过大;(战斗过程中出现的连击提示,应尽可能地减小这种界面的复杂度,
不要把静态的东西(头像、置顶的怪物的血条)也放在UIPanel里面)
动态字体(字体在不停变换时):在手机上,文本所对应的字体、材质、纹理会不停地进行更新,每一次想往纹理里面添加新的文字,当发现纹理不够时,会触发刷新的操作,会根据当前创建纹理大小的限制(比如需要创建512*512,但根据某些机制发现只能创建256*256的),则会先把屏幕上**状态的文本填入纹理,对于出现过的,但是已经被禁用的文字就会全部被剔除掉,这个过程相当于完全更新了纹理里面的内容,这种操作也是耗时最大的操作;
动态字体优化的两种方式:
1:如果字体可以做成静态的,则优先做成静态的,就可以避免Font.CacheFontForText;
但也会出现一些问题(比如:在一些剧情比较复杂的游戏中,涉及的剧情非常多,每一段剧情需要的文本量非常大,如果做成静态的,可能带来10多张1024*2048这种纹理,这就不同通过转成静态字体这种方式来处理这种情况);
2:文本只出现一次,后面不会出现,可以让它预先出现,比如创建一个Label,里面有这个文字,并且是处于**状态的,只是相机看不到,可以把这些峰值移动到可以卡顿的地方,在真正播放剧情时,通过移动或者修改Layer让这些文字更流畅地逐渐显示出来;
进入读条时,先把这些文本放成**状态,让动态字体需要的纹理先生成好;
多个动态的物体,放在一个Panel好,还是放在多个Panel好;
通常进行分组比较好,可以少量地提高合并网格的计算量(优化量并不多,因为网格总量是一样的,只是分成几个分组而已),影响比较大的是:Camera.Render里面的CreateVBO;
做UI元素的分离时有一个好处:
一个屏幕上存在多个UI元素,只有少量的UI元素在移动,可以做动态Panel的划分,对于静态不动的网格不应该去更新;