用Unity作個遊戲(四) - 基於UGUI的MVP框架

本文首發自inspoy的雜七雜八 | 菜雞inspoy的學習記錄php

前言

UI在任何遊戲裏都是個重要的東西,做爲一個程序員咱們暫時先不考慮如何設計UI纔好看,優先仍是考慮怎麼高效地實現功能。
在不少重度UI的遊戲中,UI佔的比重常常超過核心玩法,UI變多的時候咱們須要用編輯器來設計UI,用代碼生成工具來生成相關的代碼,以後咱們只關心如何實現相關的邏輯就好了,Unity的場景文件徹底不用修改,由於全部的UI都是代碼控制動態添加移除的。
這裏使用MVP的架構來實現,Model爲主要以單例形式存在的類,用來存儲相關的數據;View爲根據UI的Prefab生成出來的代碼,人工不修改,純自動生成;Presenter就是咱們須要寫代碼邏輯的地方了。html

設想的工做流

  1. UI設計人員用Unity Editor設計UI
  2. 將整個UI View保存爲Prefab
  3. 使用代碼生成工具生成VIew和Presenter(僅第一次須要)
  4. 在Presenter中編寫代碼邏輯
  5. UI須要調整,修改Prefab
  6. 從新生成View的代碼
  7. 修改Presenter中的邏輯

這樣子最大的好處在於,當需求有變化時,咱們只須要在編輯器裏調整各個UI控件的佈局,而後生成相關代碼就好了,並不用修改View相關的代碼,只須要考慮遊戲邏輯方面的修改就夠了。
無論UI怎樣多怎樣複雜,Unity的Scene文件永遠不用修改,多人合做時,最大化地分隔開了不一樣人的工做,最大程度上地避免了衝突的發生。git

UI事件

這是個重點,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...
};複製代碼

MVP

固然這裏只有V和Pgithub

View

首先須要用編輯器建立並設計UI,最後保存成Prefab
Prefab的結構是這樣的:
架構

0401

藍色的部分就是咱們保存成Prefab的了,以vwXXX命名,其父層級 Canvas - UICamera - UIRoot每一個場景只有一個。爲了方便,我規定咱們遊戲的UI分辨率以1280*720爲基準,UI切圖素材均參照這個分辨率進行製做。

其中UI繪製的話UGUI支持3中:直接疊加,UI攝像機,做爲3D物體被主攝像機渲染。咱們使用單獨的UI攝像機來實現,Canvas - Render Mode選擇Screen Space - Camera,而後再Canvas下面建立一個攝像機子節點,命名爲UICamera,並將其設置到Canvas中。
編輯器

0402

如今咱們發現Game窗口如今顯示的是UICamera拍攝到的東西,因此要設置一下UICamera的屬性,首先Clear Flags改爲Depth only,默認的選項是要從新繪製天空盒,這樣一來比他層級低的攝像機拍攝到的畫面就徹底被擋住了。Culling Mask選擇UI,做用是隻讓這個攝像機拍攝UI層的內容(Canvas節點下全部的子節點默認都是UI層,能夠在Transform組件上面的位置看到Tag和Layer,就能發現Layer都已是UI了)。
ide

0403

另一點,截圖中沒有截到: Camera的Projection一項,要改爲正交投影Orthographic,默認的透視投影不能保證UI徹底佔滿UICamera的鏡頭
其中UICamera的Transform有個偏移1280,這個不重要,只是我不想在編輯UI的時候看到場景上的東西,也就是在Scene窗口中把UI和場景物件分開

接下來是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

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);複製代碼

最後就能夠看效果啦

0404

點擊按鈕以後就能夠發現上面的文字變化了
0405

完整代碼

上面貼出的代碼片斷因爲篇幅限制只保留了關鍵部分,完整的代碼可在個人github上找到

相關文章
相關標籤/搜索