通过 EventTrigger 使用 c# 事件

0x00 相当难过

先说说题外话

正常的 UI 事件是这样的(我的理解中):在 UI 对象上挂载脚本,脚本中处理对应的逻辑,比如:在处理指针按下,要在脚本中处理实现一个 IPointerDown 的接口。这看起来很直接,但是在使用中会让逻辑很分散。这就是让我相当难受的做法。

而 EventTrigger 的做法类似,也是将一个实现了所有接口的脚本绑定在要触发事件的 UI 对象上,但是……他只触发一个特定事件。

我不确定从 UI 在 EventSystem 中的实现是不是通过事件的方式(我以为是,需要学习一下,搞搞清楚),之前我也是并不理解为什么要在事件触发的回调中触发另一个事件。

0x01

处理 UI 事件的 EventTrigger 是什么:

接受从 EventSystem 触发的事件,并为每一个事件调用注册的回调函数。EventTrigger 可以用来指定为每个 EventSystem 事件调用的函数。可以将多个函数注册给单个事件,每当 EventTrigger 收到该事件时,都会按照这些函数的提供顺序调用它们。(这个组件要挂载到 GameObject 上使用,这会导致该对象拦截所有的事件,并且这些时间都不会传播到父对象上。)

  1. 要挂上
  2. 要实现
  3. 要有注册的回调方法

关于 EventTrigger 的原理这点,在c#委托和事件中有所介绍,在这里是通过 EventSystem 触发绑定在对象上脚本的处理函数,在处理函数中,再触发定义的事件,通过事件调用实际上要执行的逻辑代码。

0x02

在官网文档中,有介绍 EventTrigger 的两种使用方式:

  1. 扩展 EventTrigger,重写( override )需要拦截的事件的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;
using UnityEngine.EventSystems;

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

...// 长,代码见附录I

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

其实这里的用法,就和原本自己定义脚本实现接口的方式相同了,将处理的逻辑写在事件的处理函数中。在上面提出的让逻辑在统一的位置处理,可以在这里触发事件。像这样……(传了一个 gist

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UIEventListener : EventTrigger
{
public delegate void UIDelegate(GameObject go);
public event UIDelegate onPointerEnter;
public override void OnPointerEnter(PointerEventData eventData)
{
if (null != onPointerEnter)
{
// 传入挂载对象作为参数可以方便在主逻辑脚本中处理
onPointerEnter(gameObject);
}
}
}

同样,这个脚本也要挂在对象上,但这里的逻辑只触发了自定义的事件,而事件就可以调用放在任意位置的逻辑代码了。但是手动挂脚本还是不怎么方便日后的修改与维护,所以添加一个静态方法用来获取特定对象的 UIEventListener 组件:

1
2
3
4
5
6
7
8
9
10
11
public static UIEventListener GetListener(GameObject go)
{
UIEventListener listener = go.GetComponent<UIEventListener>();

if (null == listener)
{// 如果找不到,那就挂一个
listener = go.AddComponent<UIEventListener>();
}

return listener;
}

这样这个问题就解决了。

  1. 也可以指定单个委托:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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); // triggers 是在 EventTrigger 中注册的所有函数
}

// PointerEventData 是 EventSystem 定义的数据类型
public void OnPointerDownDelegate(PointerEventData data)
{
Debug.Log("OnPointerDownDelegate called.");
}
}

这段代码,获取到了挂载的对象上的 EventTrigger 组件,然后监听对象的某个事件,执行特定的回调方法。

当然也可以类似的将这段代码与自动添加组件一起封装一下再使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <summary>
/// 添加EventTrigger的监听事件
/// </summary>
/// <param name="obj">添加监听的对象</param>
/// <param name="eventID">添加的监听类型</param>
/// <param name="action">触发方法</param>
private void AddTriggersListener(GameObject obj, EventTriggerType eventID, UnityAction<BaseEventData> action)
{
EventTrigger trigger = obj.GetComponent<EventTrigger>();
if (trigger == null)
{
trigger = obj.AddComponent<EventTrigger>();
}

if (trigger.triggers.Count == 0)
{
trigger.triggers = new List<EventTrigger.Entry>();
}
UnityAction<BaseEventData> callback = new UnityAction<BaseEventData>(action);
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = eventID;
entry.callback.AddListener(callback);
trigger.triggers.Add(entry);
}

0x03

在 PC 端与移动端处理 UI 事件会有所不同,可以通过实机调试就可以看出不同。比方说在 PC 端,通过鼠标来操作时,指针离开区域和指针放开的事件是可以明确区分的,但是在移动端,由于没有“指针”,在处理这两个事件时其实是同时的,如果在这两个事件之间处理逻辑,就不会符合预期了。

关于前面抛出的问题,依旧占坑,先去改我费尽苦心写下的 bug 了,明明知道 update 执行是随机的,还在触发事件时用 static 对象来拼人品……

(ps 好了,我“辛辛苦苦”改完了我“辛辛苦苦”写的bug……0点15了……

(占坑以后聊 EventSystem

0xff 附录

附录I

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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.");
}

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

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

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

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

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

public override void OnMove(AxisEventData data)
{
Debug.Log("OnMove called.");
}

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

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

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

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

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

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

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

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

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