本文首發自inspoy的雜七雜八 | 菜雞inspoy的學習記錄php
UI在任何遊戲裏都是個重要的東西,做爲一個程序員咱們暫時先不考慮如何設計UI纔好看,優先仍是考慮怎麼高效地實現功能。
在不少重度UI的遊戲中,UI佔的比重常常超過核心玩法,UI變多的時候咱們須要用編輯器來設計UI,用代碼生成工具來生成相關的代碼,以後咱們只關心如何實現相關的邏輯就好了,Unity的場景文件徹底不用修改,由於全部的UI都是代碼控制動態添加移除的。
這裏使用MVP的架構來實現,Model爲主要以單例形式存在的類,用來存儲相關的數據;View爲根據UI的Prefab生成出來的代碼,人工不修改,純自動生成;Presenter就是咱們須要寫代碼邏輯的地方了。html
這樣子最大的好處在於,當需求有變化時,咱們只須要在編輯器裏調整各個UI控件的佈局,而後生成相關代碼就好了,並不用修改View相關的代碼,只須要考慮遊戲邏輯方面的修改就夠了。
無論UI怎樣多怎樣複雜,Unity的Scene文件永遠不用修改,多人合做時,最大化地分隔開了不一樣人的工做,最大程度上地避免了衝突的發生。git
這是個重點,UGUI的UI事件都是基於Unity的EventSystem的,不過咱們以前已經本身實現了一套適合咱們的事件系統了,這裏就經過動態增長組件的方式把Unity.EventSystem的事件轉換爲咱們本身的事件,這樣就能統一處理了
以按鈕爲例,咱們須要監聽它的點按事件,爲了將這個事件以咱們SFEvent
的形式發佈,我繼承UnityEngine.EventSystems.EventTrigger
寫了一個自定義組件SFUIEventListener
程序員
public class SFUIEventListener : EventTrigger
{
public SFEventDispatcher dispatcher = null;
public static SFEventDispatcher getDispatcherWithGo(GameObject go) {
// 靜態方法,自動建立UI控件的事件派發器
var listener = go.GetComponent<SFUIEventListener>();
if (listener == null)
{
listener = go.AddComponent<SFUIEventListener>();
}
if (listener.dispatcher == null)
{
listener.dispatcher = new SFEventDispatcher(go);
}
return listener.dispatcher;
}
public override void OnPointerClick(PointerEventData) {
// 這裏是Unity原生的事件
if (dispatcher != null)
{
dispatcher.dispatchEvent(SFEvent.EVENT_UI_CLICK);
}
}
// more...
};複製代碼
固然這裏只有V和Pgithub
首先須要用編輯器建立並設計UI,最後保存成Prefab
Prefab的結構是這樣的:
架構
Canvas - UICamera - UIRoot
每一個場景只有一個。爲了方便,我規定咱們遊戲的UI分辨率以1280*720爲基準,UI切圖素材均參照這個分辨率進行製做。
其中UI繪製的話UGUI支持3中:直接疊加,UI攝像機,做爲3D物體被主攝像機渲染。咱們使用單獨的UI攝像機來實現,Canvas - Render Mode
選擇Screen Space - Camera
,而後再Canvas下面建立一個攝像機子節點,命名爲UICamera,並將其設置到Canvas中。
編輯器
如今咱們發現Game窗口如今顯示的是UICamera拍攝到的東西,因此要設置一下UICamera的屬性,首先Clear Flags
改爲Depth only
,默認的選項是要從新繪製天空盒,這樣一來比他層級低的攝像機拍攝到的畫面就徹底被擋住了。Culling Mask
選擇UI
,做用是隻讓這個攝像機拍攝UI層的內容(Canvas節點下全部的子節點默認都是UI層,能夠在Transform組件上面的位置看到Tag和Layer,就能發現Layer都已是UI了)。
ide
Orthographic
,默認的透視投影不能保證UI徹底佔滿UICamera的鏡頭
接下來是UIRoot這個節點,這實際上是一個全屏的透明Panel,只不過它的Pos Z
值爲500,做用是讓UI攝像機穩當地看到UI內容,500這個值是隨便填的,只是由於若是PosZ爲默認值0的話UICamera會拍不到UI內容,大於一個值(好像是20多)纔會正常顯示。wordpress
好了廢話說完,就能夠導出了,導出的工具涉及到編輯器的擴展,暫時還不會弄= =先留個坑,以後再填(不是
最終導出的結果大概是這個樣子:函數
public class SFTestView : SFBaseView
{
public Text lblTitle{ get { return m_lblTitle; } }
public Button btnOk { get { return m_btnOk; } }
private Text m_lblTitle;
private Button m_btnOk;
private SFTestPresenter m_presenter;
void Start() {
GameObject lblTitleGO = SFUtils.findChildWithParent(gameObject, "lblTitle");
if (lblTitleGO != null)
{
m_lblTitle = lblTitleGO.GetComponent<Text>();
}
// other widgets
m_presenter = new SFTestPresenter();
m_presenter.initWithView(this);
}
}複製代碼
其中SFBaseView
繼承自MonoBehavior,提供了一個添加事件監聽的公共方法:
public void addEventListener(Component widget, string eventType, SFListenerSelector sel) {
var dispatcher = SFUIEventListener.getDispatcherWithGo(widget.gameObject);
dispatcher.addEventListener(eventType, sel);
}複製代碼
最後別忘記把View腳本掛載在Prefab上
Presenter文件會在第一次導出UI View時候一併建立出來,初始默認的內容很是簡單:
public class SFTestPresenter
{
SFTestView m_view;
public void initWithView(SFTestView view) {
m_view = view;
}
}複製代碼
以後咱們就能夠開始寫邏輯了,好比咱們想給這個按鈕加一個點擊事件,當點擊時改變lblTitle的文本。很是方便,首先在Presenter類中的initWithView()
方法中添加點擊事件監聽:
m_view.addEventListener(m_view.btnOk, SFEvent.EVENT_UI_CLICK, onButtonClicked);複製代碼
而後實現回調函數
void onBtnClicked(SFEvent e) {
m_view.lblTitle.text = "Button Clicked!";
}複製代碼
最後實現添加UI View到場景的操做,這裏我用了一個靜態變量來存儲當前場景的UIRoot節點,這個靜態變量屬於類SFSceneManager
,這個類掛載在每一個場景的一個空節點(Empty GO)中,在場景加載時找到該場景的UIRoot節點並保存到靜態變量中,並在場景卸載時清空靜態變量。
public class SFSceneManager : MonoBehaviour
{
static public GameObject uiRoot = null;
void Start() {
var uiRootGO = GameObject.Find("UIRoot");
if (uiRootGO == null)
{
SFUtils.logWarning("當前場景沒有找到UIRoot節點");
}
uiRoot = uiRootGO;
}
}複製代碼
因而咱們就能夠在任何一個地方添加UI了,只須要編寫以下代碼:
GameObject.Instantiate(vwPrefab, SFSceneManager.uiRoot.transform);複製代碼
最後就能夠看效果啦
上面貼出的代碼片斷因爲篇幅限制只保留了關鍵部分,完整的代碼可在個人github上找到