Unity3D UGUI 源码学习 EventSystem

Unity的UI系统中,EventSystem负责管理调度事件,控制各输入模块、射线投射以及事件动作的执行。UI的事件系统处理用户的交互动作,通过BaseInput来获取用户输入的信息和状态,通过InputModule处理输入,产生和发送事件,通过RayCaster判断和选择需要响应交互事件的对象,最终由ExecuteEvents执行响应的动作,调用EventSystemHandler,以完成交互。

重要的成员

  • public static EventSystem current { get; set; }

    EventSystem的全局单例。

  • List<BaseInputModule> m_SystemInputModules

    维护了一个列表,处于激活状态的输入模块。BaseInputModule是输入模块的基类,衍生类有TouchInputModuleStandaloneInputModule

  • BaseInputModule m_CurrentInputModule

    当前正在响应的输入模块,私有方法ChangeEventModule会更新和改变此成员

  • GameObject m_FirstSelected

    首个选中的对象,在StandaloneInputModule中会用到

  • GameObject m_CurrentSelected

    当前选中的对象,可由各个输入模块调用EventSystemSetSelectedGameObject方法来更新和改变此成员。在执行事件的动作时以此成员为对象,即ExecuteEvents.Execute(m_CurrentSelected, ...)

  • bool m_Paused

    表示事件系统是否处在暂停状态

  • bool m_SelectionGuard

    选择的保护状态,当选择了一个新的对象时,会先将该值置为true,在完成新旧对象选择状态及事件的执行后,再将该值置为false,详见SetSelectedGameObject

  • BaseEventData m_DummyData

    一份伪造的BaseEventData假数据。

重要的方法

  • void UpdateModules()

    当输入模块的激活状态改变时(OnEnableOnDisable)会调用此函数来更新EventSystem中管理的输入模块的列表。

  • void SetSelectedGameObject(GameObject selected, BaseEventData pointer)

    设置当前选中的对象。还有一个重载方法void SetSelectedGameObject(GameObject selected)。此方法会被一些衍生自Selectable的类直接调用,指定当前响应事件的对象。

  • void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)

    会从RaycasterManager获取所有RayCaster(都继承于BaseRaycaster),并调用每个RayCaster的Raycast方法,将所有的射线投射结果存入raycastResults中,在函数返回之前,将所有的射线投射结果排序,排序依据见RaycastComparer

    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
    private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
    {
    if (lhs.module != rhs.module)
    {
    if (lhs.module.eventCamera != null && rhs.module.eventCamera != null && lhs.module.eventCamera.depth != rhs.module.eventCamera.depth)
    {
    // need to reverse the standard compareTo
    if (lhs.module.eventCamera.depth < rhs.module.eventCamera.depth)
    return 1;
    if (lhs.module.eventCamera.depth == rhs.module.eventCamera.depth)
    return 0;

    return -1;
    }

    if (lhs.module.sortOrderPriority != rhs.module.sortOrderPriority)
    return rhs.module.sortOrderPriority.CompareTo(lhs.module.sortOrderPriority);

    if (lhs.module.renderOrderPriority != rhs.module.renderOrderPriority)
    return rhs.module.renderOrderPriority.CompareTo(lhs.module.renderOrderPriority);
    }

    if (lhs.sortingLayer != rhs.sortingLayer)
    {
    // Uses the layer value to properly compare the relative order of the layers.
    var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
    var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
    return rid.CompareTo(lid);
    }
    if (lhs.sortingOrder != rhs.sortingOrder)
        return rhs.sortingOrder.CompareTo(lhs.sortingOrder);
    
    if (lhs.depth != rhs.depth)
        return rhs.depth.CompareTo(lhs.depth);
    
    if (lhs.distance != rhs.distance)
        return lhs.distance.CompareTo(rhs.distance);
    
    return lhs.index.CompareTo(rhs.index);
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    排序依据首先是输入模块相机深度、sortOrder、renderOrder,然后是射线投射结果(`RaycastResult`)的`sortingLayer`、`sortingOrder`、`depth`、`distance`最后是`index`。具体这些参数后边再展开讨论。

    - `bool IsPointerOverGameObject()`

    有重载形式`bool IsPointerOverGameObject(int pointerId)`,缺省传入参数为鼠标左键的ID,此函数调用并返回当前输入模块的`IsPointerOverGameObject(int pointerId)`方法:

    ```C#
    public bool IsPointerOverGameObject(int pointerId)
    {
    if (m_CurrentInputModule == null)
    return false;

    return m_CurrentInputModule.IsPointerOverGameObject(pointerId);
    }

    这个方法通常可用于判断当前是否点击在UI上。

  • void TickModules()

    每帧都会调用,遍历当前的所有输入模块,调用其UpdateModule()方法:

    1
    2
    3
    4
    5
    6
    7
    8
    private void TickModules()
    {
    for (var i = 0; i < m_SystemInputModules.Count; i++)
    {
    if (m_SystemInputModules[i] != null)
    m_SystemInputModules[i].UpdateModule();
    }
    }
  • void Update()

    最主要的逻辑,在每帧内都会处理一下的事情:

    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
    protected virtual void Update()
    {
    if (current != this || m_Paused)
    return;
    TickModules();

    bool changedModule = false;
    for (var i = 0; i < m_SystemInputModules.Count; i++)
    {
    var module = m_SystemInputModules[i];
    if (module.IsModuleSupported() && module.ShouldActivateModule())
    {
    if (m_CurrentInputModule != module)
    {
    ChangeEventModule(module);
    changedModule = true;
    }
    break;
    }
    }

    // no event module set... set the first valid one...
    if (m_CurrentInputModule == null)
    {
    for (var i = 0; i < m_SystemInputModules.Count; i++)
    {
    var module = m_SystemInputModules[i];
    if (module.IsModuleSupported())
    {
    ChangeEventModule(module);
    changedModule = true;
    break;
    }
    }
    }

    if (!changedModule && m_CurrentInputModule != null)
    m_CurrentInputModule.Process();
    }

    首先会TickModules()更新输入模块;其次检查输入模块是否发生变化,ChangeEventModule(module)会把module设置为m_CurrentInputModule。如果输入模块没有发生变化则会调用该模块的Process()方法。

其它密切配合的类

负责处理输入并产生事件的 BaseInputModule及其衍生类,以及负责射线投射的BaseRaycaster 及其衍生类,会在后边的文章讨论。


本系列其它文章详见Unity3D UGUI 源码学习

REFERENCE

https://bitbucket.org/Unity-Technologies/ui