Unity基础课程之物理引擎5-射线的使用方法总结

news2024/11/16 9:57:51

     在实际游戏开发时,不可避免地要用到各种射线检测。即便是一个不怎么用到物理系统的游戏,也很可能要用到射线检测机制。换句话说,射线检测在现代游戏开发中应用得非常广泛,超越了物理游戏的范围。下面简单举几个例子。

(1)游戏中有单击地面的操作,因此要发射射线以确定是否点中了可单击区域和单击位置的坐标。

(2)在判定子弹或技能是否击中目标时,如果采用碰撞体需要考虑子弹速度,且存在穿透问题,而射线是没有速度的(瞬时发生),不仅易于使用,而且综合效率更高。

(3)在3D动作游戏或2D动作游戏中,判断玩家是否落地时,可以向角色脚下发射射线;判断玩家是否接触墙壁时,可以往左右两侧发射射线;判断玩家是否需要低头时,可以往头顶发射射线;判断玩家是否需要攀爬时,同样也可以采用射线检测的方法。

(4)因为射线与视线一样会被障碍物阻挡,所以在游戏AI设计中,可以用射线模拟AI角色的视线

     

注意,上所述的各种射线检测都是以物理系统为基础的。射线需要与碰撞体和触发器配合才能发挥出作用

下面来介绍一下射线编程方法。常用的直线型射线用类型Ray表示。Ray包含了origin(起点)和direction(方向)的定义,起点和方向都用Vector3类型表示,前者是一个坐标,后者是一个表示方向的向量。有很多方法可以在游戏世界中发射一条射线,最常用的方法是Physics.Raycast()和Physics.RaycastAll()。由于实践中有各式各样的具体应用场景,因此Physics.Raycast()方法的重载有10种以上,不过实际大同小异,例如以下3种。

bool Raycast(Vector3 origin, Vector3 direction);

bool Raycast(Vector3 origin, Vector3 direction, float maxDistance);

bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, int layerMask);

以上3个函数共同的参数都是发射点坐标和方向向量,返回值都是是否击中了某个碰撞体或触发器。第3个参数maxDistance的作用是指定射线的最大长度。虽然名字叫作“射线”,但与几何中的射线不同,这里的“射线”更多是“发射”的意思。例如游戏中经常通过往角色脚下发射很短的射线(0.01,代表1厘米)来判断角色是否站在地上。除了指定方向和位置的射线以外,以下还有一类很常用的重载形式。

bool Raycast(Ray ray, out RaycastHit hitInfo);

bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance);

bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);

这种形式的射线检测用了一种常用结构体Ray(射线),它只是将射线数据对象先单独创建出来,并没有实际区别。Ray对象有多种创建方法,例如以下方法。

//  创建从原点向上的射线
Ray ray = new Ray(Vector3.zero, Vector3.up);

//  获得当前鼠标指针在屏幕上的位置(单位是像素)
Vector2 mousePos = Input.mousePosition;
//  创建一条射线,起点是摄像机位置,方向指向鼠标指针所在的点(隐含了从屏幕到世界的坐标转换)
Ray ray2 = Camera.main.ScreenPointToRay(mousePos);

//  之后可以将ray或ray2发射出去,例如:
Physics.Raycast(ray, 10000, LayerMask.GetMask("Default"));

 这些重载形式的第2个参数,即类型为RaycastHit的参数hitInfo也很有用,它保存着详细的碰撞信息,如碰撞点的配置、法线等。碰撞信息会在第3.2.6小节重点详细讲解。

3.2.5 层和层遮罩

很多时候,需要射线仅被某些物体阻挡,例如希望检测地面的射线只检测地面,而不要检测其他东西,也就是说应当穿过地面以外的东西。那么这里就要用到Layer和Layer Mask(层遮罩)的概念了。“层”的概念让物理系统变得更加好用和实用。例如一条子弹射线,仅让它碰到Ground(地面)、Player(玩家角色)和Obstacle(障碍物)这3个层,而不会和其他层的物体碰撞,其编写代码如下。

int mask = LayerMask.GetMask("Ground", "Player", "Obstacle");
if (Physics.Raycast(transform.position, Vector3.forward, mask))
{
    // 碰到了物体
}

某些读者可能会很好奇,“与某3层碰撞”这一条件竟然用一个int就能表示。这其实是一种二进制的妙用,用一个int最多可以表示32个层的遮罩,Layer和Tag最多也只有32个,这不是巧合。如果让mask表示这3层以外的所有层,则用一个二进制的取反运算即可,其方法如下。

mask = ~mask; // 英文波浪线,代表二进制取反

mask = ~mask; // 英文波浪线,代表二进制取反

有时需要改变物体所在的层,如将一个物体设置在Default层上,其方法如下。

gameObject.layer = LayerMask.NameToLayer("Default");

可以通过函数LayerMask.NameToLayer()将层名称转化为整数表示的层,也可以用函数LayerMask.LayerToName()将表示层的整数转化为层名字。

3.2.6 射线编程详解

1. 射线碰撞信息

前文举例的函数的返回值仅仅是“是否碰到了物体”,而无法确定碰撞点是哪里,也不知道碰到的物体是哪一个。射线检测其实有着丰富的碰撞信息,如可以获取到碰撞点坐标、被碰撞物体的所有信息,甚至可以获取到碰撞点的法线(碰撞点所在物体平面的朝向)。这些丰富的碰撞信息,都被保存在RaycastHit结构体中。例如,以下几个Raycast()函数的重载可以获取到碰撞信息。 

bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float 
maxDistance);
bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float 
maxDistance, int layerMask);
bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);

    private void TestRay()
    {
        // 声明变量,用于保存碰撞信息
        RaycastHit hitInfo;
        // 发射射线,起点是当前物体的位置,方向是世界前方
        if (Physics.Raycast(transform.position, Vector3.forward, out hitInfo))
        {
            //  如果确实碰到物体,会运行到这里。没碰到物体就不会

            //  获取碰撞点的坐标(世界坐标)
            Vector3 point = hitInfo.point;
            //  获取对方的碰撞体组件
            Collider coll = hitInfo.collider;
            //  获取对方的Transform组件
            Transform trans = hitInfo.transform;
            //  获取对方的物体名称
            string name = coll.gameObject.name;

            //  获取碰撞点的法线向量
            Vector3 normal = hitInfo.normal;
        }

 以上例子基本涵盖了能从hitInfo中获取到的信息,更多碰撞信息可以查阅Raycastlift结构体的定义。

2. 其他形状的射线

射线不仅可以有长度,还可以有粗细和形状。除了前面所提到的直线射线,还有球形射线、盒子射线和胶囊体射线,如图3-7所示。

图3-7 3种形状的射线示意图

 

与发射射线类似,各种形状的射线也有很多种函数重载,以下是几种常用的重载形式。

//  球形射线:
bool SphereCast(Ray ray, float radius);
bool SphereCast(Ray ray, float radius, out RaycastHit hitInfo);

//  盒子射线:
bool BoxCast(Vector3 center, Vector3 halfExtents, Vector3 direction);
bool BoxCast(Vector3 center, Vector3 halfExtents, Vector3 direction, out 
RaycastHit hitInfo, Quaternion orientation);

//  胶囊体射线:
bool CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction);
bool CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction, 
out RaycastHit hitInfo, float maxDistance);

 可以看出,球形射线、盒子射线和胶囊体射线的发射函数与直线型射线是类似的。区别在于,球形射线需要指定球的半径;

盒子射线需要指定盒子的中心点和盒子的半边长(边长的一半),如果有必要再加上盒子的朝向;胶囊体的形状更为复杂,需要用point1、point2和radius(半径)这3个参数指定胶囊体的起点和形状。

在实践中有各种不同的需求和情况,在必要时可以进一步查阅相关资料,并对参数的用法做实际的试验。本小节的最后还会介绍射线调试的一些技巧。

3. 穿过多个物体的射线

有时需要射线在遇到第一个物体时不停止,继续前进,最终穿过多个物体。使用Physics.RaycastAll()函数可以获取到射线沿途碰到的所有碰撞信息,该函数的返回值是RaycastHit数组。

RaycastHit[] RaycastAll(Ray ray, float maxDistance);
RaycastHit[] RaycastAll(Vector3 origin, Vector3 direction, float maxDistance);
RaycastHit[] RaycastAll(Ray ray, float maxDistance, int layerMask);
RaycastHit[] RaycastAll(Ray ray);

同样,也有球形穿越射线、盒子穿越射线和胶囊体穿越射线,函数名称分别为SpherecastAll、BoxcastAll和CapsulecastAll。

4. 区域覆盖型射线(Overlap)

有时需要检测一个空间范围,例如炸弹爆炸时,范围10米之内的物体都会受到波及,那么这里需要的就不是一条射线,而是一个半径为10米的球形区域。物理系统也提供了这类函数,它们均以Physics.Overlap开头,列举如下。

Collider[] OverlapBox(Vector3 center, Vector3 halfExtents, Quaternion 
orientation, int layerMask);
Collider[] OverlapCapsule(Vector3 point0, Vector3 point1, float radius, int 
layerMask);
Collider[] OverlapSphere(Vector3 position, float radius, int layerMask);

以球形覆盖检测OverlapSphere()为例,调用该函数时,会返回原点为position、半径为radius的球体内,满足一定条件的碰撞体集合(以数组表示),而这个球体称为“3D相交球”。

5. 射线调试技巧

射线检测函数类型多、重载多、参数多,可能会让读者看得一头雾水。在实际游戏开发中,虽然这些参数不容易填写正确,但也有很好的方法可以提高编程的效率。这个方法就是使用Debug.DrawLine()函数和Debug.DrawRay()函数,将看不见的射线以可视化的形式表现出来,方便查看参数是否正确。Debug.DrawLine()函数和Debug.DrawRay()函数的常用形式如下。

void DrawLine(Vector3 start, Vector3 end, Color color);
void DrawLine(Vector3 start, Vector3 end, Color color, float duration);

void DrawRay(Vector3 start, Vector3 dir, Color color);
void DrawRay(Vector3 start, Vector3 dir, Color color, float duration);

 Debug.DrawLine()函数通过指定线段的起点、终点和颜色(默认红色),绘制一条线段;

Debug.DrawRay函数则是通过指定起点和方向向量,绘制一条射线。

两者的用法是相似的。使用时要注意,发射射线时,参数通常为起点、方向向量和长度,而DrawLine()方法用的是起点和终点。应正确使用向量加法,避免看到的线条与实际射线不一致。下面举个例子以供读者参考。

//  以一个简单的射线为例
Raycast(起点, 方向向量, 长度);

//  对应的可视化线条
DrawLine(起点, 起点+方向向量.normalized * 长度, Color.red);
//  其中nomalized是将向量标准化,即方向不变长度变为1

需要说明的是,这种绘制方法仅在开发期生效,不会出现在最终的游戏发布版中。在默认情况下,该辅助线仅在编辑器的场景窗口中可见。

如果要在Game窗口中看到它,则需要单击Game窗口右上角的Gizmos(辅助线框)按钮,而且无论怎么设置,它都不会出现在最终的游戏发布版中。

以上函数的最后一个参数,即持续时间(duration)可以省略,省略后这条参考线只出现一帧。如果在代码中每帧都绘制线条,那么就可以省略该参数。如果这个线条只出现一帧且看不清,则可以填写一个较大的持续时间(单位是秒),让射线停留在屏幕上方以便查看。

以上内容来源于《Unity3d 脚本编程与开发》 如侵告删

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

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

相关文章

游戏设计模式专栏(八):Cocos中最常见的设计模式之一

点击上方亿元程序员关注和★星标 引言 大家好,我是亿元程序员,一位有着8年游戏行业经验的主程。 本系列是《和8年游戏主程一起学习设计模式》,让糟糕的代码在潜移默化中升华,欢迎大家关注分享收藏订阅。 组合模式是一种在Cocos…

前端JavaScript入门到精通,javascript核心进阶ES6语法、API、js高级等基础知识和实战 —— JS进阶(三)

思维导图 1.编程思想 1.1 面向过程编程 1.2 面向对象编程 (oop) 2. 构造函数 3. 原型 3.1 原型 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IE…

精品Python的农村振兴平台防贫助农

《[含文档PPT源码等]精品Python的农村振兴平台设计与实现-爬虫》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等&#xff01; 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;python 使用框架&#xff1a;Django 前端技术&#xff1a;J…

LeetCode【279】完全平方数

题目&#xff1a; 思路&#xff1a; https://www.acwing.com/solution/leetcode/content/114877/ 1、给定数字&#xff0c;是由若干个完全平方数组合而成&#xff0c;求使用的最小的完全平方数的个数&#xff0c;如果这些完全平方数已知&#xff0c;则完全等同于百元百鸡问题…

adobe firefly image2重磅发布

萤火虫图像2&#xff08;Firefly Image2&#xff09;是由adobe的一种新的图像生成模型。它是萤火虫图像的改进版本&#xff0c;具有以下特点&#xff1a; 更逼真的图像&#xff1a;萤火虫图像2使用了更先进的图像生成技术&#xff0c;能够生成更逼真的图像。更丰富的细节&…

codesys【手轮】

一般4线&#xff0c;也有6线 电压&#xff1a;DC5v&#xff0c;12v&#xff0c;24v 脉冲当量&#xff1a;一圈100脉&#xff0c;25脉 计数器不能【-1000】【1000】 因为一循环会多一个计数 要【-1000】【999】或者【-999】【1000】 PLC计数案例&#xff1a; // QQ750273008…

AndroidStudio模拟器,没有Google Play的就有ROOT权限

正确选择版本 测试 D:\>adb shell emulator64_x86_64:/ $ su emulator64_x86_64:/ #

selinux相关学习笔记-简单selinux部分的解决

selinux问题判断&#xff1a; 1 日志查看&#xff1a; logcat -b all 查看所有日志 如果自己程序有类似如下的avc:denied打印&#xff0c;基本上可以认为有selinux问题&#xff0c;这里有avc: denied相关的关键字 I Thread-2: type1400 audit(0.0:53): avc: denied { search }…

1688拍立淘API接口分享

拍立淘接口&#xff0c;顾名思义&#xff0c;就是通过图片搜索到相关商品列表。通过此接口&#xff0c;可以实现图片搜索爆款商品等功能。 接口地址&#xff1a;1688.item_search_img 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&…

C++11新特性(右值引用,万能转发)

这篇文章是C的重中之重&#xff0c;通过这篇文章你能体会到C/C大佬们对性能的极致追求&#xff0c;你能感受到独属C/C人的浪漫&#xff0c;对高消耗的零容忍&#xff0c;对高性能的不倦探索。右值引用是由Scott Meyers在他的著名书籍《Effective C》中提出的&#xff0c;因为其…

【软件测试学习】—软件测试知识点总结(二)

【软件测试学习】—软件测试的分类&#xff08;二&#xff09; 一、软件测试的分类 二、软件的生命周期 三、软件测试的工作流程 四、软件测试用例设计方法 &#xff08;一&#xff09;、等价类划分 定义&#xff1a;等价类划分是一种典型的、重要的黑盒测试的方法&#xff…

Python:如何在一个月内学会爬取大规模数据

Python爬虫为什么受欢迎 如果你仔细观察&#xff0c;就不难发现&#xff0c;懂爬虫、学习爬虫的人越来越多&#xff0c;一方面&#xff0c;互联网可以获取的数据越来越多&#xff0c;另一方面&#xff0c;像 Python这样的编程语言提供越来越多的优秀工具&#xff0c;让爬虫变得…

Python+Tkinter 图形化界面基础篇:多线程和异步编程

PythonTkinter 图形化界面基础篇&#xff1a;多线程和异步编程 引言为什么需要多线程和异步编程&#xff1f;使用多线程多线程示例步骤 1 &#xff1a;导入必要的模块步骤 2 &#xff1a;创建主窗口和按钮步骤 3 &#xff1a;创建下载线程步骤 4 &#xff1a;启动主事件循环 使…

SIT1050,可替代TIJA050,5V 供电,±40V 接口耐压,1Mbps 高速 CAN 总线收发器

SIT1050 是一款应用于 CAN 协议控制器和物理总线之间的接口芯片&#xff0c;可应用于卡车、公交、 小汽车、工业控制等领域&#xff0c;速率可达到 1Mbps &#xff0c;具有在总线与 CAN 协议控制器之间进行差分信 号传输的能力。 特点 ➢ 完全兼容 “ ISO 11898 ” 标…

谷粒商城中消息队列的使用

目录 一、概述 二、步骤 三、说明 四、详细步骤 五、总结 一、概述 在订单服务中使用到了消息队列 具体就是解决关单还有自动解锁库存的功能 其实就是使用消息队列的延迟队列的功能 达到一个定时任务的作用 使用消息队列到达最终一致性的效果 比如说库存 当下单之后 …

超强大的 Nginx 可视化管理平台 Nginx-Proxy-Manager

一、简介 Nginx-Proxy-Manager 是一个基于 Web 的 Nginx 服务器管理工具&#xff0c;它允许用户通过浏览器界面轻松地管理和监控 Nginx 服务器。通过 Nginx-Proxy-Manager&#xff0c;可以获得受信任的 SSL 证书&#xff0c;并通过单独的配置、自定义和入侵保护来管理多个代理…

通过线程池方式改造Stream.parallel()并行流

目录 一、IntStream.rangeClosed并行流二、线程池方式改造1、创建线程池2、线程类3、信心满满&#xff0c;走起来 三、再次解决并发时i原子性问题四、并行流与多线程1、并行和并发的区别&#xff1f;2、并行和并发的使用场景 大家好&#xff0c;我是哪吒。 上一篇简单聊一聊公…

从解决问题到人生规划

从解决问题到人生规划&#xff0c;如何通过深度思考&#xff0c;让自己成为这个世界上最顶级的人才&#xff1f; 我们对于问题的理解一般有6个层次&#xff0c;每个层次的深度不同&#xff0c;决定了我们思考的深度和看问题的眼界。 首先&#xff0c;来想象这样一个场景&#x…

graphviz 绘制二叉树

代码 digraph BalancedBinaryTree {node [fontname"Arial", shapecircle, stylefilled, color"#ffffff", fillcolor"#0077be", fontsize12, width0.7, height0.7];edge [fontname"Arial", fontsize10, color"#333333", arr…

上海亚商投顾:沪指冲高回落 医药、芯片股全天领涨

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日小幅反弹&#xff0c;创业板指盘中涨超1.6%&#xff0c;午后涨幅有所收窄。医药医疗股全线走强&#…