Unity UI

Event System

Raycasters

事件系统需要一种方法来检测当前输入事件需要发送到的位置,而此方法由射线投射器 (Raycaster) 提供。给定屏幕空间位置的情况下,射线投射器将收集所有潜在目标,确定它们是否在给定位置下,然后返回最接近屏幕的对象。提供了几种类型的射线投射器:

  • 图形射线投射器 (Graphic Raycaster) - 用于 UI 元素,位于画布上,并在画布中搜索
  • 2D 物理射线投射器 (Physics 2D Raycaster) - 用于 2D 物理元素
  • 物理射线投射器 (Physics Raycaster) - 用于 3D 物理元素
    当场景中存在并启用了射线投射器时,只要从输入模块发出查询,事件系统就会使用该射线投射器。

如果使用多个射线投射器,那么这些射线投射器都会进行针对性投射,并且结果将根据与元素的距离进行排序。

自定义消息

之前我以为 event system 就是遍历场景中所有的 IEventSystemHandler,然后比如点击事件发生的时候,再调用所有实现了 IPointerClickHandler 接口的回调函数。那么我按下面的方式自定义了一个事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 文件名:CustomMessageTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public interface ICustomEventHandler: IEventSystemHandler
{
void Function1();
void Function2();
}

public class CustomMessageTest : MonoBehaviour, ICustomEventHandler
{
public void Function1()
{
Debug.Log("Function1 被执行了");
}

public void Function2()
{
Debug.Log("Function2 被执行了");
}
}

只要我想办法说明 ICustomEventHandler 对应的事件发生了,那么所有挂着这个脚本的物体,应该可以自动调用 Function1 和 Function2 才对。因为系统自带的这些事件接口就是这样,只要实现了对应的接口,就会在事件发生的时候自动执行。
但事实上 ICustomEventHandler 这个事件发生并不是广播的方式,而且点对点的方式。

1
2
public static bool Execute(GameObject target, 
EventSystems.BaseEventData eventData, EventFunction<T> functor);

从这个函数的参数也可以看出来,需要一个 target,eventData 和方法,所以如果想要实现物体一旦实现了 ICustomEventHandler,就可以在这个方法执行的时候调用 Function1 或者 Function2 或者两个一起的效果,需要遍历场景中所有的 ICustomEventHandler 再调用 ExecuteEvents.Execute 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 文件名:CustomMessageTestMain.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CustomMessageTestMain : MonoBehaviour
{
CustomMessageTest[] customMeassge;
private void Awake()
{
customMeassge = GameObject.FindObjectsOfType<CustomMessageTest>();
}
private void OnGUI()
{
if (GUILayout.Button("触发自定义事件"))
{
BaseEventData data = new BaseEventData(EventSystem.current);
foreach(CustomMessageTest msg in customMeassge)
{
ExecuteEvents.Execute<CustomMessageTest>(msg.gameObject, data, (x, y) => {
Debug.LogFormat("x = {0},y = {1}", x.name, y);
x.Function1();
});
}
}
}
}

起初我不理解为什么自定义的消息将事件发生了进行广播,也就是不用我们自己去寻找场景中的所有实现了对应接口的物体进行通知,我后来认为可能是为了传参数和自定义 eventData,Function1() 是可以传参数的,只要在调用的时候也把参数加上就好了,然后 eventData 虽然目前都是 EventSystem.current,但说不定后面会有其他的 EventSystem。
然后也疑惑了一下为什么不能将点击事件放到一个接口里面,而是要像现在一样整 5 个接口(IPointerEnterHandler,IPointerExitHandler,IPointerDownHandler,IPointerUpHandler,IPointerClickHandler)。因为 UGUI 背后应该也是可以自定义事件发生时执行的方法的,也就是像上面一样我可以自定义是执行 Function1,还是 Function2,还是两个一起执行,甚至还能加一个 debug 的 log 输出。我想应该是因为有的物体只想注册 Click 的 事件,但如果这样设计就需要实现所有的接口,那为什么不能整一个基类实现所有的接口,然后其子类可以复写相应的回调函数呢?可能 Unity.EventSystem.EventTrigger 就是因此而生的吧。