从今天开始通过几篇文章一步步深入, 围绕事件系统展开对UGUI源码的解析.
网上大部分文章讲的是事件系统是什么, 怎么用. 我的文章会在这些基础之上进一步探讨其原理和设计思想, 当然, 只是我的一家之言, 也不一定正确(特别是不同版本之间的差异是存在的). 所以还是希望能给大家提供的是一种思路, 省去大量实践和抠细节的研究, 大家可以基于我的研究(也可以纠正), 发散出自己的理解.
好了, 下面开始今天的内容.
广义的事件系统是指Unity中整个事件相关的一整个系统, 而狭义的事件系统指的是一个专门的组件: EventSystem.
下面我们会使用事件系统和EventSystem来对应描述两者.
本文将对事件系统和其主要的模块做一个系统性的概述.
根据官方文档的说明, 手册(Manual/EventSystem.html
), 脚本API(ScriptReference/EventSystems.EventSystem.html
),
事件系统主要负责:
整个事件系统由很多角色组成, 互相协作来完成事件相关工作.
事件系统主要由以下几个部分组成:
EventSystem
组件BaseInputModule
和其子类下面我们分别简要说明.
EventSystem相当于事件系统的管理器, 负责协调各个模块.
EventSystem主要负责:
EventSystem需要与其它组件协同工作, 根据任务不同, 搭配的组件不同, Unity建议我们一个场景只有一个EventSystem, 当然, 这并不是强制要求.
输入模块主要处理输入, 也是整个事件系统的主要部分, Unity提供了下面的几个类来抽象输入的处理:
BaseInputModule
:整个输入模块的基类
PointerInputModule : BaseInputModule
: 处理指针设备输入的基类
StandaloneInputModule: PointerInputModule
: 独立输入模块, 目前是主要的输入处理模块
TouchInputModule : PointerInputModule: 处理触摸事件输入, (已经废弃, 内容包含到StandaloneInputModule
中)
指针输入设备指的是那些在2d表面追踪输入位置的设备, 事件系统支持的指针输入有: 触摸, 鼠标和触控笔. 详情在这里. 虽然这里面讲的是新的输入系统, 但是概念是一样的, 不影响理解.
这就是经常听到的射线检测器, 原理是在点击或者触摸的位置发射一条射线, 然后收集所有被射线穿透的对象, 然后返回最可能(比如最接近屏幕的对象)的目标. 供事件系统使用.
Unity提供了三种射线投射器:
只要场景中存在射线投射器, 然后从输入模块发出检测请求, 那么事件系统就会使用它, 这是通过射线管理器(RaycasterManager
)来实现的.
每种射线投射器都有特定的检测对象, 不需要我们过多关心.
UGUI使用了新的消息系统来处理消息分发, 这个实现很巧妙, 没有使用常规的显得比较繁重的消息系统, 而是使用了一个静态的类ExecuteEvents
和一个消息接口IEventSystemHandler
, 通过每次分发时查询对象身上的实现了IEventSystemHandler
接口的所有组件, 然后向这些组件分发消息的机制, 消息系统本身不维护消息和其处理器.
事件系统所有的事件都实现IEventSystemHandler
接口, 这是消息系统的基础, Unity提供了以下事件:
通过在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(target, null, (x,y)=>x.Message1());
上面添加事件的方式需要脚本实现事件接口, 我们也可以通过事件触发器来拦截所有事件, 处理我们想要处理的事件.
有两种事件事件触发器的方式, 一个是通过继承, 然后重写指定的事件方法, 如下:
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.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组件.
好了, 今天就是这样, 希望对大家有所帮助.