UIGU源码分析18 :射线

源码18 :射线

本来是想把UGUI的各个组件分析完 再看其射线相关的实现,但是除了之前EventSystem有RaycastAll,Graphic里也有很多射线相关的函数,感觉有必要先分析分析。

RaycasterManager

 internal static class RaycasterManager
    {
        private static readonly List<BaseRaycaster> s_Raycasters = new List<BaseRaycaster>();

        public static void AddRaycaster(BaseRaycaster baseRaycaster)
        {
            if (s_Raycasters.Contains(baseRaycaster))
                return;

            s_Raycasters.Add(baseRaycaster);
        }

        public static List<BaseRaycaster> GetRaycasters()
        {
            return s_Raycasters;
        }

        public static void RemoveRaycasters(BaseRaycaster baseRaycaster)
        {
            if (!s_Raycasters.Contains(baseRaycaster))
                return;
            s_Raycasters.Remove(baseRaycaster);
        }
    }

顾名思义 RaycasterManager 是一个 Raycaster管理器,负责缓存所有的BaseRaycaster

BaseRaycaster

    /// <summary>
    /// Base class for any RayCaster.
    /// </summary>
    /// <remarks>
    /// A Raycaster is responsible for raycasting against scene elements to determine if the cursor is over them. Default Raycasters include PhysicsRaycaster, Physics2DRaycaster, GraphicRaycaster.
    /// Custom raycasters can be added by extending this class.
    /// </remarks>
public abstract class BaseRaycaster : UIBehaviour
{
	...
}

看上面注释:射线投射器是负责对场景元素进行投射,从而来确定鼠标是否在他们上面,默认的投射器包括:

PhysicsRaycaster Physics2DRaycaster GraphicRaycaster

可以通过扩展此类来添加自定义光线投射器。

BaseRaycaster 是个抽象类 在扩展射线投射器的时候 有几个函数和属性必须实现:

       /// <summary>
        /// Raycast against the scene.
        /// </summary>
        /// <param name="eventData">Current event data.</param>
        /// <param name="resultAppendList">List of hit Objects.</param>
        public abstract void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList);
        
                /// <summary>
        /// The camera that will generate rays for this raycaster.
        /// </summary>
        public abstract Camera eventCamera { get; }

Raycast 是BaseRaycaster基础方法,传入一个PointerEventData,Raycast将会把接收到射线检测的物体添加到resultAppendList上。

eventCamera 是用来生成射线的摄像机。

其他一些 属性/函数:

  • rootRaycaster Hierarchy 中最上层的Raycaster

  • OnEnable 和OnDisable 在组件激活和关闭时 添加/移除 从RaycasterManager中


PhysicsRaycaster

故名思意:PhysicsRaycaster 是负责3d物理组件相关的射线检测

    /// <summary>
    /// Simple event system using physics raycasts.
    /// </summary>
    [AddComponentMenu("Event/Physics Raycaster")]
    [RequireComponent(typeof(Camera))]
    /// <summary>
    /// Raycaster for casting against 3D Physics components.
    /// </summary>
    public class PhysicsRaycaster : BaseRaycaster
    {
        /// <summary>
        /// Const to use for clarity when no event mask is set
        /// </summary>
        protected const int kNoEventMaskSet = -1;

        protected Camera m_EventCamera;

        /// <summary>
        /// Layer mask used to filter events. Always combined with the camera's culling mask if a camera is used.
        /// </summary>
        [SerializeField]
        protected LayerMask m_EventMask = kNoEventMaskSet;

        /// <summary>
        /// The max number of intersections allowed. 0 = allocating version anything else is non alloc.
        /// </summary>
        [SerializeField]
        protected int m_MaxRayIntersections = 0;
        protected int m_LastMaxRayIntersections = 0;
		
		#if PACKAGE_PHYSICS
        RaycastHit[] m_Hits;
		#endif
		...
}
       public override Camera eventCamera
        {
            get
            {
                if (m_EventCamera == null)
                    m_EventCamera = GetComponent<Camera>();
                return m_EventCamera ?? Camera.main;
            }
        }

实现了BaseRaycaster 中的eventCamera属性 这里很简单 就是获取对象上的Camera组件,没有的话就直接取主相机

    public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
        {
#if PACKAGE_PHYSICS
            Ray ray = new Ray();
            int displayIndex = 0;
            float distanceToClipPlane = 0;
            if (!ComputeRayAndDistance(eventData, ref ray, ref displayIndex, ref distanceToClipPlane))
                return;

            int hitCount = 0;

            if (m_MaxRayIntersections == 0)
            {
                if (ReflectionMethodsCache.Singleton.raycast3DAll == null)
                    return;

                m_Hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, finalEventMask);
                hitCount = m_Hits.Length;
            }
            else
            {
                if (ReflectionMethodsCache.Singleton.getRaycastNonAlloc == null)
                    return;
                if (m_LastMaxRayIntersections != m_MaxRayIntersections)
                {
                    m_Hits = new RaycastHit[m_MaxRayIntersections];
                    m_LastMaxRayIntersections = m_MaxRayIntersections;
                }

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

            if (hitCount != 0)
            {
                if (hitCount > 1)
                    System.Array.Sort(m_Hits, 0, hitCount, RaycastHitComparer.instance);

                for (int b = 0, bmax = hitCount; b < bmax; ++b)
                {
                    var result = new RaycastResult
                    {
                        gameObject = m_Hits[b].collider.gameObject,
                        module = this,
                        distance = m_Hits[b].distance,
                        worldPosition = m_Hits[b].point,
                        worldNormal = m_Hits[b].normal,
                        screenPosition = eventData.position,
                        displayIndex = displayIndex,
                        index = resultAppendList.Count,
                        sortingLayer = 0,
                        sortingOrder = 0
                    };
                    resultAppendList.Add(result);
                }
            }
#endif
        }

这里重写了BaseRaycaster的Raycast抽象方法,实际上是通过ReflectionMethodsCache类反射Physics模块对应的API来获取射线照射的3D对象,然后包装成RaycastResult 加入到resultAppendList中。 个人感觉这部分代码作为UGUI的源码这么写的话是为了降低UI和Physics模块的耦合。

Physics2DRaycaster和PhysicsRaycaster 类似 这里就不在详细描述了。


GraphicRaycaster

    [AddComponentMenu("Event/Graphic Raycaster")]
    [RequireComponent(typeof(Canvas))]
    /// <summary>
    /// A derived BaseRaycaster to raycast against Graphic elements.
    /// </summary>
    public class GraphicRaycaster : BaseRaycaster
    {
    	...
    }

GraphicRaycaster 使用的时候是需要和Canvas组件进行绑定

除了重写了BaseRaycaster里的属性和方法外 ,额外定义了一些和UI相关的字段

  • ignoreReversedGraphics 是否忽略正反翻转的Graphics 默认是忽略。

  • 重写eventCamera 获取指定相机UI为 ScreenSpaceOverlay模式或者ScreenSpaceCamera时没有指定相机就直接返回NUll 其他就直接返回UICamera。

  • 重写sortOrderPriority 获取Canvas的sortingOrder

  • renderOrderPriority 获取Canvas的renderOrder

最终就是重写的Raycast 方法。


我们前面分析过输入模块的输入过程:

    protected PointerEventData GetTouchPointerEventData(Touch input, out bool pressed, out bool released)
    {
        PointerEventData pointerData;
        var created = GetPointerData(input.fingerId, out pointerData, true);

        pointerData.Reset();

        pressed = created || (input.phase == TouchPhase.Began);
        released = (input.phase == TouchPhase.Canceled) || (input.phase == TouchPhase.Ended);

        if (created)
            pointerData.position = input.position;

        if (pressed)
            pointerData.delta = Vector2.zero;
        else
            pointerData.delta = input.position - pointerData.position;

        pointerData.position = input.position;

        pointerData.button = PointerEventData.InputButton.Left;

        if (input.phase == TouchPhase.Canceled)
        {
            pointerData.pointerCurrentRaycast = new RaycastResult();
        }
        else
        {
            eventSystem.RaycastAll(pointerData, m_RaycastResultCache);

            var raycast = FindFirstRaycast(m_RaycastResultCache);
            pointerData.pointerCurrentRaycast = raycast;
            m_RaycastResultCache.Clear();
        }
        return pointerData;
    }

在获取PointerData数据后 会调用RaycastAll方法取得射线结果的缓存。并从中取出第一个有效的缓存结果,赋值给PointData。

   /// <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();
        var modulesCount = modules.Count;
        for (int i = 0; i < modulesCount; ++i)
        {
            var module = modules[i];
            if (module == null || !module.IsActive())
                continue;

            module.Raycast(eventData, raycastResults);
        }

        raycastResults.Sort(s_RaycastComparer);
    }

raycastAll会让所有BaseRaycaster全部进行射线发射,并缓存到raycastResults,最后进行排序。


GraphicRaycaster.Raycaster

GraphicRaycaster通常是和Canvas绑定在一起的。

(D:\UnityProjectSpace\BlogRecord\UGUI源码分析\Image\image-20220416233206344-16501231335821.png)]

当射线检测的时候,GraphicRaycaster发射出射线,会对当前Canvas下的所有Graphic元素进行检测。取得m_RaycastResults后会再一次进行筛选,勾选了ignoreReversedGraphics的Graphic会被剔除掉。最后取得的结果返回给PointerEventData。

代码比较长 我就不贴了

猜你喜欢

转载自blog.csdn.net/NippyLi/article/details/124223219