Unity 内部的 Event System(组件)

Event System 是 Unity 提供的内部 GameObject 响应输入(如键盘输入、鼠标输入、触摸输入等)事件的一种实现,它由多个组件组成,协同工作来实现整个事件通信。

组成 Event System 的组件包括:

EventSystem 组件

EventSystem 组件主要实现以下功能:

  • 控制哪个 GameObject 被选中响应事件

  • 控制选择使用哪一种 Input Module(同一时刻只有一个 Input Module 能处于激活状态)

  • 管理 Raycasting

  • 管理更新 Input Module

在 Unity 编辑器中,可以直接为场景添加 EventSystem 组件(一个场景通常只包含一个 EventSystem 组件)。可以直接设置一些属性以达到自定义需求,如下:

属性 成员变量 描述
First Selected m_FirstSelected EventSystem 中第一个被选中的对象
Send Navigation Event m_sendNavigationEvents EventSystem 是否接收处理 Navigation 事件(move / submit / cancel)
Drag Threshold m_DragThreshold 可以判定为拖拽(drag)的阈值(单位: 像素)

除了上面三个可以在编辑器中编辑的属性之外,还有很多其他特性,如: m_HasFocus 可以用来表示当前 EventSystem 是否处于拥有焦点状态,如果当前没有获得焦点,诸如 Input Module 相关的更新和激活方法不会被调用,事件处理方法 Process() 也不会进行。

EventSystem 组件也是一个 MonoBehaviour,因此它具有完整的 Unity 生命周期。除了有一些生命周期相关的回调,它也有一些其它比较重要的方法。

void UpdateModules() 方法

这个方法作用就是重新计算当前 m_SystemInputModules 中的 Input Modules,保证 m_SystemInputModules 中的 Input Module 都是出于激活状态的。这个方法会在 BaseInputModule 类的 OnEnable()OnDisable() 方法中被调用,以注册或反注册对应的 Input Module。

void SetSelectedGameObject(GameObject selected, BaseEventData pointer) 方法

设置当前被选中的对象,属性 m_CurrentSelected 会被赋值为被选中的 GameObject。设置过程中,会给之前选中的对象发送 ExecuteEvents.deselectHandler 事件,给被设置的对象发送 ExecuteEvents.selectHandler 事件。这个方法会在需要设置 Selected 对象的时候被调用,如 InputField 组件接收到 Pointer Down 事件时,就会将其对应的 GameObject 设置为 Event System 当前选中对象(m_CurrentSelected)。方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void SetSelectedGameObject(GameObject selected, BaseEventData pointer)
{
if (m_SelectionGuard)
return;
m_SelectionGuard = true;
if (selected == m_CurrentSelected)
{
m_SelectionGuard = false;
return;
}
ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.deselectHandler);
m_CurrentSelected = selected;
ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.selectHandler);
m_SelectionGuard = false;
}

void RaycastAll(PointerEventData eventData, List raycastResults) 方法

这个方法用来进行射线检测得到可以拦截事件的对象。方法实现很简单,使用 RaycasterManager 中的所有处于激活状态的 BaseRaycaster 进行检测,得到响应事件的对象。这个方法会在 PointerInputModule 类的 GetTouchPointerEventDataGetMousePointerEventData 方法中被调用,用于创建对应的可被分发拦截处理的具体事件。

bool IsPointerOverGameObject(int pointerId) 方法

用来判断指定 ID 的 Pointer 是否还在 EventSystem 的那个对象上。方法内部最终调用 PointerInputModule 类的 IsPointerOverGameObject 方法,在 PointerInputModule 类的方法实现也很简单,就是根据 pointerId 获取对应的 PointerEventData,然后判断如果 PointerEventData 中 pointerEnter 对应的 GameObject 不为空,这说明这个 ID 对应的 Pointer 还在 EventSystem 的对象上。

void Update() 方法

  1. 首先调用了 TickModules() 去更新当前 Event System 中的所有的 Input Modules (调用了对应 Input Module 的 UpdateModule() 方法),这样 Input Module 中的 m_LastMousePositionm_MousePosition 就得到了更新;

  2. 如果当前 m_CurrentInputModule 不为空,就将当前 Event System 中挂载所有的 Input Modules 中的第一个可用且处于激活状态的 Input Module 设置给 m_CurrentInputModule

  3. 如果从来没有设置过 m_CurrentInputModule,就将挂载所有的 Input Modules 中的第一个可用 Input Module 设置给 m_CurrentInputModule

  4. 最后就会调用 Input Module 组件的 Process() 方法处理输入并产生事件、拦截处理事件。

上面的第二步和第三步中设置 m_CurrentInputModule 会调用 ChangeEventModule 方法。如果是切换 Input Module,则会反激活上一个 Input Module;还会激活当前要设置的 Input Module。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
private void ChangeEventModule(BaseInputModule module)
{
if (m_CurrentInputModule == module)
return;
if (m_CurrentInputModule != null)
m_CurrentInputModule.DeactivateModule();
if (module != null)
module.ActivateModule();
m_CurrentInputModule = module;
}

void OnDisable() 方法

在这个方法中,主要就是将当前的 Input Module (m_CurrentInputModule) 反激活,调用了对应 InputModule 的 DeactivateModule() 方法。