使用Unity制作UGUI的UI流程管理机制

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

自从 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 流程管理机制至少要满足两个条件:

1.玩家使用时不会迷路。

2.不管未来变更多少画面,都不需要修改或维护返回按钮。

条件满足

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

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

  • 每个 UI 画面间的进场和退场动态,

  • 进退场画面进行中时,UI 事件不能有作用。

  • 退场 UI 画面即使游戏画面上看不到,也不应该让它持续执行。

UI 画布的基本结构

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

建立 UI 的主画布(Canvas),并在其 Canvas Scaler 设置好 Ui Scale Mode 以及 Reference Resolution,这主要是为了使将来 UI 面临不同画面比例的装置时,可以有基本的自适应调整,详细可以参考另一篇文章「Unity:UGUI 应对各种荧幕自动调整大小及位置」

设置 UI Scale Mode 与 Reference Resolution

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

使用 Image 制作透明遮挡层 model 物件

制作动画

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

动画播放期间开启「透明遮挡层」

设置动画控制

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

预设,Unity 会自动将 Clip 放到 Animator 做为 state

首先,先建立一個 Trigger 参数(Out),这是要让 UI 画面进场后,维持在进场动画播放完毕之后的动画状态为(Open)时,让程序通知 Animator 转换到播放退场动画状态(Closed)用的。

在 Animator 的 Parameters 建立一个 Trigger

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

设置 Transition 里de几个相关栏位

然后,再从 Closed 状态建立 Transition 连接到 Exit 状态,并在 Inspector view 设置过渡时Transition Duration 为 0,把 Exit Time 设置为 1,为什麽要这样做呢?理论上,当 Closed 播放完毕之后,UI 画面就会完成退场的动作,在它下次进场之前,都不需要去理会它,但为了不让不在游戏画面中的 UI 画面继续执行,浪费效能资源,我们会希望完成退场之后,可以将这些 UI 画面整个 GameObject 都关闭,所以,我们后续会撰写 Animator 的 State 专用的 StateMachineBehaviour 来做这件事,而 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 挂上我们自已写的 ,并在製作动画档时,在 Animation view 加入 Event,自从 发佈后,就不再需要这麽麻烦,我们可以直接点选 Animator view 裡的任意状态,并在 Inspector view 中点击 Add Behaviour 按钮,建立 Animator 状态专用的 ,如此就可以直接透过撰写这个 的内容来对 Animator 裡的状态针对状态开始、进行中、结束.. 等等的时机进行其他操作,因此,我们要让 UI 画面退场之后关闭本身 GameObject,只需要为 Closed 建立 StateMachineBehaviour 并在 OnStateExit 加入简短的一行程式码即可。

为 Animator 的 State 加入 Behaviour

UI流程管理脚本

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

1. 建立一个列表来纪录 UI 画面历程,用来将曾经进入的 UI 画面依序记录下来,以方便依序返回。

2. 先将第一个开启的 UI 画面记录到历程中,当历程只剩下一笔时,就不能再返回。

3. 不可以进入与目前 UI 画面相同的画面。

4. 即将要进入或返回的目标画面必须移到最上层。

5. 往前进入的目标画面必须记录到历程中。

6. 返回时,必须要将目前画面从历程纪录中移除。

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

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

套用

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

设置 Start Screen 及 Out Trigger 栏位

然后,在每个 UI 的 Button 中的 On Click 设置附有这个 UIManager 的 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 设置,整个机制相当的弹性,但是,玩家操作上却不至于混乱,在后续维护上也将会更为轻松。

猜你喜欢

转载自blog.csdn.net/qq826364410/article/details/80520788