【Unity入门】详解Unity中的射线与射线检测

目录

前言

碰撞检测可以帮助我们实现诸如抵达某个地点自动触发剧情、判断子弹是否击中玩家等功能,但我如果想要实现如当鼠标悬浮某个人物上,自动弹出该人物信息,要如何判断呢?这时使用碰撞检测,从摄像机生成一个透明碰撞体朝着人物移动,等碰撞到了人物再弹出该人物信息?会不会太繁琐了。或许你又会想,若我直接生成一个足够长的透明碰撞体呢,是不是在创建的那一刻就可以触发该人物的弹出信息逻辑?没错这样的确可以,而这就是射线!不过是把无限长的透明碰撞体变为了无限长的一条线,仅此而已。

一、射线的创建方法

常用的直线射线类型用类型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);

参数:

  1. origin:射线在世界坐标系中的起点。
  2. direction:射线的方向。
  3. hitInfo:如果返回 true,则 hitInfo 将包含有关最近的碰撞体的命中位置的更多信息。(另请参阅:RaycastHit)。
  4. maxDistance:射线应检查碰撞的最大距离。(可选,不写默认无限长)
  5. layerMask:层遮罩,用于在投射射线时有选择地忽略碰撞体。(可选,不写默认检测所有层)
  6. 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);

参数:

  1. ray 光线的起点和方向。
  2. hitInfo:如果返回 true,则 hitInfo 将包含有关最近的碰撞体的命中位置的更多信息。(另请参阅:RaycastHit)。
  3. maxDistance 射线应检查碰撞的最大距离。(可选,不写默认无限长)
  4. layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。(可选,不写默认检测所有层)
  5. queryTriggerInteraction 指定该查询是否应该命中触发器。

返回

bool 当光线与任何碰撞体相交时,返回 true,否则返回 false

Physics.Raycast 更多详情

2、RaycastAll()

使用射线Ray

csharp 复制代码
public static RaycastHit[] RaycastAll (Ray ray, float maxDistance= Mathf.Infinity, int layerMask= DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);

参数:

  1. ray 光线的起点和方向。
  2. maxDistance 从射线起点开始,允许射线命中的最大距离。(可选,不写默认无限长)
  3. layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。(可选,不写默认检测所有层)
  4. 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);

参数

  1. origin 射线在世界坐标系中的起点。
  2. direction 射线的方向。
  3. maxDistance 从射线起点开始,允许射线命中的最大距离。(可选,不写默认无限长)
  4. layermask 层遮罩,用于在投射射线时有选择地忽略碰撞体。(可选,不写默认检测所有层)
  5. queryTriggerInteraction 指定该查询是否应该命中触发器。

Physics.RaycastAll 更多详情

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",即粒子效果在中文中的写法)来增强可视化效果。

  1. 检测碰撞并发射粒子:
    在这个场景中,我们将使用射线投射来检测是否与一个可交互的对象发生碰撞,然后在碰撞点发射一些粒子效果。
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);
        }
    }
}

在这个示例中,我们在碰撞点发射了一个粒子效果,从而在用户与可交互对象发生碰撞时产生视觉效果。

  1. 物体拾取与交互:
    在这个场景中,我们将使用射线投射来检测是否与一个可拾取的物体发生碰撞,然后可以将物体拾取并交互。
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) 来与拾取的物体进行交互。

  1. 物体高亮效果:
    在这个场景中,我们将使用射线投射来检测是否与一个物体发生碰撞,然后在碰撞对象上显示一个高亮的粒子效果。
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;
            }
        }
    }
}

在这个示例中,我们使用高亮粒子效果来显示玩家当前所指的物体。粒子效果在物体上播放和停止,从而实现高亮效果。

  1. 通过鼠标点击的位置获取与射线碰撞的物体
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);

参数:

  1. start 应作为该直线起始点的世界空间中的点。
  2. end 应作为该直线结束点的世界空间中的点。
  3. color 该直线的颜色。
  4. duration 该直线的可见长度应为多长。
  5. 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);

参数:

  1. start 应作为该射线起始点的世界空间中的点。
  2. dir 该射线的方向和长度。
  3. color 绘制的直线的颜色。
  4. duration 该直线的可见时长(单位为秒)。
  5. depthTest 该直线是否应被靠近此摄像机的其他对象遮挡?

描述

在世界坐标中绘制一条从 start 到 start + dir 的直线。

duration 参数确定在绘制该直线所在的帧之后该直线可见时长。如果持续时间为 0(默认值),则该直线被渲染 1 帧。

如果将 depthTest 设置为 true,则该直线将被此场景中更靠近摄像机的其他对象遮挡。将在该 Editor 的场景视图中绘制该直线。如果在游戏视图中启用了辅助图标绘图,则在该视图中也将绘制该直线。

Debug.DrawRay 更多详情

效果图可以看上面,都有涉及使用到。需要说明的是在默认的情况下,该辅助线仅在编辑器的场景窗口可见,如果要在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开关。

相关推荐
天人合一peng2 小时前
unity 生成标记根据背景色标记变色
unity·游戏引擎
天人合一peng6 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安7 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU27 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法7 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
迪捷软件8 小时前
显控系统虚拟仿真的工程化路径
游戏引擎·cocos2d
凡情11 小时前
android隐私合规检测
android·unity
小贺儿开发12 小时前
Unity3D 本地 Stable Diffusion 文生图效果演示
人工智能·unity·stable diffusion·文生图·ai绘画·本地化
Swift社区12 小时前
传统游戏引擎 vs 鸿蒙 System 架构
架构·游戏引擎·harmonyos
mxwin1 天前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader