【转】UGUI VS NGUI

原文:http://gad.qq.com/college/articledetail/7191053

 

注[1]:该比较是基于15年-16年期间使用NGUI(3.8.0版本)与UGUI(4.6.9版本)所得

注[2]:仅对工作中经常接触到的功能做总结,如有疏漏,欢迎指正讨论

渊源

先来段小八卦,听说UGUI的主创人员是从NGUI招过去的,所以,UGUI中有很多概念,对于用过NGUI的童鞋来说,看起来都似曾相识。

先来个概念对比:

  NGUI UGUI
锚点 Anchor RectTransform Anchor
图片 Sprite Image
文字 Label Text
根节点 UIRoot Canvas
UI面板 Panel Canvas
UI容器 uiWidget Panel
事件交互 Collider EventSystem
单张贴图 Texture RawImage
UI相机 camera + UICamera camera + EventSystem

再看下UI主要的通用几个组件:

  NGUI UGUI
Button
Toggle
Scroll Bar
Progress Bar
Input Field
Popup List ×
Localization ×
Play Sound ×
Scroll View

总体来说,对于基本的UI元素, NGUI与UGUI都有;在组件的丰富度上,UGUI略逊于NGUI。例如,UGUI没有Localization、Play Sound组件;NGUI的Label是支持静态图文混排跟文字URL超链接的,而UGUI不支持。所以使用UGUI的时候需要自行开发以上缺失的组件及功能。

 

差异化

RectTransform

UGUI采用RectTransform的概念,将NGUI中的Transform+Widget+Anchor的功能,封装了起来。

  UGUI NGUI
Pivot RectTransform Widget
Width、Height RectTransform Widget
x、y、z RectTransform Transform
Anchor RectTransform Anchor
  UGUI VS NGUI UGUI VS NGUI

相对于NGUI的Anchor可以直接对某个GO进行锚点而言,UGUI新的锚点设计显然只能针对父节点来进行,是做不到跨层级锚点的,丧失了灵活性;但是也解决了NGUI多重锚点之间,由于时序问题,控件“锚飞了”的情况。

比较难理解的,应该是Anchor按比例锚点做相对布局的情况,普通的使用方法,可以参考Unity的官方文档[1],这里不展开。

要使用脚本来修改UI控件,情况就会变得比较复杂,需要理解绝对布局(anchor四个角都在一起)跟相对布局(anchor四个角分开,会按父控件的百分比来定义该控件)的情况。同时,也要理解Transform与anchoredPosition的关系。

曾经碰到过一个问题,UI偶现消失。后来发现是因为开始创建的时候没有对Localposition做重置,UGUI会通过anchoredPosition来重置Transform的x跟y,但没有重置z值,所以z值是乱的,所以当Z值大于UICamera的Clip范围的时候,就会被裁剪掉,消失了。

 

层级管理概念

UGUI采用Hierarchy排序的方式,替代了NGUI中的Depth排序。

更精准的说,NGUI的排序是通过Depth、Z值、RenderQueue共同影响的,整体规则过于复杂;而UGUI采用的排序比较简单,在Canvas内部元素采用Hierarchy方式排序,在Canvas同级之间通过Sort Order或者是Hierarchy来进行排序;UI的组织方式,就是层级的可见顺序,跟平常的认知具有一致性。

3DMesh嵌套问题

通常UI开发中都会存在UI与Unity3DMesh嵌套的需求,特别常见的是角色与粒子特效。通常有两种方案来处理嵌套。第一种方案是将3DMesh渲染到一张RenderTexture中,再用这张RenderTexture来与其他的UI做排序。这种方案在UGUI与NGUI中都可用,比较适合角色这种渲染目标唯一并使用控件唯一的需求。

另一种方案是将3DMesh的渲染与UI一起排序,UGUI跟NGUI都没有提供将Unity3DMesh内嵌为一个UI元素进行排序的功能。

在NGUI中,官方论坛推荐使用一个脚本来动态修改Mesh的RenderQueue,达到跟UI排序的目的。具体逻辑是每帧获取目标UI的RenderQueue,同步更改Mesh的RenderQueue。因为NGUI默认的排序方式,是每个drawcall对应一个3000以上的RenderQueue。

在UGUI中,采用的是Sorting Order的方式来做排序,需要修改的是粒子Renderer的Sorting Order来跟UI进行排序。所以使用Sorting Order来管理canvas之间的层级关系时,需要留出足够的Order厚度来支持Canvas内部元素嵌套使用粒子特效。

 

Mask裁剪

NGUI中的Mask裁剪,是通过透明度裁剪实现的;UGUI中的Mask裁剪,是通过Stancil裁剪实现的。透明度裁剪,需要在shader中做额外的透明度因子计算,并且被裁剪掉的像素(透明度为0的像素)还是会参与到后续的Blending等渲染流程;Stancil裁剪,需要额外一个pass来渲染mask区域,但没通过Stencil的像素会被丢弃,不参与后续渲染流程。权衡下来,UGUI的做法会更省一点。特别是被裁剪区域特别大,或者区域内重叠框体特别多的情况。

3DMesh裁剪

在UI的mask组件内部层级嵌套使用3DMesh时,粒子也需要被裁剪。NGUI与UGUI都需要对嵌套的3DMesh做裁剪处理。

 

图集管理模式

UGUI不再使用自定义Atlas资源,image中使用的是Sprite,图集可以用Unity提供的Sprite Picker来合成。

NGUI抛弃了Unity的Sprite组件,自定义了一套图集资源及管理模式(Atlas),提供合图工具、Sprite选取窗口、并提供了对单个资源做静态深加工(加阴影、轮廓)的功能。

UGUI VS NGUI UGUI VS NGUI

UGUI整合了Unity的Sprite,使用Sprite Picker来做合图集,功能上简化了许多,但可以直接从Unity的Project窗口中直接把Sprite或者Multiple sprite中的某个Sprite直接拖到Image中,操作上简化了些。

对于小型游戏UI来说,UGUI的简化版功能会更便捷些。但是对于MMORPG这种有复杂UI层级的游戏来说,使用UGUI的自动合图集,由于可控度太低,大概率会造成内存浪费,或者Drawcall Issue。所以,复杂UI层级的游戏,使用NGUI的图集管理模式,可控度会更高些。在UGUI中,可以通过使用第三方工具(如TexturePicker)合图后转成Multiple Sprite来达到跟NGUI相似的可控度。

采用Multiple Sprite有个缺点,不能直接动态加载这个MultipleSprite中包含的某个Sprite,需要使用LoadAll接口将该图集所有的Sprite都加载以后,再遍历对比出所需的Sprite。而Unity的LoadAll函数,是会对Resource中所有文件进行遍历的。当Resource中的文件数量非常庞大的时候,遍历耗时会造成性能卡顿。

这个问题,可以通过将自定义Altas中的Sprite导出成Prefab,并提供一个Sprite Prefab的加载缓存管理器即可。除此之外,精简Resource中的文件数量也是一种很好的方案。“Best Practices for the Resources System —— Don't use it”。[2]

 

渲染性能

Batch算法

NGUI的Panel内部排序:在Widget新增时,是通过Insert到List中进行排序的,整个算法的时间复杂度是O(n),元素越多,效率越差。在Rebuild时,直接遍历WidgetList,按材质、贴图、shader来拆drawcall,进行batch。

UGUI的canvas内部排序:由于没有明确的指定次序,UGUI需要按照顺序,对每个UI控件遍历出相交情况,再查看是否可以合批,算法比NGUI更复杂些(判断相交的算法有优化,时间复杂度小于O(n2))。

Native Code 优势

然而,NGUI实质上是创建自定义Mesh,使用Mesh Renderer来做渲染的。对UI的合批操作逻辑在C#层。UGUI定义了Canvas组件,使用Canvas Renderer来做渲染,在C++层做合批逻辑,所以UGUI处理动态层的性能会比NGUI更佳。

对于有频繁动画需求的UI,使用UGUI会比使用NGUI有性能优势。

 

EventSystem & Raycasters

UGUI中采用Event&Raycasters的设计,来替代NGUI中的Collider&sendMessage。

在NGUI中,默认控件是不参与交互的,除非加上Collider;而在UGUI中,默认控件是参与交互的,除非使用canvas Group组件来禁止交互(在5.x版本中,可以通过去掉勾选raycast Target来禁止)。所以在使用UGUI的过程中,突然发现某个点击无效了,可以优先考虑是否是最近有人添加新的Text把消息遮挡了。

NGUI的交互事件是通过SendMessage来发送消息的,SendMessage的性能比较差,Delegate与SendMessage的性能测算,前者会快10倍左右[3]。UGUI的交互事件改为通过事件回调机制来发送消息,对于交互输入这种比较频繁调用的消息来说,性能上提升不少,比较直接反馈是scroll view的滑动流畅度。

 

Layout布局

UGUI的布局,采用Layout组件群(Layout Element、GridLayoutGroup、Content Size Fitter等),来替代NGUI中的Table跟Grid。

对于UGUI来说,Layout是比较费的组件,因为每次被标记Dirty时,它需要重算所有的子元素的大小跟位置。一般来说,只用在跟内容的相对大小相对位置有关联的复杂布局中。例如说scrollview中动态添加新Item或者是文本背景纹理需要根据文本的长宽动态变化的时候[4]。

平时可以在编辑模式下,用Layout组件来辅助界面快速布局。另外比较小规模的相对布局(如2*2table),可以使用RectTransform的Anchor替代。因为RectTransform的计算是在C++层发生的,比同等的Layout更效率

 

UI动画

NGUI整合了ITween,并将ITween的使用封装成脚本,可以非常方便制作出各种旋转、平移、缩放的效果,易用性很强。

UGUI官方文档建议是采用Unity的Animation System来制作UI动画[5],但是使用Animation动画有个比较明显的缺陷,在UI频繁显隐的时候(Active/Inactive),Animator会重新Rebind一次Controller,导致无意义的性能损耗,所以UGUI的动画实现,可以有另一个option,通过整合DoTween来实现。Dotween不存在Animation的反复初始化问题,并且它使用了一些缓存策略,相对于ITween来说,每帧耗时更短,效率更高,产生GC更少。[6]

 

总结

从易用性角度来看,NGUI比UGUI好用一点点

NGUI的工具支持度更高:

Drawcall Tool,可以查看UI的Drawcall分布以及哪些元素影响了Drawcall分布,可以很方便的调整顺序优化Drawcall;

Prefab Toolbar,可以将Prefab模板直接保存成可视态,通过拖曳到Hierarchy创建新组合;

Editor Right - Click Menu, 在编辑场景中,右键弹出的操作菜单,可以在多重叠控件中,拾取某一个,或者对某个控件进行层级操作。在开发多层级复杂UI的时候,这个功能是可以提升效率的。

ITween, NGUI整合了ITween并提供了封装后的使用脚本,可以很方便快捷的完成UI的动画需求,如旋转、缩放、变色、移动等。

UGUI的概念封装更好,可视化更高:

将层级管理改为Hierarchy排序以后,比较符合人类认知;

对于可交互空间的navigation顺序,可以直接给出可视图;

深度利用Unity的内核,如Inspector,Sprite等;

 

从可扩展性来看,UGUI略逊NGUI一点点

UGUI与NGUI都可以通过简单组件组合,来满足大多数的需求;也可以通过继承扩展现有组件来满足特殊需求;

NGUI更极端一点,可以通过直接修改源码来满足需求。UGUI虽然也开放了源码,但只是C#部分,C++部分是无法做改动的。

 

从性能来看,UGUI比NGUI有底层优势

UGUI的排序及合批逻辑都在C++层处理,效率更高;

采用EventSystem的分发模式,效率更高;

 

结论

除以上总结外,UGUI还有几个优势:全免费、官方身份、跟Unity可深度融合(多线程)等。

选用UGUI应该是趋势,因为性能是关键, 易用性可以通过开发辅助工具或者扩展组件来弥补。随着使用者越来越多,UGUI会越来越强大。

 

Reference

[1]  Unity Manual - UI - Base Layout

[2]  Best Practices -- The Resources folder

[3]  Unity3D的Delegate和SendMessage的性能差测试

[4]  UGUI中实现底图自适应size的方法

[5]  Unity Manual - UI - Animation Integration

[6]  Unity CPU占用-DoTween与iTween效率对比

 

posted on 2017-03-10 20:34  mimime  阅读(679)  评论(0编辑  收藏  举报