前言
提醒:為了能夠將知識點學得更加透徹、記得更加牢固 我會通過教學講解的方式把知識寫下來 因為在過程中會讓人從學生變成老師 這個過程會挖掘出新的知識和觀點 是一個自我思維切換而達成的知識深度挖掘和提升的過程 如果能幫助到大家那就最好 如果有講錯的地方還請多多指教!我只是一只菜雞 感謝理解!
為什么要使用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/convert/json2csharp/
我們直接選擇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、最后
如果你彈出了面板 那就證明成功了,如果沒彈出也不要灰心好好研究下代碼看看報錯,在上面的案例中我們只做了面板入棧的操作,你可以試着完成下面板出棧的操作,非常簡單的只需要寫一行代碼就好了,寫代碼就是要思考為什么,而不是什么都跟着敲,試着自己在這套框架上添加一些新的功能吧!
最后還是要說明下 文章主要是用來鞏固我學到的知識用的,所以沒那些大佬講的這么詳細,要是把你看懵了!那我就非常抱歉了,建議關掉去看一遍完整的教程哈哈哈哈~