UI框架总揽:
(如果开发过程测试中遇到找不到面板 报空的错误异常 请仔细检查json文件 查看是否名称和路径存在拼写错误的情况 如果在VS中看不出异常 最好在文件夹中打开json文件检查一下 因为有些字符在VS中并不明显 如空格 )
创建测试面板(非框架部分)
将面板以Prefab形式放入Resources文件夹下面,便于框架加载面板
(注意Resources不要拼写错误,否则会读取出错)
创建json文件和UIPanelType枚举类来保存所有的面板信息
UIPanelType.cs:
每添加一个面板都要在此枚举类里面添加对应面板的类型
public enum UIPanelType
{
ItemMessage,
Knapsack,
MainMenu,
Shop,
Skill,
System,
Task
}
UIPanelType.json(保存面板信息的json文件):
1.此文件需要加载,保存到Resources文件下
2.保存了面板的类型(相当于名称)和面板Prefab的路径(实例化面板时要用到)
3.每添加一个面板,都要在此文件添加该面板对应的信息
此文本信息尤为重要 在反序列化时(从文本信息到对象) 需要通过此文本信息的名称和路径找到图一Resources文件夹下我们之前创建好的面板预置体 将其正确实例化出来
{
"infoList":
[
{"panelTypeString":"ItemMessage",
"path":"UIPanel/ItemMessagePanel"},
{"panelTypeString":"Knapsack",
"path":"UIPanel/KnapsackPanel"},
{"panelTypeString":"MainMenu",
"path":"UIPanel/MainMenuPanel"},
{"panelTypeString":"Shop",
"path":"UIPanel/ShopPanel"},
{"panelTypeString":"Skill",
"path":"UIPanel/SkillPanel"},
{"panelTypeString":"System",
"path":"UIPanel/SystemPanel"},
{"panelTypeString":"Task",
"path":"UIPanel/TaskPanel"}
]
}
UIPanelInfo(面板信息集合类)
用于和json文件进行映射()
[Serializable]
public class UIPanelInfo:ISerializationCallbackReceiver {
[NonSerialized]
public UIPanelType panelType;
public string panelTypeString;
//{
// get
// {
// return panelType.ToString();
// }
// set
// {
// UIPanelType type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), value);
// panelType = type;
// }
//}
public string path;
//反序列化 从文本信息 到对象
public void OnAfterDeserialize()
{
//Debug.Log(panelTypeString);
UIPanelType type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType),panelTypeString);
//将字符串型转化为(Enum)枚举类型。
// 例如:现在有个字符串sString,一个枚举EnumName,希望把sString类型转换成EnumName类型格式如下:
//(EnumName)EnumName.Parse(typeof(EnumName), sString)
panelType = type;
}
public void OnBeforeSerialize()
{
}
}
开发UIManger来解析面板信息json文件(框架核心:UIManager)
UIManager为框架的核心,全局只有一个UIManager,因此UIManager要使用单例模式
现阶段的完整的UIManager.cs(后续还会继续完善):
public class UIManager {
/// <summary>
/// 单例模式的核心
/// 1.定义一个静态的对象 在外界访问 在内部构造
/// 2.构造方法私有化
/// </summary>
private static UIManager _instance;
public static UIManager Instance
{
get
{
if (_instance==null)
{
_instance = new UIManager();
}
return _instance;
}
}
private Dictionary<UIPanelType, string> panelPathDict;//存储所有面板Prefab的路径
private UIManager()
{
ParseUIPanelTypeJson();
}
[Serializable]
class UIPanelTypeJson
{
public List<UIPanelInfo> infoList;
}
private void ParseUIPanelTypeJson()
{
panelPathDict = new Dictionary<UIPanelType, string>();
TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//读取Resources文件夹下的
// “UIPanelType”json文件
UIPanelTypeJson jsonObject = JsonUtility.FromJson<UIPanelTypeJson>(ta.text);
//将json文件转化成对象
//这将创建一个新的实例MyClass并使用JSON数据在其上设置值。
//如果JSON数据包含不映射到MyClass,
//然后,这些值将被忽略,如果JSON数据缺少MyClass,则这些字段将留在其构造的值中返回的对象中。
foreach (UIPanelInfo info in jsonObject.infoList)
{
panelPathDict.Add(info.panelType, info.path);//将转化出来的对象信息存进字典里去
}
}
开发BasePanel抽象类(各个UIPanel的基类)
BasePanel给每个Panel定义了四个事件:
OnEnter:面板进入时调用
OnPause:面板停止时调用(鼠标与面板的交互停止)
OnResume:面板恢复使用时调用(鼠标与面板的交互恢复)
OnExit:面板退出时调用
using UnityEngine;
public class BasePanel : MonoBehaviour
{
public abstract void OnEnter(){ }
public abstract void OnPause(){ }
public abstract void OnResume(){ }
public abstract void OnExit(){ }
}
给各个面板添加UIPanel类(非框架部分)
(此处就不把全部面板的脚本都放上来了 )
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class KnapsackPanel :BasePanel {
private CanvasGroup canvasGroup;
void Start () {
if (canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
}
public override void OnEnter()
{
if (canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
canvasGroup.alpha = 1;//设置当前面板的透明度
canvasGroup.blocksRaycasts = true;//设置当前面板为可交互的面板
//设置面板的出场动画
Vector3 temp = transform.localPosition;
temp.x = 600;
transform.localPosition = temp;
transform.DOLocalMoveX(0, .5f);
}
public override void OnExit()
{
//canvasGroup.alpha = 0;
canvasGroup.blocksRaycasts = false;
//设置面板的离场动画
transform.DOLocalMoveX(600, .5f).OnComplete(()=>canvasGroup.alpha=0);
}
public override void OnPause()
{
canvasGroup.blocksRaycasts = false;
}
public override void OnResume()
{
canvasGroup.blocksRaycasts = true;
}
//当前面板关闭按钮点击事件
public void OnClosePanel()
{
UIManager.Instance.PopPanel();
}
public void OnItemButtonClick()
{
UIManager.Instance.PushPanel(UIPanelType.ItemMessage);
}
}
对UI面板Prefab实例化的创建与管理
获取Panel(如果panel没有被实例化,进行实例化,并且存储到已经实例化好的panel字典中) (这部分代码也是在UIManager中实现的)
private Dictionary<UIPanelType, BasePanel> panelDict; //保存所有实例化面板的游戏物体身上的BasePanel组件
/// <summary>
/// 根据面板类型 得到实例化的面板
/// </summary>
/// <param name="panelType"></param>
/// <returns></returns>
private BasePanel GetPanel(UIPanelType panelType)
{
//如果字典不存在 则构造一个新的字典
if (panelDict ==null)
{
panelDict = new Dictionary<UIPanelType, BasePanel>();
}
BasePanel panel;
panelDict.TryGetValue(panelType, out panel);
//BasePanel panel = panelDict.TryGet(panelType); //方法①
if (panel==null)
{
//如果找不到,那么就找这个面板prefab的路径,然后去根据prefab去实例化面板
//string path; //方法①
//panelPathDict.TryGetValue(panelType, out path);//方法①
string path = panelPathDict.TryGet(panelType);//方法② 通过字典的扩展方法实现
GameObject instPanel = GameObject.Instantiate(Resources.Load(path)) as GameObject;
instPanel.transform.SetParent(CanvasTransform,false);
panelDict.Add(panelType, instPanel.GetComponent<BasePanel>());
return instPanel.GetComponent<BasePanel>();
}
else
{
return panel;
}
}
字典的扩展:
/// <summary>
/// 对Dictionary的扩展
/// </summary>
public static class DictionaryExtension {
public static Tvalue TryGet<Tkey, Tvalue>(this Dictionary<Tkey, Tvalue> dict, Tkey key)
{
Tvalue value;
dict.TryGetValue(key, out value);
return value;
}
}
开发界面存储栈,并进行出栈和入栈操作
【设计理念】 (这部分代码也是在UIManager中实现的)
1.鼠标只允许和一个界面进行交互,当一个界面显示后,上一个界面要停止和鼠标的交互
2.界面显示,进行入栈操作(Push,OnEnter),停止上一个界面(OnPause)
3.界面隐藏,进行出栈操作(Pop,OnExit),恢复上一个界面(OnResume)
/// <summary>
/// 把某个页面入栈 把某个页面显示在界面上
/// </summary>
public void PushPanel(UIPanelType panelType)
{
if (panelStack==null)
panelStack = new Stack<BasePanel>();
//判断一下栈里面是否有页面
if (panelStack.Count>0)
{
BasePanel topPanel = panelStack.Peek();
topPanel.OnPause();
}
BasePanel panel = GetPanel(panelType);
panel.OnEnter();
panelStack.Push(panel);
}
/// <summary>
/// 出栈 把页面从界面上移除
/// </summary>
public void PopPanel()
{
if (panelStack == null)
panelStack = new Stack<BasePanel>();
if (panelStack.Count <= 0) return;
//关闭栈顶页面的显示
BasePanel topPanel = panelStack.Pop();
topPanel.OnExit();
//恢复上一页面的显示
if (panelStack.Count <= 0) return;
BasePanel topPanel2 = panelStack.Peek();
topPanel2.OnResume();
}
控制Panel之间的跳转(非框架部分)
【GameRoot】 实例化游戏中的主面板
public class GameRoot : MonoBehaviour
{
private void Start()
{
UIManager.Instance.PushPanel(UIPanelType.MainMenu);
//UIManager.Instance.Test();
}
}
完善面板中的一些操作,实现面板之间的跳转,此部分不是框架部分,省略!
using UnityEngine;
using UnityEngine.UI;
public class MainMenuPanel : BasePanel
{
private Button TaskButton;
private Button KnapsackButton;
private Button SkillButton;
private Button ShopButton;
private Button SystemButton;
private CanvasGroup canvasGroup;
void Awake()
{
TaskButton = transform.Find("IconPanel/TaskButton").GetComponent<Button>();
KnapsackButton = transform.Find("IconPanel/KnapsackButton").GetComponent<Button>();
SkillButton = transform.Find("IconPanel/SkillButton").GetComponent<Button>();
ShopButton = transform.Find("IconPanel/ShopButton").GetComponent<Button>();
SystemButton = transform.Find("IconPanel/SystemButton").GetComponent<Button>();
canvasGroup = transform.GetComponent<CanvasGroup>();
TaskButton.onClick.AddListener(OnTaskButtonClick);
KnapsackButton.onClick.AddListener(OnKnapsackButtonClick);
SkillButton.onClick.AddListener(OnSkillButtonClick);
ShopButton.onClick.AddListener(OnShopButtonClick);
SystemButton.onClick.AddListener(OnSystemButtonClick);
}
void OnDestory()
{
TaskButton.onClick.RemoveListener(OnTaskButtonClick);
KnapsackButton.onClick.RemoveListener(OnKnapsackButtonClick);
SkillButton.onClick.RemoveListener(OnSkillButtonClick);
ShopButton.onClick.RemoveListener(OnShopButtonClick);
SystemButton.onClick.RemoveListener(OnSystemButtonClick);
}
public override void OnEnter()
{
}
public override void OnPause()
{
canvasGroup.blocksRaycasts = false;
}
public override void OnResume()
{
canvasGroup.blocksRaycasts = true;
}
public override void OnExit()
{
}
private void OnTaskButtonClick()
{
UIPanelManager.Instance.PushPanel(UIPanelType.Task);
}
private void OnKnapsackButtonClick()
{
UIPanelManager.Instance.PushPanel(UIPanelType.Knapsack);
}
private void OnSkillButtonClick()
{
UIPanelManager.Instance.PushPanel(UIPanelType.Skill);
}
private void OnShopButtonClick()
{
UIPanelManager.Instance.PushPanel(UIPanelType.Shop);
}
private void OnSystemButtonClick()
{
UIPanelManager.Instance.PushPanel(UIPanelType.System);
}
}
最后附上UIManager的完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class UIManager {
/// <summary>
/// 单例模式的核心
/// 1.定义一个静态的对象 在外界访问 在内部构造
/// 2.构造方法私有化
/// </summary>
private static UIManager _instance;
public static UIManager Instance
{
get
{
if (_instance==null)
{
_instance = new UIManager();
}
return _instance;
}
}
private Transform canvasTransform;
private Transform CanvasTransform
{
get
{
if (canvasTransform == null)
{
canvasTransform = GameObject.Find("Canvas").transform;
}
return canvasTransform;
}
}
private Dictionary<UIPanelType, string> panelPathDict;//存储所有面板Prefab的路径
private Dictionary<UIPanelType, BasePanel> panelDict; //保存所有实例化面板的游戏物体身上的BasePanel组件
private Stack<BasePanel> panelStack;
private UIManager()
{
ParseUIPanelTypeJson();
}
/// <summary>
/// 把某个页面入栈 把某个页面显示在界面上
/// </summary>
public void PushPanel(UIPanelType panelType)
{
if (panelStack==null)
panelStack = new Stack<BasePanel>();
//判断一下栈里面是否有页面
if (panelStack.Count>0)
{
BasePanel topPanel = panelStack.Peek();
topPanel.OnPause();
}
BasePanel panel = GetPanel(panelType);
panel.OnEnter();
panelStack.Push(panel);
}
/// <summary>
/// 出栈 把页面从界面上移除
/// </summary>
public void PopPanel()
{
if (panelStack == null)
panelStack = new Stack<BasePanel>();
if (panelStack.Count <= 0) return;
//关闭栈顶页面的显示
BasePanel topPanel = panelStack.Pop();
topPanel.OnExit();
//恢复上一页面的显示
if (panelStack.Count <= 0) return;
BasePanel topPanel2 = panelStack.Peek();
topPanel2.OnResume();
}
/// <summary>
/// 根据面板类型 得到实例化的面板
/// </summary>
/// <param name="panelType"></param>
/// <returns></returns>
private BasePanel GetPanel(UIPanelType panelType)
{
if (panelDict ==null)
{
panelDict = new Dictionary<UIPanelType, BasePanel>();
}
BasePanel panel;
panelDict.TryGetValue(panelType, out panel); //TODO
//BasePanel panel = panelDict.TryGet(panelType);
if (panel==null)
{
//如果找不到,那么就找这个面板prefab的路径,然后去根据prefab去实例化面板
//string path;
//panelPathDict.TryGetValue(panelType, out path);
string path = panelPathDict.TryGet(panelType);
//Debug.Log(path);
GameObject instPanel = GameObject.Instantiate(Resources.Load(path)) as GameObject;
instPanel.transform.SetParent(CanvasTransform,false);
panelDict.Add(panelType, instPanel.GetComponent<BasePanel>());
return instPanel.GetComponent<BasePanel>();
}
else
{
return panel;
}
}
[Serializable]
class UIPanelTypeJson
{
public List<UIPanelInfo> infoList;
}
private void ParseUIPanelTypeJson()
{
panelPathDict = new Dictionary<UIPanelType, string>();
TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//读取Resources文件夹下的“UIPanelType”json文件
UIPanelTypeJson jsonObject = JsonUtility.FromJson<UIPanelTypeJson>(ta.text);//将json文件转化成对象 //这将创建一个新的实例MyClass并使用JSON数据在其上设置值。如果JSON数据包含不映射到MyClass,
//然后,这些值将被忽略,如果JSON数据缺少MyClass,则这些字段将留在其构造的值中返回的对象中。
foreach (UIPanelInfo info in jsonObject.infoList)
{
panelPathDict.Add(info.panelType, info.path);//将转化出来的对象信息存进字典里去
}
}
public void Test()
{
string path;
panelPathDict.TryGetValue(UIPanelType.Knapsack, out path);
// Debug.Log(path);
//bool isContain = panelPathDict.ContainsValue("UIPanel/ItemMessagePanel");
bool isContain = panelPathDict.ContainsKey(UIPanelType.MainMenu);
bool isContain1 = panelPathDict.ContainsKey(UIPanelType.Shop);
//Debug.Log(isContain);
//Debug.Log(isContain1);
}
}