使用工具:VS2017,Unity2017.3,DoTween插件
使用语言:c#
作者:Gemini_xujian
继上一篇文章内容,这节课讲解一下如何在实际案例中使用UGUI搭建UI框架。
UI框架的作用:
1.管理场景中所有的面板
2.管理面板之间的跳转
01-unity项目创建以及前期准备
首先创建一个新的unity工程,命名为UIFramewrok,导入素材资源,并在unity中创建Image、Scenes、Sprites、UIFramework、Resources/UIPanel这几个文件夹,其中UIFramework文件夹方便以后我们导出,导入到其他工程使用我们的UI框架;Resources/UIPanel用来存放我们做的UI面板。
02-设计主菜单面板、任务面板、背包和弹框信息面板、以及其他的一些面板
几个面板效果如图所示:
主面板:
任务面板:
背包面板:
弹框面板:
还有其他一些界面就不一一列举了,将所有搭建好的UI面板放到Resources/UIPanel文件夹下,做成预制体。然后将在Hierarchy面板中仅仅只保留一个主面板,如图:
03-通过所有的json和枚举保存所有的面板信息
在unity工程中创建一个c#脚本命名为UIPanelType,用VS打开将类关键字class改为enum枚举类型,然后删除继承的父类和里面的方法,在里面将所有的面板预制体作为枚举值并保存。
using System.Collections; using System.Collections.Generic; using UnityEngine; public enum UIPanelType { MainMenu, ItemMessage, Knapsack, Shop, Skill, Task, System }
然后在unity工程中创建一个txt文件,将之命名为UIPanelType.json,然后用VS打开并将面板名和面板路径以键值对的方式进行写入,代码如下:
UIPanelType.json
{ "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"} ] }
04-开发UIManager解析面板信息json
在unity的UIFramework中创建一个UIManager类,然后将继承信息等删除,并在UIFramework中创建一个Resources文件夹,将之前写好的UIPanelType文件拖入此文件夹中(方便在脚本中得到)。
UIManager类的代码如下所示:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// UI框架的核心管理类 /// 解析并保存所有面板信息(panelPathDict) /// </summary> public class UIManager { ///单例模式的核心 ///1.定义一个静态的对象,在外界访问,在内部构造 ///2.构造方法私有化 private static UIManager _instance;//单例模式 public static UIManager Instance { get { if (_instance == null) _instance = new UIManager(); return _instance; } } private Dictionary<UIPanelType, string> panelPathDict;//存储所有面板prefab的路径 /// <summary> /// 在初始化的时候解析json /// </summary> private UIManager() { ParseUIPanelTypeJson(); } [Serializable] class UIPanelTypeJson { public List<UIPanelInfo> infoList; } /// <summary> /// 解析json数据 /// </summary> private void ParseUIPanelTypeJson() { panelPathDict = new Dictionary<UIPanelType, string>();//初始化字典 TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//得到我们的UPanelType.json文件 UIPanelTypeJson jsonObject= JsonUtility.FromJson<UIPanelTypeJson>(ta.text);//将json数据转换成类中的数据,并返回一个list foreach (UIPanelInfo info in jsonObject.infoList) { panelPathDict.Add(info.panelType,info.path);//将list中的参数存储在字典中 } } /// <summary> /// just for test /// </summary> public void Test() { string path; panelPathDict.TryGetValue(UIPanelType.Knapsack,out path); Debug.Log(path); } }
在当前的UIManager类中,将这个类使用了单例模式,并在里面使用了内部类UIPaneltypeJson,这个类的作用是将json文本 的内容序列化,因为我们的json文本整体是一个对象,而在对象里面使用了数组的形式进行数据的存储,所以在UIPanelTypeJson类中,我们定义了一个UIPanelInfo类型的List集合,这个类型是我们定义的针对json对象数组中的每一个对象进行的序列化;目前UIManager类中的核心方法是ParseUIPanelTypeJson()方法,这个方法用来解析我们的json数据,并将解析好的json数据保存在字典中。
下面是UIPanelInfo类的代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; [Serializable]//表示可序列化的 //类中的参数要与UIPanelType.json文件中的参数保持一致 public class UIPanelInfo :ISerializationCallbackReceiver{ [NonSerialized]//不去序列化 public UIPanelType panelType; public string panelTypeString; //{ // get // { // return panelType.ToString(); // } // set // { // UIPanelType type = (UIPanelType)Enum.Parse(typeof(UIPanelType), value); // panelType = type; // } //} public string path; //类在序列化之前调用此方法 public void OnBeforeSerialize() { } //类在是序列化成功之后调用此方法 public void OnAfterDeserialize() { UIPanelType type = (UIPanelType)Enum.Parse(typeof(UIPanelType), panelTypeString); panelType = type; } }
在UIPanelInfo这个类中,我们除了给出的几个对应的字段值外,还给了这个类一个继承类ISerializationCallbackReceiver,这个类中有两个方法需要去实现,第一个是OnBeforeSerialize()方法,这个方法是在类进行序列化操作之前执行的,第二个是OnAfterDeserialize()方法,这个方法会在序列化完成之后执行,因为我们使用了unity自带的一种json解析方法,所以,我们只能通过此种方式进行序列化,在这个方法中,我们将得到的字符串值转成UIPanelType类型,并赋给了我们在上面定义的同类型的panelType,这个类型因为我们给了一个不进行序列化的标识,所以就不会在序列化的时候使用这个字段序列化。这个就是我们序列化的类。
在我们写好相关内容之后,我们需要做下测试。就如UIManager类中的Test()方法,我们在unity的UIFramewrok文件夹中创建一个GameRoot的脚本,然后在start()方法中调用UIManaager类中的test()方法,并将脚本绑定在canvas上面,这个类也就做为了我们使用这个UI框架的启动类,代码如图所示:
GameRoot:
using System.Collections; using System.Collections.Generic; using UnityEngine; //相当于一个启动器 public class GameRoot : MonoBehaviour { // Use this for initialization void Start () { UIManager.Instance.Test();//测试方法 } }
运行unity,在控制台会有正确的输出:
05-开发BasePanel面板基类
继上一次操作内容,打开unity项目,对项目目录结构进行一些调整。首先在UIFramework文件夹下创建Base文件夹和Manager文件夹,然后创建一个BasePanel类作为所有UI面板类的基类,用来统一为面板添加相同的功能。再然后,为每个UI面板prefab创建一个UIPanel类,使之继承自BasePanel类,将自带的start方法和update方法删除,最后将所有的面板类和面板基类放入Base文件夹,将之前创建的UIManager类和GameRoot类放入Manager文件夹中。
目录结构和代码如图所示:
BasePanel:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class BasePanel : MonoBehaviour { }
MainMenuPanel(只列举一个示例):
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MainmenuPanel : BasePanel { }
目录结构:
面板上需要挂载相应脚本(以MainMenuPanel面板为例):
06-控制UI面板prefab的实例化创建和管理
我们主要是要针对UIManager类做一些修改,先上代码:
UIManager:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// UI框架的核心管理类 /// 解析并保存所有面板信息(panelPathDict) /// </summary> public class UIManager { ///单例模式的核心 ///1.定义一个静态的对象,在外界访问,在内部构造 ///2.构造方法私有化 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组件 /// <summary> /// 在初始化的时候解析json /// </summary> private UIManager() { ParseUIPanelTypeJson(); } /// <summary> /// 根据面板类型得到实例化的面板 /// </summary> /// <param name="panelType"></param> /// <returns></returns> public BasePanel GetPanel(UIPanelType panelType) { //如果panelDict==NULL,那么就新创建一个panelDict if (panelDict == null) { panelDict = new Dictionary<UIPanelType, BasePanel>(); } BasePanel panel; panelDict.TryGetValue(panelType,out panel);//从字典中得到我们需要的panel //如果没有得到面板,就找这个面板的prefab的路径,然后根据prefab去实例化面板 if (panel == null) { string path; panelPathDict.TryGetValue(panelType,out path); GameObject instPanel= GameObject.Instantiate(Resources.Load(path))as GameObject; instPanel.transform.SetParent( CanvasTransform);//设置实例化的panel的父物体为canvas panelDict.Add(panelType,instPanel.GetComponent<BasePanel>());//将这个panel类添加到panelDict中方便下次使用 return instPanel.GetComponent<BasePanel>();//返回我们新创建的物体上的basepanel } else { return panel;//如果panel已经存在panelDict中,我们直接将它返回 } } [Serializable] class UIPanelTypeJson { public List<UIPanelInfo> infoList; } /// <summary> /// 解析json数据 /// </summary> private void ParseUIPanelTypeJson() { panelPathDict = new Dictionary<UIPanelType, string>();//初始化字典 TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//得到我们的UPanelType.json文件 UIPanelTypeJson jsonObject= JsonUtility.FromJson<UIPanelTypeJson>(ta.text);//将json数据转换成类中的数据,并返回一个list foreach (UIPanelInfo info in jsonObject.infoList) { panelPathDict.Add(info.panelType,info.path);//将list中的参数存储在字典中 } } /// <summary> /// just for test /// </summary> public void Test() { string path; panelPathDict.TryGetValue(UIPanelType.Knapsack,out path); Debug.Log(path); } }
我已经将新修改的地方给标识了出来,大体讲一下思路。首先,我们需要创建一个字典用来存储当前已经实例化的面板,然后我们创建了一个方法用来得到我们需要的面板,在方法GetPanel()中,我们首先需要判断panelDict字典是否为空,如果为空,则需要去初始化它;然后,我们需要判断我们需要的到达的面板是否存在panelDict中,如果没有,则说明我们的面板没有实例化过,我们就需要去实例化这个面板,实例化后,再将这个面板设置父物体为canvas,所以我们还需要在得到一下canvas的transform组件,得到之后,使用SetParent()方法进行父对象设置,设置好后,将实例化物体上的basePanel组件添加到panelDcit字典中,并将我们需要的basepanel类返回。而如果在判断是否在字典中时已经存在,我们就直接将面板对应basepanel类返回即可。
06-开发字典拓展类
在我们UImanager类中,我们多次使用了Dictionary类中的TryGetValue()方法,在使用过程中,我们需要用两步操作来得到我们需要的内容,这多少一点繁琐,因此我们可以在Dictionary类的基础上对它进行扩展。
首先,我们在UIFramework文件夹中创建一个名为DictionaryExtension的脚本。
打开脚本,将继承类删除,并将自带的方法删除,并将类改为静态类。
然后,定义一个方法,名为TryGet,这个方法用来对TryGetValue做些封装,方便我们的复用。
代码如下:
DictionaryExtension:
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 字典扩展类,对Dictionary的扩展 /// </summary> public static class DictionaryExtension { /// <summary> /// 尝试根据key得到value,得到了直接返回value,没有得到直接返回null /// dict表示我们要操作的字典对象 /// </summary> public static Tvalue TryGet<Tkey,Tvalue>(this Dictionary<Tkey,Tvalue> dict,Tkey key) { Tvalue value; dict.TryGetValue(key,out value); return value; } }
在扩展的方法中,我们使用了泛型的方式,然后通过key值得到我们需要的value值,如果有则返回值,没有则返回空。
接下来我们只需要在我们的UIManager类中将相应的代码进行更换即可,代码如下:
UIManager:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// UI框架的核心管理类 /// 解析并保存所有面板信息(panelPathDict) /// </summary> public class UIManager { ///单例模式的核心 ///1.定义一个静态的对象,在外界访问,在内部构造 ///2.构造方法私有化 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组件 /// <summary> /// 在初始化的时候解析json /// </summary> private UIManager() { ParseUIPanelTypeJson(); } /// <summary> /// 根据面板类型得到实例化的面板 /// </summary> /// <param name="panelType"></param> /// <returns></returns> public BasePanel GetPanel(UIPanelType panelType) { //如果panelDict==NULL,那么就新创建一个panelDict if (panelDict == null) { panelDict = new Dictionary<UIPanelType, BasePanel>(); } //BasePanel panel; //panelDict.TryGetValue(panelType,out panel);//从字典中得到我们需要的panel BasePanel panel = panelDict.TryGet(panelType); //如果没有得到面板,就找这个面板的prefab的路径,然后根据prefab去实例化面板 if (panel == null) { //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);//设置实例化的panel的父物体为canvas,并让物体保持局部位置,而非世界位置,将第二个参数设置为false panelDict.Add(panelType,instPanel.GetComponent<BasePanel>());//将这个panel类添加到panelDict中方便下次使用 return instPanel.GetComponent<BasePanel>();//返回我们新创建的物体上的basepanel } else { return panel;//如果panel已经存在panelDict中,我们直接将它返回 } } [Serializable] class UIPanelTypeJson { public List<UIPanelInfo> infoList; } /// <summary> /// 解析json数据 /// </summary> private void ParseUIPanelTypeJson() { panelPathDict = new Dictionary<UIPanelType, string>();//初始化字典 TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//得到我们的UPanelType.json文件 UIPanelTypeJson jsonObject= JsonUtility.FromJson<UIPanelTypeJson>(ta.text);//将json数据转换成类中的数据,并返回一个list foreach (UIPanelInfo info in jsonObject.infoList) { panelPathDict.Add(info.panelType,info.path);//将list中的参数存储在字典中 } } /// <summary> /// just for test /// </summary> public void Test() { string path; panelPathDict.TryGetValue(UIPanelType.Knapsack,out path); Debug.Log(path); } }
这样更方便我们的使用和对方法的调用。
07-分析界面的存储栈,创建stack存储面板界面并控制面板之间的跳转
分析:在对面板进行管理时,我们可以使用栈的结构进行管理,栈的特性为先进后出,符合页面的显示方式。
操作:先上修改后的代码:
UIManager:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// UI框架的核心管理类 /// 解析并保存所有面板信息(panelPathDict) /// </summary> public class UIManager { ///单例模式的核心 ///1.定义一个静态的对象,在外界访问,在内部构造 ///2.构造方法私有化 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;//面板界面的容器栈 /// <summary> /// 在初始化的时候解析json /// </summary> private UIManager() { ParseUIPanelTypeJson(); } /// <summary> /// 入栈,把某个页面显示在界面上 /// </summary> public void PushPanel(UIPanelType panelType) { if (panelStack == null) { panelStack = new Stack<BasePanel>();//如何panelstack为空,就创建一个panelstack } BasePanel panel = GetPanel(panelType);//根据面板类型得到面板 panelStack.Push(panel);//入栈 } /// <summary> /// 出栈,把页面从界面上移除 /// </summary> public void PopPanel() { } /// <summary> /// 根据面板类型得到实例化的面板 /// </summary> /// <param name="panelType"></param> /// <returns></returns> private BasePanel GetPanel(UIPanelType panelType) { //如果panelDict==NULL,那么就新创建一个panelDict if (panelDict == null) { panelDict = new Dictionary<UIPanelType, BasePanel>(); } //BasePanel panel; //panelDict.TryGetValue(panelType,out panel);//从字典中得到我们需要的panel BasePanel panel = panelDict.TryGet(panelType); //如果没有得到面板,就找这个面板的prefab的路径,然后根据prefab去实例化面板 if (panel == null) { //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);//设置实例化的panel的父物体为canvas panelDict.Add(panelType,instPanel.GetComponent<BasePanel>());//将这个panel类添加到panelDict中方便下次使用 return instPanel.GetComponent<BasePanel>();//返回我们新创建的物体上的basepanel } else { return panel;//如果panel已经存在panelDict中,我们直接将它返回 } } [Serializable] class UIPanelTypeJson { public List<UIPanelInfo> infoList; } /// <summary> /// 解析json数据 /// </summary> private void ParseUIPanelTypeJson() { panelPathDict = new Dictionary<UIPanelType, string>();//初始化字典 TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//得到我们的UPanelType.json文件 UIPanelTypeJson jsonObject= JsonUtility.FromJson<UIPanelTypeJson>(ta.text);//将json数据转换成类中的数据,并返回一个list foreach (UIPanelInfo info in jsonObject.infoList) { panelPathDict.Add(info.panelType,info.path);//将list中的参数存储在字典中 } } /// <summary> /// just for test /// </summary> public void Test() { string path; panelPathDict.TryGetValue(UIPanelType.Knapsack,out path); Debug.Log(path); } }
MainMenuPanel:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class MainmenuPanel : BasePanel
{
public void PushPanel(string panelTypeString)
{
UIPanelType panelType = (UIPanelType)Enum.Parse(typeof(UIPanelType), panelTypeString);
UIManager.Instance.PushPanel(panelType);
}
}
GameRoot:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//相当于一个启动器
public class GameRoot : MonoBehaviour {
// Use this for initialization
void Start () {
UIManager.Instance.PushPanel(UIPanelType.MainMenu);
}
}
在我们已经分析并决定使用栈的方式进行页面之间的加载和跳转后,首先我们在UIManager中创建一个栈,类型为BasePanel,然后创建两个方法用来管理入栈和出栈,先处理的是入栈,在PushPanel方法中,先去判断panelstack是否为空,如果为空则创建,然后通过之前写好的用来得到basepanel的方法得到,最后将得到的面板进行入栈,表示我们已经加载过的页面。
然后我们在MainMenu类中定义一个方法用来处理点击相应按钮弹出相应页面的功能,通过传入相应的面板名称字符串参数,转成枚举类型后,将我们需要的页面显示出来。我们为MainMenu界面里的按钮添加点击事件,并附上相应的参数,然后应用一下,最后在GameRoot脚本中,调用显示MainMenu页面。
08-分析页面的状态,开发页面状态函数,控制界面的出栈和关闭
分析:界面状态可分为:界面显示、界面暂停、界面恢复(继续)、界面移除
操作:首先将过程中修改的脚本贴上来:
UIMananger:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// UI框架的核心管理类
/// 解析并保存所有面板信息(panelPathDict)
/// </summary>
public class UIManager {
///单例模式的核心
///1.定义一个静态的对象,在外界访问,在内部构造
///2.构造方法私有化
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;//面板界面的容器栈
/// <summary>
/// 在初始化的时候解析json
/// </summary>
private UIManager()
{
ParseUIPanelTypeJson();
}
/// <summary>
/// 入栈,把某个页面显示在界面上
/// </summary>
public void PushPanel(UIPanelType panelType)
{
if (panelStack == null)
{
panelStack = new Stack<BasePanel>();//如何panelstack为空,就创建一个panelstack
}
//判断一下栈里面是否有页面
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>();//判断栈顶是否为空,为空创建
}
//如果栈顶元素数量为0,则直接返回结束方法
if (panelStack.Count <= 0)
{
return;
}
//关闭栈顶页面的显示
BasePanel topPanel = panelStack.Pop();
topPanel.OnExit();//执行退出页面的推出方法
if (panelStack.Count <= 0)
{
return;
}
BasePanel topPanel2 = panelStack.Peek();//得到当前的栈顶元素
topPanel2.OnResume();//执行当前栈顶UI面板的继续方法
}
/// <summary>
/// 根据面板类型得到实例化的面板
/// </summary>
/// <param name="panelType"></param>
/// <returns></returns>
private BasePanel GetPanel(UIPanelType panelType)
{
//如果panelDict==NULL,那么就新创建一个panelDict
if (panelDict == null)
{
panelDict = new Dictionary<UIPanelType, BasePanel>();
}
//BasePanel panel;
//panelDict.TryGetValue(panelType,out panel);//从字典中得到我们需要的panel
BasePanel panel = panelDict.TryGet(panelType);
//如果没有得到面板,就找这个面板的prefab的路径,然后根据prefab去实例化面板
if (panel == null)
{
//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);//设置实例化的panel的父物体为canvas
panelDict.Add(panelType,instPanel.GetComponent<BasePanel>());//将这个panel类添加到panelDict中方便下次使用
return instPanel.GetComponent<BasePanel>();//返回我们新创建的物体上的basepanel
}
else
{
return panel;//如果panel已经存在panelDict中,我们直接将它返回
}
}
[Serializable]
class UIPanelTypeJson
{
public List<UIPanelInfo> infoList;
}
/// <summary>
/// 解析json数据
/// </summary>
private void ParseUIPanelTypeJson()
{
panelPathDict = new Dictionary<UIPanelType, string>();//初始化字典
TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//得到我们的UPanelType.json文件
UIPanelTypeJson jsonObject= JsonUtility.FromJson<UIPanelTypeJson>(ta.text);//将json数据转换成类中的数据,并返回一个list
foreach (UIPanelInfo info in jsonObject.infoList)
{
panelPathDict.Add(info.panelType,info.path);//将list中的参数存储在字典中
}
}
/// <summary>
/// just for test
/// </summary>
public void Test()
{
string path;
panelPathDict.TryGetValue(UIPanelType.Knapsack,out path);
Debug.Log(path);
}
}
BasePanel:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BasePanel : MonoBehaviour {
/// <summary>
/// 界面被显示
/// </summary>
public virtual void OnEnter()
{
}
/// <summary>
/// 界面暂停
/// </summary>
public virtual void OnPause()
{
}
/// <summary>
/// 界面继续
/// </summary>
public virtual void OnResume()
{
}
/// <summary>
/// 界面退出
/// </summary>
public virtual void OnExit()
{
}
}
MainMenuPanel:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class MainmenuPanel : BasePanel
{
private CanvasGroup canvasGroup;
private void Start()
{
canvasGroup = GetComponent<CanvasGroup>();
}
public override void OnPause()
{
canvasGroup.blocksRaycasts = false;//当弹出新的面板的时候,让主菜单面板不再和鼠标交互
}
public override void OnResume()
{
canvasGroup.blocksRaycasts = true;
}
public void PushPanel(string panelTypeString)
{
UIPanelType panelType = (UIPanelType)Enum.Parse(typeof(UIPanelType), panelTypeString);
UIManager.Instance.PushPanel(panelType);
}
}
KnapsackPanel:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class KnapsackPanel : BasePanel
{
private CanvasGroup canvasGroup;
private void Start()
{
canvasGroup = GetComponent<CanvasGroup>();
}
public override void OnEnter()
{
if(canvasGroup==null)
canvasGroup = GetComponent<CanvasGroup>();
canvasGroup.alpha = 1;
canvasGroup.blocksRaycasts = true;
}
public override void OnExit()
{
canvasGroup.alpha = 0;
canvasGroup.blocksRaycasts = false;
}
public void OnClose()
{
UIManager.Instance.PopPanel();
}
}
在完成了上一步的操作后,我们需要对页面的状态进行管理。
首先,我们在BasePanel类中添加四个方法,分别代表了页面的四种不同状态。
然后,我们在UIManager类中对之前的PushPanel方法做了一些修改,并完善了PopPanel方法,这两个方法在逻辑上是共通的,只要明白了其中一个方法的逻辑顺序,那么另一个也就懂了,我简单讲解一下PopPanel的逻辑:PopPanel方法是将当前位于栈容器中最上层的元素移除掉,为了实现这个目标并保证我们能够在实际运行时完成正常交互,我们需要考虑到所有需要完成的操作。首先要判断栈是否为空,为空创建;然后判断栈内元素数量是否为0,为0说明栈里已经没有元素,无法执行移除操作,直接返回;如果不为0,则将栈顶元素移除,并执行此栈顶元素(即UI面板)的basepanel组件中的onexit方法;完成操作之后,再次判断栈顶元素数量是否为0,为0返回;不为0则得到当前栈顶元素并执行它的onresume方法,此方法是页面处于继续状态时执行的,也就是页面已经存在于栈中,但在执行PopPanel方法时并不是最上层元素而是第二层元素的那个页面,会在这时被执行onresume方法,这样就完成了我们的出栈以及状态信息的更新。
完善好UIManager后,在MainMenu类中完成MainMenu页面的状态方法的内容。一是暂停状态时,而是继续状态时,暂停时则将面板的交互禁用,继续是启用。而且,还需要你将MainMenu的prefab添加一个canvasgroup组件,用来处理面板的交互管理。
既然我们需要进行页面间的跳转,那么我们还需要将其他页面的状态方法进行重写,这里以KnapsackPanel为例。我们需要完成此页面的进入状态、退出状态进行内容的填充。在方法中使用的canvasgroup.alpha是用来显示或不显示此页面的,canvasgroup.blockraycasts方法是用来启用或禁用页面交互的。close方法是点击关闭按钮后执行的,所以需要给close按钮添加上点击事件,调用的方法是close(),并且也需要给这个页面的prefab添加上canvasGroup组件才可完成正常的交互。其他页面的代码类似于KnapsackPanel脚本。
最终就实现了不同页面之间的跳转,效果如图:
09-完善面板之间的跳转并给面板添加动画
先上修改的脚本:
KnapsackPanel:
using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening;//DoTween插件使用的命名空间 public class KnapsackPanel : BasePanel { private CanvasGroup canvasGroup; private void Start() { 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 = 1000; transform.localPosition = temp; transform.DOLocalMoveX(0, .5f); } public override void OnPause() { canvasGroup.blocksRaycasts = false; } public override void OnResume() { canvasGroup.blocksRaycasts = true; } public override void OnExit() { //canvasGroup.alpha = 0; canvasGroup.blocksRaycasts = false; transform.DOLocalMoveX(1000, .5f).OnComplete(() => canvasGroup.alpha = 0);//OnComplete使用了lambda表达式 } public void ItemClick() { UIManager.Instance.PushPanel(UIPanelType.ItemMessage); } public void OnClose() { UIManager.Instance.PopPanel(); } }
SkillPanel:
using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; public class SkillPanel : BasePanel { private CanvasGroup canvasGroup; private void Start() { canvasGroup = GetComponent<CanvasGroup>(); } public override void OnEnter() { if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>(); canvasGroup.alpha = 0; canvasGroup.blocksRaycasts = true; canvasGroup.DOFade(1,.5f); } public override void OnExit() { //canvasGroup.alpha = 0; canvasGroup.blocksRaycasts = false; canvasGroup.DOFade(0,.5f); } public void OnClose() { UIManager.Instance.PopPanel(); } }
我们想要添加动画效果的途径有很多,可以使用内置的方式,也可以使用外部插件,在这里为了方便我直接使用了DoTween插件,如果没有听说过这个插件的同学可以自行百度一下,包括插件的下载我就不提供下载地址了。
首先我们需要导入一下DoTween插件,导入完成后,我们就可以直接修改我们的面板的相关代码了,在上面贴的代码中,修改的地方是对DoTween插件库方法的一些调用,这里只使用了其中很少的几个方法,大家可以试一下它提供的其他的一些方法。
10-UI框架的导出
在unity中,右键Project栏有一个Export package选项,点击后只选择Demigiant(DoTween插件)和UIFramework文件夹,然后点击导出即可。
附整个UI框架的结构: