添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

0x00 相当难过

先说说题外话

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

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

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

0x01

处理 UI 事件的 EventTrigger 是什么:

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

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

    0x02

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

  • 扩展 EventTrigger,重写( override )需要拦截的事件的函数:
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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
    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.");
    }
    }