UGUI——EventSystems和射线检测

我的notion

处理输入、射线和发送事件。继承自UIBehaviour

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.

  • 事件系统负责责加工和处理场景中的事件。一个场景应该只包含一个EventSystem。EventSystem与许多模块协同工作,主要只是保存状态并将功能委托给特定的、可重写的组件。
  • 当EventSystem启动时,它会搜索附加到同一个游戏对象的任何BaseInputModule,并将它们添加到内部列表中。更新时,每个附加的模块都会收到一个UpdateModules调用,模块可以在其中修改内部状态。每个模块更新后,活动模块都有进程调用执行。这个是可以进行自定义模块处理的地方。

EventSystem的简单介绍和使用


静态属性:current 返回当前的EventSystem;set是将指定的eventSystem移到数组开头

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

/// <summary>
/// Return the current EventSystem.
/// </summary>
public static EventSystem current
{
    get { return m_EventSystems.Count > 0 ? m_EventSystems[0] : null; }
    set
    {
        int index = m_EventSystems.IndexOf(value);

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

在 OnEnable中,eventSystem将自身插入m_EventSystems

protected override void OnEnable()
{
    base.OnEnable();
    m_EventSystems.Add(this);
}

在OnDisable中,从m_EventSystems中移出,并清理非静态属性

 protected override void OnDisable()
{
    if (m_CurrentInputModule != null)
    {
        m_CurrentInputModule.DeactivateModule();
        m_CurrentInputModule = null;
    }

    m_EventSystems.Remove(this);

    base.OnDisable();
}

一个场景只有一个eventSystem,m_EventSystems还是设置为list,猜测目的是为了场景切换时,现在的场景OnDisable后current会立刻切换为list中的下一个。


UpdateModules

更新对baseInputModule的管理。由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);
    }
}

baseInputModule


https://img-blog.csdnimg.cn/20200419181040832.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODIwNjc1,size_16,color_FFFFFF,t_70


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()
{
    if (current != this)
        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();
}

先触发所有baseInputModules的UpdateModule,现在只有一个StandaloneInputModule了,其UpdateModule接口用来更新鼠标拖拽信息和位置。更新baseinputmodule,接着调用当前baseinputmodule的process接口,设置PointerEventData数据,触发输出相关事件。(看到了连点的记录,0.3秒之内点击同一个物体会累加点击次数)

https://img-blog.csdnimg.cn/20201130125056490.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Z6dGZ6dGZ6dA==,size_16,color_FFFFFF,t_70

Pointerinputmodule内部有个m_PointerData,维护了各个inputid对应的pointdata,如鼠标左键,点击等 看下EventSystem的IsPointerOverGameObject接口:调用的是当前baseinput的IsPointerOverGameObject,该接口获取pointdata的pointerEnter数据。

// 默认去鼠标左键 PointerInputModule.kMouseLeftId
public bool IsPointerOverGameObject(int pointerId)
{
    if (m_CurrentInputModule == null)
        return false;

    return m_CurrentInputModule.IsPointerOverGameObject(pointerId);
}
PointerInputModule:
public override bool IsPointerOverGameObject(int pointerId)
{
    var lastPointer = GetLastPointerEventData(pointerId);
    if (lastPointer != null)
        return lastPointer.pointerEnter != null;
    return false;
}
/// <summary>
/// Return the last PointerEventData for the given touch / mouse id.
/// </summary>
protected PointerEventData GetLastPointerEventData(int id)
{
    PointerEventData data;
    GetPointerData(id, out data, false);
    return data;
}

Process

m_PointerData的数据填充,会处理touch和mouse事件

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

    bool usedEvent = SendUpdateEventToSelectedObject();

    // case 1004066 - touch / mouse events should be processed before navigation events in case
    // they change the current selected gameobject and the submit button is a touch / mouse button.

    // touch needs to take precedence because of the mouse emulation layer
    if (!ProcessTouchEvents() && input.mousePresent)
        ProcessMouseEvent();

    if (eventSystem.sendNavigationEvents)
    {
        if (!usedEvent)
            usedEvent |= SendMoveEventToSelectedObject();

        if (!usedEvent)
            SendSubmitEventToSelectedObject();
    }
}

以ProcessTouchEvents为例:

遍历所有的pointerid,获取(没有就创建PointerData),然后填充数据,根据不同的操作状态填入不同数据

namespace UnityEngine.EventSystems
{
    /// <summary>
    /// Each touch event creates one of these containing all the relevant information.
    /// </summary>
    public class PointerEventData : BaseEventData
    {
        /// <summary>
        /// Input press tracking.
        /// </summary>
        public enum InputButton
        {
            /// <summary>
            /// Left button
            /// </summary>
            Left = 0,

            /// <summary>
            /// Right button.
            /// </summary>
            Right = 1,

            /// <summary>
            /// Middle button
            /// </summary>
            Middle = 2
        }

        /// <summary>
        /// The state of a press for the given frame.
        /// </summary>
        public enum FramePressState
        {
            /// <summary>
            /// Button was pressed this frame.
            /// </summary>
            Pressed,

            /// <summary>
            /// Button was released this frame.
            /// </summary>
            Released,

            /// <summary>
            /// Button was pressed and released this frame.
            /// </summary>
            PressedAndReleased,

            /// <summary>
            /// Same as last frame.
            /// </summary>
            NotChanged
        }

        /// <summary>
        /// The object that received 'OnPointerEnter'.
        /// </summary>
        public GameObject pointerEnter { get; set; }

        // The object that received OnPointerDown
        private GameObject m_PointerPress;

        /// <summary>
        /// The raw GameObject for the last press event. This means that it is the 'pressed' GameObject even if it can not receive the press event itself.
        /// </summary>
        public GameObject lastPress { get; private set; }

        /// <summary>
        /// The object that the press happened on even if it can not handle the press event.
        /// </summary>
        public GameObject rawPointerPress { get; set; }

        /// <summary>
        /// The object that is receiving 'OnDrag'.
        /// </summary>
        public GameObject pointerDrag { get; set; }

        /// <summary>
        /// RaycastResult associated with the current event.
        /// </summary>
        public RaycastResult pointerCurrentRaycast { get; set; }

        /// <summary>
        /// RaycastResult associated with the pointer press.
        /// </summary>
        public RaycastResult pointerPressRaycast { get; set; }

        public List<GameObject> hovered = new List<GameObject>();

        /// <summary>
        /// Is it possible to click this frame
        /// </summary>
        public bool eligibleForClick { get; set; }

        /// <summary>
        /// Id of the pointer (touch id).
        /// </summary>
        public int pointerId { get; set; }

        /// <summary>
        /// Current pointer position.
        /// </summary>
        public Vector2 position { get; set; }

        /// <summary>
        /// Pointer delta since last update.
        /// </summary>
        public Vector2 delta { get; set; }

        /// <summary>
        /// Position of the press.
        /// </summary>
        public Vector2 pressPosition { get; set; }

        /// <summary>
        /// World-space position where a ray cast into the screen hits something
        /// </summary>

        [Obsolete("Use either pointerCurrentRaycast.worldPosition or pointerPressRaycast.worldPosition")]
        public Vector3 worldPosition { get; set; }

        /// <summary>
        /// World-space normal where a ray cast into the screen hits something
        /// </summary>
        [Obsolete("Use either pointerCurrentRaycast.worldNormal or pointerPressRaycast.worldNormal")]
        public Vector3 worldNormal { get; set; }

        /// <summary>
        /// The last time a click event was sent. Used for double click
        /// </summary>
        public float clickTime { get; set; }

        /// <summary>
        /// Number of clicks in a row.
        /// </summary>
        /// <example>
        /// <code>
        /// using UnityEngine;
        /// using System.Collections;
        /// using UnityEngine.UI;
        /// using UnityEngine.EventSystems;// Required when using Event data.
        ///
        /// public class ExampleClass : MonoBehaviour, IPointerDownHandler
        /// {
        ///     public void OnPointerDown(PointerEventData eventData)
        ///     {
        ///         //Grab the number of consecutive clicks and assign it to an integer varible.
        ///         int i = eventData.clickCount;
        ///         //Display the click count.
        ///         Debug.Log(i);
        ///     }
        /// }
        /// </code>
        /// </example>
        public int clickCount { get; set; }

        /// <summary>
        /// The amount of scroll since the last update.
        /// </summary>
        public Vector2 scrollDelta { get; set; }

        /// <summary>
        /// Should a drag threshold be used?
        /// </summary>
        /// <remarks>
        /// If you do not want a drag threshold set this to false in IInitializePotentialDragHandler.OnInitializePotentialDrag.
        /// </remarks>
        public bool useDragThreshold { get; set; }

        /// <summary>
        /// Is a drag operation currently occuring.
        /// </summary>
        public bool dragging { get; set; }

        /// <summary>
        /// The EventSystems.PointerEventData.InputButton for this event.
        /// </summary>
        public InputButton button { get; set; }

        public PointerEventData(EventSystem eventSystem) : base(eventSystem)
        {
            eligibleForClick = false;

            pointerId = -1;
            position = Vector2.zero; // Current position of the mouse or touch event
            delta = Vector2.zero; // Delta since last update
            pressPosition = Vector2.zero; // Delta since the event started being tracked
            clickTime = 0.0f; // The last time a click event was sent out (used for double-clicks)
            clickCount = 0; // Number of clicks in a row. 2 for a double-click for example.

            scrollDelta = Vector2.zero;
            useDragThreshold = true;
            dragging = false;
            button = InputButton.Left;
        }

        /// <summary>
        /// Is the pointer moving.
        /// </summary>
        public bool IsPointerMoving()
        {
            return delta.sqrMagnitude > 0.0f;
        }

        /// <summary>
        /// Is scroll being used on the input device.
        /// </summary>
        public bool IsScrolling()
        {
            return scrollDelta.sqrMagnitude > 0.0f;
        }

        /// <summary>
        /// The camera associated with the last OnPointerEnter event.
        /// </summary>
        public Camera enterEventCamera
        {
            get { return pointerCurrentRaycast.module == null ? null : pointerCurrentRaycast.module.eventCamera; }
        }

        /// <summary>
        /// The camera associated with the last OnPointerPress event.
        /// </summary>
        public Camera pressEventCamera
        {
            get { return pointerPressRaycast.module == null ? null : pointerPressRaycast.module.eventCamera; }
        }

        /// <summary>
        /// The GameObject that received the OnPointerDown.
        /// </summary>
        public GameObject pointerPress
        {
            get { return m_PointerPress; }
            set
            {
                if (m_PointerPress == value)
                    return;

                lastPress = m_PointerPress;
                m_PointerPress = value;
            }
        }

        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendLine("<b>Position</b>: " + position);
            sb.AppendLine("<b>delta</b>: " + delta);
            sb.AppendLine("<b>eligibleForClick</b>: " + eligibleForClick);
            sb.AppendLine("<b>pointerEnter</b>: " + pointerEnter);
            sb.AppendLine("<b>pointerPress</b>: " + pointerPress);
            sb.AppendLine("<b>lastPointerPress</b>: " + lastPress);
            sb.AppendLine("<b>pointerDrag</b>: " + pointerDrag);
            sb.AppendLine("<b>Use Drag Threshold</b>: " + useDragThreshold);
            sb.AppendLine("<b>Current Raycast:</b>");
            sb.AppendLine(pointerCurrentRaycast.ToString());
            sb.AppendLine("<b>Press Raycast:</b>");
            sb.AppendLine(pointerPressRaycast.ToString());
            return sb.ToString();
        }
    }
}
private bool ProcessTouchEvents()
{
    for (int i = 0; i < input.touchCount; ++i)
    {
        Touch touch = input.GetTouch(i);

        if (touch.type == TouchType.Indirect)
            continue;

        bool released;
        bool pressed;
        var pointer = GetTouchPointerEventData(touch, out pressed, out released);

        ProcessTouchPress(pointer, pressed, released);

        if (!released)
        {
            ProcessMove(pointer);
            ProcessDrag(pointer);
        }
        else
            RemovePointerData(pointer);
    }
    return input.touchCount > 0;
}

RaycastAll

RaycastAll方法使用射线从相机到PointerEventData在UI上的位置,然后对检测到的所有对象按depth从小到大进行排序。

 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);
 }

https://img-blog.csdnimg.cn/20201201211433165.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Z6dGZ6dGZ6dA==,size_16,color_FFFFFF,t_70

调用BaseRaycaster的Raycast,以GraphicRaycaster为例: 首先将屏幕点转换为视窗坐标,然后创建一个射线Ray,再调用Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);获取射线结果:

  1. 获取canvas下的所有Graphics组件,没有采用遍历的方式,而是在 Graphic 的OnEnable和OnCanvasHierarchyChanged里调用GraphicRegistry.RegisterGraphicForCanvas(canvas, this)将自身和所在的canvas关联起来,事件保存好。该canvas是离graphic节点最近的一个。
  2. 遍历这些组件,调用组件的Raycast接口:graphic.Raycast(pointerPosition, eventCamera)
  3. 如果返回true就将其加入结果中

Graphic的Raycast最后调的是IsRaycastLocationValid接口,该接口可通过继承ICanvasRaycastFilter接口类重写。Image类重写如下:首先判断点是否在改对象内,然后计算对应的像素点的alpha和alphaHitTestMinimumThreshold大小。

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;
    }
}

Raycast

https://img-blog.csdnimg.cn/20201203180649701.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Z6dGZ6dGZ6dA==,size_16,color_FFFFFF,t_70&ynotemdtimestamp=1613957147093

BaseRaycaster

class BaseRaycaster : UIBehaviour

继承UIBehaviour,在OnEnable时将自身加入到RaycasterManager.AddRaycaster(this);在OnDisable时移除。重写了OnCanvasHierarchyChanged和OnTransformParentChanged,以更新m_RootRaycaster :

protected override void OnCanvasHierarchyChanged()
{
     base.OnCanvasHierarchyChanged();
     m_RootRaycaster = null;
 }

 protected override void OnTransformParentChanged()
 {
     base.OnTransformParentChanged();
     m_RootRaycaster = null;
 }

m_RootRaycaster :获取父节点的所有BaseRaycaster中的最后一个

public BaseRaycaster rootRaycaster
{
     get
     {
         if (m_RootRaycaster == null)
         {
             var baseRaycasters = GetComponentsInParent<BaseRaycaster>();
             if (baseRaycasters.Length != 0)
                 m_RootRaycaster = baseRaycasters[baseRaycasters.Length - 1];
         }

         return m_RootRaycaster;
     }
 }

GameObject.GetComponentsInParent 获取父对象组件列表

Component[] GetComponentsInParent(Type type, bool includeInactive = false);
public void GetComponentsInParent (bool includeInactive, List<T> results);
public T[] GetComponentsInParent ();
public T[] GetComponentsInParent (bool includeInactive);

type	要检索的组件的类型。
includeInactive	在发现的集内是否应包含非活动组件?

返回 GameObject 或其任何父项中类型为 type 的所有组件。 组件搜索是在父对象上以递归方式执行的,因此包含父项的父项,依此类推。

优先级,需要重载,默认最小:

/// <summary>/// Priority of the raycaster based upon sort order./// </summary>public virtual int sortOrderPriority
 {
     get { return int.MinValue; }
 }

 /// <summary>/// Priority of the raycaster based upon render order./// </summary>public virtual int renderOrderPriority
 {
     get { return int.MinValue; }
 }

GraphicRaycaster

    public class GraphicRaycaster : BaseRaycaster

定义了几种射线检测类型:

 public enum BlockingObjects
 {
     /// <summary>/// Perform no raycasts./// </summary>
     None = 0,
     /// <summary>/// Perform a 2D raycast check to check for blocking 2D elements/// </summary>
     TwoD = 1,
     /// <summary>/// Perform a 3D raycast check to check for blocking 3D elements/// </summary>
     ThreeD = 2,
     /// <summary>/// Perform a 2D and a 3D raycasts to check for blocking 2D and 3D elements./// </summary>
     All = 3,
 }

优先级

public override int sortOrderPriority
{
    get
    {
        // We need to return the sorting order here as distance will all be 0 for overlay.if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
            return canvas.sortingOrder;

        return base.sortOrderPriority;
    }
}
public override int renderOrderPriority
{
    get
    {
        // We need to return the sorting order here as distance will all be 0 for overlay.if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
            return canvas.rootCanvas.renderOrder;

        return base.renderOrderPriority;
    }
}

https://img-blog.csdnimg.cn/2020120318330077.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Z6dGZ6dGZ6dA==,size_16,color_FFFFFF,t_70&ynotemdtimestamp=1613957147093

sortingOrder 排序图层中的画布顺序。 renderOrder 将画布发射到场景的渲染顺序。(只读) 目前只有 Screen Space - Overlay 画布是正确排序的,因为 Screen Space - Camera 和 World Space 是根据到摄像机的距离发射和排序的。

添加链接描述

核心逻辑

  1. 获取身上Canvas组件关联的已注册Graphic set集合(Graphic组件会在onenble、ondisable、OnBeforeTransformParentChanged、OnTransformParentChanged、OnCanvasHierarchyChanged时调用GraphicRegistryd 接口,注册/反注册自身) var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
/// <summary>/// Get the list of associated graphics that are registered to a canvas./// </summary>/// <param name="canvas">The canvas whose Graphics we are looking for</param>/// <returns>The list of all Graphics for the given Canvas.</returns>public static IList<Graphic> GetGraphicsForCanvas(Canvas canvas)
{
    IndexedSet<Graphic> graphics;
    if (instance.m_Graphics.TryGetValue(canvas, out graphics))
        return graphics;

    return s_EmptyList;
}

  1. 获取事件摄像机,canvas.renderMode 等于 RenderMode.ScreenSpaceOverlay 时 事件摄像机为空,为RenderMode.ScreenSpaceCamera时,摄像机可以被指定,若没有被指定则为空;为RenderMode.WorldSpace时,摄像机为Camera.main (主摄像机)
  2. 从currentEventCamera身上获取targetDisplay(目标显示屏幕ID),若没有事件摄像机则从Canvas获取targetDisplay
  3. Display.RelativeMouseAt(eventData.position)将鼠标点击到的屏幕坐标点转为相对于鼠标的位置名为:eventPosition, 若相对位置不等于(0,0)则判断,它的z值和targetDisplay是否一致,若不一样则return出去。不进行GraphicRaycaster的Raycast方法了;(即代表确保用户点击的屏幕界面ID和摄像机/相关canvas的targetDisplayID要一样,点击的是摄像机/canvas能处理的屏幕界面才继续处理) 处理多个显示设备的
public static Vector3 RelativeMouseAt (Vector3 inputMouseCoordinates);
inputMouseCoordinates	作为坐标的鼠标输入位置。
RelativeMouseAt can be used to query relative mouse input coordinates and the screen in which Mouse Input is recorded. This is only valid on the Windows Desktop platforms with Multiple Displays. x, y returns the coordinates in relative space and z returns the screen in which Mouse Input is handled.
可用于查询相对鼠标输入坐标和记录鼠标输入的屏幕。这仅在具有多个显示的Windows桌面平台上有效。x, y返回相对空间中的坐标,z返回处理鼠标输入的屏幕。

  1. 如果没有摄像机(即canvas.renderMode == RenderMode.ScreenSpaceOverlay),根据屏幕宽高规范化坐标(持多屏幕的坐标转换),否则将eventPosition相对位置(屏幕坐标)转换到视角坐标,然后判断这个视角坐标是否在屏幕内(pos = currentEventCamera.ScreenToViewportPoint(eventPosition);),若在屏幕内继续进行方法,否则return出去 ScreenToViewportPoint,WorldToViewportPoint,ViewportToWorldPoint的运用,实现一个简单的对三维中物体的拖拽移动效果
  2. 创建一个空射线ray = new Ray();
  3. 如果有摄像机,则ray = currentEventCamera.ScreenPointToRay(eventPosition); 此方法的作用是可以从Camera的近视口nearClip向前发射一条射线到屏幕上的position点。参考点position用实际像素值的方式来决定Ray到屏幕的位置。参考点position的X轴分量或Y轴分量从0增长到最大值时,Ray从屏幕一边移动到另一边。当Ray未能碰撞到物体时hit.point返回值为Vector3(0,0,0)。参考点position的Z轴分量值无效。
  4. canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)时 8.1 当事件摄像机不为空时,计算出 distanceToClipPlane = Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection)摄像机远近界面距离/ray.direction.z ,当射线z非常接近0时Mathf.Approximately(0.0f, projectionDirection) (比较两个浮点数值,看它们是否非常接近),这个值为无穷大 8.2 根据 blockingObjects的类型,调用 ReflectionMethodsCache.Singleton的射线方法,反射调用Physics.RaycastAll方法 获取射线碰到的所有物体,然后获取最近的第一个物体的距离hitDIstance 9.Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);传入了canvas , 当前事件摄像机, 鼠标点击位置, 上面获取的canvas关联的Graphic图形列表, 一个空的RaycastResult列表 9.1 遍历canvasGraphics, 排除depth == -1(这样被认为是图形不可见), 排除raycastTarget为false的(无法进行被射线检测的被排除), 排除graphic.canvasRenderer.cull为true的(意味着被裁剪的会排除掉)。 调用RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera)方法, 方法会传入graphic.rectTransform(RectTransform),pointerPosition位置信息,事件摄像机,返回true代表位置信息处于这个图形的区域内,否则返回false,当返回false时会被排除掉。 调用事件摄像机的世界转屏幕坐标点方法,将图形的世界位置转屏幕坐标,判断其屏幕坐标Z值是否大于事件摄像机的远截面,若大于则排除掉。(表示图形所处的深度位置已经不在摄像机视角里了) 调用Graphic的Raycast方法,传入鼠标点击位置和摄像机,进行射线检测若检测到了返回true,否则返回false 如果检测到了,会将该graphic存入一个列表s_SortedGraphics 经过上面的遍历后拿到了一个射线检测到的图形列表(经过了一堆过滤和最后的检测)。 9.2 对这个列表进行排序,根据Graphic.depth深度值来进行倒序 9.3 将这个列表存入m_RaycastResults 返回给外部使用
  5. 将显示的、距离在[0,hitdistance]之间的graphic加入resultAppendList
    • 判断是否需要忽略背面图形检测,根据ignoreReversedGraphics参数
    • 若需要忽略,则判断摄像机是否为空,为空则表示面朝前面,用Vector3.forward作为摄像机朝向,将摄像机朝向与图形朝向进行点积运算,结果>0代表需要考虑封装入RaycastResult,否则不考虑封装。 若摄像机不为空则将摄像机真实朝向和图形朝向点积>0则封装 (点积操作>0表示图形正面朝向摄像机,反之是背面朝向)
 if (ignoreReversedGraphics)
 {
     if (currentEventCamera == null)
     {
         // If we dont have a camera we know that we should always be facing forwardvar dir = go.transform.rotation * Vector3.forward;
         appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
     }
     else
     {
         // If we have a camera compare the direction against the cameras forward.var cameraFoward = currentEventCamera.transform.rotation * Vector3.forward;
         var dir = go.transform.rotation * Vector3.forward;
         appendGraphic = Vector3.Dot(cameraFoward, dir) > 0;
     }
 }

优化

https://blog.csdn.net/cyf649669121/article/details/83661023

PhysicsRaycaster

/// <summary>/// Returns a ray going from camera through the event position and the distance between the near and far clipping planes along that ray./// </summary>/// <param name="eventData">The pointer event for which we will cast a ray.</param>/// <param name="ray">The ray to use.</param>/// <param name="distanceToClipPlane">The distance between the near and far clipping planes along the ray.</param>/// <returns>True if the operation was successful. false if it was not possible to compute, such as the eventPosition being outside of the view.</returns>protected bool ComputeRayAndDistance(PointerEventData eventData, ref Ray ray, ref float distanceToClipPlane)
	... 
 ray = eventCamera.ScreenPointToRay(eventPosition);
 // compensate far plane distance - see MouseEvents.csfloat projectionDirection = ray.direction.z;
 distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection)
     ? Mathf.Infinity
     : Mathf.Abs((eventCamera.farClipPlane - eventCamera.nearClipPlane) / projectionDirection);
 return true;
 }

  1. 和GraphicRaycaster一样的多屏幕坐标处理
  2. 判断点是否在viewRect内
  3. 从摄像机向鼠标点发送一条射线,获取射线和射线到碰撞点的距离
  4. 接下来和GraphicRaycaster一样,调用singleton接口获取碰撞信息,组织结果

Physics2DRaycaster

仅maxRayIntersections 为0时调用的射线方法不同

if (maxRayIntersections == 0)
            {
                if (ReflectionMethodsCache.Singleton.getRayIntersectionAll == null)
                    return;

                m_Hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, finalEventMask);
                hitCount = m_Hits.Length;
            }

猜你喜欢

转载自blog.csdn.net/fztfztfzt/article/details/113996864