NGUI 层级问题总结

发表于2016-06-27
评论0 1.77w浏览

        层级问题一直以来都是令人头疼的问题。尤其是项目后期复杂度增加以后,更多的开发人员参与,使得它维护起来相当困难。这里做一个简单的总结来给其它同学做个参考。

层级系统的理解

      要解决层级问题,首先要对层级系统有一个较深入的理解,各个参数是怎么影响层级的。由于项目使用的是Unity4.6 NGUI3.5.6,所以这里重点说一下NGUI里面的各种参数是怎样影响层级的。NGUI里面可以调控层级的各个参数:

l  CameraDepth


       CameraDepths是指不同相机之前的深度属性,在渲染顺序的优先度里面是最高的,Depth越大,渲染的图像越靠前,和空间无关。这种方法常常用来解决3D物体和UI混合显示的时候的层级问题。举个例子:



(图片来自网络)

       图中3个角色是3D模型,使用3D相机,前面的UI和后面的背景是2D的,我们只需使用3Camera,并且分别设置它们的深度即可实现。

l  Panelrenderqueue以及Depth



       这一点需要特别注意,很多坑都来自这个地方。在讲Panel层级的原理之前,我们需要先了解一下Drawcall的概念,Drawcall的意思是CPU对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西。在NGUI里面Drawcall变得更加具体,它指是UIDrawCall类的一个实例,每个实例中包含着一次对图形接口的调用。在一个Panel中至少有一个Drawcall而且可能包含多个Drawcall。网上有很多如何减少Drawcall提升效率的文章,在这里就不赘述了,我们重点说层级问题。


 

                                                        图为一个Drawcall的内容

       每个Drawcall都有它的Renderqueue也就是渲染队列中的值。Renderqueue越大说明在渲染队列中越靠后,也就是越晚渲染,大家在画图的时候都明白,后面画的总是会遮住前面画的。



       知道原理以后回过头来看Panel中的参数:这两个参数并不是独立无关的,而是相互影响的,首先看RenderQ选项,里面有三个选项:  

        Automatic代表由NGUI自动来设置Panel上面每个DrawcallRenderqueue

当这个选项激活时,NGUI会根据PanelDepth进行排序,然后根据排序结果给每个panel相应的startingRenderQueue也就是下面的Start At的值。代码如下:



       注意看标记的地方,通俗的讲,如果AB两个panelA panel显示在前,那么A panelStart At值就是B panel Start At值加上B panelDrawcall数量。

      Start At代表指定一个起点,比如上面图中的3400,其它的Drawcall由起点往上累加;



        如图,某个panel包含两个Drawcall,这两个DrawcallRenderQ是由WigetDepth决定的。图中第二个Drawcall所画的WigetDepth必定比第一个Drawcall所画WigetDepth要大。

Explicit代表这个Panel上面所有的Drawcall都指定为某个值。



       可以看到,当我们不选择Automatic时,PanelDepth对于Panel的渲染层级已经没有任何影响了,但是别忘了,除了渲染层级还有交互层级,具体有什么影响,我们会在下面讲到。

 

l  WigetDepth



         首先如果一个Panel中包含多个Drawcall,如上所述,这些DrawcallRenderqueue是由其渲染的WigetDepth决定的。其次,WigetDepth只会影响同一个Drawcall中的Wiget层次(不同Drawcall还是由DrawcallRenderqueue决定),之前我一直在想一个问题,既然在同一个Drawcall中,说明只渲染了一次啊,那么其中的WigetDepth是怎么生效进而影响层级的呢?仔细看了看源代码,发现了其中的“黑科技”:原来WigetDepth是通过影响Drawcall中的顶点缓存序列来达到影响层级的效果的。这里我要啰嗦一下,NGUI的一个Drawcall中包含一个Mesh, 它是由Drawcall内所有Wiget的顶点信息生成的,顶点信息存储的顺序是由Depth决定的,越靠后的层级就越高。(注:所以游戏中改变WigetDepth可能会导致Mesh重新生成相当影响效率)

         上面说到交互层级,什么是交互层级呢,就是如果有两个按钮叠到一起,按下去,哪一个会生效呢?这时就要交互层级来决定了,交互层级是由PanelDepthWigetDepth共同决定的,如下:(注意:这里还有个条件是UICamer中的EventType设置为UI选项)



       之前说过当我们不选择Automatic时,PanelDepth对于Panel的渲染层级已经没有任何影响了,也就是说渲染层级可能会与交互层级不一致,这就是一个坑了,需要多加注意。

         到这里我们把影响层级的因素都捋了一遍,有人会说z轴没有影响吗,答案是对UI来说z轴的确没有影响,因为UI所用的shader是不写入深度缓存的。说完了影响层级的一些因素以及它们的原理,下面会说一下常见的情景以及对应的解决方法。

层级的设计

       理解层级系统后,我们就可以尝试建立一个易于维护的UI层级管理方案,这样在后面的开发中可以大 大减少层级方面的bug,减少新增UI的测试工作量。由于项目的不同,UI的表现也会有着很大的差异,所以这里只做一个大概的设计。

      一般情况下,每一个UI面板包含一个或者多个Panel,每个Panel里有数个Wiget。对于Wiget来说只要考虑depth就好了,需要注意的地方是如果AB在一个图集,C在一个图集,而这时Cdepth夹在AB之间,那么就会产生一个额外的Drawcall,这种情况要尽量避免。

       对于panel来说既要看Renderqueue又要考虑Depth,先看看Renderqueue的处理对于PanelrenderQ选项的选择,由于一个panel中大部分情况下都会有多个Drawcall,所以第三个选项Explicit 明显是不合适的。Automatic看起来很方便,但是它是按照Panel中记录的Drawcall来累加Renderqueue的,实际项目中,panel中可能有一些元素(比如特效)有不同的Renderqueue,而这些元素的Drawcall又不会被Panel记录,有可能出现特效穿透
的情况:



       这种情况下,就只能手动去指定Start At了。如果选择手动指定Start At, 我们可以根据depth与要指定的Renderqueue的值做一个对应关系,这样我们只需要关心depth就可以了,降低出错几率。比如我们设定depth0的时候使用的Renderqueue3000depth每增加1 Renderqueue就增加10(这里要依据实际情况来决定数值,如果为10说明项目中一个panel上不会超过10Drawcall,当然如果你的一个panel上就有几十个Drawcall,请去面壁)。




        PanelDepth可以用一个枚举类型来做表示,这样做的好处是在后期的开发中可以清晰的看到所有面板所占用的深度,并行开发也不会有深度上的冲突,而且可以很快找到所有使用同一个Depth的面板。

特效的层级

         关于特效的层级网上已经有很多文章了,网上比较流行的一个做法是使用脚本将特效中材质的Renderqueue设置为特效下面WigetRenderqueue加一个offset。这个方法一般情况下可行。但是会引发两个问题,一是使用这种方法以后,Panel里的其它Wiget无法根据Depth值来遮挡这个特效了;二是上面说过的,Panel无法记录这个特效的Renderqueue,导致Automatic模式下特效的穿透。

         这里我推荐一个做法,具体的思路就是增加一个UITexture将它的材质设置成一个特殊的材质,这样它就会在Panel中形成一个Drawcall,然后将特效的Renderqueue设置成这个DrawcallRenderqueue即可。这样就可以用UITextureDepth控制特效的层级。熟悉NGUI的同学也可以根据这个思路直接写一个类继承与UIWiget来控制特效层级。

        以上就是对UI层级问题的一个总结,可能不够全面,希望可以抛砖引玉。个人认为UI的层级一开始一定要设计好,否则越到项目后期越难维护。 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引