一、引言
近期在做自己的小demo的UI面板,为了便捷自己的开发,于是乎突发奇想捣鼓了一个UI小框架,其实也并不是个完整的框架,只能说是一个比较方便的UI管理器。在游戏开发中,UI框架是一个至关重要的组成部分。它不仅能够提供良好的用户交互体验,还能有效地管理和控制游戏中的界面显示与隐藏。今天我将介绍如何使用Unity实现一个简单而强大的UI框架。
二、结构
1.PanelBase面板基类
首先,我们定义一个PanelBase
类作为所有UI面板的基类。该基类包含了一些基本的生命周期方法和显示/隐藏的动画效果。以下是代码示例:
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
namespace Mr.Le.Utility.UI
{
[RequireComponent(typeof(CanvasGroup))]
public abstract class PanelBase : MonoBehaviour
{
#region 字段
[SerializeField, Header("淡入淡出的速度")] protected float alphaSpeed = 2f;
private CanvasGroup _canvasGroup;
private bool _isShow; //是否显示
private Action _action;
private Ani ani;
//给面板的组件做缓存
private Dictionary<string, Component> cacheCompents = new Dictionary<string, Component>();
#endregion
#region 生命周期
protected virtual void Awake()
{
_canvasGroup = GetComponent<CanvasGroup>();
}
private void Start()
{
Init();
}
protected virtual void Update()
{
ShowOrHideAni();
}
protected virtual void OnDisable()
{
cacheCompents.Clear();
}
#endregion
#region 方法
/// <summary>
/// 初始化面板
/// </summary>
protected abstract void Init();
/// <summary>
/// 显示
/// </summary>
/// <param name="ani"></param>
public void ShowMe(Ani ani = Ani.None)
{
_canvasGroup.alpha = 0f;
_isShow = true;
this.ani = ani;
}
/// <summary>
/// 隐藏
/// </summary>
/// <param name="task"></param>
public void HideMe(Action task)
{
_canvasGroup.alpha = 1f;
_isShow = false;
if (task != null)
_action = task;
}
private void ShowOrHideAni()
{
if (_isShow && _canvasGroup.alpha < 1f)
{
switch (ani)
{
case Ani.None:
_canvasGroup.alpha = 1f;
break;
case Ani.Fade:
_canvasGroup.alpha = Mathf.Clamp(_canvasGroup.alpha + (Time.deltaTime * alphaSpeed), 0f, 1f);
break;
}
}
else if (!_isShow && _canvasGroup.alpha > 0f)
{
_canvasGroup.alpha = Mathf.Clamp(_canvasGroup.alpha - (Time.deltaTime * alphaSpeed), 0f, 1f);
if (_canvasGroup.alpha <= 0f)
_action?.Invoke();
}
}
/// <summary>
/// 获取当前面板下的指定组件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="childObjectName"></param>
/// <param name="parent"></param>
/// <returns></returns>
protected T GetController<T>(string childObjectName, Transform parent = null) where T : Component
{
parent = parent ?? this.transform;
if (cacheCompents.ContainsKey(childObjectName))
return cacheCompents[childObjectName] as T;
Transform childTransform = parent.Find(childObjectName);
if (childTransform != null)
{
T component = childTransform.GetComponent<T>();
if (component != null)
{
if (!cacheCompents.ContainsKey(childObjectName))
{
cacheCompents.Add(childObjectName, component);
}
return component;
}
}
//第一层没有找到指定的子对象 继续递归扫描
int childCount = parent.childCount;
for (int i = 0; i < childCount; i++)
{
//检测当前父对象是不是有子对象,没有直接跳出
if (parent.GetChild(i) == null) continue;
T component = GetController<T>(childObjectName, parent.GetChild(i));
if (component != null)
{
if (!cacheCompents.ContainsKey(childObjectName))
{
cacheCompents.Add(childObjectName, component);
}
return component;
}
}
//到这里没找到那就是没有
return null;
}
#endregion
}
/// <summary>
/// 面板显示隐藏动画效果枚举
/// </summary>
public enum Ani
{
None,//无效果
Fade //淡入淡出
//更多UI效果动画......
}
/// <summary>
/// 面板显示层级枚举
/// </summary>
public enum PanelShowLayer
{
Rearmost, //最后方
Rear, //后方
Middle, //中间
Front, //前方
Forefront //最前方
}
}
2.UIManager面板管理类
接下来,我们定义一个UIManager
类作为UI的管理器,负责管理面板的显示与隐藏。该管理器采用了单例模式,确保在整个游戏中只有一个实例。以下是代码示例:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mr.Le.Utility.UI;
using Mr.Le.Utility.Singleton;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;
namespace Mr.Le.Utility.Manager
{
public class UIManager : MonoSingleton<UIManager>
{
#region 字段
//显示的层
private Transform rearmost;
private Transform rear;
private Transform middle;
private Transform front;
private Transform forefront;
//层名称
private const string rearmostName = "Rearmost";
private const string rearName = "Rear";
private const string middleName = "Middle";
private const string frontName = "Front";
private const string forefrontName = "Forefront";
//面板预制体的路径 (Resouces目录下)
private const string PanelObjPath = "UI/Panel/";
//记录当前已经显示的面板
private Dictionary<string, PanelBase> panelDic = new Dictionary<string, PanelBase>();
#endregion
#region 属性
public Transform Rearmost
{
get
{
if (rearmost == null)
{
rearmost = transform.Find(rearmostName);
}
return rearmost;
}
}
public Transform Rear
{
get
{
if (rear == null)
{
rear = transform.Find(rearName);
}
return rear;
}
}
public Transform Middle
{
get
{
if (middle == null)
{
middle = transform.Find(middleName);
}
return middle;
}
}
public Transform Front
{
get
{
if (front == null)
{
front = transform.Find(frontName);
}
return front;
}
}
public Transform Forefront
{
get
{
if (forefront == null)
{
forefront = transform.Find(forefrontName);
}
return forefront;
}
}
#endregion
#region 生命周期
protected override void Awake()
{
base.Awake();
CreateOverlayCanvas();
CreateEventSystem();
}
#endregion
#region 外部方法
/// <summary>
/// 显示面板
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="layer"></param>
/// <param name="type"></param>
/// <returns></returns>
public T ShowPanel<T>(PanelShowLayer layer = PanelShowLayer.Middle, Ani type = Ani.None) where T : PanelBase
{
string panelName = typeof(T).Name;
if (panelDic.ContainsKey(panelName))
{
return panelDic[panelName] as T;
}
GameObject panel = Instantiate(Resources.Load<GameObject>(PanelObjPath + panelName));
//记录该面板要在哪个层中显示
Transform parent = null;
switch (layer)
{
case PanelShowLayer.Rearmost:
parent = Rearmost;
break;
case PanelShowLayer.Rear:
parent = Rear;
break;
case PanelShowLayer.Middle:
parent = Middle;
break;
case PanelShowLayer.Front:
parent = Front;
break;
case PanelShowLayer.Forefront:
parent = Forefront;
break;
}
//修正面板位置
panel.transform.SetParent(transform, false);
//在指定的层显示该面板
panel.transform.SetParent(parent);
T panelT = panel.GetComponent<T>();
//记录到字典
panelDic.Add(panelName, panelT);
switch (type)
{
case Ani.None:
panelT.ShowMe();
break;
case Ani.Fade:
panelT.ShowMe(Ani.Fade);
break;
}
return panelT;
}
/// <summary>
/// 隐藏面板
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="type"></param>
/// <param name="task"></param>
public void HidePanel<T>(Ani type = Ani.None, Action task = null) where T : PanelBase
{
string panelName = typeof(T).Name;
if (panelDic.ContainsKey(panelName))
{
T panelT = panelDic[panelName] as T;
switch (type)
{
case Ani.None:
Destroy(panelT.gameObject);
panelDic.Remove(panelName);
break;
case Ani.Fade:
panelT.HideMe(() =>
{
Destroy(panelT.gameObject);
panelDic.Remove(panelName);
});
break;
}
task?.Invoke();
}
else
{
Debug.LogErrorFormat("{0}面板不存在!", panelName);
}
}
/// <summary>
/// 获取面板
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetPanel<T>() where T : PanelBase
{
string panelName = typeof(T).Name;
if (panelDic.ContainsKey(panelName))
return panelDic[panelName] as T;
else
{
Debug.LogErrorFormat("{0}面板不存在!", panelName);
return null;
}
}
/// <summary>
/// 判断面板是否显示
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool IsPanelShowed<T>() where T : PanelBase
{
string panelName = typeof(T).Name;
if (panelDic.ContainsKey(panelName))
return true;
return false;
}
#endregion
#region 内部方法
/// <summary>
/// 创建Canvas
/// </summary>
private void CreateOverlayCanvas()
{
if (FindObjectOfType<Canvas>()) return;
//修改Layer
gameObject.layer = LayerMask.NameToLayer("UI");
//添加并设置Canvas组件
Canvas canvas = gameObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 30000;
//添加并设置CanvasScaler组件
CanvasScaler canvasScaler = gameObject.AddComponent<CanvasScaler>();
canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
canvasScaler.referenceResolution = new Vector2(Screen.width, Screen.height);
canvasScaler.matchWidthOrHeight = Screen.width > Screen.height ? 1 : 0;
//添加并设置GraphicRayCaster组件
GraphicRaycaster graphicRaycaster = gameObject.AddComponent<GraphicRaycaster>();
//添加子物体 作为显示层的父物体
//Rearmost层的父物体
GameObject rearmost = new GameObject(rearmostName);
rearmost.transform.SetParent(transform, false);
//Rear层的父物体
GameObject rear = new GameObject(rearName);
rear.transform.SetParent(transform, false);
//Middle层的父物体
GameObject middle = new GameObject(middleName);
middle.transform.SetParent(transform, false);
//Front层的父物体
GameObject front = new GameObject(frontName);
front.transform.SetParent(transform, false);
//Forefront层的父物体
GameObject forefront = new GameObject(forefrontName);
forefront.transform.SetParent(transform, false);
}
/// <summary>
/// 创建EventSystem
/// </summary>
private void CreateEventSystem()
{
if (FindObjectOfType<EventSystem>()) return;
GameObject eventSystem = new GameObject("EventSystem");
//切换场景不销毁
DontDestroyOnLoad(eventSystem);
eventSystem.AddComponent<EventSystem>();
eventSystem.AddComponent<StandaloneInputModule>();
}
#endregion
}
}
没错,我写的这个UI小框架就只有这两个模块,是不是很简单哈哈哈,但是确实蛮好用的,下面我会介绍用法。
三、使用方式
1、自定义自己的UI面板
将自己自定义的面板预制体放到Resources文件夹的指定路径下,面板预制体挂载指定的脚本。
注意:脚本名称需要与面板预制体名称一致,并且将脚本继承自面板基类。
2.调用方式
LoginPanel的代码:
using Mr.Le.Utility.Manager;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
namespace Mr.Le.Utility.UI
{
public class LoginPanel : PanelBase
{
private Button button;
protected override void Init()
{
button = GetController<Button>("switchButton");
button.onClick.AddListener(SwitchButtonClick);
}
private void SwitchButtonClick()
{
UIManager.Instance.ShowPanel<GamePanel>(PanelShowLayer.Middle, Ani.Fade);
UIManager.Instance.HidePanel<LoginPanel>(Ani.Fade, () =>
{
Debug.LogFormat("隐藏{0}完毕!", gameObject.name);
});
}
}
}
GamePanel代码:
using Mr.Le.Utility.Manager;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace Mr.Le.Utility.UI
{
public class GamePanel : PanelBase
{
private Button button;
protected override void Init()
{
Debug.Log("欢迎来到游戏界面!");
button = GetController<Button>("backButton");
button.onClick.AddListener(BackButtonClick);
}
private void BackButtonClick()
{
UIManager.Instance.ShowPanel<LoginPanel>(PanelShowLayer.Middle, Ani.Fade);
UIManager.Instance.HidePanel<GamePanel>(Ani.Fade, () =>
{
Debug.LogFormat("隐藏{0}完毕!", gameObject.name);
});
}
}
}
3.效果演示
四、总结
通过上述代码,我们实现了一个简单而灵活的UI框架。使用该框架,你可以轻松地创建、显示和隐藏UI面板,并通过UIManager进行统一的管理。当然,这只是一个基础框架,你可以根据实际需求进行扩展和优化。
希望这篇文章能够帮助你在Unity中更好地组织和管理UI界面,提高游戏开发的效率。如果有任何疑问或建议,欢迎留言交流!