天天飞车之使用NGUI制作UI
为了加深各位同学对这套系统的了解,先简单介绍一下 NGUI , NGUI 全称是( Next-Gen UI )意为下一代 UI 系统,可见该作者对他这个作品多么自信。
不过这个系统(或这套代码,或这套解决方案)确实有自信的资本,它至少有如下几个优势:
1. 它很好的组织了游戏内 UI 的各个元件、模块,提供了完整的解决方案
2. 它直接使用引擎的绘图接口,有极佳的性能
3. 它设计合理,概念清晰,易于理解
NGUI 虽然是基于 unity3d 引擎来开发的,但它的设计思想有一定的前瞻性,同样的设计思想,也可以应用到其他引擎。就 Unity3d 内使用 NGUI 制作 UI 界面这一套流程来说,我们仍然是使用Unity3d 引擎本身定义的元素(比如 GameObject 、 Mesh 、 Transform 等)来进行组合(拼装), NGUI 只是基于 Unity3d 引擎的这些元素,按照自己的方式组合和运行,因此,如何组合这些元素(按 NGUI 的规范要求),是我们主要考虑的问题和需要学习的点(当然,了解 Unity3d 也是必须的)。
按照 NGUI 的设计和游戏制作的要求,我们在拼装界面时,至少需要考虑以下两个点:
1. 性能问题(影响游戏运行的效率,一定是排在首位)
2. 组织形式问题(根据游戏内使用的要求(程序、动画),合理的组织 UI 元素)
当然,对于美术同学还有效果问题,但是效果始终和性能对立,在我们游戏中,为了缩小包量,节省内存,不仅贴图大量的使用九宫格,贴图的品质也进行了压缩,因此,最终效果与设计图产生了一定差距。
不过此问题并非不能解决,如果从设计之初,就开始考虑九宫格复用的问题,贴图共用的问题,减少相同或相似贴图素材的重复设计,避免重复劳动,更有效的组织资源,强化贴图资源管理,多在设计上做优化,避免使用大块、大面积的贴图、避免图案四周大面积留白,尽量用小而精的元素来组合,这样就可以在保证设计效果的同时,又缩减贴图总量,更有效的利用贴图资源。
NGUI 界面在引擎中是按照树形结构来组织,如图:
图中所有的节点都是一个 GameObject ,只是这些节点有不同的类型(上面挂载了不同的Component ),比如,根节点一定是 UIRoot ( NGUI 的根节点,当点选到该节点上,就可以看到检视视图中显示出了这个 Component )。如图:
根节点之下一般会挂一个相机,用来渲染 UI (有时候会挂两个,用来渲染不同的层,如上图所示),然后至少需要一个 UIPanel ,用来实际绘制这个 UIPanel 之下的各个元件
如图所示 MenuPopUp 就是一个 UIPanel ,在这个节点下的所有子节点,如果是 UIWidget 的派生类型(比如 UILabel 、 UISprite )都会被这个 Panel 实际绘制出来,图中该 Panel 下挂载了 9 个Widgets (元件),将使用 2 个 Draw Calls 绘制, Draw Calls 可看作性能指标,越少越好。
因此, NGUI 的一般组织形式就是:
UIRoot ->UICamera ->UIPanel ->UIWidget
同时有这四个基本元素,就可以在编辑器内看到你需要绘制的 UI 了。以上图所示的结构为例,看到的图案如下:
在这四个基本元素之外,还有一个重要的元素需要单独来说,就是 UIAnchor (锚点),锚点负责 UI的对齐功能,比如某一个小元件需要对齐右上角,另外一个需要对齐右下角,则这两个元件,分别隶属于两个锚点。那么为什么需要锚点呢?因为,游戏内 UI 都需要解决多分辨率适配的问题。比如,美术同学按照 iphone4s 的基础分辨率设定的 UI ( 960 640 )在 iPad 上必须能正确显示( 1024 * 768 ),这两种分辨率的长宽比不同,如果需要某个元件能自动靠边,则需要正确的设定它的锚点。
因此,所需的锚点不会很多,按照通常的设计(比如魔兽世界的 UI 系统或 NGUI 2.2.0 的设定)锚点只有 9 个,上下左右,左上、右上、左下、右下,中间(新版本的 NGUI 中,锚点的机制略有不同,我们之后讨论),要保证显示正确,理论上所有的元件,都需要挂在某个锚点之下,如果不在任何锚点之下,则默认显示在屏幕中间。
因此,完整的组织形式是:
UIRoot ->UICamera ->UIPanel ->UIAchor ->UIWidget
这个形式是完整的,没有其他内容了。但值得注意的是,锚点和 UIPanel 的位置可以根据实际业务需求灵活变化。比如如下形式也是可以的:
UIRoot ->UICamera ->UIAchor ->UIPanel ->UIWidget
这两种形式的区别是,第一个形式所有的元件都在同一个 Panel 中绘制,这样有最佳的渲染效率,以我们项目为例,游戏内 UI 就用这种形式。第二个形式有最好的灵活性,我们的车库界面由于有众多的Menu ,每个 Menu 都是一个单独的功能块,一个 Menu 中可能存在多个部件需要对齐不同的点的情况,所以用第二种形式组织,这个原则在新老版本的 NGUI 中都是如此。
由于游戏内 UI 改动较小,不大会大规模制作 UI 功能,所以美术同学可能会重点关注第二种组织形式下如何正确的组织和建立 UI 。
基本的工作流程是,将整理好的切图放到项目某一个目录中,然后用 NGUI 的 Atlas Maker 来建立(或更新)图集( Atlas ),然后用 NGUI 的 Create a widget 来建立各种元件(组合成树),在编辑器中调整到最佳的效果。 Atlas 如下图所示:
Atlas Maker 会自动将美术同学切好的切图合并为一整张贴图,最重要的是,他会自动使用最佳的拼合方式来最大限度的利用这张贴图的空间,因此,小而精,并且四周不要留白,是节省贴图大小的关键。在我们的项目中,切图资源放在 Pic 目录中,这部分资源不会被打包到最终的游戏包中, Atlas Maker 但生成的 CarIcon.png 这张大图会被打包到最终的游戏包中,引擎打包时会做依赖关系检测。
以做一个按钮为例,可以点击编辑器菜单栏中 NGUI ,选择 Create a widget ,在弹出面板中指定图集( Atlas ),指定字体,选择模版为 Button ,然后点击 Add To ,这样就在你当前选择的GameObject 下建立了一个 Button 元件。
然后需要调整这个 Button 到最终效果, Button 的第一个子节点是 Background 是背景图片,一般是 UISlicedSprite 类型,这个类型的 Sprite 是九宫格伸缩型,可以自由拖动大小,第二个子节点是Label 用来显示按钮上的文字。
其他类型的元件,依此类推,就不一一赘述了。
重点说一下九宫格,按上图所示, Background 是按钮 Button 的背景图片,这张图片的大小是25*80 像素,但显示出来的效果如下:
这个 UISlicedSprite 会自动按照设定的九宫格拉伸中间的区域,这样就可以用一张很小的贴图来做一个很大的元素,以上图的对话框为例,总共使用了 3 张切图,都是九宫格方式制作,总贴图大小不超过 100*100 像素。在我们项目中,可共用的九宫格切图,单独放在一张 Common Atlas 贴图中,这样供所有的 Menu 来使用,不用重复占用内存。如下图所示:
在 UI 制作的过程中经常会遇到一个棘手的问题“显示层级错误”,应该在前面的元件,却被后面的元件挡住了,这个问题是由于元件和 Panel 的组织结构错误引起的(需要说明的是 NGUI 的最新版本中已经从 Panel 这一层解决了此问题,可以保证渲染层次正确,但由于我们项目使用的 Unity3d 3.5.7版本,因此不可能升级到最新的 NGUI 版本,所以此问题需要单独重点说明)
NGUI 为了保证渲染效率,同一个 Panel 下的所有子物体,会被合并成一个 Mesh 来渲染,因此,当同一个 Panel 下使用两个不同的 Atlas 图集时,渲染的次序,就不是单单由组织的树形结构来决定的了, Z 轴负方向上值更大的元素会显示在前面, Z 轴正方向上值越大的元素越在后面。如图:
好友列表背景图的 Z 轴为 5 ,则肯定在后面显示了。通常情况下,文字的 UILabel 都会设置为 -1 或-2 ,这样保证文字总是显示在最前面。但即使是这样,在同一个 UIPanel 中理顺显示层次关系还是很困难,仍然可能出现显示错误的情况,比如有一个组合是如下形式:
其中 UIPanel 下四项的 Z 轴值如下:
UIPanel
BG 0
BG Title -1
TipsBG -1
Tips Label -3
在这个例子中,同一个 Panel 下,有两个主要的部分,一个是背景图和 Title ,另一个是弹出 Tips 提示框(也包括一个背景和一行文字),他们的层级关系是 0 -1 -2 -3 ,看起来这样完全没有问题,当显示 Tips 时,应该挡住背景和 Title ,但事实上,会显示错误, TitleLabel 的文字会透过 TipsBG 盖到上面来,两行文字都压在 TipsBG 上面了。如图:
这是因为,对于 UIPanel 来说,两个背景是同一张贴图,他们会合并为一个 Mesh ,两个文字是另一个 Mesh 将合并在一起,因此,结果就变成了, BG 和 Tips BG 的 Z 值平均为 -0.5 ,两个 Label 的Z 值会平均为 -1.5 ,因此,文字就全部压住背景了。
解决此问题的办法是,要么 TipsBG 不要盖住 BG Title ,从菜单设计上避免覆盖的情况,另外一种是将这两个面板分成两个 UIPanel ( 4 个 drawcalls )牺牲性能( NGUI 的新版本就是此方式)。
了解了以上内容,基本的 UI 制作应没有大问题了,但需要注意的是,不同的应用场景,仍然会有很多灵活的变化,比如结合引擎的动画系统(用来表现 UI 动画,则需要单独插入一个物体挂载)、组合特效,等等,可以在实践中了解。
小结, NGUI 提供了灵活组织 UI 的全套流程,但制作的好坏仍然需要人来控制,有效的组织形式,良好的管理,优质的拼图,可以让 UI 既出彩又高效。反之,则每个点上的损失会被整体放大,以至于怎么弄都弄不好,弄不顺。
新版本 NGUI 对齐方式介绍
天天飞车使用的 NGUI 2.2.0 是经典的 9 点对齐,但此解决方案有一些缺点,比如,有时候我们希望好友列表在不同的分辨率上能自动撑满屏幕,这就要求好友列表的裁剪框能自动居上,居下对齐,做到跟随屏幕大小变化。因此,某一元件按照某一个点对齐这种思路就行不通了, NGUI 的新版本中,将锚点分散到每个元件中,也就是每个元件都自带锚点功能,可以指定该元件以哪一个元件为目标进行对齐,并指定上下左右四个方向的各自对齐点,这样解决了元件根据分辨率自动调整大小的问题,并且大小可以随父节点改变。
但这样又有另外一个问题,就是如果我想某元件对齐之后,又可以被 animation 控制来做 FadeIn 、FadeOut 动画,就不行了,因为该元件的锚点会卡死该元件的位置, animation 控制不了。因此,NGUI 还是保留了原有的经典 9 点对齐的代码,让用户根据需求来选择。