基於UGUI的UI框架
一.Demo展示
二.關鍵類
MonoSingle
繼承MonoBehaviour的單例基類;做了一些特殊處理;
保證場景中必須有GameInit名稱的物體,所有單例管理器腳本都掛在該物體上;
繼承單例基類后,需要私有化構造;
public class MonoSingle<T> : MonoBehaviour where T :MonoSingle<T>
{
protected static T instance;
public static T I
{
get
{
if (instance == null)
{
GameObject go = GameObject.Find("GameInit");
if (go == null)
{
go = new GameObject("GameInit");
DontDestroyOnLoad(go);
}
instance = go.GetComponent<T>();
if (instance == null)
instance = go.AddComponent<T>();
}
return instance;
}
}
}
UIType
所有UI的面板都需要在這個類中添加常量字段,方便比對;
public class UIType
{
public const string UIMain = "panmain";
public const string UIInventory = "paninventory";
public const string UIShop = "panshop";
public const string UIQuest = "panquest";
public const string UIEquipment = "panequipment";
public const string UISkill = "panskill";
}
創建UI層級枚舉,根據層級設置UI面板的父節點
public enum UILayer
{
Back, //背景層
Default, //默認層
Pop, //彈窗
Top //頂層,適用懸浮等
}
UIBase
所有UI面板的基類;創建新的UI面板必須繼承UIBase同時重寫虛方法
public abstract class UIBase : MonoBehaviour
{
//層級字段,根據層級設置父節點
public UILayer uiLayer;
//UI類型字段
public string uiType;
//面板進入時調用
public virtual void OnEnter()
{
//設置父節點
transform.SetParent(UIManager.I.dicLayer[uiLayer]);
}
//面板停止時調用(鼠標與面板的交互停止)
public virtual void OnPause()
{
}
//面板恢復使用時調用(鼠標與面板的交互恢復)
public virtual void OnResume()
{
}
//面板退出時調用
public virtual void OnExit()
{
}
}
UIManager
//partial拆分類,有點像.h和.cpp的區別,但是只是把一個類分成兩個寫
public partial class UIManager : MonoSingle<UIManager>
{
//所有UI面板perfab的路徑key:UIType——value:Resources下的路徑
public Dictionary<string, string> dicPath;
//根據上面路徑,加載的好的具體的UI面板類
public Dictionary<string, UIBase> dicPanel;
//棧存儲所有打開的UI面板,打開UI,push棧,關閉UI,pop棧
private Stack<UIBase> panelStack;
//canvas.transform 方便設置父節點
public Transform canvasTf;
//存儲UI層級節點,方便設置父節點
public Dictionary<UILayer, Transform> dicLayer;
//存儲UI層級對應的名稱,用來加載ui層級節點時命名
public Dictionary<UILayer, string> dicLayerName;
private UIManager()
{
//初始化perfab路徑和ui層級節點
InitPath();
InitUILayer();
}
public void Awake()
{
canvasTf = GameObject.Find("Canvas").transform;
//加載層級節點
LoadLayer();
}
private void LoadLayer()
{
dicLayer = new Dictionary<UILayer, Transform>();
for (int i = 0; i < dicLayerName.Count; ++i)
{
GameObject layer = new GameObject(dicLayerName[(UILayer)i]);
layer.transform.SetParent(canvasTf);
dicLayer.Add((UILayer) i, layer.transform);
}
}
//獲取dicPanel中存儲的基層UIBase的類,如果為空,先加載添加進去
private UIBase GetPanel(string panelType)
{
if (dicPanel == null)
dicPanel = new Dictionary<string, UIBase>();
panelType = panelType.ToLower();
if (dicPanel.ContainsKey(panelType))
return dicPanel[panelType];
else
{
string path = string.Empty;
if (dicPath.ContainsKey(panelType))
path = dicPath[panelType];
else
return null;
GameObject go = Resources.Load<GameObject>(path);
GameObject goPanel = GameObject.Instantiate(go, canvasTf, false);
UIBase panel = goPanel.GetComponent<UIBase>();
dicPanel.Add(panelType, panel);
return panel;
}
}
//打開UI界面
public void PushPanel(string panelType)
{
if (panelStack == null)
{
panelStack = new Stack<UIBase>();
}
//停止上一個界面
if (panelStack.Count > 0)
{
UIBase top = panelStack.Peek();
top.OnPause();
}
UIBase panel = GetPanel(panelType);
panelStack.Push(panel);
panel.OnEnter();
}
//關閉最上層界面
public void PopPanel()
{
if (panelStack == null)
{
panelStack = new Stack<UIBase>();
}
if (panelStack.Count <= 0)
{
return;
}
//退出棧頂面板
UIBase top = panelStack.Pop();
top.OnExit();
//恢復上一個面板
if (panelStack.Count > 0)
{
UIBase panel = panelStack.Peek();
panel.OnResume();
}
}
//獲取最上層面板
public UIBase GetTopPanel()
{
if (panelStack.Count > 0)
return panelStack.Peek();
else
return null;
}
}
使用partial最主要原因是想把加載和邏輯分開,加載部分類可以使用ScriptableObject自動生成或使用json外部讀取;
我這里demo就手動添加了;
public partial class UIManager : MonoSingle<UIManager>
{
private void InitPath()
{
dicPath = new Dictionary<string, string>();
//自動生成代碼,或使用json加載
dicPath["panmain"] = "UIPanel/PanMain";
dicPath["paninventory"] = "UIPanel/paninventory";
dicPath["panskill"] = "UIPanel/panskill";
dicPath["panquest"] = "UIPanel/panquest";
dicPath["panequipment"] = "UIPanel/panequipment";
dicPath["panshop"] = "UIPanel/panshop";
}
private void InitUILayer()
{
dicLayerName = new Dictionary<UILayer, string>();
dicLayerName.Add(UILayer.Back,"Front");
dicLayerName.Add(UILayer.Default,"Default");
dicLayerName.Add(UILayer.Pop,"Pop");
dicLayerName.Add(UILayer.Top,"Top");
}
}
三.測試用類
Pan開頭類
PanMain主界面,打開后一直顯示,不會隱藏;
public class PanMain : UIBase
{
PanMain()
{
//初始化UIBase中層級和類型字段
uiLayer = UILayer.Default;
uiType = UIType.UIMain;
}
//打開界面按鈕,公有字段,inspector界面賦值
public Button btnInventory;
public Button btnShop;
public Button btnSkill;
public Button btnQuest;
public Button btnEquipment;
public override void OnEnter()
{
base.OnEnter();
Debug.Log("打開Mian");
//封裝的按鈕添加事件方法,點擊打開或關閉
AddBtnListener(btnEquipment,UIType.UIEquipment);
AddBtnListener(btnShop,UIType.UIShop);
AddBtnListener(btnSkill,UIType.UISkill);
AddBtnListener(btnQuest,UIType.UIQuest);
AddBtnListener(btnInventory,UIType.UIInventory);
}
private void AddBtnListener(Button go,string type)
{
go.onClick.AddListener(() =>
{
if (UIManager.I.GetTopPanel().uiType == type)
UIManager.I.PopPanel();
else
UIManager.I.PushPanel(type);
});
}
public override void OnPause()
{
//取消下面注釋,打開新界面時,主界面按鈕會失效
//ChangeBtnState(false);
}
public override void OnResume()
{
//ChangeBtnState(true);
}
public override void OnExit()
{
}
//按鈕失效代碼
public void ChangeBtnState(bool value)
{
btnInventory.enabled = value;
btnShop.enabled = value;
btnSkill.enabled = value;
btnQuest.enabled = value;
btnEquipment.enabled = value;
}
}
PanInventory背包,其他shop,quest,equip,skill等邏輯相似,就只分析一個了;
public class PanInventory : UIBase
{
PanInventory()
{
uiLayer = UILayer.Default;
uiType = UIType.UIInventory;
}
//關閉界面按鈕
public Button btnClose;
public void Start()
{
btnClose.onClick.AddListener(() => { UIManager.I.PopPanel(); });
}
public override void OnEnter()
{
base.OnEnter();
this.gameObject.SetActive(true);
}
//非頂層時按鈕是否失效,可自行設置;
public override void OnPause()
{
//btnClose.enabled = false;
}
public override void OnResume()
{
//btnClose.enabled = true;
}
public override void OnExit()
{
this.gameObject.SetActive(false);
}
}
GameInit
public class GameInit : MonoSingle<GameInit>
{
private void Awake()
{
DontDestroyOnLoad(this.gameObject);
//打開主界面
UIManager.I.PushPanel(UIType.UIMain);
}
}
運行前:
運行后:
由於目前硬件性能和內存完全夠用的情況下,所有UI面板加載一次后不會被銷毀,只會被隱藏;
該Demo已被我上傳至Gitee,可自行下載學習;