目录
- 前言
- 一、射线的创建方法
- 二、射线检测
-
- 1、Raycast()
- 2、RaycastAll()
-
- 使用射线Ray
- [RaycastAll() 不使用射线Ray](#RaycastAll() 不使用射线Ray)
- 3、射线的碰撞信息
- 三、示例
- 四、具体使用场景
- 射线的调试方法
前言
碰撞检测可以帮助我们实现诸如抵达某个地点自动触发剧情、判断子弹是否击中玩家等功能,但我如果想要实现如当鼠标悬浮某个人物上,自动弹出该人物信息,要如何判断呢?这时使用碰撞检测,从摄像机生成一个透明碰撞体朝着人物移动,等碰撞到了人物再弹出该人物信息?会不会太繁琐了。或许你又会想,若我直接生成一个足够长的透明碰撞体呢,是不是在创建的那一刻就可以触发该人物的弹出信息逻辑?没错这样的确可以,而这就是射线!不过是把无限长的透明碰撞体变为了无限长的一条线,仅此而已。
一、射线的创建方法
常用的直线射线类型用类型Ray表示,Ray包含了 起点origin 跟 方向direction的定义,起点和方向都用Vector3类型表示,前者是一个坐标,后者是一个表示方向的向量。
csharp
Vector3 origin = transform.position;
Vector3 direction = Vector3.down;
Ray ray = new Ray(origin, direction);
二、射线检测
在游戏中发射一条射线,最常用的是Physics.Raycast()和Physics.RaycastAll(),前者只能检测一个碰撞体而被返回,后者可以一起检测多个碰撞体。如果使用 Physics.RaycastAll() 进行多次射线投射,将返回一个 RaycastHit[] 数组,其中包含所有射线与场景中对象的交互信息。这在需要获取所有碰撞点信息的情况下很有用。
1、Raycast()
从某个初始点开始,沿着特定的方向发射一条不可见且无限长的射线,通过此射线检测是否有任何模型添加了Collider碰撞器组件。一旦检测到碰撞,停止射线继续发射。
Raycast()不使用射线Ray
csharp
public static bool Raycast (Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
参数:
- origin:射线在世界坐标系中的起点。
- direction:射线的方向。
- hitInfo:如果返回 true,则 hitInfo 将包含有关最近的碰撞体的命中位置的更多信息。(另请参阅:RaycastHit)。
- maxDistance:射线应检查碰撞的最大距离。(可选,不写默认无限长)
- layerMask:层遮罩,用于在投射射线时有选择地忽略碰撞体。(可选,不写默认检测所有层)
- queryTriggerInteraction:指定该查询是否应该命中触发器。可以通过指定 queryTriggerInteraction 来控制是让触发碰撞体生成命中效果,还是使用全局 Physics.queriesHitTriggers 设置。
返回
bool 当光线与任何碰撞体相交时,返回 true,否则返回 false
Raycast()使用射线Ray
csharp
public static bool Raycast (Ray ray, out RaycastHit hitInfo, float maxDistance= Mathf.Infinity, int layerMask= DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);
参数:
- ray 光线的起点和方向。
- hitInfo:如果返回 true,则 hitInfo 将包含有关最近的碰撞体的命中位置的更多信息。(另请参阅:RaycastHit)。
- maxDistance 射线应检查碰撞的最大距离。(可选,不写默认无限长)
- layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。(可选,不写默认检测所有层)
- queryTriggerInteraction 指定该查询是否应该命中触发器。
返回
bool 当光线与任何碰撞体相交时,返回 true,否则返回 false
2、RaycastAll()
使用射线Ray
csharp
public static RaycastHit[] RaycastAll (Ray ray, float maxDistance= Mathf.Infinity, int layerMask= DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);
参数:
- ray 光线的起点和方向。
- maxDistance 从射线起点开始,允许射线命中的最大距离。(可选,不写默认无限长)
- layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。(可选,不写默认检测所有层)
- queryTriggerInteraction 指定该查询是否应该命中触发器。
返回
RaycastHit[] RaycastHit 对象的数组。注意,这些结果的顺序未定义。
描述
向场景中投射射线并返回所有命中对象。注意,这些结果的顺序未定义。
RaycastAll() 不使用射线Ray
csharp
public static RaycastHit[] RaycastAll (Vector3 origin, Vector3 direction, float maxDistance= Mathf.Infinity, int layerMask= DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);
参数
- origin 射线在世界坐标系中的起点。
- direction 射线的方向。
- maxDistance 从射线起点开始,允许射线命中的最大距离。(可选,不写默认无限长)
- layermask 层遮罩,用于在投射射线时有选择地忽略碰撞体。(可选,不写默认检测所有层)
- queryTriggerInteraction 指定该查询是否应该命中触发器。
3、射线的碰撞信息
我们再重点讲讲第三个参数RaycastHit,若没有这个参数,我们的返回值仅仅只是"是否碰到了物体",而无法确定碰撞点在哪,也不知道碰撞体是哪一个。射线检测其实有着非常丰富的碰撞信息,如可以获得其碰撞坐标,信息,以及法线。这些丰富的信息都被保存在RaycastHit结构体中。
综合用法演示如下:
csharp
//声明变量,用于保存信息
RaycastHit hitInfo;
//发射射线,起点是当前物体位置,方向是世界前方
if(Physics.Raycast(transform.position,Vector3.forward,out hitInfo))
{
//如果碰到物体,运行此处,返回的是bOOl值
//获取碰撞点坐标
Vector3 point = hitInfo.point;
//获取对方碰撞体组件
Collider coll = hitInfo.collider;
//获取对方的Transgorm组件
Transform trans = hitInfo.transform;
//获取对方的物体名称
string name = hitInfo.collider.name;
//获取碰撞点的法线向量
Vector3 normal = hitInfo.normal;
}
注意,2D游戏跟3D游戏获取碰撞体的方式有些不一样,具体对比如下
csharp
//声明变量,将碰撞信息直接赋值给变量
RaycastHit2D hitInfo=Physics2D.Raycast(transform.position,Vector3.forward);
//发射射线,起点是当前物体位置,方向是世界前方
if(hitInfo.collider != null)
{
//如果检测的碰撞体不为空,运行此处。
//获取碰撞点坐标
Vector3 point = hitInfo.point;
//获取对方碰撞体组件
Collider coll = hitInfo.collider;
//获取对方的Transgorm组件
Transform trans = hitInfo.transform;
//获取对方的物体名称
string name = hitInfo.collider.name;
//获取碰撞点的法线向量
Vector3 normal = hitInfo.normal;
}
三、示例
csharp
//第一个简单小栗子
void Update()
{
Ray ray = new Ray(transform.position, transform.forward);
//声明一个Ray结构体,用于存储该射线的发射点,方向
RaycastHit hitInfo;
//声明一个RaycastHit结构体,存储碰撞信息
if (Physics.Raycast(ray, out hitInfo))
{
Debug.Log(hitInfo.collider.gameObject.name);
//这里使用了RaycastHit结构体中的collider属性
//因为hitInfo是一个结构体类型,其collider属性用于存储射线检测到的碰撞器。
//通过collider.gameObject.name,来获取该碰撞器的游戏对象的名字。
}
}
csharp
//第二个简单小栗子
void Update()
{
RaycastHit hitInfo;
if (Physics.Raycast(transform.position, transform.forward, out hitInfo))
{
Debug.Log(hitInfo.collider.gameObject.name);
}
}
四、具体使用场景
当使用 RaycastHit 类进行射线投射时,有许多不同的使用场景,包括检测碰撞、拾取物体、瞄准检测、物体高亮等。在以下每个场景示例中,我将向您展示如何使用 RaycastHit,并加入一些粒子效果("lizi",即粒子效果在中文中的写法)来增强可视化效果。
- 检测碰撞并发射粒子:
在这个场景中,我们将使用射线投射来检测是否与一个可交互的对象发生碰撞,然后在碰撞点发射一些粒子效果。
csharp
using UnityEngine;
public class RaycastCollisionExample : MonoBehaviour
{
public ParticleSystem collisionParticles; // 粒子效果
void Update()
{
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo))
{
// 碰撞点发射粒子效果
if (collisionParticles != null)
{
collisionParticles.transform.position = hitInfo.point;
collisionParticles.Play();
}
Debug.Log("碰撞对象:" + hitInfo.collider.gameObject.name);
}
}
}
在这个示例中,我们在碰撞点发射了一个粒子效果,从而在用户与可交互对象发生碰撞时产生视觉效果。
- 物体拾取与交互:
在这个场景中,我们将使用射线投射来检测是否与一个可拾取的物体发生碰撞,然后可以将物体拾取并交互。
csharp
using UnityEngine;
public class ObjectPickupExample : MonoBehaviour
{
private Transform pickedObject = null; // 当前拾取的物体
void Update()
{
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo))
{
// 拾取物体
if (Input.GetKeyDown(KeyCode.E))
{
if (hitInfo.collider.CompareTag("Pickable"))
{
pickedObject = hitInfo.collider.transform;
Debug.Log("拾取了:" + pickedObject.name);
}
}
// 交互
if (Input.GetKeyDown(KeyCode.F))
{
if (pickedObject != null)
{
Debug.Log("与 " + pickedObject.name + " 进行了交互。");
}
}
}
}
}
在这个示例中,我们使用 Input.GetKeyDown(KeyCode.E) 来拾取与射线相交的可拾取物体,并使用 Input.GetKeyDown(KeyCode.F) 来与拾取的物体进行交互。
- 物体高亮效果:
在这个场景中,我们将使用射线投射来检测是否与一个物体发生碰撞,然后在碰撞对象上显示一个高亮的粒子效果。
csharp
using UnityEngine;
public class ObjectHighlightExample : MonoBehaviour
{
public ParticleSystem highlightParticles; // 高亮粒子效果
private Transform lastHighlightedObject = null; // 上一个高亮的物体
void Update()
{
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo))
{
// 高亮物体
if (hitInfo.collider.CompareTag("Highlightable"))
{
if (lastHighlightedObject != hitInfo.collider.transform)
{
if (lastHighlightedObject != null)
{
highlightParticles.Stop();
}
lastHighlightedObject = hitInfo.collider.transform;
highlightParticles.transform.position = lastHighlightedObject.position;
highlightParticles.Play();
}
}
else if (lastHighlightedObject != null)
{
highlightParticles.Stop();
lastHighlightedObject = null;
}
}
}
}
在这个示例中,我们使用高亮粒子效果来显示玩家当前所指的物体。粒子效果在物体上播放和停止,从而实现高亮效果。
- 通过鼠标点击的位置获取与射线碰撞的物体
csharp
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
/*判断鼠标是否点击在UI上*/
if (EventSystem.current.IsPointerOverGameObject() == false)
{
/*通过鼠标位置获取射线*/
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
/*用于存储射线投射操作的结果*/
RaycastHit hit;
/* Physics.Raycast参数:
* 射线在世界坐标系中的起点、
* 射线的方向和存储射线投射操作的结果、
* 射线应检查碰撞的最大距离、
* 层遮罩,用于在投射射线时有选择地忽略碰撞体*/
bool isCpllider = Physics.Raycast(ray, out hit, 1000, LayerMask.GetMask("MapCobe"));
if (isCpllider)
{
/*得到点击的MapCude*/
GameObject mapCude = hit.collider.gameObject;
Debug.Log("成功");
}
}
}
}
注意:Collider组件中Is Trigger选项的开关并不影响射线检测! 对了还有一个参数,写在Raycast末尾,QueryTriggerInteraction(指定该射线是否应该命中触发器),上面我说过Is Trigger选项的开关不影响射线检测,但是前提是QueryTriggerInteraction该参数设置为检测触发器了,你也可以将该参数设置为仅对碰撞器进行检测,这个参数可以全局设置。对于射线投射起点位于碰撞体内的情况,Raycast 不会检测到碰撞体。在所有这些示例中,都使用了 FixedUpdate 而不是 Update。请参阅事件函数的执行顺序,以了解 Update 与 FixedUpdate 的区别,以及它们与物理查询的关系。
射线的调试方法
利用Debug调试
在游戏开发中,射线的调试方法非常重要,可以将看不见的射线以可视化的形式表现出来,方便我们查看数据是否正确。这个方法是使用Debug.DrawLine()函数和Debug.DrawRay()
1、Debug.DrawLine()
csharp
public static void DrawLine (Vector3 start, Vector3 end, Color color= Color.white, float duration= 0.0f, bool depthTest= true);
参数:
- start 应作为该直线起始点的世界空间中的点。
- end 应作为该直线结束点的世界空间中的点。
- color 该直线的颜色。
- duration 该直线的可见长度应为多长。
- depthTest 该直线是否应被靠近此摄像机的对象遮挡?
描述
在指定的起始点与结束点之间绘制一条直线。
当游戏正在运行并且启用辅助图标绘图时,将在 Editor 的游戏视图中绘制直线。当直线在游戏视图中可见时,还会在场景中绘制该直线。让游戏运行并显示直线。切换到场景视图,该直线将可见。
duration 是在第一次显示该直线后该直线可见的时间长短(单位为秒)。如果持续时间为零,则该直线仅显示一帧。
注意 :这仅用于调试播放模式。应改用 Gizmos.Drawline 或 Handles.DrawLine 来绘制 Editor 辅助图标。
Debug.DrawLine 更多详情
2、Debug.DrawRay
csharp
public static void DrawRay (Vector3 start, Vector3 dir, Color color= Color.white, float duration= 0.0f, bool depthTest= true);
参数:
- start 应作为该射线起始点的世界空间中的点。
- dir 该射线的方向和长度。
- color 绘制的直线的颜色。
- duration 该直线的可见时长(单位为秒)。
- depthTest 该直线是否应被靠近此摄像机的其他对象遮挡?
描述
在世界坐标中绘制一条从 start 到 start + dir 的直线。
duration 参数确定在绘制该直线所在的帧之后该直线可见时长。如果持续时间为 0(默认值),则该直线被渲染 1 帧。
如果将 depthTest 设置为 true,则该直线将被此场景中更靠近摄像机的其他对象遮挡。将在该 Editor 的场景视图中绘制该直线。如果在游戏视图中启用了辅助图标绘图,则在该视图中也将绘制该直线。
效果图可以看上面,都有涉及使用到。需要说明的是在默认的情况下,该辅助线仅在编辑器的场景窗口可见,如果要在game窗口看到,则需要单机Game窗口右上角的Gizmos开关。
利用Gizmos
调用OnDrawGizmos()函数或OnDrawGizmosSelected,它们同样都可以用来绘制辅助图标。区别在于
函数OnDrawGizmos()在程序一运行就执行,之后每帧都在执行,
函数OnDrawGizmosSelected()在鼠标点击到脚本挂载的物体的身上的时候运行,不管有多少父类对象,它都会执行。
这里推荐使用OnDrawGizmosSelected()
范例如下
csharp
private void OnDrawGizmosSelected()
{
Gizmos.DrawLine(Vector3 from, Vector3 to);
Gizmos.DrawRay((Vector3 from, Vector3 direction);
}
可在scence窗口查看辅助线,同样想在game窗口可以看到,则需要单机Game窗口右上角的Gizmos开关。