【Unity】射线检测

2D射线检测

Collider2D无法被3D射线检测,必须使用Physics2D

射线检测不是特别方便Debug,无法看到射线的范围,则可以使用Collider协助:

  1. 创建一个BoxCollider2D
  2. 勾选IsTrigger,这样就不会有碰撞了
  3. 设置Layer层级
1
2
3
4
5
6
7
8
9
private void ButtressCheck(BoxCollider2D coll)
{
IsButtressColliderHit = Physics2D.BoxCast(coll.bounds.center, // coll的中心世界坐标
coll.bounds.size, // coll大小
0f, // 不需要旋转
Vector2.down, // 因为不需要距离,这个可以随便写
0f, // 不需要距离
GroundLayer); // 层级
}

如此一来,这个Box检测就与coll的位置、大小一摸一样,scene中coll的绿色范围就代表着BoxCase的位置和大小

创建并使用射线

射线检测:从某个初始点开始,沿着特定的方向发射一条不可见且无限长的射线,通过此射线检测是否有任何模型添加了Collider碰撞器组件。一旦检测到碰撞,停止射线继续发射。

Collider组件中Is Trigger选项的开关并不影响射线检测
! 对了还有一个参数,写在Raycast末尾,QueryTriggerInteraction(指定该射线是否应该命中触发器),上面我说过Is Trigger选项的开关不影响射线检测,但是前提是QueryTriggerInteraction该参数设置为检测触发器了,你也可以将该参数设置为仅对碰撞器进行检测,这个参数可以全局设置。

举两个常用的例子

  1. 根据物体的指向确定射线的方向Ray ray = new Ray(transform.position, transform.forward);
  2. 根据鼠标的位置确定射线的方向Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[SerializeField] private float eventDistance = 3f;
[SerializeField] private LayerMask mask;
Ray ray;
RaycastHit hit;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 该射线的起点为this物体的position,方向为this物体的正前方(蓝色箭头)
ray = new Ray(transform.position, transform.forward);
// ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, eventDistance, mask))
{
Destroy(hit.transform.gameObject);
}
}
}

显示射线

Uniyt中通过使用Debug.DrawLine()Debug.DrawRay()都可以让射线现出原形。
但是!需要特别注意的是,这里画出的线其实跟射线毫无关联的,因为就算没有射线,这里也能画出线来。两点一线,只要确定两个点就行了。所以这里的线只是辅助开发者而已。

但是!又需要特别注意的是,如果你的射线不显示的话,估计是因为!我就是刚刚不小心把它关了,然后挠头找不到原因,我已经犯了好几次这样的错误了!!!!!这个按钮主要是用于显示和关闭场景中的辅助图形之类的(如灯光,射线、摄像机等)!

两种方法的区别

  • DrawLine:真正的两点确定一条线
  • DrawRay:从初始点出发画一条线,所以需要一个初始点,加上一个具有方向和长度的向量,就得到一条射线

一般使用DrawRay,比较贴合射线的性质。而DrawRay也有两种常用方法

1
2
3
4
// 直接了当的设置射线的起点和方向
Debug.DrawRay(transform.position, transform.position + transform.forward * 10, Color.yellow);
// 通过Ray的属性设置射线的起点和方向
Debug.DrawRay(ray.origin, ray.direction * 10000f, Color.yellow);

实际运用

筛选能击中的物体

可以通过物体的Layer控制射线需要击中的物体

如果有两种物体A,B,则可以将其的Layer设置为对应的LayerA和LayerB。

射线只想要击中A,而不受B的影响,可以将LayerMask设置为LayerA

1
2
3
4
5
6
7
[SerializeField] private float eventDistance = 3f;
[SerializeField] private LayerMask mask;
ray = new Ray(transform.position, transform.forward);
if (Physics.Raycast(ray, out hit, eventDistance, mask))
{
Destroy(hit.transform.gameObject);
}

让射线穿透

如果有两个A在一条线上,发出射线时永远只会返回离玩家最近的那个A,如何返回后面的A呢?

可以将前面的A的Layer设置为成为LayerB,这样射线就不会返回前面的A了。

1
hit.collider.gameObject.layer = 10;		// 这里数字10代表的是Layer的第10层

让射线检测多个

通过上面的学习我们知道可以通过RaycastHit结构体获得检测到的碰撞体,但似乎每次只能返回一个,如何一次返回在该条射线上所有符合标准的物体呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[SerializeField] private float eventDistance = 3f;
[SerializeField] private LayerMask mask;
Ray ray;
// 初始化一个列表
RaycastHit[] hits;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
ray = new Ray(transform.position, transform.forward);
Debug.DrawRay(ray.origin, ray.direction * eventDistance, Color.yellow);
// 将符合条件的物体添加到hits列表中
hits = Physics.RaycastAll(ray, eventDistance, mask);
foreach (var item in hits)
{
Destroy(item.collider.gameObject);
}
}
}

拓展

LayerMask的介绍

LayerMask 实际上是一个位码操作,在Unity3D中一共有32个Layer层,并且不可增加。

位运算符

按位运算符:~、|、&、^。位运算符主要用来对二进制位进行操作。

逻辑运算符:&&、||、!。逻辑运算符把语句连接成更复杂的复杂语句。

按位运算符:左移运算符<<,左移表示乘以2,左移多少位表示乘以2的几次幂。

例如:var temp = 14 << 2; 表示十进制数14转化为二进制后向左移动2位。

temp最后计算的值为 14乘以2的平方,temp = 56;

同理,右移运算符>>,移动多少位表示除以2的几次幂。

LayerMask的使用

1
2
3
4
5
6
7
8
9
10
11
LayerMask mask = 1 << 3// 表示开启Layer3。

LayerMask mask = 0 << 8// 表示关闭Layer8。

LayerMask mask = 1<<1|1<<9// 表示开启Layer1和Layer9。

LayerMask mask = 0<<4|0<<5// 表示关闭Layer4和Layer5。

LayerMask mask = ~(1 << 0); // 打开所有的层。

LayerMask mask = ~(1 << 9); // 打开除了第9之外的层。
1
2
3
LayerMask mask = ~(1<<2|1<<8);		// 表示关闭Layer2和Layer8。

LayerMask mask = 1<<3|0<<5; // 表示开启Layer3并且同时关闭Layer5。
1
2
3
LayerMask mask  = 1 << LayerMask.NameToLayer(“TestLayer”);		// 表示开启层名“TestLayer” 的层 。

LayerMask mask = 0 << LayerMask.NameToLayer(“TestLayer”); // 表示关闭层名“TestLayer” 的层 。

RaycastHit的point属性

该point的属性表达的是射线与碰撞体的交点,一般运用在moba游戏人物的移动等。