UGUI系列-原理分析(Unity3D)(三)

简介: 了解各种不同UI Render Mode

Unity UGUI 原理篇(五):Auto Layout 自动布局

目标

1.Auto Layout System 架构 2.Layout Element 元素大小 3.Horizontal丶Vertical丶Grid Layout Group 元素排列 4.Content Size 与 Aspect Ratio Fitter 大小控制

使用环境 与 版本

Window 7

Unity 5.2.4

Auto Layout System

Auto Layout System 是基于 Rect Transform Layout System 之上的系统,自动调整一个或多个的元素大小、位置、间格,又分为 Layout Controllers(父物件) 与 Layout Elements(子物件) 两部分,一个简单的 Auto Layout 架构如下 (此处介绍理论,实作留到后面)

网络异常,图片无法展示
|

Layout Element (子物件)

网络异常,图片无法展示
|

网络异常,图片无法展示
|

点选UI后,可以在 Inspector 最下方切换为 Layout Properties 看到资讯

网络异常,图片无法展示
|

网络异常,图片无法展示
|

Layout Controllers 透过不同的佈局方式,取得 Layout Element size 分配子物件,基本原则如下

首先分配 Minimum Size

如果还有足够空间,分配 Preferred Size

如果还有额外空间,分配 Flexible Size

从以下图片可以看出图片宽度的增长方式 (此处介绍理论,实作留到后面)

网络异常,图片无法展示
|

网络异常,图片无法展示
|

首先分配 Minimum Size (300,红色部分)

如果还有足够空间,分配 Preferred Size (300~500,绿色部分)

如果还有额外空间,分配 Flexible Size:1 (500~700,蓝色部分)

比较特别的是 Flexible,他是代表著整个大小的比例,如果 Layout 下有2个物体,分别给 Flexible 设定为 0.3 与 0.7,那比例就会变成下图 (3:7)

网络异常,图片无法展示
|

另外要注意的是,Text、Image Component 会根据内容大小自动分配 Preferred Size

Layout Controllers (父物件) Layout Group

不会控制 Layout Controllers (父物件)自身大小,而是控制子物件大小与位置,在大多数情况下,根据每个元素的 minimum、preferred、flexible 大小分配适当的空间,layout group 之间也可以嵌套,又分为 Horizontal(水平)、Vertical(垂直)、Grid(格状) 3种

Horizontal Layout Group

网络异常,图片无法展示
|

水平方向(Width) 排列子物件

组件位置:Unity Menu Item → Component → Layout → Horizontal Layout Group

Padding:填充内部空间

Spacing:每个元素间格

Child Alignment:当没有填满全部空间时,子物件对齐位置

Child Force Expand:强制控制子物件填满空间

透过实例理解各参数:

A.开新 Scene Unity Menu Item → File → New Scene

B.新增一个 Canvas Unity Menu Item → GameObject → UI → Canvas

C.Canvas 下新增空物件,做为 Layout Controllers (以下简称父物件)

D.父物件增加 Horizontal Layout Group Component Unity Menu Item → Component → Layout → Horizontal Layout Group

E.父物件下建立5个 Button(子物件),完成后如下,当大小改变时会自动分配子物件大小

网络异常,图片无法展示
|

F.此时在 Button 的 Rect Transform Component 就不能进行调整,因为我们已经透过 Horizontal Layout Group 进行分配空间,在 Rect Transform 会显示目前被哪个 Layout Group 控制

网络异常,图片无法展示
|

G.将 Padding 数值调整如图,可以看出填充区域

网络异常,图片无法展示
|

H.将 Spacing 数值调整如图,可以看出元素区间

网络异常,图片无法展示
|

I.接下来我们将5个 Button 增加 Layout Element Component 覆盖预设大小,用于手动设定每个元素的大小 组件位置:Unity Menu Item → Component → Layout → Layout Element

J.此时将 Horizontal Layout Group 的 Child Force Expand Width 取消勾选,不强制子物件填满额外空间,而是透过 Layout Element 手动设定

K.这裡使用几种不同的设定,来理解 Horizontal Layout Group 是怎麽取得 Layout Element size 分配子物件

■ 复习一下子物件大小分配方式,如果不清楚请回去上面 Layout Elements 部分

首先分配 Minimum Size

如果还有足够空间,分配 Preferred Size

如果还有额外空间,分配 Flexible Size

■ 将5个 Button 的 Layout Element Min Width 分别改为 20、30、40、50、60,此时可以看出每个 Button 宽度分佈,改变父物件大小时子物件大小并不会改变,因为只有分配 Min Width,并不会分配额外有效空间

网络异常,图片无法展示
|

此时改变 Horizontal Layout Group 的 Child Alignment,可以看出元素对齐

网络异常,图片无法展示
|

父物件 Layout Properties Min Width = 5个按钮宽(20+30+40+50+60=200) + Spacing(40) + Padding Left、Right(20) = 260

网络异常,图片无法展示
|

■ 现在将第1个 Button 的 Layout Element 数值调整如图

网络异常,图片无法展示
|

这边设定 Preferred Width 为 100


1.首先分配 Minimum Size(20)


2.空间足够的情况下,将会分配剩下的 Preferred Size (20~100 空间),如下所示

网络异常,图片无法展示
|

■ 现在将第1个 Button 的 Layout Element 数值调整如图

网络异常,图片无法展示
|

这边设定 Flexible Width 为

(1)首先分配 Minimum Size(20)

(2)如果还有足够空间,将会分配剩下的 Preferred Size (20~100 空间)

(3)如果还有额外空间,分配剩下 Flexible Size,如下所示

网络异常,图片无法展示
|

■ 现在将 Horizontal Layout Group 的 Child Force Expand Width 勾选,让子物件强制填满

(1)首先分配 Minimum Size(20)

(2)如果还有足够空间,将会分配剩下的 Preferred Size (20~100 空间)

(3)如果还有额外空间,分配剩下元素 Flexible Size 与 Child Force Expand Width

网络异常,图片无法展示
|


结论: 上面我们看到,所有元素会先被分配 Minimum Size,接下来还有足够空间,将会分配剩下的 Preferred Size,最后才是 Flexible Size 与 Child Force Expand Width

至此我们了解到 Horizontal Layout Group 是怎麽取得 Layout Element size 分配子物件

###Vertical Layout Group

网络异常,图片无法展示
|

垂直方向(Height) 排列子物件,与 Horizontal Layout Group 只差在水平或是垂直,这边不在赘述

组件位置:Unity Menu Item → Component → Layout → Vertical Layout Group

###Grid Layout Group

网络异常,图片无法展示
|

网格方式排列子物件

组件位置:Unity Menu Item → Component → Layout → Grid Layout Group

Padding:填充内部空间

Cell Size:每个元素的宽高

网络异常,图片无法展示
|

Spacing:每个元素间格

Start Corner:开始排列的角落(位置),又分为 “左上”、”右上”、”左下”、”右下”,请仔细看元素数字

网络异常,图片无法展示
|

Start Axis:”水平” 或是 “垂直” 排列,请仔细看元素数字

网络异常,图片无法展示
|

Child Alignment:当没有填满全部空间时,子物件对齐位置

Constraint:排列限制

Flexible:自动根据大小弹性排列

Fixed Column Count:限制排列 “行数(直)”

Fixed Row Count:限制排列 “列数(横)”


Layout Fitter

控制著 Layout Controllers 自身大小,大小取决于子物件,或是设定的大小比例,又分为 Content Size Fitter 与 Aspect Ratio Fitter

Content Size Fitter

网络异常,图片无法展示
|

控制著 Layout Controllers (父物件)自身大小,大小取决于子物件的 Minimum 或是 Preferred 大小,能透过 Pivot 改变缩放方向

组件位置:Unity Menu Item → Component → Layout → Content Size Fitter

Horizontal、Vertical Fit:水平、垂直 适应调整

None 不调整

Min Size 根据子物件的 Minimum 大小进行调整

Preferred Size 根据子物件的 Preferred 大小进行调整

透过实例理解:

如果我们现在有一个需求,必需要让 “父物件大小” 根据 “子物件大小” 进行缩放,完成如下 (方便明显看出父物件大小,增加黑色外框)

网络异常,图片无法展示
|

A.开新 Scene Unity Menu Item → File → New Scene

B.新增一个 Canvas Unity Menu Item → GameObject → UI → Canvas

C.Canvas 下新增空物件,做为 Layout Controllers (以下简称父物件)

D.父物件增加 Horizontal Layout Group Component Unity Menu Item → Component → Layout → Horizontal Layout Group

这时如果增加 Button(子物件),上面有提到,Horizontal Layout Group 会根据子物件的 Layout Element 进行分配子物件大小,而不会修改父物件本身的大小,如下所示 (方便明显看出父物件大小,增加黑色外框)

网络异常,图片无法展示
|

E.父物件下增加 Button(子物件),并增加 Layout Element Component 覆盖预设大小,Minimum Width 调整为 100 组件位置:Unity Menu Item → Component → Layout → Layout Element

F.父物件增加 Content Size Fitter Component,Horizontal Fit 调整为 Min Size,透过子物件 Minimum Width 调整父物件本身大小 (Horizontal 方向其实就是取得子物件 Width)

网络异常,图片无法展示
|

G.此时如果 Button 複製增加,父物件本身的大小也会跟著改变,如下所示

网络异常,图片无法展示
|

H.调整父物件的 pivot,可以控制缩放方向,如下所示

网络异常,图片无法展示
|

I.通过上面实例,我们首先使用 Horizontal Layout Group 排列子物件,并在子物件增加 Layout Element 覆盖预设大小,最后透过 Content Size Fitter 取得子物件 Layout Element 设定父物件大小,至此父物件大小就会根据子物件大小进行缩放

###Aspect Ratio Fitter

网络异常,图片无法展示
|

控制著 Layout Controllers 自身大小,按照物件宽高比调整大小,能透过 pivot 改变缩放方向

组件位置:Unity Menu Item → Component → Layout → Aspect Ratio Fitter

Aspect Mode:调整模式

None:不调整

Width Controls Height:

基于 Width 为基准,依据比例改变 Height

网络异常,图片无法展示
|

当 Width 改变时,Height 会依比例改变

网络异常,图片无法展示
|

Height Controls Width:

基于 Height 为基准,依据比例改变 Width

网络异常,图片无法展示
|

当 Height 改变时,Width 会依比例改变

网络异常,图片无法展示
|

Fit In Parent:依据比例将 宽高、位置、anchors自动调整,使此图形大小在父物件中完全贴齐,此模式可能不会包覆所有空间

调整比例 (方便明显看出父物件增加黑底)

网络异常,图片无法展示
|

调整父物件大小,物体会依据比例贴齐父物件

网络异常,图片无法展示
|

Envelope Parent:依据比例将 宽高、位置、anchors自动调整,使此图形大小完全包覆父物件,此模式可能会超出空间

调整比例 (方便明显看出父物件增加黑框)

网络异常,图片无法展示
|

调整父物件大小,物体会依据比例包覆父物件

网络异常,图片无法展示
|

Aspect Ratio:比例,此数值为 宽/高

差别:

Content Size Fitter 是透过子物件自动进行调整大小

Aspect Ratio Fitter 是透过数值(宽高比)进行调整

后记 Auto Layout System 可以快速、方便的排列多个 UI,当大小改变时会自动调整内容,也能应用在多层崁套下,在日后调整与修改上也是非常方便与直觉,是 UI 系统中必学的功能之一 !!

参考资料 ■ Unity – Manual- Auto Layout

docs.unity3d.com/Manual/UIAu…

■ Unity – Manual- Auto Layout_UI Reference

docs.unity3d.com/Manual/comp…


Unity:UGUI 应对各种萤幕自动调整大小及位置


UGUI自适应

以前曾经发佈过两篇有关 GUI 自动调整的文章「Unity 自动调整 GUI 缩放比例及位置」以及「Unity:应对各种萤幕比例自动调整画面缩放及位置」, 自从 Unity 于 4.6 版发佈了新的 GUI 系统(UGUI)之后,使用 Unity 製作 UI 变得更为简单方便,同时也比较不需要依赖第三方製作的 UI 工具了,在事件系统上也有了一些革新,UI 事件与 Component 之间变得更加视觉化,彼此间的藕合度大大降低,而使得使用上更为弹性,虽然,UGUI 解决了许多以前 UI 製作上面的问题,但实际开发时,还是有些部分需要使用者做些调整。

在 Unity 4.6 发佈之后,UGUI 相关的游戏物件,不像一般的游戏物件使用 Transform,而是 Rect Transform,其中多了宽、高以及 Anchors、Pivot 等栏位,这些栏位对 UI 製作的视觉化设计上有很大的帮助,而其中的 Anchors 更是让 UI 整体应对于各种不同比例画面有著更加强大及弹性的控制权。

如果在 Unity 的 Game view 使用 Free Asspect 观察画面时,任意的拉动视窗边缘去调整画面比例,可以发现 UGUI 本身是并不会移动位置或缩放大小,如果将 320x480 变为 480x320,就有可能使 UI 被画面边缘切掉,这时候 Anchors 就能发挥很大的用处,使得 UI 图片或按钮等元素都能跟随画面比例的改变而变更其宽高大小,但其美中不足的是文字并不会因此跟随改变大小,当然,如果文字有勾选 Best Fit 的话,也会有自动变更 Size 的效果,只是,按钮、图片等 UI 元素跟随画面比例改变,也改变了本身的宽高比例,但文字却只是改变 Font Size,所以,当画面比例改变了,图片变形了,文字并未跟著变形,那麽文字与其他 UI 之间的距离或是按钮边缘与按钮内的文字的间隙就可能会发生奇怪的变化,这些都是我们所不希望看到的,所以,最好的状况应该是,当画面比例改变了,UI 的图片、文字、按钮等文字都能维持原本的长宽比例自动缩放大小及位置。

网络异常,图片无法展示
|

正常的纵向画面

网络异常,图片无法展示
|

转为横向时,画面被切掉了

网络异常,图片无法展示
|

为 UI 设置好 Anchors

网络异常,图片无法展示
|

转为横向后,UI 自动变形调整,但文字不同

当在场景中建立第一个 UGUI 的游戏物件时,一定会先自动建立 Canvas 的游戏物件,在这个物件中,预设配置了 Canvas、Canvas Scaler、Graphic Raycaster 这三个 Component,其中的 Canvas Scaler 正是用来控制 UGUI 整体的缩放,所以只要依照以下步骤,就能达到最基本的依照原比例调整 UI 的目的:

  • 1.让所有的 UI 的 Anchors 维持在预设值(0.5)。
  • 2.将 Canvas Scaler 的 Ui Scale Mode 栏位设置为 Scale With Screen Size。
  • 3.在 Reference Resolution 栏位输入基础解析度的宽(X)、高(Y)。
  • 4.Screen Match Mode 栏位选择 Match Width Or Height。
  • 5.如果画面为横向,Match 栏位选输入 0,如果画面为纵向,Match 栏位输入 1。

网络异常,图片无法展示
|

让 Anchors 维持在预设值 0.5

网络异常,图片无法展示
|

设置 Canvas Scaler 的相关栏位

如此,当画面从纵向转为横向,GUI 就会自动维持原比例调整其大小及位置。

网络异常,图片无法展示
|

转为横向画面,UI 的比例及位置不变

但是,这是在比例相同直接横向转纵向时才会如此理想,当遇到比例不同的画面,例如原本是 iPhone 4 的 640x960 设计的 UI 佈局,遇上了 iPhone 5 的 640x1136 画面,就会发生左右两边被切掉的情形。

网络异常,图片无法展示
|

当比例不同时,边缘被切掉了

这时我们就必须将 Canvas 游戏物件的 Canvas Scaler componet 的 Match 栏位改为 0,使其 UI 依照原本比例缩放调整到画面内。

网络异常,图片无法展示
|

调整 Canvas Scaler 的 Match 使被切掉的部分回到画面内。

为了因应可能遇到各种未知的画面比例(特别是 Android 以及桌机平台),我们可以为 Canvas 挂上包含以下这一段程式码的 component 来自动调整 Canvas Scaler component 的 Match 栏位。

void Awake(){
    CanvasScaler canvasScaler = GetComponent<CanvasScaler>();
    float screenWidthScale = Screen.width / canvasScaler.referenceResolution.x;
    float screenHeightScale = Screen.height / canvasScaler.referenceResolution.y;
    canvasScaler.matchWidthOrHeight = screenWidthScale > screenHeightScale ? 1 : 0;
}
复制代码

完成以上的工作,将来在 UI 製作上就会轻鬆很多,如果没有特殊需求,基本上不需要动到 UI 的 Anchors 值,大部份都只是单纯的调整 UI 大小及位置就行了,面对各种画面比例,也将维持原设计的佈局自动调整,另外,如果需要製作 UI 的动态缩放、位移等等使画面更生动的话,以变更其 Local space 的数值为主,那麽比例缩放的部分都不需要特别去考虑,Canvas Scaler 会帮我们处理。

不过,有一个例外,目前是无法使用此办法解决的,就是当 UI 文字使用了 Rich Text 的 Size 标签去指定文字大小时,文字的大小是不会受到 Canvas Scaler 影响的,它指定了绝对的文字 Size,这一点要特别注意。

附带一提,除了 UI 佈局以及变化之外,有时候也会想在 UI 上做些粒子特效,或是让非 GUI 的游戏物件在画面上与 UI 呈现互动的效果,此时,只要把 Canvas 游戏物件的 Canvas component 的 Render Mode 栏位改为 Screen Space - Camera 并设定好 Render Camera 及 Plane Distance 的内容,那麽放置于 UI 平面与其指定的 Camera 中间的物件就可以显示于 UI 之上与其互动,如果是 Sprite 游戏物件,则可以依据 Sorting Layer 与 GUI 之间调整前后顺序;不过,当遇到自动调整过的 GUI 画面,这些非 GUI 的游戏物件大小及位置可能会与预期的不同,为了避免此情况发生,可以参考之前发佈过的文章内容「Unity:应对各种萤幕比例自动调整画面缩放及位置」(该文章是Unity4.3版本的,所有就没给大伙们搞过来)去调整 Camera。

网络异常,图片无法展示
|

改变 Canvas 的 Render Mode 为 Screen Space - Camera

PS:目前使用 Unity 版本为 5.0.1f1。


Unity:制作 UGUI 的 UI 流程管理机制

为什么需要UI流程管理机制

自从 Unity 4.6 发布新 GUI 系统之后,Unity 终于有个比较完整的视觉化编辑 UI 工具可以使用,于是,我们可以很方便、直觉的在画面上添加按钮,使用拖曳、下拉选单等几个动作就能设置好 UI 事件应该执行哪个 GameObject 上的哪个 Component 中的功能,所以透过 UI 去触发我们自己撰写的程式功能也变得非常简便,但是整个游戏内容可能会有相当多的画面,不同的 UI 按钮或行为将转向不同的画面,也需要开启不同 UI 视图,如果没有规划好 UI 画面的动线规则,在複杂的画面转换间,将可能会发生很难维护的情形,甚至在未来多次的变更修改后产生不必要的 Bug 而使得 UI 画面动线变得相当混乱。

不管 UI 做得多漂亮、多华丽,反馈、排版、效果等这些细工做得多好,如果整体架构、动线流程出错了,做了那么多细工的辛苦就会白费掉,因为,会让玩家迷路的 UI 动线,使用起来就是会不舒服;即使使用 UI 的动线设计都没问题,但没有规划一致性的流程管理,在制作及维护上也容易造成一些困扰。

举例来说,大部份的 UI 画面都会有个「返回」按钮可以回到前一个画面,当这个 UI 画面是从 A 画面进来时,应该可以返回到 A 画面,从 B 画面进来时,应该可以返回到 B 画面,就像我们平常使用网页浏览器一样,不管目前的页面是从哪裡进来的,按下「上一页」就是会回到进来目前网页之前的那个网页,UI 上的「返回」按钮也是一样的功能,只是,这个返回是个按钮,它会被我们设置执行某个 Component 的某个功能,透过这个功能来使它开启某个 UI 画面,所以,我们很直觉的就会有点像写死的程式一样,A 画面进入 B 画面,所以点击 B 画面的返回按钮就是开启 A 画面,如果,C 画面也可以进入 B 画面呢?那麽就做个暂存纪录让 B 画面判断是从 A 还是 C 画面进入的,或者进入 B 画面的同时,通知 B 画面说是从哪裡进来的,此时,第一个问题来了,每当多出一个会进入到 B 画面的画面,B 画面的返回按钮功能就要进行修改来达到正确的判断;第二个问题,假设 A、B 画面都可以进入 C 画面,C 画面可以进入 A、D 画面,A、D 画面都可以进入 B 画面... 等等较深入的交叉动线,新增一个 UI 画面或者更动到某个 UI 画面流程,就几乎全部画面的返回键都要进行修改,这种工程实在太浩大了,牵一髮而动全身的维护状况是最容易产生未知 Bug 的。

有于此,设计的 UI 流程管理机制至少要满足两个条件:

  • 玩家使用时不会迷路。
  • 不管未来变更多少画面,都不需要修改或维护返回按钮。

要满足这两个条件,有一个最直接的做法,也就是前面提到的,网页浏览器的「上一页」功能,这个功能是如何实现的呢?概念其实就是浏览纪录,也就是说我们只要把玩家操作时,所切换过的 UI 画面依序记录下历程,再依序返回,这样子,玩家使用时不会迷路,我们在制作及维护时,也不需要为每个画面的返回按钮客制化。

流程上的概念没问题了,另外就是一些其他要注意的部分:

  • 每个 UI 画面间的进场和退场动态,
  • 进退场画面进行中时,UI 事件不能有作用。
  • 退场 UI 画面即使游戏画面上看不到,也不应该让它持续执行。

前面写了那麽多,最重要的还是如何实作,以下是整个实作过程的影片,影片内并没有太详细说明,所以,在下面的文章内容提供进一步的详细解说:


UI 画布的基本结构

先决定我们要使用怎么样的画面比例、解析度来编排制作 UI,通常会跟 Game view 设置好要预览的画面相同

网络异常,图片无法展示
|

建立 UI 的主画布(Canvas),并在其 Canvas Scaler 设置好 Ui Scale Mode 以及 Reference Resolution,这主要是为了使将来 UI 面临不同画面比例的装置时,可以有基本的自适应调整

网络异常,图片无法展示
|

接下来,每个 UI 画面都应该另外建立 Canvas,并将画面内的其他 UI 元件放置在该 Canvas 之下,如此前面主画布里设置好的 Canvas Scaler 也将直接适用于其下的每个 UI 画面,另外,这麽做的另一个好处是,之后可以直接变更 Canvas 的 Sort Order 来排列 UI 画面的前后顺序。同时,每个画面除了 UI 元件之外,还需要使用 Image 制作一个覆盖全画面大小的「透明遮挡层」,并放置于其它 UI 元件之上,平常处于 GameObject 被关闭的状态,当 UI 画面在进行进场、退场动态时,才将它开启,这样可以保护 UI 的 Button 不会被点击到。

网络异常,图片无法展示
|


制作动画

制作 UI 画面的进场、退场动画档分别命名为 Open、Closed,最简单的就是淡入、淡出或者是放大、缩小的方式,当然,也可以设计更丰富的动画效果,最重要的是,进、退场动画档播放期间要记得开启前面製作的「透明遮挡层」。

网络异常,图片无法展示
|
动画播放期间开启「透明遮挡层」


设置动画控制

当我们为 UI 画面制作好进场、退场的动画档之后,开启 Animator view,可以发现 Unity 已经自动帮我们在 Animator 建立两个与动画档相同名称的动画状态(State)。

网络异常,图片无法展示
|
预设,Unity 会自动将 AnimationClip 放到 Animator 做为  state

接着,从 Open 状态建立 Transition 连接到 Closed 状态,并在 Inspector view 设置其中的过渡条件,由于 Unity 预设会认为两个动画状态之间的转换是需要混合过程的(例如,人物角色閒置的动作转换到跑步动作),这个混合过程会牺牲一点动画档本身的播放时间,在 UI 画面转换的动画并不需要这种混合过程,所以在此,我们可以直接将过渡时间 Transition Duration 设置为 0,让它很明确的播放 Closed 动画;由于,Open 状态并不需要播放结束之后马上转换到 Closed 状态,而是等待程式通知 Animator 触发 Out 参数才转换到 Closed 状态,所以,在此也要取消勾选 Has Exit Time,并且在 Conditions 设置一个过渡条件 Out。

网络异常,图片无法展示
|

设置 Transition 裡的几个相关栏位

然后,再从 Closed 状态建立 Transition 连接到 Exit 状态,并在 Inspector view 设置过渡时间 Transition Duration 为 0,把 Exit Time 设置为 1,为什麽要这样做呢?理论上,当 Closed 播放完毕之后,UI 画面就会完成退场的动作,在它下次进场之前,都不需要去理会它,但为了不让不在游戏画面中的 UI 画面继续执行,浪费效能资源,我们会希望完成退场之后,可以将这些 UI 画面整个 GameObject 都关闭,所以,我们后续会撰写 Animator 的 State 专用的 StateMachineBehaviour script来做这件事,而 StateMachineBehaviour 的 OnStateExit 是在状态结束「之后」才会被呼叫,所以,如果没有为 Closed 建立 Transition 连接出来的话,StateMachineBehaviour 的 OnStateExit 将不会执行,另外,Exit Time 是以 0 到 1 来代表整个动画时间从开始到结束的时间点,我们希望 Closed 是能够明确的播放到结束时间才真的结束,所以在此的 Exit Time 才直接设置为 1。

网络异常,图片无法展示
|

设置好正确的时间值

由于,在 Unity 裡建立动画档时,预设会认为该动画是要重複循环播放的,所以,我们还要另外手动找出 Open 及 Closed 动画档,并在 Inspector view 裡将 Loop Time 取消勾选。

网络异常,图片无法展示
|

取消 Loop Time

###动画状态脚本 这部分,最主要的是要让 UI 画面退场之后,也能同时将它的 GameObject 整个关闭掉,以往,我们是在 GameObject 挂上我们自已写的 Script,并在製作动画档时,在 Animation view 加入 Event,自从 Unity 5.0 发佈后,就不再需要这麽麻烦,我们可以直接点选 Animator view 裡的任意状态,并在 Inspector view 中点击 Add Behaviour 按钮,建立 Animator 状态专用的 Script,如此就可以直接透过撰写这个 Script 的内容来对 Animator 裡的状态针对状态开始、进行中、结束.. 等等的时机进行其他操作,因此,我们要让 UI 画面退场之后关闭本身 GameObject,只需要为 Closed 建立 StateMachineBehaviour 并在 OnStateExit 加入简短的一行程式码即可。

网络异常,图片无法展示
|

为 Animator 的 State 加入 Behaviour

using UnityEngine;
public class UIStateClosed : StateMachineBehaviour {
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        animator.gameObject.SetActive(false);
    }
}
复制代码


UI流程管理脚本

接下来算是重头戏了,做完前面的工作,剩下的就是靠这个 Script 去做主要的管理,而这个 Script 主要做哪些工作呢?我们将其列出如下:

  1. 建立一个列表来纪录 UI 画面历程,用来将曾经进入的 UI 画面依序记录下来,以方便依序返回。
  2. 先将第一个开启的 UI 画面记录到历程中,当历程只剩下一笔时,就不能再返回。
  3. 不可以进入与目前 UI 画面相同的画面。
  4. 即将要进入或返回的目标画面必须移到最上层。
  5. 往前进入的目标画面必须记录到历程中。
  6. 返回时,必须要将目前画面从历程纪录中移除。

另外,之前的文章「Unity:认识 Tag 与 Layer 的差异与应用」曾提到过程式码中应该避免写死的字串值,所以,这个 Script 因为会使用到 Animator 的 Parameters 中设定的名称来通知 Animator 转换到 Closed 状态,所以也需要宣告一个 public 栏位来设置该名称。

有了以上的工作需求,我们就可以撰写出如下的程式码:

using UnityEngine;
using System.Collections.Generic;
public class UIManager : MonoBehaviour {
    public GameObject startScreen;
    public string outTrigger;
    private List<GameObject> screenHistory;
    void Awake(){
        this.screenHistory = new List<GameObject>{this.startScreen};
    }
    public void ToScreen(GameObject target){
        GameObject current = this.screenHistory[this.screenHistory.Count - 1];
        if(target == null || target == current) return;
        this.PlayScreen(current , target , false , this.screenHistory.Count);
        this.screenHistory.Add(target);
    }
    public void GoBack(){
        if(this.screenHistory.Count > 1){
            int currentIndex = this.screenHistory.Count - 1;
            this.PlayScreen(this.screenHistory[currentIndex] , this.screenHistory[currentIndex - 1] , true , currentIndex - 2);
            this.screenHistory.RemoveAt(currentIndex);
        }
    }
    private void PlayScreen(GameObject current , GameObject target , bool isBack , int order){
        current.GetComponent<Animator>().SetTrigger(this.outTrigger);
        if(isBack){
            current.GetComponent<Canvas>().sortingOrder = order;
        }else{
            current.GetComponent<Canvas>().sortingOrder = order - 1;
            target.GetComponent<Canvas>().sortingOrder = order;
        }
        target.SetActive(true);
    }
}
复制代码

套用

完成以上的工作以及程式撰写之后,只要建立一个空的 GameObject 并将 UIManager script 挂上去,在 Start Screen 栏位放入第一个要显示的 UI 画面 GameObject,在 Out Trigger 填写跟 Animator 设置的 Trigger 参数相同的名称。

网络异常,图片无法展示
|

设置 Start Screen 及 Out Trigger 栏位

然后,在每个 UI 的 Button 中的 On Click 设置附有这个 UIManager script 的 GameObject,如果按钮是要前往下一个画面,则在下拉选单选择 ToScreen 并在其参数栏位设置目标 UI 画面的 GameObject。如果按钮是要返回上一个画面,则在下拉选单选择 GoBack。

网络异常,图片无法展示
|

前往下个 UI 画面,选择 ToScreen

网络异常,图片无法展示
|

设置好下个 UI 画面的 GameObject

网络异常,图片无法展示
|

返回上个 UI 画面,选择 GoBack

接下来,执行时,就能将每个进入的画面历程记录下来,并在每次返回时依序返回上一个画面,每个即将显示的 UI 画面也将会是在游戏画面的最上层,而不会被遮挡到,同时,在画面的进、退场动态期间也不会被不小心按到按钮而跳到非预期的画面;由于,动线被定义为从哪裡进去就从哪裡返回,所以,使用者在众多的画面浏览间,会比较直觉的操作而不容易迷路,同时,因为画面历程的纪录,返回按钮一律透过 GoBack 去处理时,将来不管是新增多少 UI 画面,或是 UI 画面流程如何改变,返回功能都不需要再进行任何修改,也可以返回到正确的画面。

以上,这个简单的 UI 流程管理机制算是完成了,之后,我们如果对于 UI 执行转换动作的逻辑要进行变更(例如,目标画面的排序规则),则只需要修改 UIManager 裡面的 PlayScreen;如果,要换成别的进场、退场动画效果,也只需要替换掉 Animator 裡面各状态的 motion;如果做了多种版本的 UIManager 并存于同一个专案或场景中,也可直接变更 UI 裡 Button 的 On Click 设置,整个机制相当的弹性,但是,玩家操作上却不至于混乱,在后续维护上也将会更为轻松。

案例工程:pan.baidu.com/s/1sk9bnGh 密码:do7y

PS:目前使用 Unity 版本为 5.1.0f3


相关文章
|
C# 图形学
【unity之c#】所以迭代器的原理知识你还清楚吗?
【unity之c#】所以迭代器的原理知识你还清楚吗?
254 0
|
编解码 前端开发 算法
|
前端开发 图形学
|
.NET C# 图形学
Unity跨平台的机制原理
首先需要了解的是,Unity3D的C#基础脚本模块是通过Mono来实现的。 什么是Mono? 参考下百度百科:Mono是一个由Novell公司(由Xamarin发起)主持的项目,并由Miguel de lcaza领导的,一个致力于开创.NET在Linux上使用的开源工程。
3737 0
|
测试技术 图形学
【Unity Shader】(五) ------ 透明效果之半透明效果的实现及原理
笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题 【Unity Shader学习笔记】(三) ---------------- 光照模型原理及漫反射和高光反射的实现 【Unity Shader】(四) ------ 纹理之法线纹理、单张纹理及遮罩纹理的实现   前言 相信读者对透明效果都不陌生,因为透明效果是游戏中经常使用的一种效果。
4637 0
|
图形学
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的实现
【Unity Shader】(三) ---------------- 光照模型原理及漫反射和高光反射的实现 【Unity Shader】(四) ------ 纹理之法线纹理、单张纹理及遮罩纹理的实现 【Unity Shader】(五) ------ 透明效果之半透明效果的实现及原理   本文主要参考了冯乐乐老师的《Unity Shader入门精要 》一书,再加上网上一些参考资料而写。
2167 0
|
测试技术 Go 图形学
unity中实现简单对象池,附教程原理
Unity对象池的创建与使用 本文提供全流程,中文翻译。 Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例) Chinar —— ...
2769 0
|
3月前
|
机器学习/深度学习 人工智能 图形学
卓伊凡的第一款独立游戏-详细介绍游戏开发引擎unity-以及详细介绍windows和mac的安装步骤【01】
卓伊凡的第一款独立游戏-详细介绍游戏开发引擎unity-以及详细介绍windows和mac的安装步骤【01】
383 9

热门文章

最新文章