1. 程式人生 > >Unity手遊框架 之 介面管理(一)

Unity手遊框架 之 介面管理(一)

引子

介面管理是做什麼用的?為了回答這個問題讓我們先來羅列與介面操作相關的程式碼:

  • 在網路訊息的回掉函式中我們發現了對介面的顯示、隱藏及遮擋關係調整的操作。
public static void OnPacket_UserLogon(object pack)
{
    ... ...
    Debug.Log("Logon Succeed");
    Gamedata.me.userinfo = userLogon.UserInfo;
    PanelManager.me.HideAll(); // 介面的隱藏
    PanelManager.me.Get((int)PanelId.PanelMain).Show(); // 介面的顯示
    PanelManager.me.Get((int)PanelId.PanelMain).ToTop(); // 調整介面到最前方    
    ...
... }
  • 在介面的初始化函式中我們發現了獲取介面控制元件的操作
public class PanelLogon : PanelBase
{
    InputField username; // 使用者名稱
    InputField password; // 密碼
    public override void Init()
    {
        ... ...
        username = controls.GetInputField("InputFieldUsername");
        password = controls.GetInputField("InputFieldPassword"
); ... ... } }
  • 在遊戲初始化階段,我們發現了為向PanelManager中註冊Prfab的操作。
PanelManager.me.RegistPrefab((int)PanelId.PanelLogon, AssetManager.me.GetPanel("PanelLogon"));
PanelManager.me.RegistPrefab((int)PanelId.PanelMain, AssetManager.me.GetPanel("PanelMain"));
  • 在某介面中我們發現了事件處理函式。
void ProcessProgress(EventProgress evt)
{
    int
intProgress = (int)(evt.progress * 100); txProcess.text = intProgress.ToString(); }

整理以上羅列出的程式碼,給出本文的介面管理的內容:

  • 介面的顯示、隱藏、遮擋關係。
  • 介面的載入與解除安裝。
  • 介面控制元件的獲得。
  • 介面中的事件響應。
  • 除以上在程式碼中羅列的內容外,本文還加入了部分介面管理與熱跟新融合的部分內容。

本文的思路: 整理分析常見的介面管理的資料結構和遮擋關係管理方法,並給出本文采用的遮擋關係。整理分析常見的控制元件獲得方式,給出本文采用的控制元件獲取方式。講解PanelBase和PanelManager中的主要函式來理解介面的顯示、隱藏、載入及解除安裝的實現。介面中全域性事件的相應會放到事件系統中講解,最後附錄中給出本文的完整的原始碼。

介面管理的資料結構和遮擋關係的管理方法

介面管理的資料結構一般有兩中:棧結構和對結構。
* 棧結構為基礎的介面管理。
在這種方式中介面的顯示相當與如棧,而介面的隱藏則相當於出棧。這種方式的好處是結構清晰、遮擋關係的管理簡單並天生就有介面顯示流的逆向追蹤能力。當顯示一個介面時,將這個介面放入棧頂並顯示,處於棧頂的介面永遠處於遮擋關係的最前方。當隱藏一個介面是將從這個介面到棧頂的介面一律出棧並隱藏。這種方式的不足是經常不能滿足策劃對界管理的要求,於是加入各種補丁使得原本清晰簡單的結構變的複雜而混亂。
* 以堆結構為基礎的介面管理。
這種方式在實現遮擋關係管理上要比棧結構複雜,要實現諸如ToTop、ToBottom、MoveForward、MoveBackward、SetQueueIndex等控制遮擋關係的函式。

本文實現的是閹割版的堆結構為基礎的介面管理,即僅實現了眾多遮擋控制函式中的ToTop,在使用上顯示即ToTop。另外,有些複雜的介面管理還實現了介面顯示流的逆向追蹤,這個也不在本文的討論範圍。

介面中控制元件的取得

常見的控制元件獲取的三種方式
* 拖拽式。 這種方式大概是這樣實現的, 首先在程式碼中宣告控制元件變數,在製作Panel的prefab時將相應的控制元件拖拽到prefab上。 程式碼中大概是這樣的:

class PanelHero : Behaviour
{
    public Button btnUpLevel;
    public Button btnUpStar;
    public void Awake()
    {
        btnUpLevel.onClick.AddListener(OnUpLevelClick);
        btnUpStar.OnClick.AddListener(OnUpStarClick);
    }
    ... ...
}
  • 路徑查詢式。這中方式與第一中方式類似, 但在製作介面的Prefab時不需要將控制元件拖拽到相應的變數上,在載入介面是由程式通過路徑查詢的方式找到相應的空間, 程式碼大概是這樣的。
class PanelHero : Behaviour
{
    public Button btnUpLevel;
    public Button btnUpStar;
    public void Awake()
    {
        btnUpLevel = transform.Find("HeroLevel/ButtonUpLevel").GetComponent<Button>();
        btnUpStar = trnaform.Find("HeroQualityAndStar/ButtonUpStar").GetComponent<Button>();
        btnUpLevel.onClick.AddListener(OnUpLevelClick);
        btnUpStar.OnClick.AddListener(OnUpStarClick);
    }
    ... ...
}
  • 註冊式。 這種方式需要兩個類, Register和Container, 在製作控制元件時將Container置於Panel上, 將Register置於要與程式產生關聯的控制元件上, 在程式中使用使用首先得到Container,然後在Container中取得相應的控制元件。 程式碼如下
    // 控制元件註冊器
    class UiControlRegistor : MonoBehaviour
    {
        // 指定的目標容器
        public UiControlContainer PointedContainer = null;
        public virtual void Awake()
        {
            container.RegistObject(name, gameObject); // 註冊本控制元件
        }

        // 得到控制元件的目標容器
        public UiControlContainer container
        {
            // 如果制定了目標容器就返回制定的目標容器,
            // 否則依次向跟節點查詢容器, 返回路徑上的第一個容器作為目標容器
            get { return PointedContainer ? PointedContainer :
             transform.GetComponentInParent<UiControlContainer>(); }
        }
    }
    // 控制元件容器
    class UiControlContainer : MonoBehaviour
    {
        // GameObject 型別的容器
        Dictionary<string, GameObject> objects = new Dictionary<string, GameObject>();

        // 泛型的控制元件註冊函式
        bool RegistT<T>(Dictionary<string, T> datas, string name, T unit)
        {
            if (datas.ContainsKey(name))
            {
                Debug.LogError("name(" + name + ") already exist");
                return false;
            }
            datas.Add(name, unit);
            return true;
        }

        // GameObject型別的控制元件註冊函式
        public bool RegistObject(string name, GameObject obj)
        {
            return RegistT(objects, name, obj);
        }
        ... ...
    }

下面給出這三種方案的對比:
第一種方案(拖拽式)的優點, 簡潔,直觀,效率高。缺點prefab與對應的指令碼關聯行強,難於分離指令碼開發與perfab製作,很難支援lua熱更新。
第二種方案(路徑查詢式)的優點,簡潔,直觀, prefab的製作不再依賴與指令碼開發。缺點查詢的效率低,指令碼開發依舊依賴與prefab製作。這種方案可以將prefab的製作人員與指令碼開發人員分離,但每次修改prefab後腳本依舊要緊隨其後的修改。這種方案可以支援Lua熱跟新。
第三種方案(註冊式)優點prefab的製作與指令碼開發很好的分離,很好的支援lua熱跟新。效率介於前兩種方案之間,缺點是結構稍顯複雜。本文采用的方案是第三種方案。

PanelBase的主要函式

PanelBase是介面的基類, 主要的介面有Init,Show, Hide, Refresh,與層次相關的介面前面已經提到過了,本文采用的是閹割版,因此在這個類中看不到遮擋關係系列的函式。對於介面控制元件的響應函式應該放到具體的Panel裡,對於全域性事件的相應放到事件系統中講解。

/// <summary>
/// 介面的基類
/// 定義了諸如Init、Show、Hide、Refresh等可重寫的流程函式
/// </summary>
[RequireComponent(typeof(UiControlContainer))]
public class PanelBase : MonoBehaviour
{        
    /// <summary>
    /// 控制元件容器
    /// </summary>
    public UiControlContainer controls { get { return GetComponent<UiControlContainer>();  } }

    /// <summary>
    /// 所有SubPanel
    /// </summary>
    public PanelBase[] subPanels { get { return controls.panelbases.Values.ToArray<PanelBase>(); } }

    /// <summary>
    /// 根據名字獲取Button控制元件
    /// </summary>
    /// <param name="name">Button的名字</param>
    /// <returns>Button控制元件</returns>
    public virtual void Init()
    {
        foreach (PanelBase subPanel in subPanels)
        {
            bool active = gameObject.activeSelf;
            gameObject.SetActive(true);
            foreach (PanelBase subPanel in subPanels)
            {
                subPanel.Init();
            }
            gameObject.SetActive(active);
        }
    }

    /// <summary>
    /// 顯示介面
    /// </summary>
    public virtual void Show()
    {
        gameObject.SetActive(true);
    }

    /// <summary>
    /// 隱藏介面
    /// </summary>
    public virtual void Hide()
    {
        gameObject.SetActive(false);
    }

/// <summary>
/// 重新整理介面
/// </summary>
    public virtual void Refresh()
    {
    }
}

PanelManager的主要函式

PanelManager是介面管理的核心類, 主要函式有RegistPrefab、Get、HideAll、RefreshAll等

public class PanelManager : Singleton<PanelManager>
{
    // 介面的例項化函式
    public delegate PanelBase FuncInstanceUiBase(PanelBase prefab);
    public FuncInstanceUiBase InstanceUiBase;

    // prefab 容器
    private readonly Dictionary<int, PanelBase> prefabs = new Dictionary<int, PanelBase>();

    // 已經載入的介面容器
    private readonly Dictionary<int, PanelBase> uis = new Dictionary<int, PanelBase>();

    /// <summary>
    ///  註冊介面的Prefab
    /// </summary>
    /// <param name="uiid">介面的型別id</param>
    /// <param name="prefab">介面的prefab</param>
    public void RegistPrefab(int uiid, PanelBase prefab)
    {   
        prefabs.Add(uiid, prefab);
    }

    /// <summary>
    /// 獲取介面
    /// </summary>
    /// <param name="uiid">介面的型別id</param>
    /// <returns></returns>
    public PanelBase Get(int uiid)
    {
        if (uis.ContainsKey(uiid))
            return uis[uiid];

        if (prefabs.ContainsKey(uiid))
        {
            var ui = InstanceUiBase(prefabs[uiid]);
            uis.Add(uiid, ui);  
            ui.gameObject.SetActive(true);              
            ui.name = ui.name.Substring(0, ui.name.IndexOf("(Clone)"));                
            ToTop(ui);
            ui.Init();
            AdjustPanelPosition(ui);
        }

        return null;
    }

    /// <summary>
    /// 隱藏所有介面
    /// </summary>
    public void HideAll()
    {
        foreach (var ui in uis.Values)
            ui.Hide();
    }

    /// <summary>
    /// 顯示所有介面
    /// </summary>
    public void RefreshAll()
    {
        foreach (var ui in uis.Values)
            ui.Refresh();
    }
}

小結

本文從常見的介面相關的原始碼出發,提出了介面管理的基礎功能,並重點講解了介面控制元件的獲得和介面管理的資料結構。有關介面管理的高階部分,例如介面的過度動畫,介面切換觸發的功能(例如新手引導),介面的導航與追蹤等部分將在下一篇介面管理的文章中給出。

附:完整原始碼

// PanelBase.cs
// Author: Iann

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using System.Reflection;

namespace Assets.Script.Frame
{
    /// <summary>
    /// 介面的基類
    /// 定義了諸如Init、Show、Hide、Refresh等可重寫的流程函式
    /// </summary>
    [RequireComponent(typeof(UiControlContainer))]
    public class PanelBase : MonoBehaviour
    {
        /// <summary>
        /// 控制元件容器
        /// </summary>
        public UiControlContainer controls { get { return GetComponent<UiControlContainer>();  } }

        /// <summary>
        /// 所有SubPanel
        /// </summary>
        public PanelBase[] subPanels { get { return controls.panelbases.Values.ToArray<PanelBase>(); } }

        /// <summary>
        /// 根據名字獲取Button控制元件
        /// </summary>
        /// <param name="name">Button的名字</param>
        /// <returns>Button控制元件</returns>
        public virtual Button GetButton(string name)
        {
            return controls.GetButton(name);
        }

        /// <summary>
        /// 根據名字獲取Button控制元件
        /// </summary>
        /// <param name="name">Button的名字</param>
        /// <returns>Button控制元件</returns>
        public virtual Text GetText(string name)
        {
            return controls.GetText(name);
        }

        /// <summary>
        /// 根據名字獲取Image控制元件
        /// </summary>
        /// <param name="name">Image的名字</param>
        /// <returns>Image控制元件</returns>
        public virtual Image GetImage(string name)
        {
            return controls.GetImage(name);
        }

        /// <summary>
        /// 根據名字獲取SubPanel控制元件
        /// </summary>
        /// <param name="name">SubPanel的名字</param>
        /// <returns>SubPanel控制元件</returns>
        public virtual PanelBase GetSubPanel(string name)
        {
            return controls.GetPanelBase(name);
        }

        /// <summary>
        /// 介面的初始化函式
        /// 一般過載這個函式的主要目的是初始化介面控制元件
        /// 注意控制元件的註冊是在Awake函式中進行的,保證在呼叫container.get的時候控制元件註冊器的Awake已經呼叫
        /// </summary>
        public virtual void Init()
        {
#if USE_LOG && LOG_FRAME_UI
            Debug.Log("Init Panel " + name + ".");
#endif

            bool active = gameObject.activeSelf;
            gameObject.SetActive(true);  // 確保初始化前UiControlRegister的Awake已經呼叫
            foreach (PanelBase subPanel in subPanels)
            {
                subPanel.Init();
            }
            gameObject.SetActive(active);
        }

        /// <summary>
        /// 顯示介面
        /// </summary>
        public virtual void Show()
        {
#if USE_LOG && LOG_FRAME_UI
            Debug.Log("PaneBase(" + name + ").Show");
#endif
            gameObject.SetActive(true);
            //OnShow();
            Refresh();
        }

        /// <summary>
        /// 隱藏介面
        /// </summary>
        public virtual void Hide()
        {
#if USE_LOG && LOG_FRAME_UI
            Debug.Log("PaneBase(" + name + ").Hide");
#endif
            gameObject.SetActive(false);
        }

        /// <summary>
        /// 重新整理介面
        /// </summary>
        public virtual void Refresh()
        {
#if USE_LOG && LOG_FRAME_UI
            Debug.Log("PanelBase(" + name + ").Refresh");
#endif
        }

    }


}
// PanelManager.cs
// Author: Iann

using System.Collections.Generic;
using UnityEngine;

namespace Assets.Script.Frame
{
    /// <summary>
    /// 介面管理類
    /// 本類提供了介面的載入,顯示,隱藏,(關閉)功能
    /// 動畫過度暫未實現
    /// 導航功能暫未實現
    /// </summary>
    public class PanelManager : Singleton<PanelManager>
    {
        // 介面的例項化函式
        public delegate PanelBase FuncInstanceUiBase(PanelBase prefab);
        public FuncInstanceUiBase InstanceUiBase;

        // prefab 容器
        private readonly Dictionary<int, PanelBase> prefabs = new Dictionary<int, PanelBase>();

        // 已經載入的介面容器
        private readonly Dictionary<int, PanelBase> uis = new Dictionary<int, PanelBase>();


        public void Regist(int uiid, PanelBase ui)
        {
            //types.Add(typeof(T), uiid);
            uis.Add(uiid, ui);
        }

        /// <summary>
        ///  註冊介面的Prefab
        /// </summary>
        /// <param name="uiid">介面的型別id</param>
        /// <param name="prefab">介面的prefab</param>
        public void RegistPrefab(int uiid, PanelBase prefab)
        {
            if (prefab == null)
            {
                Debug.LogError(string.Format("PanelManager.RegistPrefab({0},{1}) Failed !!!",
                     uiid, prefab));
                return;
            }

#if USE_LOG
            Debug.Log(string.Format("PanelManager.RegistPrefab({0},{1}", uiid, prefab));
#endif 

            prefabs.Add(uiid, prefab);
        }

        /// <summary>
        /// 獲取介面
        /// </summary>
        /// <param name="uiid">介面的型別id</param>
        /// <returns></returns>
        public PanelBase Get(int uiid)
        {
            //如果介面已經載入過, 返回已經載入過的介面
            if (uis.ContainsKey(uiid))
                return uis[uiid];

            // 載入介面
            if (prefabs.ContainsKey(uiid))
            {
                if (prefabs[uiid] == null)
                {
                    Debug.LogError((PanelId)uiid);
                }
                var ui = InstanceUiBase(prefabs[uiid]);
                uis.Add(uiid, ui);
                ui.gameObject.SetActive(true); // 確保初始化前UiControlRegister的Awake已經呼叫
                ui.name = ui.name.Substring(0, ui.name.IndexOf("(Clone)"));                
                ToTop(ui);
                ui.Init();
                AdjustPanelPosition(ui);
                return ui;
            }

            return null;
        }

        /// <summary>
        /// 調整Panel的位置佈局
        /// </summary>
        /// <param name="ui">待調整的Panel</param>
        void AdjustPanelPosition(PanelBase ui)
        {
            ui.GetComponent<RectTransform>().SetParent(GameObject.Find("Canvas").transform);
            var rt = ui.GetComponent<RectTransform>();
            var op = ui.GetComponent<RectTransformAdjustor>();
            if (op != null)
            {
                op.Adjust();
            }
            else
            {
                ui.transform.localPosition = Vector3.zero;
                ui.transform.localScale = Vector3.one;
            }
        }

        /// <summary>
        /// 將Panel放到最前面
        /// </summary>
        /// <param name="ui">待調整的Panel</param>
        void ToTop(PanelBase ui)
        {
            ui.transform.SetAsLastSibling();
        }

        /// <summary>
        /// 隱藏所有介面
        /// </summary>
        public void HideAll()
        {
            foreach (var ui in uis.Values)
                ui.Hide();
        }

        /// <summary>
        /// 顯示所有介面
        /// </summary>
        public void RefreshAll()
        {
            foreach (var ui in uis.Values)
                ui.Refresh();
        }
    }
}
// UiControlContainer.cs
// Author: Iann


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

namespace Assets.Script.Frame
{
    public class UiControlContainer : MonoBehaviour
    {
        private readonly Dictionary<string, Button> buttons = new Dictionary<string, Button>();
        private readonly Dictionary<string, Image> images = new Dictionary<string, Image>();
        private readonly Dictionary<string, InputField> inputs = new Dictionary<string, InputField>();
        private readonly Dictionary<string, Text> texts = new Dictionary<string, Text>();
        private readonly Dictionary<string, Slider> sliders = new Dictionary<string, Slider>();
        private readonly Dictionary<string, GameObject> objects = new Dictionary<string, GameObject>();
        public Dictionary<string, PanelBase> panelbases = new Dictionary<string, PanelBase>();

        private bool RegistT<T>(Dictionary<string, T> datas, string name, T unit)
        {
            if (datas.ContainsKey(name))
            {
                Debug.LogError("name(" + name + ") already exist");
                return false;
            }
            datas.Add(name, unit);
            return true;
        }

        private bool UnRegistT<T>(Dictionary<string, T> datas, string name)
        {
            if (!datas.ContainsKey(name))
            {
                Debug.LogError("name not exist");
                return false;
            }
            datas.Remove(name);
            return true;
        }

        // Object
        public bool RegistObject(string name, GameObject obj)
        {
            return RegistT(objects, name, obj);
        }

        public bool UnRegistObject(string name)
        {
            return UnRegistT(objects, name);
        }

        public GameObject GetObject(string name)
        {
            if (objects.ContainsKey(name))
                return objects[name];
            return null;
        }

        // Button
        public bool RegistButton(string name, Button button)
        {
            return RegistT(buttons, name, button);
        }

        public bool UnRegistButton(string name)
        {
            return UnRegistT(buttons, name);
        }

        public Button GetButton(string name)
        {
            if (buttons.ContainsKey(name))
                return buttons[name];
            return null;
        }


        // Text
        public bool RegistText(string name, Text text)
        {
            return RegistT(texts, name, text);
        }

        public bool UnRegistText(string name)
        {
            return UnRegistT(texts, name);
        }

        public Text GetText(string name)
        {
            if (texts.ContainsKey(name))
                return texts[name];
            return null;
        }

        // Slider
        public bool RegistSlider(string name, Slider slid)
        {
            return RegistT(sliders, name, slid);
        }

        public bool UnRegistSlider(string name)
        {
            return UnRegistT(sliders, name);
        }

        public Slider GetSlider(string name)
        {
            if (sliders.ContainsKey(name))
                return sliders[name];
            return null;
        }

        // Image 
        public bool RegistImage(string name, Image image)
        {
            return RegistT(images, name, image);
        }

        public bool UnRegistImage(string name)
        {
            return UnRegistT(images, name);
        }

        public Image GetImage(string name)
        {
            if (images.ContainsKey(name))
                return images[name];
            return null;
        }


        // ImputFiled 
        public bool RegistInputField(string name, InputField inputFiled)
        {
            return RegistT(inputs, name, inputFiled);
        }

        public bool UnRegistInputField(string name)
        {
            return UnRegistT(inputs, name);
        }

        public InputField GetInputField(string name)
        {
            if (inputs.ContainsKey(name))
                return inputs[name];
            return null;
        }

        // PanelBase
        public bool RegistPanelBase(string name, PanelBase panelBase)
        {
            return RegistT(panelbases, name, panelBase);
        }

        public bool UnRegistPanelBase(string name)
        {
            return UnRegistT(panelbases, name);
        }

        public PanelBase GetPanelBase(string name)
        {
            if (panelbases.ContainsKey(name))
                return panelbases[name];
            return null;
        }
    }

}
// UiControlRegistor.cs
// Author: Iann


using UnityEngine;


namespace Assets.Script.Frame
{

    /// <summary>
    /// 介面控制元件註冊器的基類
    /// </summary>
    class UiControlRegistor : MonoBehaviour
    {
        /// <summary>
        /// 指定的介面控制元件容器
        /// </summary>
        public UiControlContainer PointedContainer = null;

        /// <summary>
        /// 註冊後立即刪除本註冊器, 用於多次載入的選項
        /// </summary>
        public bool removeThisWhenAwake = false;

        /// <summary>
        /// 喚醒函式, 我們的註冊就是在這個函式中實現的
        /// </summary>
        public virtual void Awake()
        {
#if USE_LOG && LOG_FRAME_UI
            Debug.Log("UiBehaviour(" + name + ").Awake");
#endif
            container.RegistObject(name, gameObject);
            if (removeThisWhenAwake)
            {
                DestroyImmediate(this);
            }
        }

        /// <summary>
        /// 銷燬函式, 反註冊寫在這個函式中
        /// </summary>
        public virtual void Destroy()
        {
#if USE_LOG && LOG_FRAME_UI
            Debug.Log("UiBehaviour(" + name + ").Destroy");
#endif
            if (!removeThisWhenAwake)
                container.UnRegistObject(name);
        }

        /// <summary>
        ///  如果制定了目標容器就返回制定的目標容器, 
        ///  否則依次向跟節點查詢容器, 返回路徑上的第一個容器作為目標容器
        /// </summary>
        public UiControlContainer container { get { return PointedContainer ? 
            PointedContainer : transform.GetComponentInParent<UiControlContainer>(); } }
    }
}
// UiControlContainer.cs
// Author: Iann


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

namespace Assets.Script.Frame
{
    /// <summary>
    /// 控制元件容器
    /// </summary>
    public class UiControlContainer : MonoBehaviour
    {
        //各種型別的器皿
        private Dictionary<string, Button> buttons = new Dictionary<string, Button>();
        private Dictionary<string, Image> images = new Dictionary<string, Image>();
        private Dictionary<string, InputField> inputs = new Dictionary<string, InputField>();
        private Dictionary<string, Text> texts = new Dictionary<string, Text>();
        private Dictionary<string, Slider> sliders = new Dictionary<string, Slider>();
        private Dictionary<string, GameObject> objects = new Dictionary<string, GameObject>();
        private Dictionary<string, PanelBase> panelbases = new Dictionary<string, PanelBase>();

        /// <summary>
        /// 泛型的註冊函式
        /// </summary>
        /// <typeparam name="T">註冊的型別</typeparam>
        /// <param name="datas">註冊用到的器皿</param>
        /// <param name="name">控制元件名稱</param>
        /// <param name="unit">控制元件Instance</param>
        /// <returns></returns>
        private bool RegistT<T>(Dictionary<string, T> datas, string name, T unit)
        {
            if (datas.ContainsKey(name))
            {
                Debug.LogError("name(" + name + ") already exist");
                return false;
            }
            datas.Add(name, unit);
            return true;
        }

        /// <summary>
        /// 泛型的反註冊函式
        /// </summary>
        /// <typeparam name="T">反註冊的型別</typeparam>
        /// <param name="datas">反註冊用到的器皿</param>
        /// <param name="name">控制元件名稱</param>
        /// <returns></returns>
        private bool UnRegistT<T>(Dictionary<string, T> datas, string name)
        {
            if (!datas.ContainsKey(name))
            {
                Debug.LogError("name not exist"<