Unity实现一个UI框架

一、引言

近期在做自己的小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界面,提高游戏开发的效率。如果有任何疑问或建议,欢迎留言交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值