前言

对于游戏来说射线检测是区别于物理检测来说另一个重要的场景对象交互方式

概念

直接上unity源码

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
public struct Ray
{
private Vector3 m_Origin;
private Vector3 m_Direction;

public Ray(Vector3 origin, Vector3 direction)
{
this.m_Origin = origin;
this.m_Direction = direction.normalized;
}

public Vector3 origin
{
get => this.m_Origin;
set => this.m_Origin = value;
}

public Vector3 direction
{
get => this.m_Direction;
set => this.m_Direction = value.normalized;
}

public Vector3 GetPoint(float distance) => this.m_Origin + this.m_Direction * distance;

}

这段代码很简单,只包含两个属性,m_Origin 代表原点,m_Direction代表方向,且是归一化的(归一化就是将数据统一映射到[0,1]区间上)。所以射线就是有原点有方向的一条线。

作用

射线实际上是:直线上的一点和它一旁的部分所组成的图形。它由一个起始点开始,向着一个方向放出无限长的线。当线与我们想要检测的物体重合就会发出信号。

可以利用3D世界中的射线,和3D世界中的物体进行碰撞(相交)判断,利用它可以实现射击

Plane是什么

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
public partial struct Plane
{
// sizeof(Plane) is not const in C# and so cannot be used in fixed arrays, so we define it here
internal const int size = 16;

Vector3 m_Normal;
float m_Distance;

// Normal vector of the plane.
public Vector3 normal
{
get { return m_Normal; }
set { m_Normal = value; }
}
// Distance from the origin to the plane.
public float distance
{
get { return m_Distance; }
set { m_Distance = value; }
}

// Creates a plane.
public Plane(Vector3 inNormal, Vector3 inPoint)
{
m_Normal = Vector3.Normalize(inNormal);
m_Distance = -Vector3.Dot(m_Normal, inPoint);
}

// Creates a plane.
public Plane(Vector3 inNormal, float d)
{
m_Normal = Vector3.Normalize(inNormal);
m_Distance = d;
}

// Creates a plane.
public Plane(Vector3 a, Vector3 b, Vector3 c)
{
m_Normal = Vector3.Normalize(Vector3.Cross(b - a, c - a));
m_Distance = -Vector3.Dot(m_Normal, a);
}

// Sets a plane using a point that lies within it plus a normal to orient it (note that the normal must be a normalized vector).
public void SetNormalAndPosition(Vector3 inNormal, Vector3 inPoint)
{
m_Normal = Vector3.Normalize(inNormal);
m_Distance = -Vector3.Dot(inNormal, inPoint);
}

// Sets a plane using three points that lie within it. The points go around clockwise as you look down on the top surface of the plane.
public void Set3Points(Vector3 a, Vector3 b, Vector3 c)
{
m_Normal = Vector3.Normalize(Vector3.Cross(b - a, c - a));
m_Distance = -Vector3.Dot(m_Normal, a);
}


// Translates the plane into a given direction
public void Translate(Vector3 translation) { m_Distance += Vector3.Dot(m_Normal, translation); }

// Creates a plane that's translated into a given direction
public static Plane Translate(Plane plane, Vector3 translation) { return new Plane(plane.m_Normal, plane.m_Distance += Vector3.Dot(plane.m_Normal, translation)); }

// Calculates the closest point on the plane.
public Vector3 ClosestPointOnPlane(Vector3 point)
{
var pointToPlaneDistance = Vector3.Dot(m_Normal, point) + m_Distance;
return point - (m_Normal * pointToPlaneDistance);
}

// Returns a signed distance from plane to point.
public float GetDistanceToPoint(Vector3 point) { return Vector3.Dot(m_Normal, point) + m_Distance; }

// Is a point on the positive side of the plane?
public bool GetSide(Vector3 point) { return Vector3.Dot(m_Normal, point) + m_Distance > 0.0F; }

// Are two points on the same side of the plane?
public bool SameSide(Vector3 inPt0, Vector3 inPt1)
{
float d0 = GetDistanceToPoint(inPt0);
float d1 = GetDistanceToPoint(inPt1);
return (d0 > 0.0f && d1 > 0.0f) ||
(d0 <= 0.0f && d1 <= 0.0f);
}

// Intersects a ray with the plane.
public bool Raycast(Ray ray, out float enter)
{
float vdot = Vector3.Dot(ray.direction, m_Normal);
float ndot = -Vector3.Dot(ray.origin, m_Normal) - m_Distance;

if (Mathf.Approximately(vdot, 0.0f))
{
enter = 0.0F;
return false;
}

enter = ndot / vdot;

return enter > 0.0F;
}

}

我们发现这个类很简单,就包含两个属性 m_Normal 法线和 m_Distance 距离(表示从原点到这个平面的距离),那有了这两个属性为什么就可以获取到该平面上的某个点呢?

我们着重分析下面这个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public bool Raycast(Ray ray, out float enter)
{
float vdot = Vector3.Dot(ray.direction, m_Normal);
float ndot = -Vector3.Dot(ray.origin, m_Normal) - m_Distance;

if (Mathf.Approximately(vdot, 0.0f))
{
enter = 0.0F;
return false;
}

enter = ndot / vdot;

return enter > 0.0F;
}

这个接口是求射线和平面的相交的。

这里面用到了点乘。

通过注释相信你已经理解了这个接口,通过L 代入到 ray.GetPoint(L);就可以求得我们点击到平面上某个点的世界坐标。

以上就是射线的用法,其中有很多数学和几何方面的知识,如果对这方面有所理解的话,那么理解射线检测就没什么难的了。

API相关调用使用

普通射线检测

(一般用于检测某一个物体)

1
2
3
4
5
6
7
8
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin ,ray.direction , Color.red);
RaycastHit hit;

if(Physics .Raycast (ray,out hit,int.MaxValue,1<<LayerMask .NameToLayer ("layername")))
{
Debug.Log("检测到物体");
}

直线射线检测多个物体

1
2
3
4
5
6
7
8
9
10
11
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin ,ray.direction , Color.red);
RaycastHit[] hit = Physics.RaycastAll(ray, Mathf.Infinity, 1 << LayerMask.NameToLayer("layername"));

if(hit .Length >0)
{
for (int i = 0; i < hit.Length ; i++)
{
Debug.Log("检测到物体"+hit[i].collider.name );
}
}

球形射线检测

(一般用于检测周边物体)

1
2
3
4
5
6
7
8
9
10
11
12
int radius = 3;
Collider[] cols = Physics.OverlapSphere(this.transform.position, radius, LayerMask.NameToLayer("layername"));

if(cols.Length >0)
{
for (int i = 0; i < cols.Length; i++)
{

Debug.Log("检测到物体" + cols[i].name);

}
}

画出球形检测范围方法,可用

1
2
3
4
private void OnDrawGizmos()
{
Gizmos.DrawWireSphere(this.transform.position, 3);
}

Unity 常用射线检测方法_unity射线检测-CSDN博客

简析Unity射线检测的概念与应用 - 知乎 (zhihu.com)

Unity 射线检测的原理分析 - 知乎 (zhihu.com)

【唐老狮】Unity——物理系统(射线检测) - 知乎 (zhihu.com)