The principle of click judgment in UGUI

insert image description here
First of all, you need to understand the code structure in EventSystem. The EventSystem directory contains 4 subfolders, namely EventData, InputModules, Raycasters and UIElements. Under UIElements is the UI Toolkit related code. No research here, mainly focus on the other three parts

EventData

insert image description here
EventData stores event information, PointerEventData stores click, drag and other information, and AxisEventData stores axis event data, such as handle rocker

InputModules

insert image description here
BaseInputModule (abstract class) raises events and sends them to GameObjects for processing

Input is the input system interface, which can obtain input information such as the mouse position, but this part of the code is not open source, and BaseInput is the encapsulation of the Input class

BaseInputModule has a variable m_DefaultInput of BaseInput type, through which subclasses get some input information

PointerInputModule (abstract class) is used to handle click-type input

StandaloneInputModule is a specific implementation on PC, Mac&Linux, and TouchInputModule is a specific implementation on mobile platforms such as IOS and Android

insert image description here
The StandaloneInputModule component is hung on the EventSystem, and the BaseInput script will be added at runtime

Raycasters

insert image description here
There is a List in RaycasterManager to manage all Raycasters

BaseRaycaster (abstract class) is responsible for performing ray detection on scene elements to determine whether the cursor is on them. The two fields sortOrderPriority and renderOrderPriority are used to sort the results, registered to RaycasterManager in OnEnable, and removed in OnDisable. Raycast is an abstract method , implemented in a subclass

PhysicsRaycaster performs ray detection on 3D components, and the results are sorted according to distance

Physics2DRaycaster is for ray detection of 2D components

GraphicRaycaster performs ray detection on Graphic elements. The base class of all UI elements (Image, RawImage, etc.) is Graphic. The most important thing here are 2 Raycast overloaded functions, and the unimportant code is omitted here

public class GraphicRaycaster : BaseRaycaster
{
    
    
    [NonSerialized] private List<Graphic> m_RaycastResults = new List<Graphic>();

    /// <summary>
    /// 对Canvas相关的Graphic元素进行射线检测,结果存入resultAppendList
    /// </summary>
    public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
    {
    
    
        if (canvas == null)
            return;

		// 获取Canvas上所有可点击,即raycastTarget = true的Graphic元素
        var canvasGraphics = GraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);
        if (canvasGraphics == null || canvasGraphics.Count == 0)
            return;

        int displayIndex;
        // Canvas为ScreenSpaceOverlay模式下默认为null
        var currentEventCamera = eventCamera;

        if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
            displayIndex = canvas.targetDisplay;
        else
            displayIndex = currentEventCamera.targetDisplay;

		// 获取鼠标位置
        var eventPosition = Display.RelativeMouseAt(eventData.position);
        if (eventPosition != Vector3.zero)
        {
    
    
            int eventDisplayIndex = (int)eventPosition.z;
            if (eventDisplayIndex != displayIndex)
                return;
        }
        else
        {
    
    
            eventPosition = eventData.position;
        }

        // 把eventPosition转换到相机空间
        Vector2 pos;
        if (currentEventCamera == null)
        {
    
    
            float w = Screen.width;
            float h = Screen.height;
            if (displayIndex > 0 && displayIndex < Display.displays.Length)
            {
    
    
                w = Display.displays[displayIndex].systemWidth;
                h = Display.displays[displayIndex].systemHeight;
            }
            pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
        }
        else
            pos = currentEventCamera.ScreenToViewportPoint(eventPosition);

        // 不在相机视口内
        if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
            return;

        float hitDistance = float.MaxValue;

        Ray ray = new Ray();
		// currentEventCamera不为空,摄像机发射射线
        if (currentEventCamera != null)
            ray = currentEventCamera.ScreenPointToRay(eventPosition);

        if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
        {
    
    
			// 这里主要检测2D/3D物体对UI的遮挡的,略
        }

        m_RaycastResults.Clear();

        Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);

        int totalCount = m_RaycastResults.Count;
        for (var index = 0; index < totalCount; index++)
        {
    
    
            var go = m_RaycastResults[index].gameObject;
            bool appendGraphic = true;

            if (ignoreReversedGraphics)
            {
    
    
                if (currentEventCamera == null)
                {
    
    
                    // 点乘判断元素是否面向前方
                    var dir = go.transform.rotation * Vector3.forward;
                    appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
                }
                else
                {
    
    
                    // 与摄像头的前方进行比较
                    var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane;
                    appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0;
                }
            }

            if (appendGraphic)
            {
    
    
                float distance = 0;
                Transform trans = go.transform;
                Vector3 transForward = trans.forward;

                if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
                    distance = 0;
                else
                {
    
    
                    distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));

                    // 是否在相机后面
                    if (distance < 0)
                        continue;
                }

                if (distance >= hitDistance)
                    continue;

                var castResult = new RaycastResult
                {
    
    
                    gameObject = go,
                    module = this,
                    distance = distance,
                    screenPosition = eventPosition,
                    displayIndex = displayIndex,
                    index = resultAppendList.Count,
                    depth = m_RaycastResults[index].depth,
                    sortingLayer = canvas.sortingLayerID,
                    sortingOrder = canvas.sortingOrder,
                    worldPosition = ray.origin + ray.direction * distance,
                    worldNormal = -transForward
                };
                resultAppendList.Add(castResult);
            }
        }
    }

    /// <summary>
    /// 重载函数,收集点击位置处的Graphic元素
    /// </summary>
    [NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
    private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
    {
    
    
		// foundGraphics是Canvas上所有可点击的Graphic元素
        int totalCount = foundGraphics.Count;
        for (int i = 0; i < totalCount; ++i)
        {
    
    
            Graphic graphic = foundGraphics[i];

            // -1表示Canvas尚未对其进行处理,也就是说它实际上并未绘制
            if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)
                continue;

			// 检测点击位置是否在RectTransform形成的矩形内
            if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera, graphic.raycastPadding))
                continue;

			// 是否超出相机远裁剪面
            if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
                continue;

			// 进行后续的检测
            if (graphic.Raycast(pointerPosition, eventCamera))
            {
    
    
                s_SortedGraphics.Add(graphic);
            }
        }

		// 根据深度进行排序
        s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
        totalCount = s_SortedGraphics.Count;
        for (int i = 0; i < totalCount; ++i)
            results.Add(s_SortedGraphics[i]);

        s_SortedGraphics.Clear();
    }
}

Jump to the graphic.Raycast method to view the subsequent detection, here get all the components on the graphic and convert them to the interface ICanvasRaycastFilter, and then call the IsRaycastLocationValid method of the interface for the final detection

public abstract class Graphic: UIBehaviour,ICanvasElement
{
    
    
    /// <summary>
    /// 确定点击的位置是否合法,sp是Screen point,返回True表示合法
    /// </summary>
    public virtual bool Raycast(Vector2 sp, Camera eventCamera)
    {
    
    
        if (!isActiveAndEnabled)
            return false;

        var t = transform;
        var components = ListPool<Component>.Get();

        bool ignoreParentGroups = false;
        bool continueTraversal = true;

        while (t != null)
        {
    
    
            t.GetComponents(components);
            for (var i = 0; i < components.Count; i++)
            {
    
    
                var canvas = components[i] as Canvas;
                if (canvas != null && canvas.overrideSorting)
                    continueTraversal = false;

                var filter = components[i] as ICanvasRaycastFilter;

                if (filter == null)
                    continue;

                var raycastValid = true;

                var group = components[i] as CanvasGroup;
                if (group != null)
                {
    
    
                    if (ignoreParentGroups == false && group.ignoreParentGroups)
                    {
    
    
                        ignoreParentGroups = true;
                        raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
                    }
                    else if (!ignoreParentGroups)
                        raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
                }
                else
                {
    
    
                    raycastValid = filter.IsRaycastLocationValid(sp, eventCamera);
                }

                if (!raycastValid)
                {
    
    
                    ListPool<Component>.Release(components);
                    return false;
                }
            }
            t = continueTraversal ? t.parent : null;
        }
        ListPool<Component>.Release(components);
        return true;
    }
}

insert image description here
Image, Mask and CanvasGroup all implement the ICanvasRaycastFilter interface, see the specific implementation of Image

public class Image : MaskableGraphic, ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter
{
    
    
	/// <summary>
    /// 计算位置是否为有效的命中位置,根据图片的Alpha值是否大于阈值
    /// </summary>
	public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
    {
    
    
        if (alphaHitTestMinimumThreshold <= 0)
            return true;

        if (alphaHitTestMinimumThreshold > 1)
            return false;

        if (activeSprite == null)
            return true;

        Vector2 local;
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local))
            return false;

        Rect rect = GetPixelAdjustedRect();

        // Convert to have lower left corner as reference point.
        local.x += rectTransform.pivot.x * rect.width;
        local.y += rectTransform.pivot.y * rect.height;

        local = MapCoordinate(local, rect);

        // Convert local coordinates to texture space.
        Rect spriteRect = activeSprite.textureRect;
        float x = (spriteRect.x + local.x) / activeSprite.texture.width;
        float y = (spriteRect.y + local.y) / activeSprite.texture.height;

        try
        {
    
    
            return activeSprite.texture.GetPixelBilinear(x, y).a >= alphaHitTestMinimumThreshold;
        }
        catch (UnityException e)
        {
    
    
            Debug.LogError("Using alphaHitTestMinimumThreshold greater than 0 on Image whose sprite texture cannot be read. " + e.Message + " Also make sure to disable sprite packing for this sprite.", this);
            return true;
        }
    }
}

Note: All UI elements that check RaycastTarget will perform ray detection, participate in some coordinates, sorting calculations, too many will affect efficiency, so do not check RaycastTarget for elements that do not need to interact

EventSystem

EventSystem handles input, ray detection and sending events. There is a list m_SystemInputModules in EventSystem to save all input modules, update the current input module m_CurrentInputModule in Update, and call the CurrentInputModule.Process method for input processing

In the input module such as PointerInputModule, EventSystem.RaycastAll is called to perform ray detection and save the results in a list, and then call EventSystem.RaycastComparer to sort the results

public class EventSystem : UIBehaviour
{
    
    
	// 所有的输入模块
    private List<BaseInputModule> m_SystemInputModules = new List<BaseInputModule>();
	// 当前输入模块,PC上就是StandaloneInputModule
    private BaseInputModule m_CurrentInputModule;

    protected virtual void Update()
    {
    
    
    	// 更新m_SystemInputModules中所有的输入模块
        TickModules();

		// 更新当前输入模块
        bool changedModule = false;
        var systemInputModulesCount = m_SystemInputModules.Count;
        for (var i = 0; i < systemInputModulesCount; i++)
        {
    
    
            var module = m_SystemInputModules[i];
            if (module.IsModuleSupported() && module.ShouldActivateModule())
            {
    
    
                if (m_CurrentInputModule != module)
                {
    
    
                    ChangeEventModule(module);
                    changedModule = true;
                }
                break;
            }
        }

        // 当前输入模块为空,选择第一个有效的,略

		// Process方法中处理输入
        if (!changedModule && m_CurrentInputModule != null)
            m_CurrentInputModule.Process();
    }

    /// <summary>
    /// 进行射线检测并将结果添加到raycastResults
    /// </summary>
    public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
    {
    
    
        raycastResults.Clear();
        // 获取所有注册的BaseRaycaster对象,如GraphicRaycaster,PhysicsRaycaster,Physics2DRaycaster
        var modules = RaycasterManager.GetRaycasters();
        var modulesCount = modules.Count;
        for (int i = 0; i < modulesCount; ++i)
        {
    
    
            var module = modules[i];
            if (module == null || !module.IsActive())
                continue;

			// 进行射线检测,并将结果添加到raycastResults
            module.Raycast(eventData, raycastResults);
        }

		// 对结果进行排序,保证UI优先处理
        raycastResults.Sort(s_RaycastComparer);
    }

	private static readonly Comparison<RaycastResult> s_RaycastComparer = RaycastComparer;

	/// <summary>
    /// 对射线检测的结果进行比较
    /// </summary>
    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)
            {
    
    
                // 比较camera深度
                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);
        }

		// 比较sortingLayer
        if (lhs.sortingLayer != rhs.sortingLayer)
        {
    
    
            var rid = SortingLayer.GetLayerValueFromID(rhs.sortingLayer);
            var lid = SortingLayer.GetLayerValueFromID(lhs.sortingLayer);
            return rid.CompareTo(lid);
        }

		// 比较sortingOrder 
        if (lhs.sortingOrder != rhs.sortingOrder)
            return rhs.sortingOrder.CompareTo(lhs.sortingOrder);

        // 比较深度
        if (lhs.depth != rhs.depth && lhs.module.rootRaycaster == rhs.module.rootRaycaster)
            return rhs.depth.CompareTo(lhs.depth);

		// 比较距离
        if (lhs.distance != rhs.distance)
            return lhs.distance.CompareTo(rhs.distance);

		// 比较index
        return lhs.index.CompareTo(rhs.index);
    }
}

Check out the methods related to mouse events in StandaloneInputModule, here we only focus on the click method for brevity

public class StandaloneInputModule : PointerInputModule
{
    
    
    public override void Process()
    {
    
    
        if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
            return;

        bool usedEvent = SendUpdateEventToSelectedObject();

        // 优先处理触摸事件,如果有鼠标则处理鼠标事件
        if (!ProcessTouchEvents() && input.mousePresent)
            ProcessMouseEvent();
    }

	protected void ProcessMouseEvent()
    {
    
    
        ProcessMouseEvent(0);
    }

	/// <summary>
    /// 处理所有的鼠标事件
    /// </summary>
    protected void ProcessMouseEvent(int id)
    {
    
    
        var mouseData = GetMousePointerEventData(id);
        var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData;

        m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;

        // 优先处理鼠标左键
        ProcessMousePress(leftButtonData);
        // 处理右键和中键,按下和拖动事件,略。。。
    }

    /// <summary>
    /// 计算和处理任何鼠标按钮的状态变化
    /// </summary>
    protected void ProcessMousePress(MouseButtonEventData data)
    {
    
    
        var pointerEvent = data.buttonData;
        var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;

        // 按下
        if (data.PressedThisFrame())
        {
    
    
        	// 给pointerEvent填充数据,略
        }

        // 抬起
        if (data.ReleasedThisFrame())
        {
    
    
            ReleaseMouse(pointerEvent, currentOverGo);
        }
    }

    private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo)
    {
    
    
    	// 触发抬起事件
        ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
        var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);

        if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
        {
    
    
        	// 触发鼠标点击事件
            ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
        }
        // 处理拖动相关事件,略
    }
}

View the specific implementation of ExecuteEvents, simply put it is to convert the component to IEventSystemHandler, and then execute the delegate method

public static class ExecuteEvents
{
    
    
    public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
    {
    
    
        var internalHandlers = ListPool<IEventSystemHandler>.Get();
        GetEventList<T>(target, internalHandlers);

        var internalHandlersCount = internalHandlers.Count;
        for (var i = 0; i < internalHandlersCount; i++)
        {
    
    
            T arg;
            try
            {
    
    
            	// 转换成目标类型,如IPointerClickHandler
                arg = (T)internalHandlers[i];
            }
            catch (Exception e)
            {
    
    
                var temp = internalHandlers[i];
                Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e));
                continue;
            }

            try
            {
    
    
            	// 执行委托
                functor(arg, eventData);
            }
            catch (Exception e)
            {
    
    
                Debug.LogException(e);
            }
        }

        var handlerCount = internalHandlers.Count;
        ListPool<IEventSystemHandler>.Release(internalHandlers);
        return handlerCount > 0;
    }

	/// <summary>
    /// 将组件转换成IEventSystemHandler,并放入列表中
    /// </summary>
    private static void GetEventList<T>(GameObject go, IList<IEventSystemHandler> results) where T : IEventSystemHandler
    {
    
    
        if (go == null || !go.activeInHierarchy)
            return;

        var components = ListPool<Component>.Get();
        go.GetComponents(components);

        var componentsCount = components.Count;
        for (var i = 0; i < componentsCount; i++)
        {
    
    
            if (!ShouldSendToComponent<T>(components[i]))
                continue;
            results.Add(components[i] as IEventSystemHandler);
        }
        ListPool<Component>.Release(components);
    }
}

insert image description here
IEventSystemHandler in the above code is the parent interface of many other interfaces. Button implements IPointerClickHandler
, so clicking Button will call its OnPointerClick method through IPointerClickHandler interface

reference

Research on the principle of click judgment in UGUI

[UGUI source code 1] 6 thousand words take you to get started with UGUI source code

Click event mechanism of UGUI

Guess you like

Origin blog.csdn.net/sinat_34014668/article/details/130303862
Recommended