本UI框架源码大部分来自于siki老师的教程,该框架较简单,目的是方便初学者理解,若需应用于实际开发需进一步完善
前期准备
Unity(版本不限)、litjson(密码:hs80)——JSON解析插件
编写框架
-
将已下载的litjson.dll拖入Unity工程中的任意位置
-
新建Resources文件夹(位置可随意),在此文件夹中存放空预制体,表示各个UI面板,例如:
-
新建UIPanelTypeJson.json,置于Resources文件夹下(后期需加载)
{
"panelInfoList":
[
{"panelType":"MainMenu","path":"UIPanel/MainMenuPanel"},
{"panelType":"PageOne","path":"UIPanel/PageOnePanel"},
{"panelType":"PageTwo","path":"UIPanel/PageTwoPanel"},
{"panelType":"PageThree","path":"UIPanel/PageThreePanel"}
]
}
- 新建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";
}
- 新建BasePanel.cs
public abstract class BasePanel : MonoBehaviour
{
public abstract void OnEnter();
public abstract void OnPause();
public abstract void OnResume();
public abstract void OnExit();
}
- 新建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;
}
}
- 新建UIPanelInfo.cs
using System;
[Serializable]
public class UIPanelInfo
{
public string panelType;
public string path;
public UIPanelInfo()
{
}
}
- 新建UIPanelInfoList.cs
using System;
using System.Collections.Generic;
[Serializable]
public class UIPanelInfoList
{
public List<UIPanelInfo> panelInfoList;
public UIPanelInfoList() { }
}
- 新建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);
}
}
}
- 新建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);
}
}
- 新建各个面板的逻辑脚本,例如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()
{
}
}
- 根据需求为第二步创建的预制体添加内容 Tips: 若使用UGUI来创建UI界面,则需事先在Project Settings/Editor中设置UI Enviroment,作用是设置打开预设时加载的场景,该场景可以是带有空Canvas的场景,注意点如下:
- 直接在空预制体中添加UI元素,会导致创建新的Canvas,而代码中实例化预制体时位置会错乱,所以需要提前设置UI Enviroment
- 设置好后,双击打开空预制体,修改组件Transform为Rect Transform(因为以上设置只对包含Rect Transform组件的预制体生效),添加任意UI元素,再次双击打开该预制体,删除原UI元素,接下来放置所需的UI元素即可
- 为以上预制体的根节点绑定对应的脚本(第11步)
- 测试脚本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
进一步改进
- 基类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)基类新增两个虚函数
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);
}
}