Unity中的射线和射线图层过滤使用方法
射线
通常在Unity中使用射线通常离不开两个东西,一个是Ray另一个是RaycastHit。Ray和RaycastHit都是struct结构体类型。
Ray是具有开始点和方向的无穷线。说白了就是数学上的射线。那么RaycastHit是什么呢?他是获取射线投射碰撞返回的信息的一个结构体。它里面储存了关于射线碰撞的大量信息,如碰到游戏对象的刚体、碰撞器、UV纹理坐标等等。Ray是射线,RaycastHit是接收射线返回的信息。
Ray
利用VS查看一下Ray的定义:
里面有两个Vector3结构体,其中direction是射线的方向,drigin是射线的原点。用这两个Vector3就能确定一条射线。通过查询Ray构造函数,我们得知定义一条射线的方法。例如,从当前位置向前发射一条射线:
1 //从当前变换位置开始,沿当前变换的Z方向创建一条射线。 2 3 Ray ray = new Ray(transform.position, transform.forward);
RaycastHit
利用VS查看一下RaycastHit,相比Ray它包含了更丰富的内容:
point接触点,normal法线,distance距离,rigidbody刚体,transform变换,textureCoord纹理坐标...等诸多射线返回信息的属性。
Physics.Raycast 射线投射
但是到此为止,查阅Ray和RaycastHit里并没有定义让这射线发出去的方法。原来Unity把射线发射的方法定义在了Physics物理里面,这里面包含了大量的全局物理属性和辅助方法。射线发出的方法也在内。例如:Raycast(),RaycastAll(),RaycastNonAlloc()。其中Raycast()最为常用,广范。
Physics.Raycast()在场景中投下可与所有碰撞器碰撞的一条光线。
Raycast()有很多重载。
参数如下:
Vector3 origin:在世界坐标,射线的起始点。
Vector3 direction:射线的方向。
float distance:射线的长度。
int layermask:投射射线,选择投射的层蒙版。(需要注意:并不指Layer索引层,而是指图层掩码参数!!往下看)
out RaycastHit hitInfo:包含碰到器碰撞的更多信息。
QueryTriggerInteraction queryTriggerInteraction:指定是否查询碰到触发器。
例子1
发射射线检测:
1 using UnityEngine;using System.Collections; 2 public class ExampleClass : MonoBehaviour { 3 void Update() { 4 if (Physics.Raycast(transform.position,transform.forward , 100))//从当前位置向前发射一条长为100的射线是否碰到东西 5 print("前方100米内有东西!"); 6 } 7 }
例子2
发射已有射线接收并存储返回的碰撞信息:
1 using UnityEngine;using System.Collections; 2 public class ExampleClass : MonoBehaviour { 3 void Update() { 4 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//相机到鼠标位置发射一条射线 5 RaycastHit hit;//碰撞信息 6 if (Physics.Raycast(ray, out hit, 100))//发射已有射线返回碰撞信息 7 Debug.DrawLine(ray.origin, hit.point);//打印射线 8 } 9 }
光线投射选择性的与图层碰撞
首先我在Layers添加了一个名为Enemy的User Layer 8 。然后添加了Player和Enemy两个方块,将Enemy方块标记Layer层Enemy。
为MainCamera挂载MyRay.cs脚本组件。
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 public class MyRay : MonoBehaviour { 6 7 // Use this for initialization 8 void Start () { 9 10 } 11 12 void Update() 13 { 14 15 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); 16 17 RaycastHit hit; 18 19 int layerMask = 8;//层序号 20 21 if (Physics.Raycast(ray, out hit, 1000,layerMask)) 22 23 Debug.LogWarning("碰撞敌人!"); 24 } 25 }
结果Console并没有打印碰撞敌人。
将layerMask下标值移位8。
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 public class MyRay : MonoBehaviour { 6 7 // Use this for initialization 8 void Start () { 9 10 } 11 12 void Update() 13 { 14 15 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); 16 17 RaycastHit hit; 18 19 int layerMask = 1 << 8;//图层掩码参数 20 21 if (Physics.Raycast(ray, out hit, 1000,layerMask)) 22 23 Debug.LogWarning("碰撞敌人!"); 24 } 25 }
运行之后,将鼠标光标放在Enemy方块上。Console输出:
分析上述代码:Layer的8并不是我们需要的传递的int类型参数。
原来,Unity使用一个32位的整数来代表图层掩码参数。举个例子,以下表示32位全是零:
0000 0000 0000 0000 0000 0000 0000 0000
默认情况下Unity使用前8层作为内置层。所以当你在不使用任何图层参数掩码参数的情况下调用光线投射方法时,它会对所有默认的这8层进行光线投射,可以用如下掩码表示:
0000 0000 0000 0000 0000 0000 1111 1111
我们把敌人所在层设置为第8层(下标为9),我们只希望对这个层进行光线投射。所以我们把掩码设置为:
0000 0000 0000 0000 0000 0001 0000 0000
设置这个位掩码简单的办法可以使用位移操作。我们只需要将下标为9的位置设置为1,这意味着我们只需要左移8位即可。使用向左位移操作符向左移8位,如下代码:
int layerMask =1<<8;
要是希望多个图层掩码,比如8层和9层,用按位或操作符即可:
int layerMask = (1<<8)|(1<<9);
查阅官方API之后,会发现还有其他更简单的方法,再提供几个其他方法:
官方使用案例:
1 using UnityEngine; 2 using System.Collections; 3 4 public class ExampleClass : MonoBehaviour { 5 public LayerMask mask = -1; 6 void Update() { 7 if (Physics.Raycast(transform.position, transform.forward, 100, mask.value)) 8 Debug.Log("Hit something"); 9 10 } 11 }
使用他这个方法,会检测所有碰撞层。
LayerMask结构体里有个静态方法GetMask()很好用,把LayerName传进去就自动转int类型图层掩码。比位操作符好理解多了。
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 public class MyRay : MonoBehaviour { 6 7 // Use this for initialization 8 void Start () { 9 10 } 11 12 void Update() 13 { 14 15 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); 16 17 RaycastHit hit; 18 19 if (Physics.Raycast(ray, out hit, 1000, LayerMask.GetMask("Enemy"))) 20 21 Debug.LogWarning("碰撞敌人!"); 22 23 } 24 }
补充:
layermask参数设置的总结:
1 << 10 打开第10的层。
~(1 << 10) 打开除了第10之外的层。
~(1 << 0) 打开所有的层。
(1 << 10) | (1 << 8) 打开第10和第8的层。