Unity中的UGUI源码解析之事件系统(1)-概述

Unity中的UGUI源码解析之事件系统(1)-概述

从今天开始通过几篇文章一步步深入, 围绕事件系统展开对UGUI源码的解析.

网上大部分文章讲的是事件系统是什么, 怎么用. 我的文章会在这些基础之上进一步探讨其原理和设计思想, 当然, 只是我的一家之言, 也不一定正确(特别是不同版本之间的差异是存在的). 所以还是希望能给大家提供的是一种思路, 省去大量实践和抠细节的研究, 大家可以基于我的研究(也可以纠正), 发散出自己的理解.

好了, 下面开始今天的内容.

广义的事件系统是指Unity中整个事件相关的一整个系统, 而狭义的事件系统指的是一个专门的组件: EventSystem.

下面我们会使用事件系统EventSystem来对应描述两者.

本文将对事件系统和其主要的模块做一个系统性的概述.

概述

根据官方文档的说明, 手册(Manual/EventSystem.html), 脚本API(ScriptReference/EventSystems.EventSystem.html),

事件系统主要负责:

  • 处理输入
  • 射线投射
  • 发送事件

整个事件系统由很多角色组成, 互相协作来完成事件相关工作.

事件系统主要由以下几个部分组成:

  • EventSystem组件
  • 输入模块(InputModule): BaseInputModule和其子类
  • 射线投射器(Raycaster)
  • 消息系统
  • 各种支持的事件
  • 事件触发器(EventTrigger)

下面我们分别简要说明.


EventSystem

EventSystem相当于事件系统的管理器, 负责协调各个模块.

EventSystem主要负责:

  • 事件系统对外的接口
  • 负责各个事件相关模块之间通讯的管理和协调
  • 管理选中的游戏对象
  • 管理正则使用的输入模块
  • 管理射线投射
  • 根据需要更新所有的输入模块

EventSystem需要与其它组件协同工作, 根据任务不同, 搭配的组件不同, Unity建议我们一个场景只有一个EventSystem, 当然, 这并不是强制要求.

输入模块(InputModule)

输入模块主要处理输入, 也是整个事件系统的主要部分, Unity提供了下面的几个类来抽象输入的处理:

  • BaseInputModule:整个输入模块的基类

  • PointerInputModule : BaseInputModule: 处理指针设备输入的基类

  • StandaloneInputModule: PointerInputModule: 独立输入模块, 目前是主要的输入处理模块

  • TouchInputModule : PointerInputModule: 处理触摸事件输入, (已经废弃, 内容包含到StandaloneInputModule中)

指针输入(PointerInput)

指针输入设备指的是那些在2d表面追踪输入位置的设备, 事件系统支持的指针输入有: 触摸, 鼠标和触控笔. 详情在这里. 虽然这里面讲的是新的输入系统, 但是概念是一样的, 不影响理解.

射线投射器(Raycaster)

这就是经常听到的射线检测器, 原理是在点击或者触摸的位置发射一条射线, 然后收集所有被射线穿透的对象, 然后返回最可能(比如最接近屏幕的对象)的目标. 供事件系统使用.

Unity提供了三种射线投射器:

  • 图形射线投射器 (Graphic Raycaster), 一般用于UI元素, 常挂载在Canvas上, 用于遍历该Canvas的各个元素. 就是说没有这个组件就无法触发各种UI事件.
  • 2D 物理射线投射器 (Physics 2D Raycaster), 用于2D物理元素的检测, 需要对象身上有碰撞盒子(Collider)
  • 物理射线投射器 (Physics Raycaster) - 用于 3D 物理元素, 需要对象身上有碰撞盒子(Collider)

只要场景中存在射线投射器, 然后从输入模块发出检测请求, 那么事件系统就会使用它, 这是通过射线管理器(RaycasterManager)来实现的.

每种射线投射器都有特定的检测对象, 不需要我们过多关心.

消息系统

UGUI使用了新的消息系统来处理消息分发, 这个实现很巧妙, 没有使用常规的显得比较繁重的消息系统, 而是使用了一个静态的类ExecuteEvents和一个消息接口IEventSystemHandler, 通过每次分发时查询对象身上的实现了IEventSystemHandler接口的所有组件, 然后向这些组件分发消息的机制, 消息系统本身不维护消息和其处理器.

各种支持的事件

事件系统所有的事件都实现IEventSystemHandler接口, 这是消息系统的基础, Unity提供了以下事件:

  • IPointerEnterHandler - OnPointerEnter - 当指针进入对象时调用
  • IPointerExitHandler - OnPointerExit - 当指针退出对象时调用
  • IPointerDownHandler - OnPointerDown - 在对象上按下指针时调用
  • IPointerUpHandler - OnPointerUp - Called when a pointer is released (called on the original the pressed object)
  • IPointerClickHandler - OnPointerClick - 在同一对象上按下再松开指针时调用
  • IInitializePotentialDragHandler - OnInitializePotentialDrag - 在找到拖动目标时调用,可用于初始化值
  • IBeginDragHandler - OnBeginDrag - 即将开始拖动时在拖动对象上调用
  • IDragHandler - OnDrag - 发生拖动时在拖动对象上调用
  • IEndDragHandler - OnEndDrag - 拖动完成时在拖动对象上调用
  • IDropHandler - OnDrop - 在拖动目标对象上调用
  • IScrollHandler - OnScroll - 当鼠标滚轮滚动时调用
  • IUpdateSelectedHandler - OnUpdateSelected - 每次勾选时在选定对象上调用
  • ISelectHandler - OnSelect - 当对象成为选定对象时调用
  • IDeselectHandler - OnDeselect - 取消选择选定对象时调用
  • IMoveHandler - OnMove - 发生移动事件(上、下、左、右等)时调用
  • ISubmitHandler - OnSubmit - 按下 Submit 按钮时调用
  • ICancelHandler - OnCancel - 按下 Cancel 按钮时调用

通过在Monobehavior脚本上实现这些接口就能接收事件. 也可以直接实现IEventSystemHandler接口, 自定义消息. 示例如下:

using UnityEngine;
using UnityEngine.EventSystems;


public class NavigationEventTest : MonoBehaviour, IMoveHandler
{
	public void OnMove(AxisEventData eventData) {
		Debug.LogError("onMove: " + eventData);
	}
}

// ----------------------------------------------------------------------------------------------------------------
public interface ICustomMessageTarget : IEventSystemHandler
{
    // 可通过消息系统调用的函数
    void Message1();
    void Message2();
}

public class CustomMessageTarget : MonoBehaviour, ICustomMessageTarget
{
    public void Message1()
    {
        Debug.Log ("Message 1 received");
    }

    public void Message2()
    {
        Debug.Log ("Message 2 received");
    }
}

ExecuteEvents.Execute<ICustomMessageTarget>(target, null, (x,y)=>x.Message1());

事件触发器(EventTrigger)

上面添加事件的方式需要脚本实现事件接口, 我们也可以通过事件触发器来拦截所有事件, 处理我们想要处理的事件.

有两种事件事件触发器的方式, 一个是通过继承, 然后重写指定的事件方法, 如下:

using UnityEngine;
using UnityEngine.EventSystems;

public class EventTriggerExample : EventTrigger
{
    public override void OnBeginDrag(PointerEventData data)
    {
        Debug.Log("OnBeginDrag called.");
    }

    public override void OnCancel(BaseEventData data)
    {
        Debug.Log("OnCancel called.");
    }
}

另一种是通过设置委托的方式, 如下:

using UnityEngine;
using UnityEngine.EventSystems;


public class EventTriggerDelegateExample : MonoBehaviour
{
    void Start()
    {
        EventTrigger trigger = GetComponent<EventTrigger>();
        EventTrigger.Entry entry = new EventTrigger.Entry();
        entry.eventID = EventTriggerType.PointerDown;
        entry.callback.AddListener((data) => { OnPointerDownDelegate((PointerEventData)data); });
        trigger.triggers.Add(entry);
    }

    public void OnPointerDownDelegate(PointerEventData data)
    {
        Debug.Log("OnPointerDownDelegate called.");
    }
}

通过事件触发器的方式来注册和处理事件更加灵活, 但是会拦截所有事件, 导致事件无法传递到父对象, 在使用的时候需要注意.

总结

本文对事件系统和其主要的模块做了系统性的概述. 各个部分细节会在接下来的文章单独介绍.

总的来说事件系统就是由事件系统管理器, 射线投射器, 输入模块, 消息系统组成, 各个部分相互协作, 各司其职.

总体上来看, Unity的事件系统做的比Cocos的要更简洁一点, 当然, 也缺少了很多灵活性, 这一部分需要通过自定义输入模块来达到. 但是大部分场景已经足够使用了. 很难说哪个更好一点.

在整个事件系统的最后一个部分, 我会尝试给出两者的优劣对比, 互相借鉴, 希望能碰撞出不同的火花.

下一篇文章会详细介绍EventSystem组件.

好了, 今天就是这样, 希望对大家有所帮助.

猜你喜欢

转载自blog.csdn.net/woodengm/article/details/123393108
今日推荐