目录
Ray/Raycast/Linecast//OverlapSphere简介
Ray类
Physics.Raycast方法
应用1:实现鼠标点击出射线并检测物体
应用2:实现鼠标点击拖拽物体
Physics.Linecast和Physics.OverlapSphere
应用3:进入范围时触发攻击
Ray/Raycast/Linecast//OverlapSphere简介
射线(Ray类)在三维世界中从一个点沿一个方向发射的一条无限长的线,它的主要应用,例如:
1. 在之前的文章unity oculus手柄射线中,使用从手柄射出的射线去点击VR中的UI按钮;
2. 在普通3D游戏中,用鼠标或触摸的方式去拖拽、点击物体(构建一条从屏幕向物体的射线);
3. 其他:射击中的瞄准器、从某一物体出发划出一个范围、等等……
Raycast是物理系统中的一个方法,是用射线的形式检测在射线通过的道路上是否有碰撞体存在,就像以前做过的激光门伤害小游戏一样。同样类型的还有RaycastAll
Linecast和OverlapSphere是物理系统中的线性检测和球型检测,检测在线性或球形范围内是否有碰撞体,Linecast返回布尔值,OverlapSphere返回一个数组。
Ray类
射线的两个重要参数就是起点(origin)和方向(direction)。Ray类的带参构造是public Ray(Vector3 origin, Vector3 direction) ,也就是我们在创建一条射线时需要传起点和方向两个参数。另外,这个类中只有一个方法GetPoint(),用于返回一个射线上的点。方法定义如下:
public Ray(Vector3 origin, Vector3 direction)
{
m_Origin = origin;
m_Direction = direction.normalized;
}
public Vector3 GetPoint(float distance)
{
return m_Origin + m_Direction * distance;
}
Physics.Raycast方法
作用:在场景中发射一条射线,检测其是否与碰撞器碰撞。
定义:(居然有15个重载)语法如下
public static bool Raycast(Ray ray)
public static bool Raycast(Vector3 origin, Vector3 direction)
public static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance)
//激光门中用过:
public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask)
重要参数说明:
参数 | 作用 |
---|---|
origin | 射线起点坐标(世界),Vector3 |
direction | 射线方向,Vector3 |
distance | 射线长度,float |
layermask | 选定的检测层(其他层不检测),int |
hitInfo | 检测到的第一个对象信息,RaycastHit类 |
其中的hitInfo参数是RaycastHit类,用于存储被检测到的第一个对象的信息,包括:transform、rigidbody、collider、distance(从射线起点到与碰撞对象的交点的距离 )、point(交点的坐标)、normal(射线入平面的法向量)
Physics.Line
应用1:实现鼠标点击出射线并检测物体
在应用中,我们经常会有用鼠标或者手柄去点击并拾取物体的需求,那就需要从屏幕(也就是Camera)或者手柄的位置发出一条射线到物体。下面就设置了捕捉鼠标左键的点击,并从主camera发射一条射线,方向是鼠标点击的位置,再用RaycastHit检测是否射到了敌人(敌人加入Enemy层)。update()中的代码如下:
Update()
{
if(Input.GetMouseButtonDown(0))
{//如果有鼠标左键的点击
//定义一个射线ray,设置射线从主摄像机到鼠标位置
Ray ray=Camera.main.ScreenPointToRay(Input.mousePosition);
Tools.DrawLine(transform, ray.origin, ray.direction, Color.white, 0.05f);//用Tools工具中的DrawLine方法将这条线画出来
//测试Raycast
Physics.Raycast(ray, out RaycastHit hit, 100, LayerMask.GetMask("Enemy","UI"));
if(hit.collider != null)
{
Debug.LogError("打中敌人了!");
}
}
}
效果如下图,可以看到每次射线点到红色敌人时,左下角都会有文字提示。
应用2:实现鼠标点击拖拽物体
实现过程:
第一步:从Camera向鼠标点击方向做一条射线(和应用1一样);
第二步:射线检测被拖拽物体;修改一下上面的Update()
private Transform clicked; //被鼠标点击的物体
void Update()
{
if(Input.GetMouseButtonDown(0))
{//鼠标左键点击,产生一条射线
//测试Ray,设置射线从主摄像机到鼠标位置
ray=Camera.main.ScreenPointToRay(Input.mousePosition);
Physics.Raycast(ray, out RaycastHit hit, 100, LayerMask.GetMask("object"));
//这里检测object层
if(hit.collider != null)
{
clicked = hit.transform; //射线检测到的物体之后被鼠标拖拽
}
}
if(Input.GetMouseButton(0))
{//如果收到鼠标左键持续点击,实现拖拽物体
if(clicked!= null)
{
Vector3 pos=Tools.MouseToWorld(clicked.position);//第三步实现的方法
clicked.position=new Vector3(pos.x,clicked.position.y,pos.z);
//物体高度(y)不变
}
}
if(Input.GetMouseButtonUp(0))
{//如果鼠标左键抬起,释放物体
clicked= null;
}
}
第三步:如果检测到鼠标左键持续点击,则将鼠标位置转化成三维空间位置赋给物体;
*注:应用2重点是鼠标位置转换,鼠标的点击位置是一个二维的屏幕位置,需要将之转换为三维空间的位置,这是一个需要经常用到的转换,可以将这个方法放入Tools.cs工具集中。以下就是转换的代码,看似转来转去非常复杂,可以参照文章Unity Camera的ScreenToWorldpoint API的实现了解原理。
//在Tools工具集中加入一个鼠标坐标转换的工具
//将鼠标在屏幕上点击的二维坐标转换为空间三维坐标
public static Vector3 MouseToWorld(Vector3 target)
{//需要传入一个被点击物体的位置,因为鼠标的坐标原本只有XY,没有Z轴
//将target的世界坐标转换为屏幕坐标
Vector3 targetScreen=Camera.main.WorldToScreenPoint(target);
//将鼠标在屏幕上的XY位置加上target的z位置,得到鼠标的屏幕位置
Vector3 curseScreenPos=new Vector3(Input.mousePosition.x, Input.mousePosition.y, targetScreen.z);
//最后将鼠标屏幕位置转为世界位置
Vector3 curseWorldPos=Camera.main.ScreenToWorldPoint(curseScreenPos);
return curseWorldPos;
}
效果如下:
Physics.Linecast和Physics.OverlapSphere
这两个API都是在线性或球形范围中检测碰撞体,Linecast可以用于射击游戏,而OverlapSphere之前在小游戏坦克对战中使用过。它们的语法如下:
//线性检测
public static bool Linecast(Vector3 start, Vector3 end, out RaycastHit hitInfo)
public static bool Linecast(Vector3 start, Vector3 end, int layerMask)
//球形检测,返回一个数组,列出所有被检测到的碰撞器
public static Collider[] OverlapSphere(Vector3 position, float radius)
public static Collider[] OverlapSphere(Vector3 position, float radius, int layerMask)
应用3:进入范围时触发攻击
简单做一个类似与以前做过的“坦克对战”效果。新建一个挂在主角Hero上的脚本OverlapSphere.cs
public class OverlapSphere : MonoBehaviour
{
private Collider[] colliders;
private GameObject bullet;
void Update()
{
colliders=Physics.OverlapSphere(transform.position, 2, LayerMask.GetMask("Enemy"));
//设定圆心、半径、检测层
foreach(var collider in colliders)
{
bullet = Resload.Instance.LoadPrefab("Bullet");//加载子弹
bullet.transform.position = transform.position;
bullet.transform.LookAt(collider.transform);
}
}
}
效果如下: