使用工具:VS2017,Unity2017.3,DoTween插件

使用语言:c#

作者:Gemini_xujian

参考:siki老师-《丛林战争》视频教程

继上一篇文章内容,这节课讲解一下如何在实际案例中使用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数据转换成类中的数据,并返回一个listforeach (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 initializationvoid 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,那么就新创建一个panelDictif (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的父物体为canvaspanelDict.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数据转换成类中的数据,并返回一个listforeach (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,那么就新创建一个panelDictif (panelDict == null){panelDict = new Dictionary<UIPanelType, BasePanel>();}//BasePanel panel;//panelDict.TryGetValue(panelType,out panel);//从字典中得到我们需要的panelBasePanel 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,并让物体保持局部位置,而非世界位置,将第二个参数设置为falsepanelDict.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数据转换成类中的数据,并返回一个listforeach (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,那么就新创建一个panelDictif (panelDict == null){panelDict = new Dictionary<UIPanelType, BasePanel>();}//BasePanel panel;//panelDict.TryGetValue(panelType,out panel);//从字典中得到我们需要的panelBasePanel 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的父物体为canvaspanelDict.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数据转换成类中的数据,并返回一个listforeach (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 initializationvoid 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,那么就新创建一个panelDictif (panelDict == null){panelDict = new Dictionary<UIPanelType, BasePanel>();}//BasePanel panel;//panelDict.TryGetValue(panelType,out panel);//从字典中得到我们需要的panelBasePanel 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的父物体为canvaspanelDict.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数据转换成类中的数据,并返回一个listforeach (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框架的结构:

unity网络实战开发(丛林战争)-前期知识准备(012-UI框架开发)相关推荐

  1. Java开发需要学习哪些知识?初学者怎么学好Java开发?

    根据IDC的统计数字,在所有软件开发类人才的需求中,对Java工程师的需求达到全部需求量的60%~70%,对软件开发人才的需求达到了20%,还同时以每年20%的速度在增长.由此,我国不仅在软件开发人才 ...

  2. Java开发需要学习哪些知识?小白怎么学好Java开发?

    根据IDC的统计数字,在所有软件开发类人才的需求中,对Java工程师的需求达到全部需求量的60%~70%,对软件开发人才的需求达到了20%,还同时以每年20%的速度在增长.由此,我国不仅在软件开发人才 ...

  3. 后台UI框架开发(一)

    后台UI框架开发(一) 众所周知,现在的后台管理系统的前端页面基本上都是一个样子-- 那既然,每个后台管理页面的样子都是这样的,那我们能不能设计一个页面,专门写成这个样子,只需要以面向对象的方式去使用 ...

  4. unity网络实战开发(丛林战争)-前期知识准备(011-c#连接数据库并实现增删改查以及sql注入问题)

    使用工具:VS2015,Mysql 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下数据库的前期连接准备以及通过 ...

  5. unity网络实战开发(丛林战争)-前期知识准备(004-开发TCP客户端的接收数据和发送数据)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下客户端的接收数据和发送数据. 首先在现有 ...

  6. unity网络实战开发(丛林战争)-前期知识准备(010-在服务器端解析数据)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下在服务器端解析数据. 首先,同前文类同, ...

  7. unity网络实战开发(丛林战争)-前期知识准备(006-修改服务器端开启异步处理客户端连接请求)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下修改服务器开启一步处理客户端连接请求. ...

  8. unity网络实战开发(丛林战争)-前期知识准备(008-粘包和分包及解决方案)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下什么是粘包和分包,以及处理粘包和分包问题 ...

  9. unity网络实战开发(丛林战争)-前期知识准备(003-开发服务器端的发送数据和接收数据)

    使用工具:VS2015 使用语言:c# 作者:Gemini_xujian 参考:siki老师-<丛林战争>视频教程 继上一篇文章内容,这节课讲解一下服务器端的发送数据和接收数据. 上篇文章 ...

最新文章

  1. Activity中加载器的总结
  2. C++ 流操作符重载函数
  3. oracle语句within,oracle中within group的用法
  4. 苹果、联想及华硕均看准美国电脑运输的增长
  5. [Leetcode][JAVA][第20题][Stack][Map]
  6. java处理unicode_C# JavaScript Java 与 中文 unicode 处理
  7. mac在当前文件夹打开终端,献给不喜欢终端打字的小伙伴
  8. win10计算机扫描,win10系统测试一体机扫描仪功能的解决教程
  9. 新版白话空间统计(11):ArcGIS中的PZ值标尺
  10. 树莓派小车C语言循迹,自动循迹小车_单片机/STM32/树莓派/Arduino/开发板创意项目-聚丰项目-电子发烧友网...
  11. 目的地址和ARP地址应答中的源地址
  12. 【Python百日进阶-Web开发-Feffery】Day432 - fac实例:使用国内cdn加载静态资源
  13. php 2038年,PHP 处理大于2038年以后的日期
  14. 基于javaweb的平行志愿管理系统(java+springboot+mybatis+vue+mysql)
  15. 软件测试颗粒度,测试用例之度——系列之颗粒度(上)
  16. 【无标题】利用C#连接阿里云RSD数据库mysql服务器
  17. 子网的划分 网络号 广播地址计算
  18. 千寻和省cors精度对比_使用中海达RTK验证千寻cors账号和省cors网络的测量效果
  19. [LINUX] 搭建日志服务器
  20. C#植物大战僵尸中文版

热门文章

  1. “模式识别”技术识别12306验证码
  2. WARNING:not built warning
  3. 2022CoCa: Contrastive Captioners are Image-Text Fountion Models
  4. 推荐:六款强大的开源数据挖掘工具
  5. 《人生的智慧》——人所展现的表象
  6. coursera andrew Ng老师的machine learning的课程总结(一)
  7. vmware workstation虚拟机无法连接网络
  8. 小波变换的尺度函数和小波函数分析
  9. java页面正文提取_Crux 是一个 HTML 正文内容提取库,并确定一篇文章的关键内容...
  10. Eclipse从本地压缩包安装插件