UGUI source code analysis 2: EventSystem

1. Introduction to EventSystem

EventSystem inherits from UIBehaviour. There can be multiple EventSystem components in the scene, but only one will take effect. Each EventSystem will add itself to the static variable m_EventSystems when OnEnable, and remove itself from m_EventSystems when OnDisable. Each Update will determine whether the current (each time is the first in m_EventSystems) is itself.

2.EventSystem main properties/fields

attribute/field name

illustrate

Whether to serialize to the Inspector panel

List<BaseInputModule> m_SystemInputModules

List of input modules

BaseInputModule m_CurrentInputModule

current input module

List<EventSystem> m_EventSystems

EventSystem list

EventSystem current

The currently active input module

GameObject m_FirstSelected

The object selected for the first time, you can specify an object before running, and select the object immediately after running.

yes

bool m_sendNavigationEvents

Whether to send navigation events

yes

int m_DragThreshold

The minimum pixel triggered by the drag event, indicating how many pixels the mouse moves to trigger the drag event

yes

GameObject m_CurrentSelected

The currently selected GameObject

GameObject lastSelectedGameObject

Last selected GameObject

bool m_HasFocus

program focus

Navigation events : Move, Submit, Cancel

3. The main function of Event System

EventSystem belongs to the event logic processing module in UGUI source code. All UI events are detected and executed through polling in the EventSystem class, and the main logic is formed by calling the input event detection module and the collision detection module.

3.1. Manage update input module

// 收集当前对象上所有的输入模块, 在BaseInputModule->OnEnable/OnDisable时触发
// 在BaseInputModule可用和不可用时会通过触发此方法来添加或者移除自己
public void UpdateModules()
 {
     GetComponents(m_SystemInputModules);
     for (int i = m_SystemInputModules.Count - 1; i >= 0; i--)
     {
         if (m_SystemInputModules[i] && m_SystemInputModules[i].IsActive())
             continue;

         m_SystemInputModules.RemoveAt(i);
     }
 }

// 触发输入模块的更新, 由当前激活的EventSystem在生命周期函数Update中触发
// 这里的只是更新数据, 而不进行真正的事件处理, 真正的事件处理只有当前的输入模块才处理, 也就是说同时只有一个输入模块会处理事件
private void TickModules()
{
    for (var i = 0; i < m_SystemInputModules.Count; i++)
    {
        if (m_SystemInputModules[i] != null)
            m_SystemInputModules[i].UpdateModule();
    }
}

// 更新输入模块, 切换当前激活的输入模块并处理事件
protected virtual void Update()
{
    //判断自身是否是生效的EventSystem 同一场景只有一个EventSystem会生效
    if (current != this)
        return;
    TickModules();

    //切换当前激活的InputModule
    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;
        }
    }

    //没有支持当前平台且处于激活状态的InputModule时,将第一个支持当前平台的InputModule激活
    // 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();
}

// 切换当前生效的输入模块
private void ChangeEventModule(BaseInputModule module)
{
    if (m_CurrentInputModule == module)
        return;

    if (m_CurrentInputModule != null)
        m_CurrentInputModule.DeactivateModule();

    if (module != null)
        module.ActivateModule();
    m_CurrentInputModule = module;
}

3.2. Manage the ray detection module to perform ray detection and compare the priority of the detection results

//当前所有生效的Raycaster进行射线检测,再将检测到的所有对象进行排序(RaycastComparer)
//在PointerInputModule中检测到点击或者触摸时间时触发
public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
{
    raycastResults.Clear();
    var modules = RaycasterManager.GetRaycasters();
    for (int i = 0; i < modules.Count; ++i)
    {
        var module = modules[i];
        if (module == null || !module.IsActive())
            continue;

        module.Raycast(eventData, raycastResults);
    }

    raycastResults.Sort(s_RaycastComparer);
}

///对射线穿透的所有未屏蔽的物体的优先级进行排序
/// 排序规则
/// 1.如果Canvas指定了不同的Camera,则按照Camvas深度进行排序,大的在前
/// 2.按照射线发射器的sortOrderPriority进行排序,大的在前
/// 3.按照射线发射器的renderOrderPriority进行排序,大的在前
/// 4.按照被检测物体的sortingLayer进行排序,大的在前
/// 5.按照被检测物体的sortingOrder进行排序,大的在前
/// 6.按照被检测物体的depth进行排序,在PhysicsRaycaster,Physic2DRaycaster两种射线模式下,该值为0.大的在前
/// 7.按照射线出发点到碰撞位置的距离,距离小的在前
/// 8.按照射线检测结果的索引值(index),小的放在前面。
private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
{
    if (lhs.module != rhs.module)
    {
        var lhsEventCamera = lhs.module.eventCamera;
        var rhsEventCamera = rhs.module.eventCamera;
        if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth)
        {
            // need to reverse the standard compareTo
            if (lhsEventCamera.depth < rhsEventCamera.depth)
                return 1;
            if (lhsEventCamera.depth == rhsEventCamera.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);
}

3.3. Other important interfaces

//程序聚焦
protected virtual void OnApplicationFocus(bool hasFocus)
{
    m_HasFocus = hasFocus;
}

//设置当前选中对象,通过在不同的组件对象点击回调中设置
public void SetSelectedGameObject(GameObject selected, BaseEventData pointer)
{
    if (m_SelectionGuard)       //避免同一时间设置多个选中对象
    {
        Debug.LogError("Attempting to select " + selected +  "while already selecting an object.");
        return;
    }

    m_SelectionGuard = true;
    if (selected == m_CurrentSelected)      //选中同一对象不执行事件
    {
        m_SelectionGuard = false;
        return;
    }

    // Debug.Log("Selection: new (" + selected + ") old (" + m_CurrentSelected + ")");
    //旧对象发送取消选中事件
    ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.deselectHandler);       //老对象执行取消选中事件
    m_CurrentSelected = selected;
    //新对象发送选中事件
    ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.selectHandler);         //新对象执行选中事件
    m_SelectionGuard = false;
}

Event System source code and comment analysis

using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Serialization;

namespace UnityEngine.EventSystems
{
    [AddComponentMenu("Event/Event System")]
    /// <summary>
    /// Handles input, raycasting, and sending events.
    /// </summary>
    /// <remarks>
    /// The EventSystem is responsible for processing and handling events in a Unity scene. A scene should only contain one EventSystem. The EventSystem works in conjunction with a number of modules and mostly just holds state and delegates functionality to specific, overrideable components.
    /// When the EventSystem is started it searches for any BaseInputModules attached to the same GameObject and adds them to an internal list. On update each attached module receives an UpdateModules call, where the module can modify internal state. After each module has been Updated the active module has the Process call executed.This is where custom module processing can take place.
    /// </remarks>
    public class EventSystem : UIBehaviour
        {
            private List<BaseInputModule> m_SystemInputModules = new List<BaseInputModule>();           //输入模块列表

            private BaseInputModule m_CurrentInputModule;                                               //当前输入模块

            private  static List<EventSystem> m_EventSystems = new List<EventSystem>();                 //EventSystem列表

            /// <summary>
            /// Return the current EventSystem.
            /// </summary>
            public static EventSystem current                                                           //当前生效的EventSystem
            {
                get { return m_EventSystems.Count > 0 ? m_EventSystems[0] : null; }                     //每次只会取EventSyste列表中第一个元素
                set
                {
                    int index = m_EventSystems.IndexOf(value);

                    if (index >= 0)
                    {
                        m_EventSystems.RemoveAt(index);
                        m_EventSystems.Insert(0, value);
                    }
                }
            }

            [SerializeField]
            [FormerlySerializedAs("m_Selected")]
            private GameObject m_FirstSelected;                                                         //首次选中的对象,运行前可以指定,运行后立即选中该对象

            [SerializeField]
            private bool m_sendNavigationEvents = true;                                                 //是否发送导航事件

            /// <summary>
            /// Should the EventSystem allow navigation events (move / submit / cancel).
            /// </summary>
            public bool sendNavigationEvents
            {
                get { return m_sendNavigationEvents; }
                set { m_sendNavigationEvents = value; }
            }

            [SerializeField]
            private int m_DragThreshold = 10;                                                           //拖动事件触发的最小像素,表示指针至少移动多少像素才能触发拖动事件

            /// <summary>
            /// The soft area for dragging in pixels.
            /// </summary>
            public int pixelDragThreshold
            {
                get { return m_DragThreshold; }
                set { m_DragThreshold = value; }
            }

            private GameObject m_CurrentSelected;                                                       //当前选中的GameObject

            /// <summary>
            /// The currently active EventSystems.BaseInputModule.
            /// </summary>
            public BaseInputModule currentInputModule
            {
                get { return m_CurrentInputModule; }
            }

            /// <summary>
            /// Only one object can be selected at a time. Think: controller-selected button.
            /// </summary>
            public GameObject firstSelectedGameObject
            {
                get { return m_FirstSelected; }
                set { m_FirstSelected = value; }
            }

            /// <summary>
            /// The GameObject currently considered active by the EventSystem.
            /// </summary>
            public GameObject currentSelectedGameObject
            {
                get { return m_CurrentSelected; }
            }

            [Obsolete("lastSelectedGameObject is no longer supported")]
            public GameObject lastSelectedGameObject                                                    //上次选中的GameObject
            {
                get { return null; }
            }

            private bool m_HasFocus = true;                                                             //应用焦点

            /// <summary>
            /// Flag to say whether the EventSystem thinks it should be paused or not based upon focused state.
            /// </summary>
            /// <remarks>
            /// Used to determine inside the individual InputModules if the module should be ticked while the application doesnt have focus.
            /// </remarks>
            public bool isFocused
            {
                get { return m_HasFocus; }
            }

            protected EventSystem()
            {}

        	// 收集当前对象上所有的输入模块, 在BaseInputModule->OnEnable/OnDisable时触发
			// 在BaseInputModule可用和不可用时会通过触发此方法来添加或者移除自己
            /// <summary>
            /// Recalculate the internal list of BaseInputModules.
            /// </summary>
            public void UpdateModules()
            {
                GetComponents(m_SystemInputModules);
                for (int i = m_SystemInputModules.Count - 1; i >= 0; i--)
                {
                    if (m_SystemInputModules[i] && m_SystemInputModules[i].IsActive())
                        continue;

                    m_SystemInputModules.RemoveAt(i);
                }
            }

            private bool m_SelectionGuard;                                                              //避免同一时间设置多个选中对象

            /// <summary>
            /// Returns true if the EventSystem is already in a SetSelectedGameObject.
            /// </summary>
            public bool alreadySelecting
            {
                get { return m_SelectionGuard; }
            }

            //设置当前选中对象,通过在不同的组件对象点击回调中设置
            /// <summary>
            /// Set the object as selected. Will send an OnDeselect the the old selected object and OnSelect to the new selected object.
            /// </summary>
            /// <param name="selected">GameObject to select.</param>
            public void SetSelectedGameObject(GameObject selected, BaseEventData pointer)
            {
                if (m_SelectionGuard)       //避免同一时间设置多个选中对象
                {
                    Debug.LogError("Attempting to select " + selected +  "while already selecting an object.");
                    return;
                }

                m_SelectionGuard = true;
                if (selected == m_CurrentSelected)      //选中同一对象不执行事件
                {
                    m_SelectionGuard = false;
                    return;
                }

                // Debug.Log("Selection: new (" + selected + ") old (" + m_CurrentSelected + ")");
                ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.deselectHandler);       //老对象执行取消选中事件
                m_CurrentSelected = selected;
                ExecuteEvents.Execute(m_CurrentSelected, pointer, ExecuteEvents.selectHandler);         //新对象执行选中事件
                m_SelectionGuard = false;
            }

            private BaseEventData m_DummyData;
            private BaseEventData baseEventDataCache
            {
                get
                {
                    if (m_DummyData == null)
                        m_DummyData = new BaseEventData(this);

                    return m_DummyData;
                }
            }

            /// <summary>
            /// Set the object as selected. Will send an OnDeselect the the old selected object and OnSelect to the new selected object.
            /// </summary>
            /// <param name="selected">GameObject to select.</param>
            public void SetSelectedGameObject(GameObject selected)
            {
                SetSelectedGameObject(selected, baseEventDataCache);
            }

            ///对射线穿透的所有未屏蔽的物体的优先级进行排序
            /// 排序规则
            /// 1.如果Canvas指定了不同的Camera,则按照Camvas深度进行排序,大的在前
            /// 2.按照射线发射器的sortOrderPriority进行排序,大的在前
            /// 3.按照射线发射器的renderOrderPriority进行排序,大的在前
            /// 4.按照被检测物体的sortingLayer进行排序,大的在前
            /// 5.按照被检测物体的sortingOrder进行排序,大的在前
            /// 6.按照被检测物体的depth进行排序,在PhysicsRaycaster,Physic2DRaycaster两种射线模式下,该值为0.大的在前
            /// 7.按照射线出发点到碰撞位置的距离,距离小的在前
            /// 8.按照射线检测结果的索引值(index),小的放在前面。
            private static int RaycastComparer(RaycastResult lhs, RaycastResult rhs)
            {
                if (lhs.module != rhs.module)
                {
                    var lhsEventCamera = lhs.module.eventCamera;
                    var rhsEventCamera = rhs.module.eventCamera;
                    if (lhsEventCamera != null && rhsEventCamera != null && lhsEventCamera.depth != rhsEventCamera.depth)
                    {
                        // need to reverse the standard compareTo
                        if (lhsEventCamera.depth < rhsEventCamera.depth)
                            return 1;
                        if (lhsEventCamera.depth == rhsEventCamera.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);
            }

            private static readonly Comparison<RaycastResult> s_RaycastComparer = RaycastComparer;
            
            //当前所有生效的Raycaster进行射线检测,再将检测到的所有对象进行排序(RaycastComparer)
            //在PointerInputModule中检测到点击或者触摸时间时触发
            /// <summary>
            /// Raycast into the scene using all configured BaseRaycasters.
            /// </summary>
            /// <param name="eventData">Current pointer data.</param>
            /// <param name="raycastResults">List of 'hits' to populate.</param>
            public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
            {
                raycastResults.Clear();
                var modules = RaycasterManager.GetRaycasters();
                for (int i = 0; i < modules.Count; ++i)
                {
                    var module = modules[i];
                    if (module == null || !module.IsActive())
                        continue;

                    module.Raycast(eventData, raycastResults);
                }

                raycastResults.Sort(s_RaycastComparer);
            }

            /// <summary>
            /// Is the pointer with the given ID over an EventSystem object?
            /// </summary>
            public bool IsPointerOverGameObject()
            {
                return IsPointerOverGameObject(PointerInputModule.kMouseLeftId);
            }

            /// <summary>
            /// Is the pointer with the given ID over an EventSystem object?
            /// </summary>
            /// <remarks>
            /// If you use IsPointerOverGameObject() without a parameter, it points to the "left mouse button" (pointerId = -1); therefore when you use IsPointerOverGameObject for touch, you should consider passing a pointerId to it
            /// Note that for touch, IsPointerOverGameObject should be used with ''OnMouseDown()'' or ''Input.GetMouseButtonDown(0)'' or ''Input.GetTouch(0).phase == TouchPhase.Began''.
            /// </remarks>
            /// <example>
            /// <code>
            /// using UnityEngine;
            /// using System.Collections;
            /// using UnityEngine.EventSystems;
            ///
            /// public class MouseExample : MonoBehaviour
            /// {
            ///     void Update()
            ///     {
            ///         // Check if the left mouse button was clicked
            ///         if (Input.GetMouseButtonDown(0))
            ///         {
            ///             // Check if the mouse was clicked over a UI element
            ///             if (EventSystem.current.IsPointerOverGameObject())
            ///             {
            ///                 Debug.Log("Clicked on the UI");
            ///             }
            ///         }
            ///     }
            /// }
            /// </code>
            /// </example>
            
            //具有给定ID的指针是否位于EventSystem对象上。一般可以用来判断鼠标是否点击在UI上 
            //参数pointerId 真机时需要传触摸的fingerId  不传时代表鼠标左键
            public bool IsPointerOverGameObject(int pointerId)
            {
                if (m_CurrentInputModule == null)
                    return false;

                return m_CurrentInputModule.IsPointerOverGameObject(pointerId);
            }

            //OnEnable时将自身加到静态EventSystem列表中
            protected override void OnEnable()
            {
                base.OnEnable();
                m_EventSystems.Add(this);
            }

            //OnDisable时将自身从静态EventSystem列表中移除且释放inputModule
            protected override void OnDisable()
            {
                if (m_CurrentInputModule != null)
                {
                    m_CurrentInputModule.DeactivateModule();
                    m_CurrentInputModule = null;
                }

                m_EventSystems.Remove(this);

                base.OnDisable();
            }

            // 触发输入模块的更新, 由当前激活的EventSystem在生命周期函数Update中触发
            // 这里的只是更新数据, 而不进行真正的事件处理, 真正的事件处理只有当前的输入模块才处理, 也就是说同时只有一个输入模块会处理事件
            private void TickModules()
            {
                for (var i = 0; i < m_SystemInputModules.Count; i++)
                {
                    if (m_SystemInputModules[i] != null)
                        m_SystemInputModules[i].UpdateModule();
                }
            }

            protected virtual void OnApplicationFocus(bool hasFocus)
            {
                m_HasFocus = hasFocus;
            }

            // 更新输入模块, 切换当前激活的输入模块并处理事件
            protected virtual void Update()
            {
                //判断自身是否是生效的EventSystem 同一场景只有一个EventSystem会生效
                if (current != this)
                    return;
                TickModules();

                //切换当前激活的InputModule
                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;
                    }
                }

                //没有支持当前平台且处于激活状态的InputModule时,将第一个支持当前平台的InputModule激活
                // 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();
            }

            // 切换当前生效的输入模块
            private void ChangeEventModule(BaseInputModule module)
            {
                if (m_CurrentInputModule == module)
                    return;

                if (m_CurrentInputModule != null)
                    m_CurrentInputModule.DeactivateModule();

                if (module != null)
                    module.ActivateModule();
                m_CurrentInputModule = module;
            }

            public override string ToString()
            {
                var sb = new StringBuilder();
                sb.AppendLine("<b>Selected:</b>" + currentSelectedGameObject);
                sb.AppendLine();
                sb.AppendLine();
                sb.AppendLine(m_CurrentInputModule != null ? m_CurrentInputModule.ToString() : "No module");
                return sb.ToString();
            }
        }
}

Guess you like

Origin blog.csdn.net/m0_57771536/article/details/128195124