源码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绑定在一起的。
当射线检测的时候,GraphicRaycaster发射出射线,会对当前Canvas下的所有Graphic元素进行检测。取得m_RaycastResults后会再一次进行筛选,勾选了ignoreReversedGraphics的Graphic会被剔除掉。最后取得的结果返回给PointerEventData。
代码比较长 我就不贴了