Unity——基於UGUI的UI框架


基於UGUI的UI框架


一.Demo展示


二.關鍵類

MonoSingle

繼承MonoBehaviour的單例基類;做了一些特殊處理;

保證場景中必須有GameInit名稱的物體,所有單例管理器腳本都掛在該物體上;

繼承單例基類后,需要私有化構造;

public class MonoSingle<T> : MonoBehaviour where T :MonoSingle<T>
{
    protected static T instance;

    public static T I
    {
        get
        {
            if (instance == null)
            {
                GameObject go = GameObject.Find("GameInit");
                if (go == null)
                {
                    go = new GameObject("GameInit");
                    DontDestroyOnLoad(go);
                }

                instance = go.GetComponent<T>();

                if (instance == null)
                    instance = go.AddComponent<T>();
            }
            return instance;
        }
    }
}

UIType

所有UI的面板都需要在這個類中添加常量字段,方便比對;

public class UIType
{
    public const string UIMain = "panmain";
    public const string UIInventory = "paninventory";
    public const string UIShop = "panshop";
    public const string UIQuest = "panquest";
    public const string UIEquipment = "panequipment";
    public const string UISkill = "panskill";
}

創建UI層級枚舉,根據層級設置UI面板的父節點

public enum UILayer
{
    Back,      //背景層
    Default,   //默認層
    Pop,       //彈窗
    Top        //頂層,適用懸浮等
}

UIBase

所有UI面板的基類;創建新的UI面板必須繼承UIBase同時重寫虛方法

public abstract class UIBase : MonoBehaviour
{
	//層級字段,根據層級設置父節點
    public UILayer uiLayer;
    //UI類型字段
    public string uiType;

	//面板進入時調用
    public virtual void OnEnter()
    {
        //設置父節點
        transform.SetParent(UIManager.I.dicLayer[uiLayer]);
    }

	//面板停止時調用(鼠標與面板的交互停止)
    public virtual void OnPause()
    {
    }

	//面板恢復使用時調用(鼠標與面板的交互恢復)
    public virtual void OnResume()
    {
    }
	
	//面板退出時調用
    public virtual void OnExit()
    {
    }
}

UIManager

//partial拆分類,有點像.h和.cpp的區別,但是只是把一個類分成兩個寫
public partial class UIManager : MonoSingle<UIManager>
{
    //所有UI面板perfab的路徑key:UIType——value:Resources下的路徑
    public Dictionary<string, string> dicPath;
    //根據上面路徑,加載的好的具體的UI面板類
    public Dictionary<string, UIBase> dicPanel;
    //棧存儲所有打開的UI面板,打開UI,push棧,關閉UI,pop棧
    private Stack<UIBase> panelStack;
    //canvas.transform 方便設置父節點
    public Transform canvasTf;
    //存儲UI層級節點,方便設置父節點
    public Dictionary<UILayer, Transform> dicLayer;
    //存儲UI層級對應的名稱,用來加載ui層級節點時命名
    public Dictionary<UILayer, string> dicLayerName;
    
    private UIManager()
    {
        //初始化perfab路徑和ui層級節點
        InitPath();
        InitUILayer();
    }
    
    public void Awake()
    {
        canvasTf = GameObject.Find("Canvas").transform;
        //加載層級節點   
        LoadLayer();
    }
    
    private void LoadLayer()
    {
        dicLayer = new Dictionary<UILayer, Transform>();
        for (int i = 0; i < dicLayerName.Count; ++i)
        {
            GameObject layer = new GameObject(dicLayerName[(UILayer)i]);
            layer.transform.SetParent(canvasTf);
            dicLayer.Add((UILayer) i, layer.transform);
        }
    }

    //獲取dicPanel中存儲的基層UIBase的類,如果為空,先加載添加進去
    private UIBase GetPanel(string panelType)
    {
        if (dicPanel == null)
            dicPanel = new Dictionary<string, UIBase>();

        panelType = panelType.ToLower();
        if (dicPanel.ContainsKey(panelType))
            return dicPanel[panelType];
        else
        {
            string path = string.Empty;
            if (dicPath.ContainsKey(panelType))
                path = dicPath[panelType];
            else
                return null;
            
            GameObject go = Resources.Load<GameObject>(path);
            GameObject goPanel = GameObject.Instantiate(go, canvasTf, false);
            UIBase panel = goPanel.GetComponent<UIBase>();
            dicPanel.Add(panelType, panel);
            return panel;
        }
    }
        
    //打開UI界面
    public void PushPanel(string panelType)
    {
        if (panelStack == null)
        {
            panelStack = new Stack<UIBase>();
        }

        //停止上一個界面
        if (panelStack.Count > 0)
        {
            UIBase top = panelStack.Peek();
            top.OnPause();
        }

        UIBase panel = GetPanel(panelType);
        panelStack.Push(panel);
        panel.OnEnter();
    }

    //關閉最上層界面
    public void PopPanel()
    {
        if (panelStack == null)
        {
            panelStack = new Stack<UIBase>();
        }
        if (panelStack.Count <= 0)
        {
            return;
        }

        //退出棧頂面板
        UIBase top = panelStack.Pop();
        top.OnExit();

        //恢復上一個面板
        if (panelStack.Count > 0)
        {
            UIBase panel = panelStack.Peek();
            panel.OnResume();
        }
    }

    //獲取最上層面板
    public UIBase GetTopPanel()
    {
        if (panelStack.Count > 0)
            return panelStack.Peek();
        else
            return null;
    }
}

使用partial最主要原因是想把加載和邏輯分開,加載部分類可以使用ScriptableObject自動生成或使用json外部讀取;

我這里demo就手動添加了;

public partial class UIManager : MonoSingle<UIManager>
{
    private void InitPath()
    {
        dicPath = new Dictionary<string, string>();
        //自動生成代碼,或使用json加載
        dicPath["panmain"] = "UIPanel/PanMain";
        dicPath["paninventory"] = "UIPanel/paninventory";
        dicPath["panskill"] = "UIPanel/panskill";
        dicPath["panquest"] = "UIPanel/panquest";
        dicPath["panequipment"] = "UIPanel/panequipment";
        dicPath["panshop"] = "UIPanel/panshop";
    }

    private void InitUILayer()
    {
        dicLayerName = new Dictionary<UILayer, string>();
        dicLayerName.Add(UILayer.Back,"Front");
        dicLayerName.Add(UILayer.Default,"Default");
        dicLayerName.Add(UILayer.Pop,"Pop");
        dicLayerName.Add(UILayer.Top,"Top");
    }
}

三.測試用類

Pan開頭類

PanMain主界面,打開后一直顯示,不會隱藏;

public class PanMain : UIBase
{
    PanMain()
    {
        //初始化UIBase中層級和類型字段
        uiLayer = UILayer.Default;
        uiType = UIType.UIMain;
    }

    //打開界面按鈕,公有字段,inspector界面賦值
    public Button btnInventory;
    public Button btnShop;
    public Button btnSkill;
    public Button btnQuest;
    public Button btnEquipment;

    public override void OnEnter()
    {
        base.OnEnter();
        Debug.Log("打開Mian");
        
        //封裝的按鈕添加事件方法,點擊打開或關閉
        AddBtnListener(btnEquipment,UIType.UIEquipment);
        AddBtnListener(btnShop,UIType.UIShop);
        AddBtnListener(btnSkill,UIType.UISkill);
        AddBtnListener(btnQuest,UIType.UIQuest);
        AddBtnListener(btnInventory,UIType.UIInventory);
    }

    private void AddBtnListener(Button go,string type)
    {
        go.onClick.AddListener(() =>
        {
            if (UIManager.I.GetTopPanel().uiType == type)
                UIManager.I.PopPanel();
            else
                UIManager.I.PushPanel(type);
        });
    }

    public override void OnPause()
    {
        //取消下面注釋,打開新界面時,主界面按鈕會失效
        //ChangeBtnState(false);
    }

    public override void OnResume()
    {
        //ChangeBtnState(true);
    }

    public override void OnExit()
    {
    }

    //按鈕失效代碼
    public void ChangeBtnState(bool value)
    {
        btnInventory.enabled = value;
        btnShop.enabled = value;
        btnSkill.enabled = value;
        btnQuest.enabled = value;
        btnEquipment.enabled = value;
    }
}

PanInventory背包,其他shop,quest,equip,skill等邏輯相似,就只分析一個了;

public class PanInventory : UIBase
{
    PanInventory()
    {
        uiLayer = UILayer.Default;
        uiType = UIType.UIInventory;
    }

    //關閉界面按鈕
    public Button btnClose;

    public void Start()
    {
        btnClose.onClick.AddListener(() => { UIManager.I.PopPanel(); });
    }
    
    public override void OnEnter()
    {
       base.OnEnter();
       this.gameObject.SetActive(true);
    }

    //非頂層時按鈕是否失效,可自行設置;
    public override void OnPause()
    {
        //btnClose.enabled = false;
    }

    public override void OnResume()
    {
        //btnClose.enabled = true;
    }

    public override void OnExit()
    {
        this.gameObject.SetActive(false);
    }
}

GameInit

public class GameInit : MonoSingle<GameInit>
{
    private void Awake()
    {
        DontDestroyOnLoad(this.gameObject);
        //打開主界面
        UIManager.I.PushPanel(UIType.UIMain);
    }
}

運行前:

運行后:

由於目前硬件性能和內存完全夠用的情況下,所有UI面板加載一次后不會被銷毀,只會被隱藏;

該Demo已被我上傳至Gitee,可自行下載學習;

https://gitee.com/small-perilla/uiframe-demo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM