0 前言
作为初学者常用的UI设计方式以及与UI与游戏对象交互的方式是使用简单的对象拖拽,但是这样的方式耦合性很高,而且对于协作项目来说不太友好,所以使用一个单一的UI框架管理Resources中的UIPanel预制体,在需要时进行创建和销毁是很有必要的,其一可以降低UI与游戏程序之间的耦合性,提高UI的复用能力,其二可以优化游戏性能,在必要的时候才创建所需的UI组件,减少在开始时大量创建UI的性能开销。
1 程序结构
UIManager
- UIManager.cs : Class
- BasePanel.cs : Class
- UIPanelInfo.cs : Class
- UIPanelType.cs : Enum
- UIPanel.json : Json
2 工具类:UIPanelInfo.cs
该类用于对Json文件进行反序列化,将Json文本中的路径和类型字段转换为对应的对象,利用了C#中对Json进行序列化操作的原生接口ISerializationCallbackReceiver的回调方法处理了Json对象。
using System;
using UnityEngine;
[Serializable]
public class UIPanelInfo : ISerializationCallbackReceiver
{
[NonSerialized]
public UIPanelType panelType;
public string panelTypeString;
public string path;
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
var type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString);
panelType = type;
}
}
3 PanelType枚举类以及BasePanel类
PanelType类用于为单个Panel赋予唯一Key用来生成和调用Panel。
public enum UIPanelType
{
PanelType,
...
}
BasePanel类是对所有Panel的声明周期进行了定义的基类,在其中我对GetOverUI和FindUIElem方法进行了封装,用来获得所需要的对象,其余方法是对Panel生命周期的虚函数声明,包括OnEnter(进入时)、OnPause(暂停时)、OnResume(继续时)、OnExit(退出时)、OnClose(关闭时)这五个生命周期,在继承其的基类中,可以override这几个虚函数,用来对不同生命周期中的Panel进行操作,同时强推一下CanvasGroup这个组件,对于Panel的效果实现有很大帮助。
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class BasePanel : MonoBehaviour
{
private CanvasGroup canvasGroup;
public CanvasGroup CanvasGroupValue
{
get
{
return canvasGroup;
}
set
{
canvasGroup = value;
}
}
private void Awake()
{
canvasGroup = GetComponent<CanvasGroup>();
}
public virtual void OnEnter()
{
canvasGroup.alpha = 1;
}
public virtual void OnPause()
{
canvasGroup.blocksRaycasts = false;
}
public virtual void OnResume()
{
canvasGroup.blocksRaycasts = true;
}
public virtual void OnExit()
{
canvasGroup.alpha = 0;
}
public virtual void OnClose()
{
UIManager.Instance.PopPanel();
}
protected GameObject GetOverUI(GameObject canvas)
{
PointerEventData pointerEventData = new PointerEventData(EventSystem.current);
pointerEventData.position = Input.mousePosition;
GraphicRaycaster gr = canvas.GetComponent<GraphicRaycaster>();
List<RaycastResult> results = new List<RaycastResult>();
gr.Raycast(pointerEventData, results);
if (results.Count != 0)
{
return results[0].gameObject;
}
return null;
}
protected GameObject FindUIElement(string elementName)
{
GameObject returnGameObject = null;
foreach (var child in gameObject.GetComponentsInChildren<Transform>(true))
{
if (child.name.Equals(elementName))
{
returnGameObject = child.gameObject;
}
}
return returnGameObject;
}
}
4 UIManager类
UIManager类是该UI框架的主要逻辑类,这个类的数据结构由存放Json中path字段的字典、每个BasePanel组件的字典、以及一个存放了Panel对象的栈组成,同时因为UIManager的特性,在类设计上使用了单例模式。 构成该类的方法主要是对Panel栈的操作方法,包括PushPanel(入栈)和PopPanel(出栈)两个主要方法。 最后为该类设计了一个内部类用来获取Json中的path和panelType,这个类使用了JsonUtility来获得Json对象的信息。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityEngine.Video;
public class UIManager : MonoBehaviour
{
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("MainCanvas").transform;
}
return _canvasTransform;
}
}
private Dictionary<UIPanelType, string> panelPathDictionary;
private Dictionary<UIPanelType, BasePanel> panelDictionary;
private Stack<BasePanel> panelStack;
private UIManager()
{
ParseUIPanelTypeJson();
}
public void PushPanel(UIPanelType panelType)
{
if (panelStack == null)
{
panelStack = new Stack<BasePanel>();
}
if (panelStack.Count > 0)
{
BasePanel topPanel = panelStack.Peek();
topPanel.OnPause();
}
BasePanel panel = GetPanel(panelType);
panel.OnEnter();
panelStack.Push(panel);
}
public void PopPanel()
{
if (panelStack == null)
{
panelStack = new Stack<BasePanel>();
}
if (panelStack.Count <= 0)
{
return;
}
BasePanel basePanel = panelStack.Pop();
basePanel.OnExit();
if (panelStack.Count <= 0) return;
BasePanel topPanel = panelStack.Peek();
topPanel.OnResume();
}
private BasePanel GetPanel(UIPanelType panelType)
{
if (panelDictionary == null)
{
panelDictionary = new Dictionary<UIPanelType, BasePanel>();
}
panelDictionary.TryGetValue(panelType, out var panel);
if (panel == null)
{
panelPathDictionary.TryGetValue(panelType, out var path);
GameObject initPanel = Instantiate(Resources.Load(path)) as GameObject;
initPanel.transform.SetParent(CanvasTransform, false);
panelDictionary.Add(panelType, initPanel.GetComponent<BasePanel>());
return initPanel.GetComponent<BasePanel>();
}
else
{
return panel;
}
}
[SerializeField]
class UIPanelTypeJson
{
public List<UIPanelInfo> infoList;
}
private void ParseUIPanelTypeJson()
{
panelPathDictionary = new Dictionary<UIPanelType, string>();
TextAsset textAsset = Resources.Load<TextAsset>("Json/UIPanels");
UIPanelTypeJson jsObject = JsonUtility.FromJson<UIPanelTypeJson>(textAsset.text);
foreach (UIPanelInfo info in jsObject.infoList)
{
panelPathDictionary.Add(info.panelType, info.path);
}
}
}
5 UIPanelJson文件
用来存放Panel预制体路径和对应的panelType
{
"infoList": [
{
"panelTypeString" : "MainMenuPanel",
"path" : "UIPanel/MainMenuPanel"
}
]
}
5 使用方法
- 在json文件中写入对应的Panel预制体路径和panelType
{
"infoList": [
{
"panelTypeString" : "PanelType",
"path" : "UIPanel/XXXPanel"
}
]
}
- 在PanelType枚举类中写入Json文件中创建的PanelType
public enum UIPanelType
{
PanelType
}
- 在方法中调用PushPanel方法创建panelTyep对应的Panel对象
UIManager.Instance.PushPanel(panelType);
- 修改XXXPanel类中的生命周期方法来实现面板效果(示例:一个简单的UI界面类)
using System;
using System.Collections;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;
public class UserInterfacePanel : BasePanel
{
private int Health;
private int sub = 0;
private int ableBullet = 0;
void Awake()
{
EventCenter.AddListener<int>(EventType.BulletRefresh, RefreshBullet);
EventCenter.AddListener<int>(EventType.HealthRefresh, RefreshHealth);
}
private void Start()
{
StartCoroutine(CheckTheNumericValue());
}
void RefreshHealth(int health)
{
var text = transform.Find("Health").transform.Find("OnTimeHealth").GetComponent<Text>();
text.text = health.ToString();
}
void RefreshBullet(int bulletContain)
{
ableBullet = bulletContain;
}
public override void OnEnter()
{
UIManager.Instance.PushPanel(UIPanelType.TipPanel);
UIManager.Instance.PushPanel(UIPanelType.PackagePanel);
}
IEnumerator CheckTheNumericValue()
{
while (true)
{
yield return new WaitForSeconds(0.1f);
var text = FindUIElement("BulletContain").GetComponent<Text>();
if (Package.Instance.GetItem(ItemType._9mmAmmo) != null)
{
text.text = $"{ableBullet} / {Package.Instance.GetItem(ItemType._9mmAmmo).count}";
}
}
}
}
6 结篇
框架对程序结构以及性能的优化是十分强大的,可以多用。
|