0%

UGUI_Raycaster

此文来分析以下Raycaster。虽然Unity的Raycaster等一些组件跟ui放在一起,但是很多属于事件系统。

在事件系统中,Raycaster用来获取获取当前交互位置(如鼠标位置)对应的游戏物体,其使用位置在EventSystem中的RaycastAll方法。而RaycasterAll方法却是InputModule调用的。

基本流程

所有的Caycaster都继承子BaseRayster,启动时自动加入到RaycasterManager中,然后由EventSystem调用。目前存在PhysicRaycaster和Physics2DRaycaster以及GraphicRaycaster,分别为3D、2D和ui检测的Raycaster。2d和3D的Raycaster基本相同,差异在于检测时调用raycast,检测的对象不同。基本流程如下所示:

PhysicRaycaster

PhysicRaycater和Physic2DRaycaster两者均为“三维实体”,有别于UI元素。流程如下:
1)生成射线检测的ray以及计算在camera裁剪平面内的射线距离,以此来舍弃camera空间外的物体。
2)其次进行射线检测,但是射线检测时与我们常规使用Physics.Raycast方法不同,采用的是反射的方法。此部分涉及到变量m_MaxRayIntersections,此变量表示是否由射线检测的结果的数量限制。正常使用都是只返回一个结果,但是此处是返回多个结果,然后进行深度判断。
3)根据深度对结果进行排序,然后将结果添加到RaycasterResult列表中。

GraphicRaycaster

GraphicRaycaster用来进行ui检测,虽然也使用到了射线,但其当前ui并不是通过射线检测出来的,射线检测只是为了进行2D和3D物体的遮挡距离计算用的。流程如下:
1)获取canvas对应的Graphic,因为UI图像显示的核心是Graphic类,而Graphic类都注册在了CanvasRegistry中,所以通过CanvasRegistry获取所有的Canvas。
2)通过EventData的屏幕坐标,处理多屏幕的问题,在移动端是不存在此问题的。
3)通过射线检测,找到距离camera最近的2d或者3d物体,并计算距离,用来判断ui是否被遮挡。
4)通过判断点击位置是否在ui的rect范围内来确定那个ui是点击ui,并通过计算三角形法则计算点击距离。然后用3)中计算的距离计算遮挡。如果canvas是overlay,则其始终在最前方,所以不存在遮挡问题。
5)生成RaycastReuslt列表。

射线检测方法

此处进行对使用反射进行射线检测的方法进行说明,代码中通过注释说明是为了避免模块之间的强引用关系(即不需要引入相关模块的使用),所以通过反射方法来获取。也正因为如此,可以直接拷贝出来使用。
代码如下:

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
using System;
using System.Collections.Generic;
using System.Reflection;

namespace UnityEngine.UI
{
internal class ReflectionMethodsCache
{
public delegate bool Raycast3DCallback(Ray r, out RaycastHit hit, float f, int i);
public delegate RaycastHit2D Raycast2DCallback(Vector2 p1, Vector2 p2, float f, int i);
public delegate RaycastHit[] RaycastAllCallback(Ray r, float f, int i);
public delegate RaycastHit2D[] GetRayIntersectionAllCallback(Ray r, float f, int i);
public delegate int GetRayIntersectionAllNonAllocCallback(Ray r, RaycastHit2D[] results, float f, int i);
public delegate int GetRaycastNonAllocCallback(Ray r, RaycastHit[] results, float f, int i);

// We call Physics.Raycast and Physics2D.Raycast through reflection to avoid creating a hard dependency from
// this class to the Physics/Physics2D modules, which would otherwise make it impossible to make content with UI
// without force-including both modules.
public ReflectionMethodsCache()
{
var raycast3DMethodInfo = typeof(Physics).GetMethod("Raycast", new[] {typeof(Ray), typeof(RaycastHit).MakeByRefType(), typeof(float), typeof(int)});
if (raycast3DMethodInfo != null)
raycast3D = (Raycast3DCallback)UnityEngineInternal.ScriptingUtils.CreateDelegate(typeof(Raycast3DCallback), raycast3DMethodInfo);

var raycast2DMethodInfo = typeof(Physics2D).GetMethod("Raycast", new[] {typeof(Vector2), typeof(Vector2), typeof(float), typeof(int)});
if (raycast2DMethodInfo != null)
raycast2D = (Raycast2DCallback)UnityEngineInternal.ScriptingUtils.CreateDelegate(typeof(Raycast2DCallback), raycast2DMethodInfo);

var raycastAllMethodInfo = typeof(Physics).GetMethod("RaycastAll", new[] {typeof(Ray), typeof(float), typeof(int)});
if (raycastAllMethodInfo != null)
raycast3DAll = (RaycastAllCallback)UnityEngineInternal.ScriptingUtils.CreateDelegate(typeof(RaycastAllCallback), raycastAllMethodInfo);

var getRayIntersectionAllMethodInfo = typeof(Physics2D).GetMethod("GetRayIntersectionAll", new[] {typeof(Ray), typeof(float), typeof(int)});
if (getRayIntersectionAllMethodInfo != null)
getRayIntersectionAll = (GetRayIntersectionAllCallback)UnityEngineInternal.ScriptingUtils.CreateDelegate(typeof(GetRayIntersectionAllCallback), getRayIntersectionAllMethodInfo);

var getRayIntersectionAllNonAllocMethodInfo = typeof(Physics2D).GetMethod("GetRayIntersectionNonAlloc", new[] { typeof(Ray), typeof(RaycastHit2D[]), typeof(float), typeof(int) });
if (getRayIntersectionAllNonAllocMethodInfo != null)
getRayIntersectionAllNonAlloc = (GetRayIntersectionAllNonAllocCallback)UnityEngineInternal.ScriptingUtils.CreateDelegate(typeof(GetRayIntersectionAllNonAllocCallback), getRayIntersectionAllNonAllocMethodInfo);

var getRaycastAllNonAllocMethodInfo = typeof(Physics).GetMethod("RaycastNonAlloc", new[] { typeof(Ray), typeof(RaycastHit[]), typeof(float), typeof(int) });
if (getRaycastAllNonAllocMethodInfo != null)
getRaycastNonAlloc = (GetRaycastNonAllocCallback)UnityEngineInternal.ScriptingUtils.CreateDelegate(typeof(GetRaycastNonAllocCallback), getRaycastAllNonAllocMethodInfo);
}

public Raycast3DCallback raycast3D = null;
public RaycastAllCallback raycast3DAll = null;
public Raycast2DCallback raycast2D = null;
public GetRayIntersectionAllCallback getRayIntersectionAll = null;
public GetRayIntersectionAllNonAllocCallback getRayIntersectionAllNonAlloc = null;
public GetRaycastNonAllocCallback getRaycastNonAlloc = null;

private static ReflectionMethodsCache s_ReflectionMethodsCache = null;

public static ReflectionMethodsCache Singleton
{
get
{
if (s_ReflectionMethodsCache == null)
s_ReflectionMethodsCache = new ReflectionMethodsCache();
return s_ReflectionMethodsCache;
}
}
};

}

使用时直接调用如下六个回调即可:

1
2
3
4
5
6
7
8
9
10
11
12
//获取射线检测到的第一个3D物体
public Raycast3DCallback raycast3D = null;
//返回检测到的所有3D物体
public RaycastAllCallback raycast3DAll = null;
//获取射线检测到的第一个2D物体
public Raycast2DCallback raycast2D = null;
//返回检测到的所有2D游戏物体
public GetRayIntersectionAllCallback getRayIntersectionAll = null;
//返回检测到的所需2D游戏物体,即返回前n个结果,n可以自己定义
public GetRayIntersectionAllNonAllocCallback getRayIntersectionAllNonAlloc = null;
//返回检测到的所需3D游戏物体,即返回前n个结果,n可以自己定义
public GetRaycastNonAllocCallback getRaycastNonAlloc = null;