第二十八章 Unity射线检测

本章节我们介绍一下射线。射线就是从一个固定点向一个方向发射出一条直线,在发射过程中需要判断该射线有没有与游戏物体发送碰撞。射线既可以用来检测射击游戏中武器指向目标;又可以判断鼠标是否指向游戏物体。射线的创建方式,一般使用代码来实现。接下来,我们就来创建一个新的“SampleScene3.unity”场景。这里注意的是,射线检测都是以物理系统为基础的,因此只有添加碰撞体组件的游戏物体才能被射线检测到。庆幸的是,在Unity中,创建的Cube或者Sphere,都是自动附带相应的碰撞体组件。

我们创建了三个球体Sphere1,Sphere2,Sphere3,然后我们由Sphere1为起点向X轴负方向(上图左边)发射一条射线。那么这条射线就应该可以检测到Sphere2和Sphere3。

接下来,我们创建一个“RayScript.cs”脚本文件,附加到Sphere1上面,内容如下。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RayScript : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            // 射线的起点(Sphere1的位置)
            Vector3 origin = transform.position;
            // 射线的方向(X轴负方向)
            Vector3 direction = Vector3.left;
            // 射线碰撞的目标对象
            RaycastHit hitInfo;
            // 射线的最大长度
            float maxDistance = 100;

            // 创建射线,返回是否检测到碰撞对象
            bool raycast = Physics.Raycast(origin, direction, out hitInfo, maxDistance);

            // 如果发生碰撞,碰撞信息就被存储到 hitInfo 中
            if (raycast)
            {
                // 获取碰撞点坐标
                Vector3 point = hitInfo.point;
                Debug.Log("碰撞点坐标:" + point);

                // 获取碰撞目标的名称
                string name = hitInfo.collider.name;
                Debug.Log("碰撞对象名称:" + name);

                // 获取目标的碰撞体组件
                Collider coll = hitInfo.collider;

                //获取目标的Transgorm组件
                Transform trans = hitInfo.transform;
            }
        }
    }
}

上面的代码非常简单,我们使用四个参数来,通过Physics.Raycast方法创建一条射线,然后使用第三个参数RaycastHit hitInfo就是我们需要的碰撞目标的信息。我们可以通过这个对象获取到射线和目标的碰撞点位置信息,也可以获取目标的游戏对象名称,以及它碰撞体collider组件或者transform变换组件。其实,Physics.Raycast方法还有第五个参数int layerMask,用来指定检测图层,而忽略其他图层。在我们上一章节中,就提到过,碰撞检测可以使用层layer来进行限制。我们可以指定层与层之间的游戏对象发生碰撞,同样这里也适用于射线的碰撞检测。这里我们就不再详细介绍这个参数了。

在游戏开发中,由于射线不可见,所以有时候,我们无法判断射线碰撞的有效性。这个时候,我们可以借助Debug.DrawLine()函数和Debug.DrawRay()来模拟射线。首先介绍DrawLine,

Debug.DrawLine(Vector3 start, Vector3 end, Color color=Color.white, float duration=0.0f, bool depthTest=true);

参数为start 直线的起点,end 直线的终点,color 直线的颜色,duration 直线的持续时间。

depthTest 直线是否被靠近摄像机的对象遮挡。

Debug.DrawRay(Vector3 start, Vector3 dir, Color color=Color.white, float duration=0.0f, bool depthTest=true);

参数为start 射线的起点,dir 射线的方向和长度,color 射线的颜色,duration 射线的持续时间,depthTest 摄像是否被靠近摄像机的对象遮挡。

接下来,我们就使用Debug.DrawRay方法来模拟上面代码案例中的摄像,增加如下代码

// 画一条蓝线来模拟射线
Debug.DrawRay(origin, direction * 100, Color.blue, 100);

接下来,我们重新Play工程,然后按下A键,回到Scene视图(不是Game视图)中查看。

我们可以看到由Sphere1发射出来的一条蓝色的模拟射线了。

由上图我们可知,射线不仅穿过了黄球Sphere2,还穿过了绿球Sphere3了。如何能得到绿色Sphere3呢?这个就需要借助Physics.RaycastAll函数。这个函数与Physics.Raycast函数的使用是相似的,但是返回的结果是不一样的。该函数的返回值是RaycastHit数组。接下来,我们重新修改一下代码,如下所示

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            // 射线的起点(Sphere1的位置)
            Vector3 origin = transform.position;
            // 射线的方向(X轴负方向)
            Vector3 direction = Vector3.left;
            // 射线碰撞的目标对象
            //RaycastHit hitInfo;
            // 射线的最大长度
            float maxDistance = 100;

            // 创建射线,返回是否检测到碰撞对象
            //bool raycast = Physics.Raycast(origin, direction, out hitInfo, maxDistance);
            RaycastHit[] hitInfos = Physics.RaycastAll(origin, direction, maxDistance);

            // 如果发生碰撞,碰撞信息就被存储到 hitInfo 中
            //if (raycast)
            foreach(RaycastHit hitInfo in hitInfos)
            {
                // 获取碰撞点坐标
                Vector3 point = hitInfo.point;
                Debug.Log("碰撞点坐标:" + point);

                // 获取碰撞目标的名称
                string name = hitInfo.collider.name;
                Debug.Log("碰撞对象名称:" + name);

                // 获取目标的碰撞体组件
                Collider coll = hitInfo.collider;

                //获取目标的Transgorm组件
                Transform trans = hitInfo.transform;
            }

            // 画一条蓝线来模拟射线
            Debug.DrawRay(origin, direction * 100, Color.blue, 100);
        }
    }

我们重新Play工程,控制台输出截图如下

在实际游戏开发中,我们有时候需要检测一定范围内是否发生碰撞。比如说,我们要检测周围100米内是否存在某些游戏对象,如果存在,就向其主动发起攻击。此时,我们使用一条射线就无法完成这样的要求。Unity为我们提供了丰富的不同形状的射线检测。这里,我们可以使用Physics.OverlapSphere创建球体碰撞检测,或者使用Physics.OverlapBox创建立方体检测。他们返回的是Collider[]数组,例如我们使用射线检测附近100米内的所有物体

Collider[] colliders = Physics.OverlapSphere(transform.postion, 100.0f);

foreach(Collider collider in colliders){ …… }

最后,我们在介绍一下摄像的另一种使用方式,就是鼠标点击选中场景中的游戏对象。它的原理非常简单,就是由相机位置向鼠标点击位置发射一条射线,然后进行碰撞检测。接下来,我们就来创建一个“RayClickScript.cs”脚本,将其附加到相机上面。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RayClickScript : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        // 鼠标左键按下
        if (Input.GetMouseButtonDown(0))
        {
            // 从相机位置发射一条射线经过屏幕上的鼠标点击位置
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            // 声明一个射线碰撞信息类
            RaycastHit hit;

            // 进行碰撞检测
            bool res = Physics.Raycast(ray, out hit);

            // 如果产生了碰撞
            if(res){
                Debug.Log("碰撞点:" + hit.point);
                Debug.Log("碰撞目标:" + hit.transform.name);
            }
        }
    }
}

然后我们Play工程,使用鼠标点击绿球Sphere3,

接下来,我们做一个有趣事情。我们点击Plane平面上一点,然后让绿球Sphere3移动到那一点。如何来完成这件有趣的事情呢?代码改动如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RayClickScript : MonoBehaviour
{
    // 绿球Sphere3
    private GameObject sphere3;

    // 是否移动
    private bool isMove = false;

    // 目标点
    private Vector3 target = Vector3.zero;

    // Start is called before the first frame update
    void Start()
    {
        // 获取绿球Sphere3
        sphere3 = GameObject.Find("Sphere3");
    }

    // Update is called once per frame
    void Update()
    {
        // 鼠标左键按下
        if (Input.GetMouseButtonDown(0))
        {
            // 从相机位置发射一条射线经过屏幕上的鼠标点击位置
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            // 声明一个射线碰撞信息类
            RaycastHit hit;

            // 进行碰撞检测
            bool res = Physics.Raycast(ray, out hit);

            // 如果产生了碰撞
            if (res && hit.transform.name == "Plane")
            {
                // 目标点(Y轴上保持不变)
                isMove = true;
                target = new Vector3(hit.point.x, sphere3.transform.position.y, hit.point.z);
                //Debug.Log("碰撞点:" + hit.point);
                //Debug.Log("碰撞目标:" + hit.transform.name);
            }
        }

        // 如果发生碰撞就让绿球移动到目标点
        if (isMove)
        {
            // 绿球朝向目标点
            sphere3.transform.LookAt(target);

            // 角色移动到目标点的距离
            float distance = Vector3.Distance(target, sphere3.transform.position);

            // 没有到达目标点就一直移动下去
            if (distance > 0.1f)
            {
                // 旋转后向前移动即可
                sphere3.transform.Translate(transform.forward * 0.2f);
            }
            else
            {
                // 移动结束
                isMove = false;
            }
        }
    }
}

上面的代码,我们就不解释了,直接Play运行查看效果

本课程涉及的内容已经共享到百度网盘:https://pan.baidu.com/s/1e1jClK3MnN66GlxBmqoJWA?pwd=b2id

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咆哮的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值