10.UI模块

10.UI模块


10.1 知识点

UI模块的作用

  • 统一管理UI面板的显示相关

示意图


面板基类

导入必要的Unity引用

使用了一些Unity的命名空间,如UnityEngine、UnityEngine.Events、UnityEngine.EventSystems、UnityEngine.UI,以便使用Unity的功能和类。

定义BaseUIPanel类

创建了一个名为BaseUIPanel的抽象类,该类继承自MonoBehaviour,表示所有UI面板都应该继承自这个基类。

声明成员变量

  • canvasGroup:用于控制UI面板的淡入淡出效果的CanvasGroup组件。
  • alphaSpeed:淡入淡出的速度。
  • isShow:标记UI面板是否正在显示。
  • hideCallBack:当UI面板淡出成功时要执行的委托函数。
  • UIElementDictionary:一个字典,用于存储所有的UI控件,以控件名字符串为键,控件列表为值。

在Awake方法中初始化UI控件

  • 获取或添加CanvasGroup组件,用于控制UI面板的透明度。
  • 调用FindPanelUIElement方法,初始化找到Panel下所有的UI控件,包括Button、Image、Text、Toggle、Slider、ScrollRect和InputField。

实现FindPanelUIElement方法

  • 通过泛型参数T,获取子对象中所有当前类型的控件脚本。
  • 遍历控件数组,根据控件名字将控件存储到UIElementDictionary中。
  • 如果是Button控件,为其添加点击事件监听器。
  • 如果是Toggle控件,为其添加值变化事件监听器。

实现OnButtonInitClick和OnToggleInitValueChanged方法

这些方法是虚方法,子类可以重写它们,根据控件名字执行不同的逻辑。

实现GetUIElement方法

通过控件名字从UIElementDictionary中获取对应类型的UI控件脚本。

实现AddUIElement和RemoveUIElement方法

用于动态添加和移除UI控件到/从UIElementDictionary中。

在Start方法中调用Init方法

Init方法是一个抽象方法,子类必须实现,用于初始化UI面板,包括按钮事件监听等内容。

实现ShowMe和HideMe方法

  • ShowMe方法用于显示UI面板,将isShow标记设置为true,并将canvasGroup的透明度设置为0,实现淡入效果。
  • HideMe方法用于隐藏UI面板,接受一个回调函数作为参数,将isShow标记设置为false,并将canvasGroup的透明度设置为1,实现淡出效果,并记录传入的淡出成功后会执行的函数。

在Update方法中处理淡入淡出效果

  • 如果isShow为true且canvasGroup的透明度不等于1,则逐渐增加透明度以实现淡入效果。
  • 如果isShow为false且canvasGroup的透明度不等于0,则逐渐减小透明度以实现淡出效果,当透明度接近0时,执行淡出回调函数。

面板管理器

导入必要的Unity引用

使用了一些Unity的命名空间,如UnityEngine、UnityEngine.Events、UnityEngine.EventSystems,以便使用Unity的功能和类。

定义BaseUIManager类

创建了一个名为BaseUIManager的类,继承自自定义的BaseSingletonInCSharp基类,表示UI管理器类。

声明成员变量

  • panelDictionary:一个字典,用于存储面板的容器,以面板名字为键,面板脚本为值。
  • UICanvasTransform:用于存储Canvas对象的引用,用于将UI面板显示在Canvas上。

在构造函数中获取Canvas对象

在构造函数中尝试通过GameObject.Find方法找到名为”UICanvas”的Canvas对象引用。如果找不到Canvas对象,就通过GameObject.Instantiate方法创建一个Canvas对象,并将其设置为DontDestroyOnLoad,以确保在场景切换时不被销毁。

实现ShowPanel方法

这个方法用于显示UI面板,接受一个泛型参数T,表示要显示的UI面板类型。

  1. 首先根据面板类型获取面板名字。
  2. 如果panelDictionary中已包含该面板名字的键,说明面板已经存在,直接返回该面板。
  3. 如果面板不存在,则加载对应的面板预制体,将其设置为Canvas的子对象,并获取面板脚本。
  4. 如果成功获取面板脚本,将面板添加到panelDictionary中,并调用其ShowMe方法来显示面板。

实现HidePanel方法

这个方法用于隐藏UI面板,接受一个泛型参数T,表示要隐藏的UI面板类型,以及一个isFade参数,用于控制是否淡出效果。

  1. 根据面板类型获取面板名字。
  2. 如果panelDictionary中包含该面板名字的键,说明面板存在。
  3. 如果isFade为true,则调用面板的HideMe方法,在面板淡出成功后销毁面板对象,并从panelDictionary中移除对应的键值对。
  4. 如果isFade为false,直接销毁面板对象,并从panelDictionary中移除对应的键值对。

实现GetPanel方法

这个方法用于获取UI面板,接受一个泛型参数T,表示要获取的UI面板类型。

  1. 根据面板类型获取面板名字。
  2. 如果panelDictionary中包含该面板名字的键,返回对应的面板脚本。
  3. 如果面板不存在,发出警告并返回null。

实现AddCustomEventListener方法

这个方法用于给UI控件添加自定义事件监听,接受控件对象、事件类型和响应函数作为参数。

  1. 首先获取控件上的EventTrigger组件,如果不存在,则添加一个EventTrigger组件。
  2. 创建一个EventTrigger.Entry对象,设置其事件类型为传入的eventTriggerType,并添加响应函数。
  3. 将该Entry对象添加到EventTrigger的事件列表中。

面板管理器对象

  • 包括一个屏幕框架的Canvas和UI摄像机

进行测试

  • 创建面板预制体,挂载面板脚本。面板脚本继承BaseUIPanel,关联面板控件并添加监听。
  • 在适当的时候调用面板管理器切换面板
BaseUIManager.Instance.ShowPanel<TestPanel2>();
BaseUIManager.Instance.HidePanel<TestPanel1>();

注意:现在UI默认都是拿Resources下UI文件夹中的同名预制体,可以按照需要进行优化。也可以传进来路径去Resources下找。也可以在定义Canvas定义不同层级子物体,指定加入到什么层级。随着版本更新,UI逻辑可以进一步优化。


10.2 知识点代码

BaseUGUIPanel

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;

// 基础UI面板类,所有UI面板应该继承自此类
public abstract class BaseUGUIPanel : MonoBehaviour
{
    private CanvasGroup canvasGroup; // 用于控制UI面板的淡入淡出效果的CanvasGroup组件
    private float alphaSpeed = 10; // 淡入淡出的速度
    private bool isShow; // 标记UI面板是否正在显示
    private UnityAction hideCallBack; // 当UI面板淡出成功时要执行的委托函数

    //通过里式转换原则 来存储所有的控件 是通过控件名字符串绑定框架的 因为可能有同名的控件 放在同一个List里
    private Dictionary<string, List<UIBehaviour>> UIElementDictionary = new Dictionary<string, List<UIBehaviour>>();

    // Awake方法在对象实例化后立即调用
    protected virtual void Awake()
    {
        // 获取或添加CanvasGroup组件
        canvasGroup = GetComponent<CanvasGroup>();
        if (canvasGroup == null)
        {
            canvasGroup = gameObject.AddComponent<CanvasGroup>();
            if (canvasGroup == null)
            {
                Debug.LogError("Failed to add CanvasGroup component.");
            }
        }

        //初始化找Panel下所有控件
        FindPanelUIElement<Button>();
        FindPanelUIElement<Image>();
        FindPanelUIElement<Text>();
        FindPanelUIElement<Toggle>();
        FindPanelUIElement<Slider>();
        FindPanelUIElement<ScrollRect>();
        FindPanelUIElement<InputField>();
    }

    /// <summary>
    /// 找到子对象的对应控件
    /// </summary>
    /// <typeparam name="T"></typeparam>
    private void FindPanelUIElement<T>() where T : UIBehaviour
    {
        //获得子对象中所有当前类型的控件脚本
        T[] controls = this.GetComponentsInChildren<T>();

        //
        for (int i = 0; i < controls.Length; ++i)
        {
            string objName = controls[i].gameObject.name;
            if (UIElementDictionary.ContainsKey(objName))
                UIElementDictionary[objName].Add(controls[i]);
            else
                UIElementDictionary.Add(objName, new List<UIBehaviour>() { controls[i] });

            //如果是按钮控件
            if (controls[i] is Button)
            {
                (controls[i] as Button).onClick.AddListener(() =>
                {
                    OnButtonInitClick(objName);
                });
            }

            //如果是单选框或者多选框
            else if (controls[i] is Toggle)
            {
                (controls[i] as Toggle).onValueChanged.AddListener((value) =>
                {
                    OnToggleInitValueChanged(objName, value);
                });
            }
        }
    }

    //按钮初始化时的监听函数 重写后 分支判断名字执行不同逻辑
    protected virtual void OnButtonInitClick(string btnName)
    {

    }

    //多选框初始化时的监听函数 重写后 分支判断名字执行不同逻辑
    protected virtual void OnToggleInitValueChanged(string toggleName, bool value)
    {

    }

    /// <summary>
    /// 得到对应名字的对应控件脚本
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="uIElement"></param>
    /// <returns></returns>
    protected T GetUIElement<T>(string uIElement) where T : UIBehaviour
    {
        if (UIElementDictionary.ContainsKey(uIElement))
        {
            for (int i = 0; i < UIElementDictionary[uIElement].Count; ++i)
            {
                if (UIElementDictionary[uIElement][i] is T)
                    return UIElementDictionary[uIElement][i] as T;
            }
        }

        return null;
    }

    // 动态添加控件
    protected void AddUIElement<T>(string name, T uiElement) where T : UIBehaviour
    {
        if (UIElementDictionary.ContainsKey(name))
        {
            UIElementDictionary[name].Add(uiElement);
        }
        else
        {
            UIElementDictionary.Add(name, new List<UIBehaviour>() { uiElement });
        }
    }

    // 动态移除控件
    protected void RemoveUIElement<T>(string name, T uiElement) where T : UIBehaviour
    {
        if (UIElementDictionary.ContainsKey(name))
        {
            UIElementDictionary[name].Remove(uiElement);
        }
    }


    // Start方法在第一帧之前调用
    protected virtual void Start()
    {
        Init(); // 调用子类的Init方法,用于初始化按钮事件监听等内容
    }

    // 子类必须实现的抽象方法,用于初始化UI面板
    public abstract void Init();

    // 显示UI面板时调用的方法
    public virtual void ShowMe()
    {
        isShow = true;
        canvasGroup.alpha = 0; // 将透明度设置为0,实现淡入效果
    }

    // 隐藏UI面板时调用的方法,接受一个回调函数作为参数
    public virtual void HideMe(UnityAction callBack)
    {
        isShow = false;
        canvasGroup.alpha = 1; // 将透明度设置为1,实现淡出效果
        hideCallBack = callBack; // 记录传入的淡出成功后会执行的函数
    }

    // Update方法在每一帧调用
    void Update()
    {
        // 淡入
        if (isShow && !Mathf.Approximately(canvasGroup.alpha, 1))
        {
            canvasGroup.alpha = Mathf.MoveTowards(canvasGroup.alpha, 1, alphaSpeed * Time.deltaTime);
        }
        // 淡出
        else if (!isShow && !Mathf.Approximately(canvasGroup.alpha, 0))
        {
            canvasGroup.alpha = Mathf.MoveTowards(canvasGroup.alpha, 0, alphaSpeed * Time.deltaTime);
            if (Mathf.Approximately(canvasGroup.alpha, 0))
            {
                hideCallBack?.Invoke(); // 在淡出完成后执行回调函数
            }
        }
    }
}

BaseUGUIManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

// 基础UI管理器类,用于管理UI面板的创建、显示和隐藏
public class BaseUGUIManager : BaseSingletonInCSharp<BaseUGUIManager>
{
    private Dictionary<string, BaseUGUIPanel> panelDictionary = new Dictionary<string, BaseUGUIPanel>(); // 存储面板的容器

    private Transform UICanvasTransform; // Canvas对象的引用

    public BaseUGUIManager()
    {
        // 在构造函数中获取Canvas对象

        try
        {
            UICanvasTransform = GameObject.Find("UICanvas").transform;

            Debug.Log("UICanvas have found.");
        }
        catch
        {
            if (UICanvasTransform == null)
            {
                UICanvasTransform = GameObject.Instantiate(Resources.Load<GameObject>("UI/UICanvas")).transform;

                Debug.Log("UICanvas Instantiate.");
            }
        }

        if (UICanvasTransform == null)
        {
            Debug.LogError("UICanvas not found.");
        }
        else
        {
            GameObject.DontDestroyOnLoad(UICanvasTransform.gameObject);
        }
    }

    // 显示面板
    public T ShowPanel<T>() where T : BaseUGUIPanel
    {
        string panelName = typeof(T).Name;

        if (panelDictionary.ContainsKey(panelName))
        {
            return panelDictionary[panelName] as T;
        }

        // 加载面板预制体
        GameObject panelObj = GameObject.Instantiate(Resources.Load<GameObject>("UI/" + panelName));
        panelObj.transform.SetParent(UICanvasTransform, false);

        // 获取面板脚本
        T panel = panelObj.GetComponent<T>();
        if (panel != null)
        {
            panelDictionary.Add(panelName, panel);
            panel.ShowMe();
        }
        else
        {
            Debug.LogError("Failed to get panel script.");
        }

        return panel;
    }

    // 隐藏面板
    public void HidePanel<T>(bool isFade = true) where T : BaseUGUIPanel
    {
        string panelName = typeof(T).Name;

        if (panelDictionary.ContainsKey(panelName))
        {
            if (isFade)
            {
                panelDictionary[panelName].HideMe(() =>
                {
                    // 面板淡出成功后销毁面板
                    GameObject.Destroy(panelDictionary[panelName].gameObject);
                    panelDictionary.Remove(panelName);
                });
            }
            else
            {
                // 直接销毁面板
                GameObject.Destroy(panelDictionary[panelName].gameObject);
                panelDictionary.Remove(panelName);
            }
        }
        else
        {
            Debug.LogWarning("Panel not found: " + panelName);
        }
    }

    // 获取面板
    public T GetPanel<T>() where T : BaseUGUIPanel
    {
        string panelName = typeof(T).Name;
        if (panelDictionary.ContainsKey(panelName))
        {
            return panelDictionary[panelName] as T;
        }
        else
        {
            Debug.LogWarning("Panel not found: " + panelName);
            return null;
        }
    }

    /// <summary>
    /// 给控件添加自定义事件监听
    /// </summary>
    /// <param name="uIElement">控件对象</param>
    /// <param name="eventTriggerType">事件类型</param>
    /// <param name="callBack">事件的响应函数</param>
    public static void AddCustomEventListener(UIBehaviour uIElement, EventTriggerType eventTriggerType, UnityAction<BaseEventData> callBack)
    {
        EventTrigger trigger = uIElement.GetComponent<EventTrigger>();
        if (trigger == null)
            trigger = uIElement.gameObject.AddComponent<EventTrigger>();

        EventTrigger.Entry entry = new EventTrigger.Entry();
        entry.eventID = eventTriggerType;
        entry.callback.AddListener(callBack);

        trigger.triggers.Add(entry);
    }
}

TestPanel1

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TestPanel1 : BaseUGUIPanel
{
    //public Button btnChangeTestPanel2;

    public override void Init()
    {
        //btnChangeTestPanel2.onClick.AddListener(() =>
        //{
        //    BaseUGUIManager.Instance.ShowPanel<TestPanel2>();
        //    BaseUGUIManager.Instance.HidePanel<TestPanel1>();
        //});

        //GetUIElement<Button>("btnChangeTestPanel2");
    }

    protected override void OnButtonInitClick(string btnName)
    {
        base.OnButtonInitClick(btnName);

        switch(btnName)
        {
            case "btnChangeTestPanel2":

                BaseUGUIManager.Instance.ShowPanel<TestPanel2>();
                BaseUGUIManager.Instance.HidePanel<TestPanel1>();

                break;
        }
    }
}

TestPanel2

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TestPanel2 : BaseUGUIPanel
{
    public Button btnChangeTestPanel1;

    public override void Init()
    {
        this.GetUIElement<Button>("btnChangeTestPanel1").onClick.AddListener((() =>
        {
            BaseUGUIManager.Instance.ShowPanel<TestPanel1>();
            BaseUGUIManager.Instance.HidePanel<TestPanel2>();
        }));
        
        // btnChangeTestPanel1.onClick.AddListener(() =>
        // {
        //     BaseUGUIManager.Instance.ShowPanel<TestPanel1>();
        //     BaseUGUIManager.Instance.HidePanel<TestPanel2>();
        // });
    }
}

Lesson10_UI模块

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson10_UI模块 : MonoBehaviour
{
    void Start()
    {
        BaseUGUIManager.Instance.ShowPanel<TestPanel1>();
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏