看見一篇介紹 Unity3D UI 框架編寫的文章,并且給出了示例代碼。然后去了解了一下。講道理,示例代碼蠻亂的,不知道有一些是不是直接從項目代碼拷貝然后簡單修改,對于一個簡單的框架來說,有很多不必要的混亂邏輯。而且我又喜歡粉色,所以就重新編寫了,話不多說先上運(yùn)行圖示。具體代碼點這里。
想了解更多信息具體查看代碼。這篇博客主要談一談 UI 框架基本的需求。UI 框架有很多實現(xiàn)方式,比如多個界面共用一個攝像機(jī)渲染還是單個界面單個攝像機(jī)渲染,但是基本的需求是一致的。掌握了基本的需求,才能適應(yīng)不同項目策劃百變的需求。最后需求分析基于手游,界面基于 NGUI 。
基本情景
游戲玩法一般分為 UI 玩法和核心玩法。假設(shè)核心玩法是戰(zhàn)斗。UI 玩法與核心相互交織連接,比如打開裝備界面鍛煉裝備,這樣戰(zhàn)斗時就更厲害,還比如打開聊天界面與好友聊天,一起組隊打 Boss 。上圖是常見的操作之一,總結(jié)為兩句話:界面之間跳轉(zhuǎn),界面與戰(zhàn)斗跳轉(zhuǎn),這是最基本的需求情景。
了解了基本情景后來探索更多的細(xì)節(jié)吧。
界面渲染
渲染是最現(xiàn)實的問題,畢竟界面要先能被看見。新創(chuàng)建一個 Camera 獨立負(fù)責(zé)界面渲染,和主場景和戰(zhàn)斗場景渲染獨立開來。對于這個只渲染 UI 的 Camera 設(shè)置 Camera.cullingMask
指定渲染的 Layer,避免冗余渲染,同樣界面(GameObject)的 Layer 也要設(shè)置為值才能被顯示 。這樣當(dāng)調(diào)節(jié)戰(zhàn)斗場景和主場景攝像機(jī)渲染時,UI 不會受到影響,相應(yīng)的修改 UI Camera 的選項時,也不會影響非 UI 的渲染。
界面跳轉(zhuǎn)與界面層級(depth,深度值)
界面跳轉(zhuǎn)與界面層級是相互關(guān)聯(lián)的。比如某個導(dǎo)航按鈕打開一個新的界面,但新的界面層級比較低,這樣在重疊區(qū)域?qū)Ш桨粹o就會遮擋新打開的界面,這不符合實際需求,因此需要合理的管理界面層級,防止出現(xiàn)錯誤的遮擋。界面跳轉(zhuǎn)涉及多個界面,界面層級涉及到渲染次序進(jìn)而影響顯示順序,NGUI 中可以用 depth 控制渲染次序,UGUI 又是另一種方式控制渲染次序,這里使用 NGUI 深度值進(jìn)行描述,目的都是一樣的,就是控制渲染秩序。
當(dāng)游戲界面足夠的簡單,UI 管理就更簡單。如下所示。比如僅僅創(chuàng)建兩個 UIPanel
,ToolPanel 深度值更大,顯示優(yōu)先級更高,PlayPanel 優(yōu)先級低一些,用來展示玩法,由于游戲界面很簡單,此時完全可以把所有界面都創(chuàng)建好,如 UI1 和 UI2 。比如需要顯示 UI2 時,設(shè)置 UI1 為 Inactive 然后設(shè)置 UI2 為 Active 。通過這種方式,你就可以完成一個超簡單的 UI 框架,當(dāng)然它的功能備受限制,但是對于簡單的小游戲來說,也足夠了。
UIRoot
PlayPanel
UI1
UI2
ToolPanel
Tool
然而很多時候游戲會涉及很多界面。這樣就得考慮更靈活的實現(xiàn)方式。
- 縮放選擇。NGUI 使用
UIRoot
組件進(jìn)行縮放。因此完全可以所有界面共用一個UIRoot
組件。如下格式。Panel1 到 Panel3 是三個不同的界面。
UIRoot
Panel1
Panel2
Panel3
當(dāng)然若不在乎冗余的 UIRoot
組件,或者想個別界面采用特俗的縮放方式,也可以每個節(jié)目單獨一個 UIRoot
組件。如下。
UIRoot1
Panel
UIRoot2
Panel
UIRoot3
Panel
- 攝像機(jī)選擇。再提一下,攝像機(jī)指定 Layer 且 UI 界面 GameObject 的 Layer 設(shè)置為值。
- 每個界面單獨一個攝像機(jī)。設(shè)置界面所在攝像機(jī)的
Camera.depth
即可設(shè)置界面層級。這種方式需要避免攝像機(jī)錯亂,例如界面 A 的攝像機(jī)又渲染了界面 B 。有一種解決方法是設(shè)置UIRoot
的位置,避免不同界面出現(xiàn)重疊。 - 所有界面共用單個攝像機(jī)。修改界面層級通過設(shè)置
UIPanel.depth
。這種方式設(shè)置界面層級時需要設(shè)置該界面所有UIPanel.depth
。比如設(shè)置 depth 是 1000,則需要排序該界面所有UIPanel
然后從小到大分別設(shè)置為 1000 、1001 、1002 等。 - 界面類型。游戲中存在不同類型的界面,有些界面就應(yīng)該具有更高的優(yōu)先級,遮擋住其他界面,例如導(dǎo)航界面。還有一種常見的就是玩法的界面,不同的玩法界面相對獨立。比如當(dāng)界面 A 與界面 B 交互時,此時采用窗口跳轉(zhuǎn)即可。
- 界面跳轉(zhuǎn)。打開新界面時,是否禁用其他渲染。關(guān)閉新界面時,恢復(fù)之前的渲染狀態(tài)。
- 禁用其他渲染(包括禁用主場景渲染)。打開全屏窗口時可以采用此方式。此時設(shè)置新界面層級,可以直接設(shè)置深度值。
- 不禁用其他渲染。打開非全屏窗口時可以采用此方式。此時設(shè)置新界面層級,需要動態(tài)計算深度值(可以遞增深度值)并設(shè)置到界面,使新界面可以遮擋其他相應(yīng)界面顯示。
- 界面深度值管理。NGUI 采用深度值。UGUI 是另外一種方式。總之目的是實現(xiàn)合理的界面遮擋。
- 界面與戰(zhàn)斗。進(jìn)入戰(zhàn)斗后,一般之前的那些玩法界面都需要被隱藏,取而代之的是用于戰(zhàn)斗中的界面。退出戰(zhàn)斗后關(guān)閉戰(zhàn)斗中的界面,然后再恢復(fù)進(jìn)入戰(zhàn)斗前的玩法界面狀態(tài)。當(dāng)然了進(jìn)入戰(zhàn)斗后如果退出了未顯示場景,此時可以停止主場景的渲染。
- 界面與消息屏蔽。比如界面 A 顯示在界面 B 的上方,一般情況下肯定都不想在界面 A 上隨便點擊,卻觸發(fā)了界面 B 上的事件,對吧。因此這一項也是需要考慮的。
這些都是基本的 UI 框架需求。這些需求可以根據(jù)項目情況或簡單或復(fù)雜實現(xiàn)。相信讀到這里,你內(nèi)心也有了一些自己的想法了,是吧。
再進(jìn)一步,資源,內(nèi)存,效率
- 界面資源。實際項目中很多界面,不可能全部載入到內(nèi)存,因此都是用到時才加載。若所有界面被單一個攝像機(jī)渲染,資源文件就不用包含攝像機(jī),只需包含相應(yīng)的面板即可。
- 資源可被保存到 .unity 文件。使用時加載 .unity 文件。
- 資源也可被保存到 prefab 文件中。
- 界面內(nèi)存。若界面不多,使用的界面就一直存放在內(nèi)存中。若需要考慮性能,則要處理資源銷毀從而騰出內(nèi)存空間。
- 效率問題。若界面很大且包含了更多的內(nèi)容,打開界面時若全部加載所有內(nèi)容,會導(dǎo)致界面打開比較慢。此時就可以考慮界面拆分。先載入必須顯示的內(nèi)容,剩下的內(nèi)容根據(jù)需要動態(tài)加載。這一步可以單個界面自己負(fù)責(zé)實現(xiàn),也可以考慮到整合到框架中,方便使用的開發(fā)者。
開發(fā)
到這一步一個框架就開發(fā)出來使用了。在實際項目需求中可能會為了便利性,進(jìn)一步增加一些通用功能。下面列舉一些項目中會實際處理的事情。
- 打開界面時,傳遞參數(shù)。
- 打開和關(guān)閉界面時,播放動畫。
- 界面的刷新,包括玩法上的主動刷新(如獲取新的道具刷新背包)或者斷線重連后的刷新。
- 界面的重復(fù)打開。
- 界面顯示特效。
- 界面顯示模型。
下面是前面提到的我重新實現(xiàn)的小框架的例子。例子很簡單,就是顯示一個界面。在 Awake
中進(jìn)行初始化處理即可。之后便可以調(diào)用 WndMgr.inst.ShowWindow(WndId.shop);
來顯示界面。
using UnityEngine;
using TinyWnd;
public class ShopWnd : Wnd
{
protected override void OnAwake()
{
_wndId = WndId.shop;
_colliderMode = WndColliderMode.transparent;
_closeClickBg = true;
_unifiedClose = true;
}
}
提一點,組件中 Awake
和 Start
執(zhí)行順序,大家都知道。要注意的地方是 Awake
和 Start
不一定是在同一幀執(zhí)行。當(dāng)調(diào)用 AddComponent
添加新的組件時,初始化完畢后 Awake
會被調(diào)用,而 Start
未被調(diào)用。因此組件的一些初始化處理放在 Awake
函數(shù)中會更加合適。這樣的話,動態(tài)添加這個組件后就可以使用了,而不用擔(dān)心一些初始化操作未被執(zhí)行。