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