关于c#:Unity面向新手超简单的UI框架

前言

揭示:为了可能将知识点学得更加透彻、记得更加牢固 我会通过教学解说的形式把常识写下来 因为在过程中会让人从学生变成老师 这个过程会开掘出新的常识和观点 是一个自我思维切换而达成的常识深度开掘和晋升的过程 如果能帮忙到大家那就最好 如果有讲错的中央还请多多指教!我只是一只菜鸡 感激了解!


为什么要应用UI框架

游戏中存在十分多的面板 例如:背包面板、商店面板、设置面板、

这些面板之间会须要频繁的调用和切换,例如:背包面板中存在各种物品,咱们鼠标放到物品上会呈现物品信息的提示框,还有设置面板须要按返回键才能够切换回主菜单面板,那咱们如何实现界面间的沟通呢?我之前的做法就是在每个我要用到的面板上都写一个脚本实现对应的性能,置信不少老手开发者都是这样过去的吧。(纳尼菜逼竟是我本人?)

尽管这样做性能是实现了,然而维护性升高了、反复的代码也变多了,为了更加不便的治理和调用场景中的面板 咱们须要写一套残缺的框架来对它们进行束缚不然很容易呈现面板共存,或者切换失败等问题。

恰好SIKI老师有一套面向老手的小型UI框架课程讲得蛮好的,最重要的是简略啊!!! 对我这种老手来说真的十分敌对!咱们一起来学习下吧!


框架的思路

先来简略介绍下这个框架的思路吧 ,框架次要实现了面板的读取存储和治理,帮忙咱们更好的进行面板之间的切换治理

首先

1、咱们通过Json文件存储咱们所有要用到的面板信息 例如:面板的名字 面板的类型 面板的加载门路 等等,按理论我的项目需要为准

2、创立一个面板抽象类(就是所有面板的基类)来实现面板共有的一些办法,比方面板进入的时候要做的一些事件,面板退出时候要做的一些事件

3、通过代码读取Json文件失去所有面板的信息,而后咱们就能通过这些信息来操作面板

4、框架最外围的中央就是如何高效的治理这些面板 先来走一遍咱们面板的生成和敞开的流程

此时玩家只能操作背包面板,无奈操作人物面板(想想是不是很多游戏都是这样)当咱们想要操作人物面板的时候 咱们须要先敞开背包面板而后能力操作人物面板

这里再画一幅图来解释

当初咱们位于最下面的设置菜单中子菜单界面,此时咱们只能操作子菜单界面,如果咱们想操作设置菜单界面的话 咱们须要先敞开子菜单,能力操作设置菜单

当初子菜单敞开了,咱们就能够操作设置菜单了!

同理如果咱们想操作主菜单 那咱们就必须要敞开设置菜单能力去操作主菜单

所以Siki老师是如何治理面板之间切换关系的呢?理解过数据结构的同学看了下面的图可能马上就懂了!对的就是用堆栈(Stack)来实现 ,堆栈是一个后进先出的对象汇合 后进先出 先进后出?这不恰好能够用来存储管理咱们的面板吗

先进后出 最先进入的是主菜单,所以咱们把他放到了容器底部,而后玩家又点击了设置菜单,于是咱们把他放到主菜单的下面,而后玩家又点击了设置菜单里的子菜单,咱们把子菜单放到了设置菜单的下面

咱们让玩家只能操作栈中的顶部面板,通过Push把面板入栈(面板进入) 通过Pop把顶部面板出栈(面板退出) 这样就轻松实现了面板之间的切换性能了

好吧说实话我感觉我讲的是一坨屎(可能只有我本人能听懂 举荐间接去看Siki老师讲的)


实操

思路理了一下!当初咱们一步一步来做吧!

1、创立Json文件,把面板信息写入到Json中

首先咱们来实现第一步

咱们通过Json文件存储咱们所有要用到的面板信息 例如:面板的名字 面板的类型 面板的加载门路 等等,按理论我的项目需要为准

在创立Json文件之前,咱们须要先把要用到的面板做成预制体寄存到Resources门路下稍后通过Resources来加载这些咱们要用到的面板,这里我本人新建了一张图作为面板来演示,命名为“SettingPanel”寄存到Resources的UIPanel文件夹下

好当初咱们就来创立一个Json文件(新建一个文本文件 后缀改成.Json就是一个Json文件了,留神咱们Json文件也要放到Resources文件夹下因为咱们稍后会通过Resources来获取它) 并往里面写上咱们面板的信息,这里为了不便小伙伴们的了解我就写简略一点

json文件

{
    "data":
    [
       {"uiPanelType":"settingPanel","uiPanelPath":"UIPanel/SettingPanel"}
    ]
}

这里简略讲下Json文件的格局吧

花括号{} 保留对象:对象能够蕴含各种数据,包含数组。
方括号[] 保留数组:数组能够蕴含对象。
数据由逗号分隔

不懂的咱们能够间接找个在线解析Json的网站 这里举荐 https://www.bejson.com/conver…

咱们间接抉择Json转C#实体类看看 咱们把写好的Json数据复制过来而后点击生成实体类看看

为了不便察看 我把生成的实体来复制过去给大伙看看


 public class DataItem
 {
       public string uiPanelType { get; set; }
 
        public string uiPanelPath { get; set; }
 }

 public class Root
 {
     public List <DataItem> data { get; set; }
 }

不晓得仔细的小伙伴发现了没有 这两个类是不是刚好映射了Json文件里的数据

认真比照一下 就能理解其中的意思

要留神的是 这些数据必须要和咱们写的Json文件里的信息对应(反过来也是一样的 总之Json和映射类的信息名肯定要保持一致) 只有写错一个单词 Json就会解析失败

//解析胜利

public string uiPanelType { get; set; }
public string uiPanelPath { get; set; }
public List <DataItem> data { get; set; }

"uiPanelType":"settingPanel","uiPanelPath":"UIPanel/SettingPanel"}



//解析失败 映射类和Json的数据名称不一样 解析会报错

public string PanelType { get; set; }
public string PanelPath { get; set; }
public List <DataItem> paneldata { get; set; }

"uiPanelType":"settingPanel","uiPanelPath":"UIPanel/SettingPanel"}

我讲的可能跟一坨屎一样 甚至更差 (我帮你们讲了) 最好就是本人去度娘或者谷爹查下材料,当初我也就会最根本的操作回头我再深刻学一下而后再写一篇对于Json的文章进去(祸患社会~)

刚刚是咱们一键生成的映射类 当初咱们本人入手来写下吧

PanelData类

public class PanelData
{
    /// <summary>
    /// UI面板类型
    /// </summary>
    public string uiPanelType;
    /// <summary>
    /// UI面板门路
    /// </summary>
    public string uiPanelPath;
}

UIPanelDataList类

public class UIPanelDataList
{
    public PanelData[] data;
}

两个映射类写完了 咱们还要写一个UIPanelType枚举类 用来定义咱们的面板类型,到时候咱们拿到Json文件里的uiPanelType数据,而后将String转换成枚举类型就能失去面板的类型了

UIPanelType类:

public enum UIPanelType
{
     settingPanel           //设置面板
}

2、创立面板的基类

创立一个面板抽象类(就是所有面板的基类)来实现面板共有的一些办法,比方面板进入的时候要做的一些事件,面板退出时候要做的一些事件

UIPanelBase类比较简单 没什么好讲了 依据本人的需要定义面板共有的一些办法

UIPanelBase类:

using UnityEngine;

public class UIPanelBase : MonoBehaviour
{
    /// <summary>
    /// 入栈状态
    /// </summary>
    public virtual void PushState()
    {
        Debug.Log("入栈状态");
    }

    /// <summary>
    /// 出栈
    /// </summary>
    public virtual void PopState()
    {
        Debug.Log("出栈状态");
    }

    /// <summary>
    /// 面板复原
    /// </summary>
    public virtual void RemotState()
    {
        Debug.Log("复原状态");
    }
}

3、读取Json文件

通过代码读取Json文件失去所有面板的信息,而后咱们就能通过这些信息来操作面板

这里我举荐应用LitJson,当然大家也能够用Unity自带的JsonUtility,哪个棘手就用那个。

要应用LitJson就须要去援用它 下载litJson.dll(这里我就不放下载链接了 Github上或者百度一下就有了) 放到Plugins文件夹下 如果没有就本人创立一个

当初咱们能够创立外围的UIManager类

MainUIManager类

首先第一步援用litJson 非常简单就一句话 “using LitJson;”

而后把MainUIManager类做成一个单例(不懂单例的能够百度看看)

using System.Collections.Generic;
using UnityEngine;
using LitJson;

public class MainUIManager : MonoBehaviour
{
    /// <summary>
    /// MainUIManager单例
    /// </summary>
    public static MainUIManager Instance;
    
    public void Awake()
    {
          Instance = this;
    }
}

单例做完后 咱们须要两个字典 一个用来存储从Json解析进去的数据 另一个用来存储面板的UIPanelBase(咱们须要在每个要操作的面板上增加一个脚本并继承UIPanelBase类 咱们拿到面板身上的UIPanelBase类就相当于拿到了面板的实例)

using System.Collections.Generic;
using UnityEngine;
using LitJson;

public class MainUIManager : MonoBehaviour
{
    /// <summary>
    /// MainUIManager单例
    /// </summary>
    public static MainUIManager Instance;
    
     /// <summary>
    /// 保留从Json文件解析进去的面板数据
    /// </summary>
    private Dictionary<UIPanelType, string> oriPanelDataDic = new Dictionary<UIPanelType, string>();
    
    /// <summary>
    /// 保留面板实例
    /// </summary>
    private Dictionary<UIPanelType, UIPanelBase> newPanelDataDic = new Dictionary<UIPanelType, UIPanelBase>();
    
    public void Awake()
    {
          Instance = this;
    }
}

我英文不是太好 取名会有点差

当初咱们就能够来读取Json文件里的数据了

using System.Collections.Generic;
using UnityEngine;
using LitJson;

public class MainUIManager : MonoBehaviour
{
    /// <summary>
    /// MainUIManager单例
    /// </summary>
    public static MainUIManager Instance;
    
     /// <summary>
    /// 保留从Json文件解析进去的面板数据
    /// </summary>
    private Dictionary<UIPanelType, string> oriPanelDataDic = new Dictionary<UIPanelType, string>();
    
    /// <summary>
    /// 保留面板实例
    /// </summary>
    private Dictionary<UIPanelType, UIPanelBase> newPanelDataDic = new Dictionary<UIPanelType, UIPanelBase>();
    
    /// <summary>
    /// 文本资源 用于读取Json文件里的数据
    /// </summary>
    private TextAsset uiPanelJson;
    
    public void Awake()
    {
          Instance = this;
    }
    
     /// <summary>
    /// 初始化Json数据
    /// </summary>
    private void InitJson()
    {
        //获取Json文件数据
        uiPanelJson = Resources.Load<TextAsset>("UIPanelData");

        //解析Json
        UIPanelDataList panelData = JsonMapper.ToObject<UIPanelDataList>(uiPanelJson.text);

        //存储数据到字典中
        foreach (PanelData item in panelData.data)
        {
            //将String类型转换成枚举类型 并存储到字典中
            oriPanelDataDic.Add((UIPanelType)System.Enum.Parse(typeof(UIPanelType), item.uiPanelType.ToString()), item.uiPanelPath);
        }
    }
    
}

接下来咱们拿到Canvas的Transform组件 等等咱们写生成面板的逻辑须要把面板的父物体设置到Canvas下 Canvas我设置了个Tag 叫“UICanvas”所以我间接通过Tag去查找它

using System.Collections.Generic;
using UnityEngine;
using LitJson;

public class MainUIManager : MonoBehaviour
{
    /// <summary>
    /// MainUIManager单例
    /// </summary>
    public static MainUIManager Instance;
    
     /// <summary>
    /// 保留从Json文件解析进去的面板数据
    /// </summary>
    private Dictionary<UIPanelType, string> oriPanelDataDic = new Dictionary<UIPanelType, string>();
    
    /// <summary>
    /// 保留面板实例
    /// </summary>
    private Dictionary<UIPanelType, UIPanelBase> newPanelDataDic = new Dictionary<UIPanelType, UIPanelBase>();
    
    /// <summary>
    /// 文本资源 用于读取Json文件里的数据
    /// </summary>
    private TextAsset uiPanelJson;
    
    private Transform uiCanvasTran;
    
    public void Awake()
    {
          Instance = this;
          InitJson();
          Init();
    }
    
     /// <summary>
    /// 初始化Json数据
    /// </summary>
    private void InitJson()
    {
        //获取Json文件数据
        uiPanelJson = Resources.Load<TextAsset>("UIPanelData");

        //解析Json
        UIPanelDataList panelData = JsonMapper.ToObject<UIPanelDataList>(uiPanelJson.text);

        //存储数据到字典中
        foreach (PanelData item in panelData.data)
        {
            //将String类型转换成枚举类型 并存储到字典中
            oriPanelDataDic.Add((UIPanelType)System.Enum.Parse(typeof(UIPanelType), item.uiPanelType.ToString()), item.uiPanelPath);
        }
    }
    
    
    /// <summary>
    /// 初始化操作
    /// </summary>
    private void Init()
    {
        if (uiCanvasTran == null)
        {
            uiCanvasTran = GameObject.FindGameObjectWithTag("UICanvas").transform;
        }
    }
}

接下来写生成面板的逻辑 也是很简略的

using System.Collections.Generic;
using UnityEngine;
using LitJson;

public class MainUIManager : MonoBehaviour
{
    /// <summary>
    /// MainUIManager单例
    /// </summary>
    public static MainUIManager Instance;
    
     /// <summary>
    /// 保留从Json文件解析进去的面板数据
    /// </summary>
    private Dictionary<UIPanelType, string> oriPanelDataDic = new Dictionary<UIPanelType, string>();
    
    /// <summary>
    /// 保留面板实例
    /// </summary>
    private Dictionary<UIPanelType, UIPanelBase> newPanelDataDic = new Dictionary<UIPanelType, UIPanelBase>();
    
    /// <summary>
    /// 文本资源 用于读取Json文件里的数据
    /// </summary>
    private TextAsset uiPanelJson;
    
    private Transform uiCanvasTran;
    
    public void Awake()
    {
          Instance = this;
          InitJson();
          Init();
    }
    
     /// <summary>
    /// 初始化Json数据
    /// </summary>
    private void InitJson()
    {
        //获取Json文件数据
        uiPanelJson = Resources.Load<TextAsset>("UIPanelData");

        //解析Json
        UIPanelDataList panelData = JsonMapper.ToObject<UIPanelDataList>(uiPanelJson.text);

        //存储数据到字典中
        foreach (PanelData item in panelData.data)
        {
            //将String类型转换成枚举类型 并存储到字典中
            oriPanelDataDic.Add((UIPanelType)System.Enum.Parse(typeof(UIPanelType), item.uiPanelType.ToString()), item.uiPanelPath);
        }
    }
    
    
    /// <summary>
    /// 初始化操作
    /// </summary>
    private void Init()
    {
        if (uiCanvasTran == null)
        {
            uiCanvasTran = GameObject.FindGameObjectWithTag("UICanvas").transform;
        }
    }
}

    /// <summary>
    /// 生成面板
    /// </summary>
    /// <param name="panelType">面板类型</param>
    /// <returns></returns>
    public UIPanelBase GeneratePanel(UIPanelType panelType)
    {
        UIPanelBase uiPanelBase;
        newPanelDataDic.TryGetValue(panelType, out uiPanelBase);

        //如果uiPanelBase为空就示意没在字典中
        if (uiPanelBase == null)
        {
            string panelPath;
            oriPanelDataDic.TryGetValue(panelType, out panelPath);
            GameObject newPanel = Instantiate(Resources.Load<GameObject>(panelPath));
            newPanel.transform.SetParent(uiCanvasTran, false);
            uiPanelBase = newPanel.GetComponent<UIPanelBase>();
            newPanelDataDic.Add(panelType, uiPanelBase);
            return uiPanelBase;
        }
        return uiPanelBase;
    }

生成面板并返回失去uiPanelBase(面板基类)后咱们就能够做面板入栈出栈的操作了

using System.Collections.Generic;
using UnityEngine;
using LitJson;

public class MainUIManager : MonoBehaviour
{
    /// <summary>
    /// MainUIManager单例
    /// </summary>
    public static MainUIManager Instance;
    
     /// <summary>
    /// 保留从Json文件解析进去的面板数据
    /// </summary>
    private Dictionary<UIPanelType, string> oriPanelDataDic = new Dictionary<UIPanelType, string>();
    
    /// <summary>
    /// 保留面板实例
    /// </summary>
    private Dictionary<UIPanelType, UIPanelBase> newPanelDataDic = new Dictionary<UIPanelType, UIPanelBase>();
    
    /// <summary>
    /// 文本资源 用于读取Json文件里的数据
    /// </summary>
    private TextAsset uiPanelJson;
    
    private Transform uiCanvasTran;
    
    private Stack<UIPanelBase> panelStack;
    
    public void Awake()
    {
          Instance = this;
          InitJson();
          Init();
    }
    
     /// <summary>
    /// 初始化Json数据
    /// </summary>
    private void InitJson()
    {
        //获取Json文件数据
        uiPanelJson = Resources.Load<TextAsset>("UIPanelData");

        //解析Json
        UIPanelDataList panelData = JsonMapper.ToObject<UIPanelDataList>(uiPanelJson.text);

        //存储数据到字典中
        foreach (PanelData item in panelData.data)
        {
            //将String类型转换成枚举类型 并存储到字典中
            oriPanelDataDic.Add((UIPanelType)System.Enum.Parse(typeof(UIPanelType), item.uiPanelType.ToString()), item.uiPanelPath);
        }
    }
    
    
    /// <summary>
    /// 初始化操作
    /// </summary>
    private void Init()
    {
        if (uiCanvasTran == null)
        {
            uiCanvasTran = GameObject.FindGameObjectWithTag("UICanvas").transform;
        }
    }


    /// <summary>
    /// 生成面板
    /// </summary>
    /// <param name="panelType">面板类型</param>
    /// <returns></returns>
    public UIPanelBase GeneratePanel(UIPanelType panelType)
    {
        UIPanelBase uiPanelBase;
        newPanelDataDic.TryGetValue(panelType, out uiPanelBase);

        //如果uiPanelBase为空就示意没在字典中
        if (uiPanelBase == null)
        {
            string panelPath;
            oriPanelDataDic.TryGetValue(panelType, out panelPath);
            GameObject newPanel = Instantiate(Resources.Load<GameObject>(panelPath));
            newPanel.transform.SetParent(uiCanvasTran, false);
            uiPanelBase = newPanel.GetComponent<UIPanelBase>();
            newPanelDataDic.Add(panelType, uiPanelBase);
            return uiPanelBase;
        }
        return uiPanelBase;
    }
    
    /// <summary>
    /// 面板入栈
    /// </summary>
    /// <param name="panelType"></param>
    public void PushPanel(UIPanelType panelType)
    {
        if(panelStack==null)
        {
            panelStack = new Stack<UIPanelBase>();
        }
        UIPanelBase uiPanelBase = GeneratePanel(panelType);
        uiPanelBase.PushState();
        panelStack.Push(uiPanelBase);
    }

    /// <summary>
    /// 面板出栈
    /// </summary>
    public void PopPanel()
    {
        if (panelStack == null) return;

        //拿到顶部数据
        UIPanelBase currentUIpanelBase = panelStack.Pop();
        currentUIpanelBase.PopState();

        if (panelStack.Count > 0)
        {
            UIPanelBase outUIpanelBase = panelStack.Peek();
            outUIpanelBase.RemotState();
        }
    }
}

这样咱们的MainUIManager类就写完了 接下来调用就很简略了 咱们为SettingPanel增加一个脚本“SettingPanel”(记得继承UIPanelBase)

public class SettingPanel : UIPanelBase
{
    public void AddButtonClick()
    {
        MainUIManager.Instance.PushPanel(UIPanelType.settingPanel);
    }
}

办法写好后 把他绑定到要点击的Button中(例如 点击设置按钮 显示设置面板 就把设置面板的脚本绑定到设置按钮的Button组件中)

啊不会绑定 间接把面板拖到OnClick上而后抉择咱们刚刚写的办法就好了 或者百度一下马上就懂了

当初咱们所有的步骤都实现了 运行游戏看看成果吧!失常来说咱们当初点击设置按钮就会弹出面板的了


4、最初

如果你弹出了面板 那就证实胜利了,如果没弹出也不要灰心好好钻研下代码看看报错,在下面的案例中咱们只做了面板入栈的操作,你能够试着实现下面板出栈的操作,非常简单的只须要写一行代码就好了,写代码就是要思考为什么,而不是什么都跟着敲,试着本人在这套框架上增加一些新的性能吧!

最初还是要阐明下 文章次要是用来坚固我学到的常识用的,所以没那些大佬讲的这么具体,要是把你看懵了!那我就十分道歉了,倡议关掉去看一遍残缺的教程哈哈哈哈~~

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理