[转载+改进]基于C#的一套Unity UI框架

907 阅读4分钟

本UI框架源码大部分来自于siki老师的教程,该框架较简单,目的是方便初学者理解,若需应用于实际开发需进一步完善

前期准备

Unity(版本不限)、litjson(密码:hs80)——JSON解析插件

编写框架

  1. 将已下载的litjson.dll拖入Unity工程中的任意位置

  2. 新建Resources文件夹(位置可随意),在此文件夹中存放空预制体,表示各个UI面板,例如:

  3. 新建UIPanelTypeJson.json,置于Resources文件夹下(后期需加载)

{
	"panelInfoList":
	[
		{"panelType":"MainMenu","path":"UIPanel/MainMenuPanel"},
		{"panelType":"PageOne","path":"UIPanel/PageOnePanel"},
		{"panelType":"PageTwo","path":"UIPanel/PageTwoPanel"},
		{"panelType":"PageThree","path":"UIPanel/PageThreePanel"}
	]
}
  1. 新建UIPanelType.cs
public class UIPanelType
{
    public const string MainMenu = "MainMenu";
    public const string PageOne = "PageOne";
    public const string PageTwo = "PageTwo";
    public const string PageThree = "PageThree";
}
  1. 新建BasePanel.cs
public abstract class BasePanel : MonoBehaviour
{
    public abstract void OnEnter();
    public abstract void OnPause();
    public abstract void OnResume();
    public abstract void OnExit();
}
  1. 新建DictTool.cs
using System.Collections.Generic;
public static class DictTool
{
    public static Tvalue GetValue<Tkey, Tvalue>(this Dictionary<Tkey, Tvalue> dict, Tkey key)
    {
        Tvalue value = default(Tvalue);
        dict.TryGetValue(key, out value);
        return value;
    }
}
  1. 新建UIPanelInfo.cs
using System;
[Serializable]
public class UIPanelInfo
{
    public string panelType;
    public string path;

    public UIPanelInfo()
    {

    }
}
  1. 新建UIPanelInfoList.cs
using System;
using System.Collections.Generic;

[Serializable]
public class UIPanelInfoList
{
    public List<UIPanelInfo> panelInfoList;

    public UIPanelInfoList() { }
}
  1. 新建UIPanelManager.cs
using System.Collections.Generic;
using System.Collections;
using System;
using UnityEngine;
using LitJson;
public class UIPanelManager
{
    private static UIPanelManager _instance;
    private Transform canvasTransform;
    private Transform CanvasTransform
    {
        get
        {
            if (canvasTransform == null)
            {
                canvasTransform = GameObject.Find("Canvas").transform;
            }
            return canvasTransform;
        }
    }
    public static UIPanelManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new UIPanelManager();
            }


            return _instance;
        }
    }

    private Dictionary<string, string> panelPathDict;
    private Dictionary<string, BasePanel> panelDict;
    private Stack<BasePanel> panelStack;

    private UIPanelManager()
    {
        ParseUIPanelTypeJson();
    }

    public void PushPanel(string panelType)
    {
        if (panelStack == null)
        {
            panelStack = new Stack<BasePanel>();
        }

        //停止上一个界面
        if (panelStack.Count > 0)
        {
            BasePanel topPanel = panelStack.Peek();
            topPanel.OnPause();
        }

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

    public void PopPanel()
    {
        if (panelStack == null)
        {
            panelStack = new Stack<BasePanel>();
        }
        if (panelStack.Count <= 0)
        {
            return;
        }

        //退出栈顶面板
        BasePanel topPanel = panelStack.Pop();
        topPanel.OnExit();

        GameObject.Destroy(topPanel.gameObject);
        //panelDict.Remove();

        //恢复上一个面板
        if (panelStack.Count > 0)
        {
            BasePanel panel = panelStack.Peek();
            panel.OnResume();
        }

    }

    private BasePanel GetPanel(string panelType)
    {

        if (panelDict == null)
        {
            panelDict = new Dictionary<string, BasePanel>();
        }

        BasePanel panel = panelDict.GetValue(panelType);

        //如果没有实例化面板,寻找路径进行实例化,并且存储到已经实例化好的字典面板中
        if (panel == null)
        {
            string path = panelPathDict.GetValue(panelType);
            GameObject panelGo = GameObject.Instantiate(Resources.Load<GameObject>(path), CanvasTransform, false);
            panel = panelGo.GetComponent<BasePanel>();
            if (! panelDict.ContainsKey(panelType))
            {
                panelDict.Add(panelType, panel);
            }
        }
        return panel;
    }

    //解析json文件
    private void ParseUIPanelTypeJson()
    {
        panelPathDict = new Dictionary<string, string>();
        TextAsset textUIPanelType = Resources.Load<TextAsset>("UIPanelTypeJson");
        UIPanelInfoList panelInfoList = JsonMapper.ToObject<UIPanelInfoList>(textUIPanelType.text);

        foreach (UIPanelInfo panelInfo in panelInfoList.panelInfoList)
        {
            panelPathDict.Add(panelInfo.panelType, panelInfo.path);
            //Debug.Log(panelInfo.panelType + ":" + panelInfo.path);
        }
    }
}
  1. 新建GameRoot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class GameRoot : MonoBehaviour
{
    void Start()
    {
        UIPanelManager panelManager = UIPanelManager.Instance;
        panelManager.PushPanel(UIPanelType.MainMenu);
    }
}
  1. 新建各个面板的逻辑脚本,例如MainMenuPanel.cs,仿照此再新建PageOnePanel.cs、PageTwoPanel.cs、PageThreePanel.cs
using UnityEngine;

public class MainMenuPanel : BasePanel
{

    public override void OnEnter()
    {
    }

    public override void OnPause()
    {
    }

    public override void OnResume()
    {
    }

    public override void OnExit()
    {
    }
}
  1. 根据需求为第二步创建的预制体添加内容 Tips: 若使用UGUI来创建UI界面,则需事先在Project Settings/Editor中设置UI Enviroment,作用是设置打开预设时加载的场景,该场景可以是带有空Canvas的场景,注意点如下:
  • 直接在空预制体中添加UI元素,会导致创建新的Canvas,而代码中实例化预制体时位置会错乱,所以需要提前设置UI Enviroment
  • 设置好后,双击打开空预制体,修改组件Transform为Rect Transform(因为以上设置只对包含Rect Transform组件的预制体生效),添加任意UI元素,再次双击打开该预制体,删除原UI元素,接下来放置所需的UI元素即可
  1. 为以上预制体的根节点绑定对应的脚本(第11步)
  2. 测试脚本GameMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GameMgr : MonoBehaviour
{
    public Button enterMainBtn, enterOneBtn, enterTwoBtn, enterThreeBtn;
    public Button exitPanelBtn;

    // Start is called before the first frame update
    void Start()
    {
        enterMainBtn.onClick.AddListener(enterMainClick);
        enterOneBtn.onClick.AddListener(enterOneClick);
        enterTwoBtn.onClick.AddListener(enterTwoClick);
        enterThreeBtn.onClick.AddListener(enterThreeClick);

        exitPanelBtn.onClick.AddListener(exitPanelClick);
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    // 加载页面
    private void enterMainClick()
    {
        UIPanelManager.Instance.PushPanel("MainMenu");
    }

    private void enterOneClick()
    {
        UIPanelManager.Instance.PushPanel("PageOne");
    }

    private void enterTwoClick()
    {
        UIPanelManager.Instance.PushPanel("PageTwo");
    }

    private void enterThreeClick()
    {
        UIPanelManager.Instance.PushPanel("PageThree");
    }

    // 退出页面
    private void exitPanelClick()
    {
        UIPanelManager.Instance.PopPanel();
    }
}

框架源码

其实对着以上步骤就可实现一个简单版的UI框架,若无法实现,可以参考本人亲测有效的源码,已打包为.unitypackage格式,下载链接如下:

kkx.lanzous.com/ieuf9k11r5e 密码:3x22

进一步改进

  1. 基类BasePanel中的修饰符由abstract修改为virtual

具体改法:原来是抽象类,删去abstract,变为普通类;原本定义的都是抽象方法,修改为虚函数

好处:继承自抽象类的继承类,必须重写所有的抽象方法,这样即使用不上基类的方法,也必须重写就显得没有必要,且后期若丰富基类的方法,那所有已写的继承类都必须新增该方法,徒增工作量;虚函数也能满足要求,若继承类没有重写对应的虚函数,则从基类调用

BasePanel.cs

public class BasePanel : MonoBehaviour
{
    public virtual void OnEnter() {}
    public virtual void OnPause() {}
    public virtual void OnResume() {}
    public virtual void OnExit() {}
}
  1. 新增打开和关闭界面的方法(当前仅显示一个页面,隐藏其他页面)

具体改法:

(1)基类新增两个虚函数

BasePanel.cs

public class BasePanel : MonoBehaviour
{
    public virtual void OnHiden() {}
    public virtual void OnShow() {}
}

(2)UI管理类新增方法

UIPanelManager.cs

// 打开页面,隐藏之前的页面
public void OpenPanel(string panelType)
{
    if (panelStack == null)
    {
        panelStack = new Stack<BasePanel>();
    }

    if (panelStack.Count > 0)
    {
        BasePanel topPanel = panelStack.Peek();
        topPanel.OnHiden();
        topPanel.gameObject.SetActive(false);
    }

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

// 关闭页面,重写显示上一个页面
public void PopPanel()
{
    if (panelStack == null)
    {
        panelStack = new Stack<BasePanel>();
    }
    if (panelStack.Count <= 0)
    {
        return;
    }

    BasePanel topPanel = panelStack.Pop();
    topPanel.OnExit();

    GameObject.Destroy(topPanel.gameObject);

    if (panelStack.Count > 0)
    {
        BasePanel panel = panelStack.Peek();
        panel.OnShow();
        panel.gameObject.SetActive(true);
    }
}