Graphic Raycaster

在UGUI中最主要的射线发生组件。其源码中最主要的方法就是Raycast

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
namespace UnityEngine.UI
{
[AddComponentMenu("Event/Graphic Raycaster")]
[RequireComponent(typeof(Canvas))]
/// <summary>
/// A derived BaseRaycaster to raycast against Graphic elements.
/// </summary>
public class GraphicRaycaster : BaseRaycaster
{
/// <summary>
/// Perform the raycast against the list of graphics associated with the Canvas.
/// </summary>
/// <param name="eventData">Current event data</param>
/// <param name="resultAppendList">List of hit objects to append new results to.</param>
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
//...
var canvasGraphics = GraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);//获取当前Canvas下所有Graphic子组件
//...
var currentEventCamera = eventCamera; // 确认事件相机,默认是主相机
//获取当前的显示器
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
displayIndex = canvas.targetDisplay;
else
displayIndex = currentEventCamera.targetDisplay;
//...
//下面是根据显示器获取分辨率的逻辑,将主显示映射到pos这个变量里(),pos是介于0~1的。如果是在副显示器操作就直接返回
Vector2 pos;
if (currentEventCamera == null)
{
//...
}
//...
//根据Graphic Raycast中设置的BlockingObjects和renderMode获取hitDistance,即射线击中的距离
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
{
#if PACKAGE_PHYSICS
//...
hitDistance = hits[0].distance;
#endif
#if PACKAGE_PHYSICS2D
hitDistance = hits[0].distance;
#endif
}
//...
Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);//这是一个静态重载,Raycast筛选的主要逻辑
//根据上面的筛选,得到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)//筛选是否一个反面的元素,交给appendGraphic
{
//...
}
if (appendGraphic)//如果是一个正面的元素,筛选是否在相机的背面
{
//...
if (distance >= hitDistance)//这里通过一个公式计算出了当前元素和摄像机的距离distance,它要小于hitDistance(点击击中的最远距离)才行
continue;
var castResult = new RaycastResult
{
//...
}
resultAppendList.Add(castResult);//最终的结果都要加入结果的list中
}
}
}

//...
/// <summary>
/// Perform a raycast into the screen and collect all graphics underneath it.
/// </summary>
[NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
//这个方法主要对Graphic的状态进行筛选
private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
{
// Necessary for the event system
int totalCount = foundGraphics.Count;
for (int i = 0; i < totalCount; ++i)
{
Graphic graphic = foundGraphics[i];

// graphic的深度是-1就表示当前UI元素根本不会绘制在屏幕上
//如果UI元素没有打开raycastTarget,也被过滤掉
if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)
continue;
//判断当前点击事件是否在目标物体的Rect里面
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera, graphic.raycastPadding))
continue;
//当前点击事件的z坐标是否超出了相机的最远面
if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
continue;
//使用Grapic.Raycast判断,主要是判断当前组件是否继承ICanvasRaycastFilter接口,具体可见源码
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();
}
}