unity3D之實現一個簡單的UI框架

寫在前面的話

我會先把源碼直接給出來。博主本身也不是什麼大牛,所以寫的也不是什麼很厲害的框架,只是一個很小很小的UI框架,給初學者一點入門的思路。
很多人在剛開始接觸框架的時候都會有一種感覺:這NM什麼DDX啊?!完全懵逼了有沒有?!我還沒開始寫代碼呢!怎麼就已經有這麼多代碼要看啊?!還有大部分新人都會吐槽的事情:爲什麼要這麼寫啊?這玩意隨便寫個腳本掛上去不是分分鐘實現功能嗎?你這繞來繞去搞毛?這個主程是不是NT?!emmmmmmmmmmm…如果你知道主程怎麼想的,你不就是主程了?
所以,初次接觸框架的話,可以抱怨,但是請儘快冷靜下來,好好看代碼。起碼要以最快的速度會用這個框架,在慢慢去分析爲什麼框架要這麼寫。其實看完後你就會發現,主程寫的框架實現一個功能確實很麻煩,但是你用起來的時候卻非常方便,因爲很多東西主程早早的就給你封裝在框架裏了,你只需要幹一些不動腦子的體力活就實現了需求,爽就一個字!這就是爲什麼要使用框架,爲什麼主程要彎彎繞繞的實現功能。

好,如果你做好了思想準備,那麼就先下載源碼,讀起來吧!這真的是一個很簡單的UI框架,內容不多。讀起來應該很容易。
UIFrame
提取碼:gt4a

那接下來我們就開始吧!

初次接觸框架的讀者可能會完全不知道從哪裏下手閱讀,那麼博主就帶着大家一起看看,這個框架主要是用來解決什麼問題的。

腳本文件夾結構

在這裏插入圖片描述
這個框架有五個基本文件夾,分別是:Managers,Models,UIBases,UIInterface,Utility。
怎麼樣?都是大家耳熟能詳的名字吧?那麼我們應該先從哪裏開始閱讀呢?自然就是Utility文件夾了。

Utility文件夾下的腳本

打開這個文件夾,有如下腳本:
在這裏插入圖片描述
下面我將依次講解這幾個腳本各自負責做些什麼。
首先,我們先看Singlton:

using System;

namespace UIFrame
{
    public class Singleton<T> where T : class
    {
        private static T instance;

        public static T Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = (T)Activator.CreateInstance(typeof(T), true);
                }
                return instance;
            }
        }
    }
}

很簡單,只有20行代碼。這個腳本只做了一件事,就是實現一個單例。特殊之處在於,這是一個單例的帶有泛型的基類,使用反射的方式搞定的單例,那麼他的作用呢?也很簡單,只要是你想要把一個腳本做成單例模式,那麼只需要繼承這個腳本,再私有化無參構造即可。文字可能解釋的不是很清楚,我們結合代碼來看,接下來我們來看看AssetsManager:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UIFrame
{
    public class AssetsManager : Singleton<AssetsManager>
    {
        private AssetsManager()
        {
            assetsChace = new Dictionary<string, object>();
        }
        /// <summary>
        /// 資源緩存池
        /// </summary>
        private Dictionary<string, object> assetsChace;
        /// <summary>
        /// 獲取資源方法
        /// </summary>
        /// <param name="assetPath">路徑</param>
        /// <returns>獲取到的對象</returns>
        public object GetAsset(string assetPath)
        {
            object asset = null;
            if (!assetsChace.TryGetValue(assetPath,out asset))
            {
                asset = Resources.Load(assetPath);
                assetsChace.Add(assetPath, asset);
            }
            return asset;
        }
    }
}

AssetsManager腳本是我用來做資源管理的腳本,那麼可能會在很多其他地方要用到,那每次要用到我都去實例化就太麻煩了,而且也不利於資源管理,所以我把他做成單例。怎麼做?就利用上面的Singleton腳本來實現,我們讓AssetsManager繼承Singleton,再私有化構造函數,就自然而然的完成了我們的需求,以後再有其它腳本要實現單例,也這樣如法炮製即可。這裏就已經體現了一些些框架的威力。也可以說是設計原則的好處。不展開了,回到正題。那麼這個腳本管理的是什麼資源呢?其實,什麼資源都可以,但我們主要是針對UI資源進行管理,首先我們寫了一個緩存池,用來緩存已經取過的資源用以二次利用。然後就是一個獲取資源的方法了,很簡單,就是傳過來一個資源的所在路徑,通過路徑將資源拿出來放進緩存後返回該資源,自己看看應該就能看懂。
好,那這個腳本就不多說了,我們看下一個,JsonPanelManager:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UIFrame
{
    public class JsonPanelManager : Singleton<JsonPanelManager>
    {
        private JsonPanelManager()
        {
            jsonPanelDataDic = new Dictionary<int, Dictionary<string, string>>();
            jsonLocalizationDic = new Dictionary<int, Dictionary<string, string[]>>();
            widgetDataDic = new Dictionary<int, Dictionary<string, string>>();
            ParseJsonPanel();
            //ParseJsonLocalization();
            ParseJsonWidget();
        }

        /// <summary>
        /// json轉換的對象
        /// </summary>
        private JsonPanelModel jsonPanelData;

        /// <summary>
        /// json轉換後的字典存儲
        /// </summary>
        private Dictionary<int, Dictionary<string,string>> jsonPanelDataDic;

        /// <summary>
        /// json轉化後的本地化文件對象
        /// </summary>
        private JsonLocalizationModel jsonLocalizationData;

        /// <summary>
        /// json轉換後的本地化文件字典
        /// </summary>
        private Dictionary<int, Dictionary<string, string[]>> jsonLocalizationDic;

        //Widget解析後的數據
        private JsonWidgetModel widgetData;
        //Widget解析後的數據【字典版】
        private Dictionary<int, Dictionary<string, string>> widgetDataDic;

        /// <summary>
        /// json解析
        /// </summary>
        private void ParseJsonPanel()
        {
            //獲取json文本對象
            TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonPanelsPath) as TextAsset;

            //解析json
            jsonPanelData = JsonUtility.FromJson<JsonPanelModel>(assetText.text);
            for (int i = 0; i < jsonPanelData.AllData.Length; i++)
            {
                //新定義一個字典
                Dictionary<string, string> crtPanelData = new Dictionary<string, string>();

                //將場景ID和字典存入
                jsonPanelDataDic.Add(i, crtPanelData);

                //將場景的所有資源路徑保存
                for (int j = 0; j < jsonPanelData.AllData[i].Data.Length; j++)
                {
                    crtPanelData.Add(jsonPanelData.AllData[i].Data[j].PanelName, jsonPanelData.AllData[i].Data[j].PanelPath);
                }
            }
        }

        /// <summary>
        /// json解析
        /// </summary>
        private void ParseJsonLocalization()
        {
            //獲取json文本對象
            TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonLanguages) as TextAsset;
            //解析json
            jsonLocalizationData = JsonUtility.FromJson<JsonLocalizationModel>(assetText.text);
            for (int i = 0; i < jsonLocalizationData.AllData.Length; i++)
            {
                //新定義一個字典
                Dictionary<string, string[]> crtPanelData = new Dictionary<string, string[]>();

                //將場景ID和字典存入
                jsonLocalizationDic.Add(i, crtPanelData);

                //將場景的所有資源路徑保存
                for (int j = 0; j < jsonLocalizationData.AllData[i].Data.Length; j++)
                {
                    crtPanelData.Add(jsonLocalizationData.AllData[i].Data[j].ObjName, jsonLocalizationData.AllData[i].Data[j].TextLanguageText);
                }
            }
        }

        private void ParseJsonWidget()
        {
            TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonWidgetsPath) as TextAsset;

            widgetData = JsonUtility.FromJson<JsonWidgetModel>(assetText.text);

            for (int i = 0; i < widgetData.AllData.Length; i++)
            {
                //創建一個字典
                Dictionary<string, string> crtDic = new Dictionary<string, string>();
                //添加一個場景ID和一個字典
                widgetDataDic.Add(i, crtDic);
                //遍歷當前場景內的所有Panel路徑數據
                for (int j = 0; j < widgetData.AllData[i].Data.Length; j++)
                {
                    //以PanelName爲Key,以PanelPath爲value進行存儲
                    crtDic.Add(widgetData.AllData[i].Data[j].WidgetName,
                        widgetData.AllData[i].Data[j].WidgetPath);
                }
            }
        }

        /// <summary>
        /// 獲取資源路徑
        /// </summary>
        /// <param name="panelName"></param>
        /// <returns></returns>
        public string GetAssetPath(string panelName,int sceneID)
        {
            if (!jsonPanelDataDic.ContainsKey(sceneID))
            {
                return null;
            }
            if (!jsonPanelDataDic[sceneID].ContainsKey(panelName))
            {
                return null;
            }
            return jsonPanelDataDic[sceneID][panelName];
        }

        /// <summary>
        /// 獲取本地化語言數組
        /// </summary>
        /// <param name="objName"></param>
        /// <param name="sceneID"></param>
        /// <returns></returns>
        public string[] GetLocalizationTextArray(string objName,int sceneID)
        {
            if (!jsonLocalizationDic.ContainsKey(sceneID))
            {
                return null;
            }
            if (!jsonLocalizationDic[sceneID].ContainsKey(objName))
            {
                return null;
            }
            return jsonLocalizationDic[sceneID][objName];
        }
        public string GetWidgetPath(string widgetName, int sceneID)
        {
            if (!widgetDataDic.ContainsKey(sceneID))
                return null;
            if (!widgetDataDic[sceneID].ContainsKey(widgetName))
                return null;
            //如果都ID和Widget在字典中都存在,則直接返回
            return widgetDataDic[sceneID][widgetName];
        }
    }
}

同樣的,JsonPanelManager 這個腳本我們也繼承了單例,那麼這個腳本主要是用來做什麼的呢?很簡單,就是解析json文件,將json文件中存儲的資源路徑都給拿出來,然後再利用上一個腳本將資源拿出來放進緩存(也就是我們自己定義的字典裏面,字典有多好用就不用我多說了吧)。至於json文件的解析,本篇博客也不贅述,都來看框架了,json解析這種東西應該沒問題吧?那這個腳本中除了獲取UI模塊資源,還寫了一個獲取UI動態元件和本地化的json解析,暫時用不到,先不說。
好,那這個文件夾就只剩最後一個腳本了,SystemDefaine:

using UnityEngine;

public static class SystemDefaine  {

    public const string JsonPanelsPath = "Configuration/UIPanelConfig";
    public const string JsonWidgetsPath = "Configuration/UIWidgetConfig";
    public const string JsonLanguages = "Configuration/LocalizationConfig";

    public enum SceneID
    {
        Login = 0,
        FightScene = 1
    }
    public static string[] UIWidgetsMark = new string[] { "_F" };
}

SystemDefaine這個腳本,單純是用來保存常數的,注意,這是一個靜態類,暫時我們只用到了最上面的json文件路徑的字符串常數。也沒什麼特殊的。其他的用到再說。

UIBases文件夾

UIBases文件夾下有如下腳本:
在這裏插入圖片描述
我們先來看UIWidgetBase:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UIInterface;

namespace UIFrame
{
    public class UIWidgetBase : UIMono
    {
    	//綁定所屬的UI模塊
        private UIModuleBase crtModule;
        //初始化UI元件
        public void UIWidgetInit(UIModuleBase uIModuleBase)
        {
            crtModule = uIModuleBase;

            UIManager.Instance.AddWidget(crtModule.name, name, this);
        }
        private void OnDisable()
        {
            UIManager.Instance.RemoveWidget(crtModule.name, name);
        }
    }
}

UIWidgetBase 是一個通過代碼添加到所有以“_F”結尾的UI元件身上的腳本組件。他繼承了UIMono,UIMono則實現了很多關於UI組件操作的接口,詳情自己去看看UIInterface文件夾,因爲過於簡單,所以博主不做講解。

我們再來看UIModuleBase:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UIFrame
{
	//該腳本掛載在每個UI模塊上,並確保擁有CanvasGroup組件
    [RequireComponent(typeof(CanvasGroup))]
    public class UIModuleBase : MonoBehaviour
    {
        protected CanvasGroup canvasGroup;
        //獲取所有的子對象
        private Transform[] allChild;
        protected virtual void Awake()
        {
            canvasGroup = GetComponent<CanvasGroup>();
            //修改對象名稱(將生成的對象名稱後面的(clone)去掉)
            gameObject.name = gameObject.name.Remove(gameObject.name.Length - "(clone)".Length);
            allChild = transform.GetComponentsInChildren<Transform>();
            AddUIWidgetBehaviour();
        }
        //綁定該模塊對應的Controller
        protected void BindController(UIControllerBase controller)
        {
            controller.ControllerInit(this);
        }
        /// <summary>
        /// 找出需要添加widgetbase的腳本
        /// </summary>
        private void AddUIWidgetBehaviour()
        {
            for (int i = 0; i < allChild.Length; i++)
            {
                for (int j = 0; j < SystemDefaine.UIWidgetsMark.Length; j++)
                {
                	//所有子對象中名稱以規定字符串結尾的都添加上一個UIWidgetBase腳本。
                    if (allChild[i].name.EndsWith(SystemDefaine.UIWidgetsMark[j]))
                    {
                        AddComponetForWidget(i);
                    }
                }
            }
        }
        /// <summary>
        /// 給對象添加widgetbase組件
        /// </summary>
        /// <param name="index"></param>
        protected virtual void AddComponetForWidget(int index)
        {
            UIWidgetBase crtbase = allChild[index].gameObject.AddComponent<UIWidgetBase>();

            crtbase.UIWidgetInit(this);
        }
        //通過UI元件名稱獲取該模塊中綁定的UI元件
        public UIWidgetBase GetWidget(string widgetName)
        {
            return UIManager.Instance.GetWidget(name, widgetName);
        }
        //以下是用以模態處理的四個操作,你可以在重寫時加入想要的模態處理效果,或者添加一些動畫之類的效果,簡單易懂,不做贅述
        public virtual void OnEnter()
        {
            canvasGroup.blocksRaycasts = true;
            //LocalizationManager.Instance.LocalizationInit();
        }
        public virtual void OnPurse()
        {
            canvasGroup.blocksRaycasts = false;
        }
        public virtual void OnResume()
        {
            canvasGroup.blocksRaycasts = true;
        }
        public virtual void OnExit()
        {
            canvasGroup.blocksRaycasts = false;
        }
    }
}

UIModuleBase這個腳本是掛載在所有UI模塊上的。但是,不是直接掛載,而是每個模塊自己寫一個腳本繼承這個腳本之後掛載。並且需要重寫這個腳本的awake方法。具體的解釋可以仔細看看代碼以及註釋,不難理解。值得一提的是,一定要記得綁定相對應的Controller,不然無法實現相關功能。

那麼最後我們來說說UIControllerBase:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UIFrame
{
    public class UIControllerBase
    {
    	//綁定相應的UIModuleBase 
        protected UIModuleBase uiModuleBase;
        //初始化控制器
        public void ControllerInit(UIModuleBase uiModuleBase)
        {
            this.uiModuleBase = uiModuleBase;
            ControllerStart();
        }
        protected virtual void ControllerStart()
        {
        }
    }
}

UIControllerBase跟UIModuleBase 一樣,每個UI模塊都需要寫一個自身的控制器繼承這個UIControllerBase,然後不需要掛載,因爲他跟UIModuleBase 綁定在一起,可以直接調用。具體用法我們後面說。我們先接着看框架。

Managers文件夾

Managers文件夾下有如下腳本:
在這裏插入圖片描述
這兩個腳本纔是我們框架的重中之重,或者說UIManager這個腳本纔是核心。
話不多說,我們直接看這個腳本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace UIFrame
{
    public class UIManager : Singleton<UIManager>
    {
        private UIManager()
        {
            canvas = GameObject.Find("Canvas").transform;
            uiModuleBases = new Dictionary<UIType, UIModuleBase>();
            uiModuleStack = new Stack<UIModuleBase>();
            uiWidgetBases = new Dictionary<string, Dictionary<string, UIWidgetBase>>();
            uiModuleList = new List<UIModuleBase>();
        }

        /// <summary>
        /// ui模塊的棧
        /// </summary>
        Stack<UIModuleBase> uiModuleStack;

        /// <summary>
        /// ui模塊的列表存儲
        /// </summary>
        List<UIModuleBase> uiModuleList;

        /// <summary>
        /// 管理所有的UI模塊
        /// </summary>
        Dictionary<UIType, UIModuleBase> uiModuleBases;

        /// <summary>
        /// 管理所有的元件
        /// </summary>
        Dictionary<string, Dictionary<string, UIWidgetBase>> uiWidgetBases;

        /// <summary>
        /// 畫布
        /// </summary>
        private Transform canvas;

        public UIModuleBase GetUIModuleByName(string panelName)
        {
            UIType type = UITypeManager.Instance.GetUiType(panelName, 0);

            return GetUIModule(type);
        }

        /// <summary>
        /// 獲取ui模塊
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public UIModuleBase GetUIModule(UIType type)
        {
            UIModuleBase crtUIMdule = null;

            if (!uiModuleBases.TryGetValue(type,out crtUIMdule))
            {
                crtUIMdule = InstantiateUIModule(AssetsManager.Instance.GetAsset(type.panelPath) as GameObject);
                uiModuleBases.Add(type, crtUIMdule);
            }
            else if(crtUIMdule == null)
            {
                crtUIMdule = InstantiateUIModule(AssetsManager.Instance.GetAsset(type.panelPath) as GameObject);

                uiModuleBases[type] = crtUIMdule;
            }
            return crtUIMdule;
        }

        /// <summary>
        /// 生成ui模塊對象
        /// </summary>
        /// <param name="prefab"></param>
        /// <returns></returns>
        private UIModuleBase InstantiateUIModule(GameObject prefab)
        {
            GameObject obj = GameObject.Instantiate(prefab);

            obj.transform.SetParent(canvas, false);

            return obj.GetComponent<UIModuleBase>();
        }

        /// <summary>
        /// 壓棧
        /// </summary>
        /// <param name="panelName">模塊名稱</param>
        /// <param name="sceneID">場景ID</param>
        public void PushUI(string panelName,int sceneID)
        {
            //定義一個uitype
            UIType type = UITypeManager.Instance.GetUiType(panelName,sceneID);

            //獲取模塊
            UIModuleBase crtbase = GetUIModule(type);

            //如果棧中已有元素
            if (uiModuleStack.Count > 0)
            {
                //當前模塊暫停使用
                uiModuleStack.Peek().OnPurse();
            }
            //新模塊壓棧
            uiModuleStack.Push(crtbase);

            //新模塊開啓
            crtbase.OnEnter();
        }

        /// <summary>
        /// 展示UI
        /// </summary>
        /// <param name="panelName"></param>
        /// <param name="sceneID"></param>
        public void ShowUI(string panelName,int sceneID)
        {
            //定義一個uitype
            UIType type = UITypeManager.Instance.GetUiType(panelName, sceneID);

            //獲取模塊
            UIModuleBase crtbase = GetUIModule(type);

            //如果棧中已有元素
            if (!uiModuleList.Contains(crtbase))
            {
                //當前模塊暫停使用
                uiModuleList.Add(crtbase);
            }
            //新模塊開啓
            crtbase.OnEnter();
        }

        /// <summary>
        /// 出棧
        /// </summary>
        public void PopUI()
        {
            if (uiModuleStack.Count > 0)
            {
                uiModuleStack.Pop().OnExit();
            }

            if (uiModuleStack.Count > 0)
            {
                uiModuleStack.Peek().OnResume();
            }
        }

        /// <summary>
        /// 註冊UI模塊元件
        /// </summary>
        /// <param name="moduleName"></param>
        private void RegisterUIModuleWidgets(string moduleName)
        {
            if (!uiWidgetBases.ContainsKey(moduleName))
            {
                uiWidgetBases.Add(moduleName, new Dictionary<string, UIWidgetBase>());
            }
        }

        /// <summary>
        /// 註銷UI模塊元件
        /// </summary>
        /// <param name="moduleName"></param>
        public void UnRegisterUIModuleWidgets(string moduleName)
        {
            if (uiWidgetBases.ContainsKey(moduleName))
            {
                uiWidgetBases.Remove(moduleName);
            }
        }

        /// <summary>
        /// 添加元件
        /// </summary>
        /// <param name="moduleName">模塊名</param>
        /// <param name="widgetName">元件名</param>
        /// <param name="widget">元件</param>
        public void AddWidget(string moduleName, string widgetName, UIWidgetBase widget)
        {
            RegisterUIModuleWidgets(moduleName);

            if (!uiWidgetBases[moduleName].ContainsKey(widgetName))
            {
                uiWidgetBases[moduleName].Add(widgetName, widget);
            }
        }

        /// <summary>
        /// 移除元件
        /// </summary>
        /// <param name="moduleName"></param>
        /// <param name="widgetName"></param>
        public void RemoveWidget(string moduleName, string widgetName)
        {
            if (!uiWidgetBases.ContainsKey(moduleName))
            {
                return;
            }
            if (!uiWidgetBases[moduleName].ContainsKey(widgetName))
            {
                return;
            }
            uiWidgetBases[moduleName].Remove(widgetName);
        }

        /// <summary>
        /// 獲取元件
        /// </summary>
        /// <param name="moduleName"></param>
        /// <param name="widgetName"></param>
        /// <returns></returns>
        public UIWidgetBase GetWidget(string moduleName, string widgetName)
        {
            //如果該模塊未註冊,註冊
            if (!uiWidgetBases.ContainsKey(moduleName))
            {
                RegisterUIModuleWidgets(moduleName);
            }

            UIWidgetBase widget = null;
            //嘗試獲取該模塊此元件並返回,沒有返回null
            uiWidgetBases[moduleName].TryGetValue(widgetName, out widget);

            return widget;
        }

        public GameObject CreateDynamicWidget(string widgetName)
        {
            string widgetPath = JsonPanelManager.Instance.GetWidgetPath(widgetName, 0);
            GameObject prefab = AssetsManager.Instance.GetAsset(widgetPath) as GameObject;

            return GameObject.Instantiate(prefab);
        }

        public GameObject CreateDynamicWidget(string widgetName, Transform parent, bool worldPosStays)
        {
            GameObject obj = CreateDynamicWidget(widgetName);

            obj.transform.SetParent(parent, worldPosStays);

            return obj;
        }
    }
}

可以這麼說,你看懂了這個腳本,那麼基本上這個框架你就看懂了六成以上。首先,這個腳本也是實現的單例。然後你會發現,我們定義了很多容器,包括棧,集合,字典等等,我的註釋也寫得很清楚他們各自負責的是什麼。讀懂其實是不難的,也沒有什麼複雜的語法,主要就是各個腳本之間相互跳轉可能會有一點繞。但這些看起來麻煩的操作都是爲了一件事:解耦合。不用我多說,大家也都知道解耦合對一個程序來說有多重要。這個腳本我能說的不多,因爲各個模塊的跳轉需要你自己在編譯器上讀代碼的時候自己跳轉看起來更方便。
下面我寫一個簡單的框架使用過程,希望能幫你更好的理解這個框架。

使用該框架

新建兩個文件夾:
在這裏插入圖片描述
方便我們管理我的腳本,這兩個文件夾名稱也很能說明問題,自然一個是放模塊腳本,一個是放控制器腳本的。
我寫了一個MainModule和一個MainController :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UIFrame;

public class MainModule : UIModuleBase {

    private MainController controller;

    protected override void Awake()
    {
        base.Awake();
        controller = new MainController();
        BindController(controller);
    }
}

MainModule繼承了UIModuleBase ,並掛載在UI模塊mainPanel上面。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UIFrame;
using Photon.Pun;
using Hashtable = ExitGames.Client.Photon.Hashtable;

public class MainController : UIControllerBase {

    protected override void ControllerStart()
    {
        base.ControllerStart();

        MainModuleInit();

        BindEvents();

    }

    void MainModuleInit()
    {
        uiModuleBase.GetWidget("PlayerNameInputField_F").SetInputFieldText("Player" + Random.Range(101, 999));

        MonoHelper.Instance.InvokeRepeat(() =>
        {
            uiModuleBase.GetWidget("NetworkingState_F").SetTextText(PhotonNetwork.NetworkClientState.ToString());
        }, 0, () => { return false; });
    }

    void SetPlayerInfo()
    {
        PhotonNetwork.LocalPlayer.NickName = uiModuleBase.GetWidget("PlayerNameInputField_F").GetInputFieldText();

        int teamIndex = uiModuleBase.GetWidget("BattleArrayDropdown_F").GetDropDownValue();

        Hashtable table = new Hashtable();

        table.Add(GameConst.TEAMINDEX, teamIndex);

        PhotonNetwork.LocalPlayer.SetCustomProperties(table);
    }

    void BindEvents()
    {
        uiModuleBase.GetWidget("BattleArrayDropdown_F").OnDropDownValueChange(OnDropDownValueChange);

        uiModuleBase.GetWidget("CreateRoomButton_F").AddOnClickListener(() =>
        {
            SetPlayerInfo();
            UIManager.Instance.PushUI("CreateRoomModule",0);
        });

        uiModuleBase.GetWidget("RandomJoinRoomButton_F").AddOnClickListener(() =>
        {
            SetPlayerInfo();

            Hashtable hashtable = new Hashtable();

            hashtable.Add("Password", "NULL");

            PhotonNetwork.JoinRandomRoom(hashtable,2);
            //UIManager.Instance.PushUI("RoomModule", 0);
        });

        uiModuleBase.GetWidget("RandomLobbyButton_F").AddOnClickListener(() =>
        {
            SetPlayerInfo();

            PhotonNetwork.JoinLobby();

            //UIManager.Instance.PushUI("LobbyModule", 0);
        });

    }

    void OnDropDownValueChange(int value)
    {
        if (value == 0)
        {
            uiModuleBase.GetWidget("BattleArrayDropdown_F").SetImageColor(Color.red);
        }
        else
        {
            uiModuleBase.GetWidget("BattleArrayDropdown_F").SetImageColor(Color.blue);
        }
    }

}

MainController 繼承了UIControllerBase ,並與MainModule 綁定在一起。通過uiModuleBase的GetWidget(string widgetName)方法可以拿到相應的UI元件,並通過UIMono中實現的接口方法完成所要實現的需求。這裏做的是一個網絡對戰遊戲的主界面,所以實現的都是網絡相關功能。也只是一些簡單的添加點擊實現,獲取UI輸入框中的內容,更改UI輸入框中的內容,都是可以一句話就解決,非常方便,也非常酷。
當然,我們還需要在遊戲一開始運行的時候,先自動push出我們的主頁面,所以還需要一個類來啓動框架。
我寫了一個Facade:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UIFrame;

public class Facade : MonoBehaviour {

    private void Start()
    {
        UIManager.Instance.PushUI("MainModule",0);
    }
}

這個腳本很簡單,只是將主模塊在遊戲開始運行時push出來。你也可以有自己的想法。

總結

如果你看到了這裏,那麼恭喜你,你已經對UI框架有了一個最最最基礎的認知。這是一個很簡單的UI框架,代碼量總共也沒多少,真正的框架可比這個複雜的多,也不是一篇博客就能說得清的,如果想深入學習,可以去找找大牛寫的框架,本博客只做入門用,甚至都算不上。希望能對小白有些幫助。一起加油吧!

純手打,碼字不易,如果對你有一絲絲幫助,請點個贊,收個藏,能給個關注就更好了!有不懂得可以私我或者下方評論交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章