【Unity 笔记】适合小型游戏的UI框架

程序语言:C#
开发平台:Visual Studio 2019
游戏引擎:Unity
版本:2019.4.6f1 【2017版本以上均可】

一、什么是UI?

答:UI设计(或称界面设计)是指对软件的人机交互、操作逻辑、界面美观的整体设计。

二、了解小型游戏的UI框架

【Unity 笔记】适合小型游戏的UI框架_第1张图片

  • UIWindow:封装UI界面的访问方式
  • UIEventListener:事件响应监听器
  • UIController:UI行为的管理
  • UIManager:管理UI的初始状态及提供查询指定UI的方法等

三、UIEventListener 事件监听

UI交互行为作为重要的环节,常以多样化操作提高用户交互体验,而如何选择响应方式,如按下时反馈、松手时反馈等,需要开发者手动调整。因而便利化代码操作以更灵活处理多种交互行为:

using UnityEngine;
using UnityEngine.EventSystems;

namespace UI.Framework
{
     
    public delegate void PointerHandler(PointerEventData data);

    /// 
    /// UI 事件监听器
    /// 
    public class UIEventListener : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler
    {
     
        public event PointerHandler Click;
        public event PointerHandler Enter;
        public event PointerHandler Exit;
        public event PointerHandler Down;
        public event PointerHandler Up;

        /// 
        /// 点击
        /// 
        /// 
        public void OnPointerClick(PointerEventData eventData)
        {
     
            Click?.Invoke(eventData);
        }

        /// 
        /// 点下
        /// 
        /// 
        public void OnPointerDown(PointerEventData eventData)
        {
     
            Down?.Invoke(eventData);
        }

        /// 
        /// 进入
        /// 
        /// 
        public void OnPointerEnter(PointerEventData eventData)
        {
     
            Enter?.Invoke(eventData);
        }

        /// 
        /// 离开
        /// 
        /// 
        public void OnPointerExit(PointerEventData eventData)
        {
     
            Exit?.Invoke(eventData);
        }

        /// 
        /// 松键
        /// 
        /// 
        public void OnPointerUp(PointerEventData eventData)
        {
     
            Up?.Invoke(eventData);
        }
    }
}`

3.1 引用命名空间

using UnityEngine;
using UnityEngine.EventSystems;
  • UnityEngine:Unity提供的扩展程序,此处引用为继承MonoBehaviour允许挂载到场景物体上
  • UnityEngine.EventSystem:Unity事件系统,此处为实现其提供的多种交互接口,以灵活化交互方式的使用

3.2 事件行为的声明

public event PointerHandler Click;
public event PointerHandler Enter;
public event PointerHandler Exit;
public event PointerHandler Down;
public event PointerHandler Up;
  • event

3.3 接口实现

使用接口实现操作方法,如下为使用点击事件接口实现点击事件行为。

public void OnPointerClick(PointerEventData eventData)
{
     
     Click?.Invoke(eventData);
}
  • OnPointerClick():由IPointClickHandler提供的实现方法,指点击行为的方法均为此
  • Click:指点击行为事件
  • Click?.Invoke(eventData):判断当前点击行为,如果是执行点击的对应事件

四、UIWindow 视图交互行为

挂载于UI窗口。用于提供给UI窗口的显隐方式,如[立即显隐],[延迟显隐],[渐变显隐]等。

using System.Collections;
using UnityEngine;
using Tool;

namespace UI.Framework
{
     
    /// 
    /// UI 窗口显隐效果【挂载于指定显隐窗口上】
    /// 
    [RequireComponent(typeof(CanvasGroup))]
    public class UIWindow : MonoBehaviour
    {
     
        //CanvasGroup提供透明度更变显隐(性能上最优)
        private CanvasGroup group;

        protected void Awake()
        {
     
            group = GetComponent<CanvasGroup>();
        }

        //立即显隐
        private void SetVisibleInstantly(bool state)
        {
     
            group.alpha = state ? 1 : 0;
            group.blocksRaycasts = state;
        }

        /// 
        /// 设置可见性
        /// 
        /// 是否可见
        /// 延迟时间,默认为0
        public void SetVisible(bool state, float delay = 0)
        {
     
            StartCoroutine(SetVisibleDelay(state, delay));
        }

        /// 
        /// 延时显隐
        /// 
        /// 是否可见
        /// 延迟时间
        /// 
        private IEnumerator SetVisibleDelay(bool state, float delay)
        {
     
            yield return new WaitForSeconds(delay);
            SetVisibleInstantly(state);
        }

        /// 
        /// 获取监听器
        /// 
        /// 
        /// 
        public UIEventListener GetListener(string childName)
        {
     
            Transform childTF = transform.FindChildTF(childName);
            if (childTF == null) return null;
            UIEventListener listener = childTF.GetComponent<UIEventListener>();
            if (listener == null) listener = childTF.gameObject.AddComponent<UIEventListener>();
            return listener;
        }
    }
}

4.1 引用命名空间

using System.Collections;
using UnityEngine;
using Tool;
  • System.Colletion:Biu La Biu La~
  • UnityEngine:同3.1描述
  • Tool:第三方工具,由作者自己准备的工具包,后续会说及此工具包内容

4.2 需求组件

[RequireComponent(typeof(CanvasGroup))]
  • 当脚本所挂载的物体上查无该组件对象时,控制台会提示需要添加该组件。如图示为CanvaGroup组件
  • 我们也可使用[AddComponent(typeof(CanvasGroup))],当物体没有该组件直接添加该组件

4.3 CanvasGroup 画布组

private CanvasGroup group;
  • 常见的UI交互,即UI的显隐,在游戏多未激活或禁用、创建或销毁。对小型主界面的游戏,不需要过于复杂的交互行为逻辑,我们选择CanvasGroup组件改变 0~1 透明度来实现UI的隐藏。
  • 使用CanvasGroup组件仅从更变显隐状态上比SetActive()的性能开支更优。

4.4 组件的获取

protected void Awake()
{
     
     group = GetComponent<CanvasGroup>();
}
  • 使用组件的功能,必然少不了对组件对象的获取

4.5 Canvas显隐行为

private void SetVisibleInstantly(bool state)
{
     
     group.alpha = state ? 1 : 0;
     group.blocksRaycasts = state;
}
  • SetVisibleInstantly():即时显隐,我们通过改变画布的alpha透明度实现这以行为。
  • blockRagtCastes:决定该CanvasGroup组件是否接口射线检测
    射线检测来自于我们的鼠标Click等行为

private IEnumerator SetVisibleDelay(bool state, float delay)
{
     
     yield return new WaitForSeconds(delay);
     SetVisibleInstantly(state);
}
  • 需求总是复杂多样,唉,我想要延迟执行UI的显隐。
  • IEnumerator:协程,允许程序暂缓一时间后执行。
    这里是延时调用SetVisibleInstantly()方法

public void SetVisible(bool state, float delay = 0)
{
     
     StartCoroutine(SetVisibleDelay(state, delay));
}
  • SetVisible():封装 直接显隐 与 延时显隐 的方法
    记忆方法名,调用总是麻烦的,就不能使用一个函数方法实现多种效果。
  • 你也可以使用方法 重载 来实现。

4.6 事件监听者

public UIEventListener GetListener(string childName)
{
     
     Transform childTF = transform.FindChildTF(childName);
     if (childTF == null) return null;
     UIEventListener listener = childTF.GetComponent<UIEventListener>();
     if (listener == null) listener = childTF.gameObject.AddComponent<UIEventListener>();
     
     return listener;
}
  • 作为UI行为的事件监听器,他需要监听来自UIController的消息通知,来进一步实现要求的显隐方法。
  • transfrom.FindChild():来自于using Tool(自己准备的工具类命名空间)。其目的是查询当前脚本上挂载的物体上的 事件监听组件
    Unity仅为我们提供查询等方法,但并未根据具体实际需求提供更多的方法。如何灵活的使用这些方法并封装成一个便利工具是开发者必备的小技能。
  • childTF == null:说明场景内并无命名 childName参数 的UI对象。
    我们也可以适当添加方法在控制台Debug告诉我们并无此对象。
  • listener == null:即查找到的UI对象上,发现脚没有 事件监听组件。给它添加一个就完事了。
  • return listener:我们最终只为获取 事件监听组件对象,返回它。

五、UIManager 视图映射与索引

用于管理所有UI状态的综合管理器,主要用于控制进入游戏后呈现的UI状态。即隐藏主菜单外的所有UI界面。

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

namespace UI.Framework
{
     
    /// 
    /// UI 管理器
    /// 
    public class UIManager : GetInstance<UIManager>
    {
     
        // 例如:游戏一开始隐藏所有UI


        private Dictionary<string, UIWindow> cache;

        /// 
        /// 保护级 防父类覆盖
        /// 
        protected override void Init()
        {
     
            base.Init();

            cache = new Dictionary<string, UIWindow>();
            //查询 UIWindow组件 的物体
            UIWindow[] ui = FindObjectsOfType<UIWindow>();
            //设置 隐藏
            for (int i = 0; i < ui.Length; i++)
            {
     
                ui[i].SetVisible(false);
                cache.Add(ui[i].GetType().Name, ui[i]);
            }
        }

        /// 
        /// 获取某一窗口
        /// 
        /// 
        /// 
        public T GetWindow<T>() where T:UIWindow
        {
      
            string key = typeof(T).Name;
            return cache[key] as T;
        }     
    }
}


5.1 引用命名空间

using System.Collections.Generic;
using Tool;
using UnityEngine;
  • 参照3.1、4.1描述

5.2 单例模式

public class UIManager : GetInstance<UIManager>
  • 单例模式:只允许存在一个且全局均能访问。
  • GetInstance:为笔者命名的单例模式,命名随意。
    通常单例命名为Singleton,继承MonoBehaviour的单例命名为MonoSingleton
  • 因单例模式只为提供全局访问,理论上两种单例均可。不过基于Unity下,笔者使用的为MonoSingleton,以便在Unity游戏中运行时被使用。

5.3 存储映射

private Dictionary<string, UIWindow> cache;
  • 此处为记录有挂载UIWindow对象的映射关系,为后续调用从中索引即可。

5.4 初始化 映射存储

protected override void Init()
{
     
     cache = new Dictionary<string, UIWindow>();
     //查询 UIWindow组件 的物体
     UIWindow[] ui = FindObjectsOfType<UIWindow>();
     //设置 隐藏
     for (int i = 0; i < ui.Length; i++)
     {
     
          ui[i].SetVisible(false);
          cache.Add(ui[i].GetType().Name, ui[i]);
     }
}
  • Init()GetInstance单例中准备的初始化方法。此为覆写单例中的Init()方法。
  • protected:保护级,只允许父类即其子类可访问。
  • 代码中为cache分配了空间用于存储索引的UIWindow组件以及其挂载的物体名称信息。后续对某一UI对象的显隐行为通过其命名索引UIWindow组件,调用其内方法做下铺垫。
  • 值得注意:我们在索引中还使用SetVisible()使UI对象的默认状态为 隐藏。

5.5 索引UIWidow对象

public T GetWindow<T>() where T:UIWindow
{
      
     string key = typeof(T).Name;
     return cache[key] as T;
} 
  • T:泛型
  • where T:Window:指定这个类型为UIWindow
  • 代码中我们通过索引这个UIWindow对象的命名,在存储的cache表中索引该UIWindow。这样我们可以直接通过命名获取其挂载的UIWindow对象,进一步使用其方法。这一体现在UIController

六、UIController 视图控制器

  用于实现各UI界面交互行为逻辑的实现,具体有[立即显隐]、[延时显隐]、[渐变显隐]等

using Tool;
using UI.Achieve;
using UI.Framework;
using UnityEngine;

namespace UI.Framework
{
     

    public class UIController : GetInstance<UIController>
    {
     
        //管理UI界面
        private void Start()
        {
     
            UIManager.Instance.GetWindow<MainButton>().SetVisible(true);  
        }

        //寻找战局
        public void FindRound()
        {
     
            UIManager.Instance.GetWindow<FindParty>().SetVisible(true);
        }

        //创建战局
        public void CreateRound()
        {
     
            UIManager.Instance.GetWindow<CreateParty>().SetVisible(true);
        }

        //设置
        public void Setting()
        {
     
            UIManager.Instance.GetWindow<Set>().SetVisible(true);
        }

        //仓库
        public void WareHouse()
        {
     
            UIManager.Instance.GetWindow<Warehouse>().SetVisible(true);
        }

        //关于我们
        public void AboutUs()
        {
     
            UIManager.Instance.GetWindow<AboutUs>().SetVisible(true);
        }

        //返回主窗口
        public void Back()
        {
     
            UIManager.Instance.GetWindow<MainButton>().SetVisible(true);
        }
    }
}

6.1 引用命名空间

using Tool;
using UI.Achieve;
using UI.Framework;
using UnityEngine;
  • using UI.Framework:上面的脚本内容均在 UI.Framework下存储,同一命名空间下可直接引用。
  • using UIAchieve:UI的实现对象,如我们在Canvas上创建了一个管理所有行为的脚本,这里并未用到。
    UIController不同,它们的目的是对所有交互行为的封装,通过一个对象实现对所有UI的控制的控制器,如同一个遥控器,具体我们想要遥控器去干什么,怎么干,是Achieve的事情。
  • 其余参数见3.1、4.1、5.1

6.2 初始准备

private void Start()
{
     
    UIManager.Instance.GetWindow<MainButton>().SetVisible(true);  
}
  • 在游戏开始时,我们通过单例直接调用 MainButton对象 显示
    UIManager中我们默认所有窗口隐藏,我们想要游戏一开始就显示该对象

6.3 封装其他按钮方法

public void FindRound()
{
     
    UIManager.Instance.GetWindow<FindParty>().SetVisible(true);
}
  • 我们只需直接调用此方法来实现点击的事件响应。当然我们可以忽略直接使用UIManager.Instance.GetWindow().SetVisible(true)来直接使用。
  • 我们通过using UI.Acheive类中实现具体行为如显示后获取玩家数据等,直接使用方法。获取数据还需另行操作。

你可能感兴趣的:(Unity,游戏开发笔记,unity)