详解Unity中的射线与射线检测
前言
碰撞检测可以帮助我们实现诸如抵达某个地点自动触发剧情、判断子弹是否击中玩家等功能,但我如果想要实现如当鼠标悬浮某个人物上,自动弹出该人物信息,要如何判断呢?这时使用碰撞检测,从摄像机生成一个透明碰撞体朝着人物移动,等碰撞到了人物再弹出该人物信息?会不会太繁琐了。或许你又会想,若我直接生成一个足够长的透明碰撞体呢,是不是在创建的那一刻就可以触发该人物的弹出信息逻辑?没错这样的确可以,而这就是射线!不过是把无限长的透明碰撞体变为了无限长的一条线,仅此而已。
目录
- 前言
- 一、创建并使用射线
- 二、让射线现出原形
- 三、Unity中的层级
- 四、应用实例
- 1.被指到的坏人会变红,而好人不会
- 2.让射线穿透检测
- 3.让射线检测多个
- 4.鼠标悬浮显示场景物体名称
- 5.鼠标点击移动
- 五、总结和参考资料
- 1.总结
- 2.参考资料
前排提醒:本文仅代表个人观点,以供交流学习,若有不同意见请评论留言,笔者一定好好学习,天天向上。
阅读此文章时,若有不理解的地方,推荐观看本文列出的参考资料来对照阅读。
了解射线检测前最好对碰撞检测有所了解,详情可参考我写的关于刚体与碰撞体的文章。
Unity版本[2019.4.10f1] 梦小天幼 & 禁止转载
视频讲解:
详解Unity的射线检测Raycast_BiLiBiLi
一、创建并使用射线
射线和碰撞检测不同的地方在于,刚体和碰撞体是作为组件添加的,代码处理碰撞逻辑。而射线则是纯代码处理的,Unity的物理引擎为我们提供了相关射线类以及函数接口,我们需要自行调用这些API来实现一条射线。
射线检测:从某个初始点开始,沿着特定的方向发射一条不可见且无限长的射线,通过此射线检测是否有任何模型添加了Collider碰撞器组件。一旦检测到碰撞,停止射线继续发射。
Collider组件中Is Trigger选项的开关并不影响射线检测
! 对了还有一个参数,写在Raycast末尾,QueryTriggerInteraction(指定该射线是否应该命中触发器),上面我说过Is Trigger选项的开关不影响射线检测,但是前提是QueryTriggerInteraction该参数设置为检测触发器了,你也可以将该参数设置为仅对碰撞器进行检测,这个参数可以全局设置。
(Physics.Raycast一共有16个重载方法,自行组合吧,今天只拿两个举例子)
Physics.Raycast(origin(V3), direction(V3), hitInfo(RaycastHit), distance(float), LayerMask(int));
射线的发射点、具体方向、碰撞信息(结构体)、距离(可选,不写默认无限长)、碰撞层(可选,不写默认检测所有层)
//第一个简单小栗子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,来获取该碰撞器的游戏对象的名字。}}
//第二个简单小栗子void Update(){RaycastHit hitInfo;if (Physics.Raycast(transform.position, transform.forward, out hitInfo)){Debug.Log(hitInfo.collider.gameObject.name);}}
本例和上一个基本差不多,唯一差别在于没有声明射线,而是直接把射线的起始点和终点作为参数赋予Raycast了。个人认为第一种比较好,声明好射线之后,如果后续还要用,可以直接拿来用了。这就相当于用speed存速度,后续要使用或修改,直接改speed即可。
关于RaycastHit结构体相关参数,参考下图(截取自Unity圣典API)
二、让射线现出原形
刚才这个例子可能不够直观,是因为这条射线是隐形的,读者只能自行根据动作来脑补,哦!原来那里有一条射线啊。有时候开发者想要这条射线显示出来,以便更好的调试游戏。而Unity中刚好能满足开发者的需求,在场景窗口让射线现出原形。
Uniyt中通过使用Debug.DrawLine()和Debug.DrawRay()都可以让射线现出原形。本例使用DrawLine
但是!需要特别注意的是,这里画出的线其实跟射线毫无关联的,因为就算没有射线,这里也能画出线来。两点一线,只要确定两个点就行了。所以这里的线只是辅助开发者而已。
但是!又需要特别注意的是,如果你的射线不显示的话,估计是因为!我就是刚刚不小心把它关了,然后挠头找不到原因,我已经犯了好几次这样的错误了!!!!!这个按钮主要是用于显示和关闭场景中的辅助图形之类的(如灯光,射线、摄像机等)!
//在原有代码的基础上,加上这句话即可(加在IF中,因为也需要每帧绘制)Debug.DrawLine(transform.position,hitInfo.point,Color.yellow);
//Debug.DrawRay(transform.position, transform.position + transform.forward * 10, Color.yellow);
这两者的不同点在于
- DrawLine :真正的两点确定一条线
- DrawRay :从初始点出发画一条射线,所以需要一个初始点,加上一个具有方向和长度的向量,就得到了一条射线。
起始点我们可以使用transfrom.position,终点呢,我们就用上面声明的hitInfo结构体中的point,该属性存储了射线碰撞到碰撞器的碰撞点,也是一个V3类型向量
三、Unity中的层级
Unity中,层有着很大的用途,比如控制摄像机仅渲染指定层来节省资源,控制光源仅照亮哪些层等,当然层也可以控制射线仅检测某些碰撞器而忽略其他的碰撞器,下面一节的应用实例就讲解的非常详细。
这里关于层级的知识需要用到位操作,以及其他乱七八糟的东西,最重要的是,笔者不会,所以就不写了。空在这里,日后补上或者新开一篇补上。
注意:第 31 层为 Editor 的预览窗口内部机制使用,为了防止冲突,请勿使用此层。(来自官方API的提醒)
完了…不认识层这个字了,层层层层层层层层层层层层层层层层层层层层层层层层层层层层层层;好了,现在你也不认识了hhhh
四、应用实例
其实射线检测内容并不是很多,反正肯定没碰撞检测的内容多,但是要想实际应用起来,也挺简单的…hhh,这里举几个简单的小栗子
1.被指到的坏人会变红,而好人不会
两个注意点,好人和坏人的区分,被指到的物体需要更换材质
代码参考与动图演示:
public LayerMask mask;public Material enemyMaterial;//用于存储需要检测的层、需要更换的材质void Update(){Ray ray = new Ray(transform.position, transform.forward);RaycastHit hitInfo;if (Physics.Raycast(ray, out hitInfo, Mathf.Infinity, mask))//这里的Mathf.Infinity代表射线无限长,省略不写也可以{hitInfo.collider.gameObject.GetComponent<Renderer>().material = enemyMaterial;//通过hitInfo存贮的碰撞信息来获取实际对象的Renderer组件,然后更换其材质。Debug.DrawLine(transform.position, hitInfo.point, Color.yellow);}else{//当射线没有检测到与物体碰撞则画一条蓝色线条,检测到了则画黄色线条,仅起到辅助作用。Debug.DrawRay(transform.position, transform.forward * 10, Color.blue);}}
2.让射线穿透检测
大家看上一个例图,似乎有一个坏人被前面的挡住了,导致最后没有变红,这里我们可以用一行代码解决这个问题,坏人被挡住的原因是因为在他前面还有一个坏人,那么!把前面那个坏人变成好人,让射线忽略他,不就能检测到后面的坏人了吗?所以原理就是,把已经检测到的物体换一个层即可。
这里放上代码和演示动态图
代码就没必要全贴上了,关键的核心代码如下:(这句话放到IF判断中,当判断发生碰撞时,就把当前碰撞物体的层改掉,这样射线就会忽略,所以就实现了这个穿透功能)
hitInfo.collider.gameObject.layer = 10;// 这里的数字10代表第十层,没错!LayerMask使用数字来表示层的。
3.让射线检测多个
通过上述学习我们知道可以通过RaycastHit结构体获得检测到的碰撞体,但是似乎每次只能返回一个,我们如果想要所有已经检测到的碰撞体的合集该怎么办呢?聪明的读者可能想到了,我们可以声明一个RaycastHit结构体数组,在if判断中,将每次检测到的值存入数组中。事实上,Unity也想到了Raycast的弊端,为此它提供了RaycastAll(),这个函数所需要的RaycastHit结构体就是一个结构体数组。
//篇幅有限,这个就不放演示图了,没啥演示的意义private void Update(){Ray ray = new Ray(transform.position, transform.forward);RaycastHit[] hitInfos;//声明了一个RaycastHit结构体数组hitInfos = Physics.RaycastAll(ray);//注意!Raycast是返回bool值,而这里则是返回一个数组Debug.DrawRay(transform.position, transform.forward * 100);//这里遍历输出检测到的游戏对象名字for (int i = 0; i < hitInfos.Length; i++){Debug.Log(hitInfos[i].collider.gameObject.name);}}
4.鼠标悬浮显示场景物体名称
原理很简单,从摄像机发射一条射线,然后获取碰撞物体的对象的名字即可
需要用到:Camera.ScreenPointToRay(position);
这个函数的作用是发射并返回一条射线,射线从相机的近裁剪面出发,穿过屏幕的XY(这里使用的当前鼠标的屏幕坐标XY)。
这里注意,我特意从摄像机出发画了一条辅助线,注意这条线只有在鼠标指在物体上后才会出现,具体看代码吧
using UnityEngine;
using UnityEngine.UI;
public class MouseShow : MonoBehaviour
{private LayerMask mask;private Text show;void Start(){//通过Start来给文本组件和需要检测的层初始化mask = LayerMask.GetMask("we");show = GameObject.Find("show").GetComponent<Text>();}void Update(){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hitInfo;if (Physics.Raycast(ray, out hitInfo, Mathf.Infinity, mask)){show.text = hitInfo.collider.gameObject.name;//把获取到的碰撞组件的对象的名字显示到UI组件上Debug.DrawLine(Camera.main.transform.position, hitInfo.point);//用于演示的线,不用管}else{show.text = "当前无物体信息";}}
}
5.鼠标点击移动
这个原理很简单,但是代码有点小绕。具体就是当你鼠标点击时,从摄像机发射一条射线,穿过鼠标的屏幕坐标XY位置,然后抵达世界空间,当检测到设置的层时(也就是地面),将当前碰撞点保存,调用Move函数,然后移动当前物体到这个位置。
private bool isNextMove = false;private LayerMask mask;private Vector3 point;void Start(){mask = LayerMask.GetMask("plane");}void Update(){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hitInfo;if (Input.GetMouseButtonDown(0))//当鼠标点击时,才触发射线检测{if (Physics.Raycast(ray, out hitInfo, mask))//当检测到地面{isNextMove = true;point = hitInfo.point;//将isNextMove设为true,然后保存当前撞击点位置}}if(isNextMove == true)//当isNextMove为真,则不停调用Move{Move(point);}}void Move(Vector3 pos){//使用Vector3的插值函数来移动位置transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * 3.0f);if (transform.position == pos)//当目标抵达位置的时候,将isNextMove置为false,等待下一次移动指令isNextMove = false;}
这个例子还是有不足之处和Bug的,比如没有写转向,导致完全是平移,还有就是如果地面处于倾斜状态,当前物体依旧是90度垂直,看起来很不和谐。读者自行补充吧,我好懒啊。。
五、总结和参考资料
1.总结
- 想要一个物体被射线检测到,就必须加上Collider组件,Is Trigger并不影响射线检测
- 生成一个射线需要五个参数,起始点,方向,碰撞信息结构体,范围距离,检测层
- 起始点一般使用transform.position来表示
- 方向一般使用transform.forward,表示当前物体正前方
- 碰撞信息使用RaycastHit结构体来存贮,需要事先声明
- 检测范围是float型,可不写,默认无限长
- 检测层可不写,默认全检测
- 如果你使用Debug.DrawLine()画了一条线而没有显示出来,不妨看看Gizmos有没有被关闭
- Raycast返回值是Bool,而RaycastAll返回值则是RaycastHit结构体数组
- 混淆点
- Ray是一个类,表示射线
- RaycastHit是一个结构体,记录射线的碰撞信息
- Raycast和RaycastAll是函数,使用射线来检测碰撞,通过Physics来调用
2.参考资料
[1]Karmotrine.简析Unity射线检测的概念与应用(知乎)
[2]BeaverJoe.【中文专题】Raycast射线检测在3D世界中的介绍(bilibli)
[3]梦天幼.详解Unity的几种移动方式实现(csdn)
[4]黑色最低调的奢华.Unity之射线穿透(csdn)
[5]游戏蛮牛.[蛮牛教程] Unity3D 浅析-Camera(摄像机)
[6]佚名.[Unity圣典 & Unity官方API]
详解Unity中的射线与射线检测相关推荐
- 详解Unity中的粒子系统Particle System (七)
前言 本篇来讲一讲Collision和Triggers模块,这两个模块主要用于粒子系统与物理世界的交互,一个是碰撞器,另一个是触发器.有了这两个模块我们又可以做出更炫酷的粒子效果啦! 目录 前言 本系 ...
- 详解Unity中的粒子系统Particle System (十二 | 终)
前言 终于来到了最后一篇,粒子系统宣告终结!这十来篇博客删删改改写了半个多月,真是离谱.今天该讲案例与粒子系统的应用,那么我们就进入正题吧! 目录 前言 本系列提要 一.如何做出效果 二.案例演示 1 ...
- 详解Unity中的粒子系统Particle System (二)
前言 上一篇我们简要讲述了粒子系统是什么,如何添加,以及基本模块的介绍,以及对于曲线和颜色编辑器的讲解.从本篇开始,我们将按照模块结构讲解下去,本篇主要讲粒子系统的主模块,该模块主要是控制粒子的初始状 ...
- 详解Unity中的粒子系统Particle System (四)
前言 上一篇我们主要讲解了Shape形状模块,以及Emission发射模块,这是粒子系统中最为重要的两个模块,学会了它们我们足以做出惊艳的效果,比如霰弹枪的发射,蘑菇蛋的冲击波等等.本篇讲的内容依旧重 ...
- 详解Unity中的粒子系统Particle System (十一)
前言 今天讲最后一个模块--External Forces,该模块需要和Force Field组件配合使用,作用是模拟物理的力对粒子的影响.至此,Unity内置粒子系统的最后一块物理砖补齐,理论上来讲 ...
- 详解Unity中的粒子系统Particle System (八)
前言 书接上回,咱们继续讲粒子系统,本篇该说Inherit Velocity和Sub Emitters这两个模块,依旧是重量级选手,特别是Sub Emitters这个模块,有了它,我们的粒子效果可以做 ...
- 详解Unity中的粒子系统Particle System (九)
前言 今天讲Texture Sheet Animation模块,先前我们已经讲了很多很多模块,通过上述模块可以实现很酷的效果,但是缺了一点真实感.比如说爆炸特效,仅指望单独的粒子来模拟真实的爆炸效果是 ...
- 详解Unity中的车轮碰撞器Wheel Collider
前言 碰撞体组件定义了物体的物理性状,Unity中有一系列基本碰撞体,我们可以使用它们组合成任意形状,但除了基本碰撞体之外,Untiy还提供了诸如车轮(Wheel).网格(Mesh).地形(Terra ...
- 详解Unity中的粒子系统Particle System (三)
前言 上一篇我们详细讲解了有关主模块的全部内容,已经对粒子系统的基本运作有了足够的了解,本篇就来讲一下被粒子系统默认启用的Emission.Shape.Renderer模块又在粒子系统中扮演着怎么样的 ...
- 详解Unity中的Nav Mesh新特性|导航寻路系统 (二)
前言 第一篇我们简要概述了新版NavMesh的一些新增功能,以及旧版的对比.本篇我们来讲一讲NavMeshSurface.NavMeshLink这两个组件的参数以及如何应用,接下来就进入正题吧. 本系 ...
最新文章
- dir在python中什么意思_python中dir是什么
- Prettier 1.15代码格式化工具新增Angular和Vue.js支持
- Resource interpreted as Stylesheet but transferred with MIME type text/html: “**.jsp“,主要因为拦截器将所有资源拦截
- TensorFlow for Hackers - Part III
- Boost:宏BOOST_NO_RTTI的使用实例
- easyui中checkbox全选全不选的时不处理最后一行的实现
- 【kafka】kafka 控制台 消费报错 cant rebalance afer 4 retries
- Spring在tomcat下使用JTA事务
- 如何在SQL Server中自动进行表分区
- python的setting怎么找_vscode配置python之settings.json 智能提示
- java string常见操作题
- 怎么用小学生计算机玩游戏,小学生不应该玩电脑游戏作文
- Protobuf, understand the hood
- [Mac]卸载诺顿(Norton)后安装Avast提示检测到冲突的解决方案
- LQ-1600K打印机色带传动故障分析
- 还在浪费时间创建工程图?6分钟教你自定义SOLIDWORKS工程图模板
- 理论篇-Linux---RAID磁盘阵列与阵列卡
- 查看计算机启动项命令,运行命令开机启动项msconfig命令简介及教程
- 微信公众号接入百度天气API接口实现代码
- Ceres Solver实现简单的光束法平差
热门文章
- SoapUI安装教程,亲测可用
- Android-SDK下载及安装配置教程
- [转]PKM2:优秀的个人知识管理工具
- python123.io在线编程,详解Python IO编程
- linux读usb转ttl串口数据,ubuntu16.04使用USB转TTL(ch340)串口转usb调试过程
- 股票历史数据下载-A股所有股票的历史数据下载
- python颜色识别_python颜色识别
- 串口调试助手fx2n_串口调试助手发送控制台达PLC命令
- Mybatis源码深度解析
- 数字信号处理-02- FPGA常用运算模块-加减法器和乘法器