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

使用工具: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框架的结构:


猜你喜欢

转载自blog.csdn.net/gemini_xujian/article/details/79628424