《Unity3D高级编程 主程手记》第四章 用户界面(五) 快速构建一个简单易用的 UI 框架

从宏观角度看 UI 框架

一个项目中拥有众多 UI,每个界面有很多组件;有不少界面是通用的;也有不少界面是父子关系;界面上的按钮需要有一个处理输入的句柄。基于这些思考,可以编写处统一的管理类、基类和输入事件响应机制。

1、管理类

我们需要用一个单实例来管理所有的 UI,让它们有统一的接口进行以上的操作,创建UI管理类是最好的选择,我们可以命名它为 UIManager,这个名字符合它代表的功能。

具体作用:

  • 创建 UI
  • 查找现有的 UI
  • 销毁 UI
  • 完成 UI 的统一接口调用和调配工作

存储内容:

  • UI 实例
  • UI 常用变量,比如屏幕的适配标准大小、Camara 等。

UIManager 是 UI 的管理员,统筹管理 UI 问题。其中涉及的管理包括上下层 UI 切换、不同的加载方式(预加载 UI 、销毁和隐藏)等,其源码如下。

cs 复制代码
public class ScreenManager : CSingleton<ScreenManager>
{
    protected Transform _transform = null;
    private Dictionary<string, UIScreenBase> _DicScreens = new Dictionary<string, UIScreenBase>();

    // 关闭所有界面
    public void CloseAll()
    {
        ...
    }

    // 是否UI正打开
    public bool IsShow(string screenID)
    {
        ...
    }

    // 关闭界面
    public void CloseScreen(UIScreenBase screen)
    {
        ...
    }

    // 创建所有界面
    public T CreateMenu<T>() where T : UIScreenBase
    {
    	...
    }

    // 找出某个界面
    public T FindMenu<T>() where T : UIScreenBase
    {
        ...
    }

    ...
}

2、基类

项目中有很多界面,这些界面都有一定的共性。共性产生统一特征的接口,如Init、Open和Close等。

继承基类 可使管理比较方便,比如上面提到的 UIManager 里的 UI 实例可以统一使用基类的方式存储。UIScreenBase

cs 复制代码
public abstract class UIScreenBase : MonoBehaviour
{
    protected bool mInitialized = false;
    protected UIState mState = UIState.None;
    public UIState State { get { return mState; } }

    public delegate void OnScreenHandlerEventHandler(UIScreenBase screen);
    public event OnScreenHandlerEventHandler onCloseScreen;

    // 初始化
	protected virtual void Init()
	{	
        mInitialized = true;
	}

	//打开
	public virtual void Open() {}

	//关闭
	public virtual void Close() {}
}

每个界面都继承自 UI 基类,每个界面成为扩展界面功能的一个类实体,可以自主定义自己的功能性的接口,同时还会受到管理类的统一调配。

3、输入事件响应机制

Unity3D 的 UGUI 输入事件响应机制建立通常有两种,一种是继承型,一种是绑定型。

继承型

事件先响应到基类,再由基类反应给父类,由父类做处理,这样 UI 既可以得到对输入事件的响应,也可以自行修改自己需要的逻辑。

绑定型

在对输入事件响应之前,为 UI 元素绑定一个事件响应的组件。

编写一个绑定型事件类 UIEvent,当某个 UI 元素需要输入事件回调时,对这个物体绑定一个 UIEvent,并且对 UIEvent 里需要的相关响应事件进行赋值或注册操作函数。当输入事件响应时,由 UIEvent 来区分输入的是什么类型的事件,再分别调用响应到具体函数。

**共同点:**都需要与UI元素关联

**区别:**继承型融入在了各种组件内,而绑定型以独立的组件形式体现出来的

cs 复制代码
/// <summary>
/// UI 事件
/// </summary>
public class UI_Event : UnityEngine.EventSystems.EventTrigger
{
    protected const float CLICK_INTERVAL_TIME = 0.2f; //const click interval time
    protected const float CLICK_INTERVAL_POS = 2; //const click interval pos

    public delegate void PointerEventDelegate ( PointerEventData eventData , UI_Event ev);
    public delegate void BaseEventDelegate ( BaseEventData eventData , UI_Event ev);
    public delegate void AxisEventDelegate ( AxisEventData eventData , UI_Event ev);

    public Dictionary<string,object> mArg = new Dictionary<string,object>();

    public BaseEventDelegate onDeselect = null;
    public PointerEventDelegate onBeginDrag = null;
    public PointerEventDelegate onDrag = null;
    public PointerEventDelegate onEndDrag = null;
    public PointerEventDelegate onDrop = null;
    public AxisEventDelegate onMove = null;
    public PointerEventDelegate onClick = null;
    public PointerEventDelegate onDown = null;
    public PointerEventDelegate onEnter = null;
    public PointerEventDelegate onExit = null;
    public PointerEventDelegate onUp = null;
    public PointerEventDelegate onScroll = null;
    public BaseEventDelegate onSelect = null;
    public BaseEventDelegate onUpdateSelect = null;
    public BaseEventDelegate onCancel = null;
    public PointerEventDelegate onInitializePotentialDrag = null;
    public BaseEventDelegate onSubmit = null;

    private static PointerEventData mPointData = null;

    // 设置参数
    public void SetData(string key , object val)
    {
        mArg[key] = val;
    }

    // 获取参数
    public D GetData<D>(string key)
    {
        if(mArg.ContainsKey(key))
        {
            return (D)mArg[key];
        }
        return default(D);
    }

    ...

    public static UI_Event Get(GameObject go)
    {
        UI_Event listener = go.GetComponent<UI_Event>();
        if (listener == null) listener = go.AddComponent<UI_Event>();
        return listener;
    }

    public override void OnBeginDrag( PointerEventData eventData ) { ... }
    public override void OnDrag( PointerEventData eventData ) { ... }
    public override void OnEndDrag( PointerEventData eventData ) { ... }
    public override void OnDrop( PointerEventData eventData ) { ... }
    public override void OnMove( AxisEventData eventData ) { ... }

    public override void OnPointerClick(PointerEventData eventData)
    {
    	...
        if(onClick != null)
        {
            onClick(eventData , this);
        }
        ...
    }

    public override void OnPointerDown (PointerEventData eventData) { ... }
    public override void OnPointerEnter (PointerEventData eventData) { ... }
    public override void OnPointerExit (PointerEventData eventData) { ... }
    public override void OnPointerUp (PointerEventData eventData) { ... }
    public override void OnScroll( PointerEventData eventData ) { ... }
}

以上代码只把事件响应最重要的部分,其余还包括组件的挂在、事件的调用及参数的设置等。

最好事先把 UI_Event 挂载到 GameObject 节点上,这样可节省 AddComponent 的消耗,如果要在按钮响应时加入参数,则可再使用 SetData 来设置当前节点的回调参数。

为什么要这么做?

  1. 统一管理所有事件句柄
  2. 更加方便地使用输入事件句柄
  3. 更方便地设置参数

4、自定义组件

(1)UI 动画组件

  • 首先它需要依赖 Unity3D 的 Animator 组件 [RequireComponent (typeof(Animator))]

  • 其次它要有播放(Play)接口用来播放指定动画,这里 Play 的参数包括动画名、播放完毕后的回调函数委托等。

  • 再次在 public 变量中需要 AutoPlay 这个参数,这样美术人员就可以在 Unity3D 界面上设置自动播放而无需程序调用了。

  • 最后需要在自动播放时选择指定的动画名和是否循环播放,以及循环播放间隔。

(2)按钮播放音效组件

(3)UI 元素跟随 3D 物体组件

比如游戏中的血条、场景中建筑物头上的标志等。通过不断地计算 3D 物体在屏幕中的位置来确定 UI 位置,当前位置不同时再进行更改以避免不必要的移动。

(4)无限滚动页面组件

比如背包界面。用一个自定义的无线滚动页面组价来替换原来的模式。

(5)其他组件

包括数字飘字组件、计数组件、下拉框组件等。

编写自定义的 UI 组件的目标就是,增加更多通用的组件,减少重复劳动,让程序员在编写 UI 界面时更加快捷、高效,同时也可提升 UI 的运行效率。拥有属于自己的一套自定义套件,对项目来说,也是一件非常有价值和高效的事。

相关推荐
咔叽布吉1 小时前
【论文阅读笔记】CamoFormer: Masked Separable Attention for Camouflaged Object Detection
论文阅读·笔记·目标检测
johnny2331 小时前
《大模型应用开发极简入门》笔记
笔记·chatgpt
亦枫Leonlew1 小时前
微积分复习笔记 Calculus Volume 1 - 4.7 Applied Optimization Problems
笔记·数学·微积分·1024程序员节
小肥象不是小飞象1 小时前
(六千字心得笔记)零基础C语言入门第八课——函数(上)
c语言·开发语言·笔记·1024程序员节
星LZX1 小时前
WireShark入门学习笔记
笔记·学习·wireshark
努力变厉害的小超超3 小时前
ArkTS中的组件基础、状态管理、样式处理、class语法以及界面渲染
笔记·鸿蒙
△曉風殘月〆4 小时前
WPF MVVM入门系列教程(二、依赖属性)
c#·wpf·mvvm
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
SoraLuna7 小时前
「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现
macos·ui·harmonyos
aloha_7897 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot