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组件.
好了, 今天就是这样, 希望对大家有所帮助.