第二十八章 Unity射线检测

news2024/11/24 10:24:59

本章节我们介绍一下射线。射线就是从一个固定点向一个方向发射出一条直线,在发射过程中需要判断该射线有没有与游戏物体发送碰撞。射线既可以用来检测射击游戏中武器指向目标;又可以判断鼠标是否指向游戏物体。射线的创建方式,一般使用代码来实现。接下来,我们就来创建一个新的“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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/489690.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

11. Kubernetes 开章

本章讲解知识点 Kubernetes 概念为什么要使用 KubernetesKubernetes 的部署架构Kubernetes 基本命令本章主要是针对 Kubernetes 基本概念为读者讲解,读者能有一个大概印象即可,不需要过于斟酌细节,针对 Kubernetes 的概念将在后面章节中详细讲解。 1. Kubernetes 概念 我们…

学习Transformer前言(Self Attention Multi head self attention)

一、前言 一直在做项目,也比较懒没有挤出时间去学习新的东西,感觉停滞很久了,好长一段时间都没有新的知识输入,早就需要就去学习transformer了,因此先来学习注意力机制,本文为个人的一个笔记总结。主要是基…

Linux系统编程(三)—— 文件编程(3)进程环境

一、main函数 现在的格式:int main(int argc, char *argv[])以前的main函数有三个参数,另一个参数就是环境变量 二、进程的终止(两种都要背下来) 2.1 正常终止 (1)从main函数返回 main函数被称为程序的…

第10章:堆

堆是什么? 堆是一种特殊的完全二叉树。 完全二叉树:每层节点都完全填满,最后一层若是没填满,则只缺少右边的节点。所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点。jav…

软考——数据结构,算法基础,程序设计语言,法律法规,多媒体基础

数据结构与算法基础 数组与矩阵线性表广义表树与二叉树图排序与查找算法基础及常见算法 数组 稀疏矩阵 直接把(0,0)带入,排除B,C 将(1,1)带入,排除D, 最终…

Python | 人脸识别系统 — 博客索引

本博客为人脸识别系统的博客索引 工具安装、环境配置:Python | 人脸识别系统 — 简介 1、UI代码 UI界面设计:Python | 人脸识别系统 — UI界面设计UI事件处理:Python | 人脸识别系统 — UI事件处理 2、用户端代码 用户端博客索引:…

Jupyter Notebook入门教程

Jupyter Notebook(又称Python Notebook)是一个交互式的笔记本,支持运行超过40种编程语言。本文中我们将介绍Jupyter Notebook的主要特点,了解为什么它能成为人们创造优美的可交互式文档和教育资源的一个强大工具。 首先&#xff…

vue diff算法与虚拟dom知识整理(4) h函数虚拟节点嵌套

那么 先补充上文有一个没强调的点 h函数 当你不需要属性时 其实是可以不传的 例如 我们打开案例 打开 src下的index.js 修改代码如下 import {init,classModule,propsModule,styleModule,eventListenersModule,h,} from "snabbdom";//创建patch函数const patch ini…

CUBLAS 和 CUDNN

文章目录 一、什么是CUBLASCUBLAS实现矩阵乘法CUBLAS中的Leading DimensionCUBLAS LEVEL3函数 : 矩阵矩阵CUBLAS实现矩阵乘法 二、cuDNN使用CuDNN实现卷积神经网络 四、CUBLAS和CUDNN实践 一、什么是CUBLAS cuBLAS是BLAS的一个实现。BLAS是一个经典的线性代数库&am…

解决C语言的缺陷【C++】

文章目录 命名空间展开了命名空间域指定访问命名空间域域作用限定符命名空间定义 C输入&输出缺省参数全缺省参数半缺省参数缺省参数应用 函数重载参数类型不同参数个数不同参数类型顺序不同 引用引用的特性引用在定义时必须初始化一个变量可以有多个引用引用一旦引用一个实…

基于Radon-分数傅里叶变换对消器的海杂波弱目标检测

海面微弱目标检测面临的主要困难来自: 慢速小目标回波微弱;空时变海杂波异常复杂,海杂波特性认知难度大;目标模型难以建立;目标、海杂波类别非平衡。 ARU效应 是由于海面波浪的起伏和涟漪引起的。在雷达回波信号中&am…

nodejs开发 | 安全工具端口扫描器

今天分享一个nodejs的demo,可以扫描出指定IP的端口开放情况。 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台。 Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境,基于 Google 的 V8…

[笔记]Python计算机视觉编程《一》 基本的图像操作和处理

文章目录 前言环境搭建 计算机视觉简介Python和NumPy第一章 基本的图像操作和处理1.1 PIL:Python图像处理类库1.1.1 转换图像格式1.1.2 创建缩略图1.1.3 复制和粘贴图像区域1.1.4 调整尺寸和旋转 1.2 Matplotlib1.2.1 绘制图像、点和线 前言 今天,图…

python基础实战6-python字符串

1字符串的表达方式 字符串是 Python 中最常用的数据类型。我们可以使用引号 ( 或 " ) 来创建字符串。 字符串表达方式 a " I m Tom" #一对双引号 b ’Tom said:" I am Tom" #一对单引号 c ‘Tom said: " I\m Tom" #转义字符 d T…

python库,科学计算与数据可视化基础,知识笔记(numpy+matplotlib)

文章目录 1、numpyndarray对象,数组指定形状创建(要会)数组的索引,切片与遍历(要会)数组的相加,转置,展开(要会)数组元素增删改查(最好会&#xf…

当CRM遇见ChatGPT,AI如何帮销售打造“最强大脑”

导读:这一轮AI浪潮将对CRM企业带来哪些新机遇? ChatGPT诞生至今不过100多天,但它已经对很多行业产生了巨大影响。 企业直面市场的一线战场——销售、营销、客服等领域也是如此。以ChatGPT为代表的人工智能技术热潮正在改变着传统营销、销售的…

MySQL--group by--聚合函数--内置函数--0415 22

目录 1.聚合函数 1.1 count 1.2 sum 1.3 avg 1.4 max 和 min 2. group by 2.1 group by 的条件筛选——having 2.2 总结 3.日期函数 4.字符串函数 concat replace substring 以首字母大写,其余字母小写的方式显示员工的姓名 5.数学函数 format rand()…

C plus plus ——【继承与派生】

系列文章目录 C plud plus ——【面向对象编程】 C plus plus ——【继承与派生】 文章目录 系列文章目录前言一、继承1.1 类的继承1.2 继承后可访问性1.3 构造函数访问顺序1.4 子类隐藏父类的成员函数 二、重载运算符2.1重载运算符的必要性2.2重载运算的形式与规则2.3 转换运…

数字信号处理学习1

基本上算是没怎么学过数字信号处理这门课,因为本科的时候,专业方向用不上,现在没法子了,专业使然,只能自己自学了,但是我又不知道该从何学起,就买了一本现代数字信号处理,结果发现人…

数据结构/队列实现栈

前言 在学习数据结构的过程当中,我们会学到栈和队列,在本篇文章中,重点讲解的是队列实现栈,在上篇文章中已经简单介绍过栈和队列的使用说明,以及栈实现队列。(2条消息) 数据结构/栈实现队列_Y君的进化史的博客-CSDN博客…