参考
Unity - 射线检测
https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.html
Unity 基础 之 Ray 射线简单介绍和使用忽略碰撞层的时候的注意事项(记得添加距离,不然 layer mask 可能无效)
一、射线检测图解
参考
浅析射线检测 raycast 的使用 !Cocos Creator 3D !
首先,我们看到的视角是这样子的。假设我们点击其中屏幕中的一个位置(图中的红点点)。
因为这个视角是摄像机提供的,我们就把这个点点和摄像机组合一条射线。
接着,检查这条射线穿过了那些物体,这些物体中可能就有我们点击的对象。
也可以这么理解,你用眼睛看着一块区域,伸出手指。你可以看到手指头挡住了一点视线,从你的视线做经过手指这个点画一条射线,这个射线穿过的物体,就刚好是你想要点击的物体。
二、Physics.Raycast
https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.Raycast.html
1.Raycast 重载方法参数
public static bool Raycast (
Vector3 origin,
Vector3 direction,
float maxDistance= Mathf.Infinity,
int layerMask= DefaultRaycastLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
public static bool Raycast (
Vector3 origin,
Vector3 direction,
out RaycastHit hitInfo,
float maxDistance,
int layerMask,
QueryTriggerInteraction queryTriggerInteraction
);
public static bool Raycast (
Ray ray,
float maxDistance= Mathf.Infinity,
int layerMask= DefaultRaycastLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
public static bool Raycast (
Ray ray,
out RaycastHit hitInfo,
float maxDistance= Mathf.Infinity,
int layerMask= DefaultRaycastLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
- origin 射线在世界坐标系中的起点。
- direction 射线的方向。
- maxDistance 射线应检查碰撞的最大距离。
- layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。
- queryTriggerInteraction 指定该查询是否应该命中触发器。
- ray 光线的起点和方向。
- hitInfo 如果返回 true,则 hitInfo 将包含有关最近的碰撞体的命中位置的更多信息。(另请参阅:RaycastHit)。
2.layerMask
设置物体的Layer层级,在摄像机中设置camera.cullingmask,可以控制摄像机的渲染层级,用在射线上,可以控制射线碰撞什么,不碰撞什么。
1 << 9 打开第9层。
~(1 << 9) 打开除了第9之外的层。
~(1 << 0) 打开所有的层。
(1 << 10) | (1 << 8) 打开第10和第8的层。
或者使用层名称
1 << LayerMask.NameToLayer("Cube"); // 等价于 (1 << 9)
~(1 << LayerMask.NameToLayer("Cube"); // 等价于 ~(1 << 9)
3.RaycastHit
https://docs.unity.cn/cn/2019.4/ScriptReference/RaycastHit.html
- barycentricCoordinate 命中的三角形的重心坐标。
- collider 命中的 Collider。
- distance 从射线原点到撞击点的距离。
- lightmapCoord 撞击点处的 UV 光照贴图坐标。
- normal 射线命中的表面的法线。
- point 世界空间中射线命中碰撞体的撞击点。
- rigidbody 命中的碰撞体的 Rigidbody。如果该碰撞体未附加到刚体,则值为 /null/。
- textureCoord 碰撞位置处的 UV 纹理坐标。
- textureCoord2 撞击点处的辅助 UV 纹理坐标。
- transform 命中的刚体或碰撞体的 Transform。
- triangleIndex 命中的三角形的索引。
三、Physics.OverlapSphere
参考
https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.OverlapSphere.html
public static Collider[] OverlapSphere (
Vector3 position,
float radius,
int layerMask= AllLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
- position 球体的中心。
- radius 球体的半径。
- layerMask Layer mask 定义要在查询中包括哪些碰撞体层。
- queryTriggerInteraction 指定该查询是否应该命中触发器。
返回
Collider[] 返回一个数组,其中包含与球体接触或位于球体内部的所有碰撞体。
描述
计算并存储接触球体或位于球体内部的碰撞体。
注:overlap 有重叠、覆盖之意。
1.攻击范围
参考
人物角色群体攻击判定(一)
人物角色群体攻击判定二(叉乘来判断敌人的位置)
人物角色群体攻击判定(三)Physics.OverlapSphere(群体攻击)
人物角色群体攻击判定四(三角区域判断)
叉乘实现角色和敌人的位置判断(左上,左下,右上,右下)
- 方式一使用触发器:当敌人进入攻击区域就加入一个集合当中, 退出攻击区域就从集合中删除. 玩家点击攻击对集合中的敌人进行伤害
- 方式二根据玩家和敌人的坐标, 进行叉乘来获取一个向量可以用它来判断敌人的位置, 敌人是否在攻击范围内。这种方式有一种重大的BUG, 假设我把敌人大小增加100倍, 很显然玩家已经在敌人的体内了. 我们是通过坐标来判断敌人是否可以攻击, 跟敌人的体积大小无关系. 所以攻击的距离是跟敌人体积的大小所决定的。
- 方式三使用Physics.OverlapSphere来检测,不方便调试, 其他都可以。
- 方式四计算三角形的角度来判定
//检测敌人
public void CheckEnemy()
{
Collider[] cols = Physics.OverlapSphere(this.transform.position, attackRange);
if (cols.Length > 0)
{
foreach (var i in cols)
{
Debug.Log(i.gameObject.tag);
if (i.gameObject.CompareTag("Enemy"))
{
//检测到敌人在主角以attackRange半径的圆里面
}
}
}
}
2.layerMask参数
参考
浅析UnityAPI【Physics.OverlapSphere】及其技巧
//代码
public Transform OverlapSphereCube;
public float SearchRadius;
//假设 SearchRadius表示的相交球的检测半径值,大到足够覆盖到Cube4
void Start()
{
SearchNearUnits();
}
public void SearchNearUnits()
{
Collider[] colliders = Physics.OverlapSphere(OverlapSphereCube.position, SearchRadius);
if(colliders.Length <= 0) return ;
for (int i = 0; i < colliders.Length; i++)
print(colliders[i].gameObject.name);
}
输出Cube的名字是4个还是5个呢?因为我们这里没有用到LayerMask这个参数(后面会讲),所以默认是返回一定半径内所有的碰撞体集合,当然也包括自身了。所以实际上应该是5个才对。
现在,Cube1、Cube2的Layer是Team1,Cube3、Cube4的Layer是Team2:
//代码
public Transform OverlapSphereCube;
public float SearchRadius;
//假设 SearchRadius表示的相交球的检测半径值,大到足够覆盖到Cube4
void Start()
{
SearchNearUnits();
}
public void SearchNearUnits()
{
Collider[] colliders = Physics.OverlapSphere(
OverlapSphereCube.position,
SearchRadius,
1 << LayerMask.NameToLayer("Team1")
);
if(colliders.Length <= 0) return ;
for (int i = 0; i < colliders.Length; i++)
print(colliders[i].gameObject.name);
}
LayerMask.NameToLayer(string layerName)的作用是将指定层的“名称”字符串转换成 对应的Int 型的LayerMask码。现在,只会检索到Team1层级的Cube1、Cube2
3.实现AOE(范围)伤害,例如手雷爆炸的范围伤害
public void Grenade_AOE_Damage(Transform _grenade, float _AOE_radius, float _damage)
{
//获取手雷_AOE_radius范围内所有的碰撞体(敌人)
Collider[] colliders = Physics.OverlapSphere(
_grenade.position, _AOE_radius, 1 << LayerMask.NameToLayer("Enemys"));
//遍历范围内所有敌人并给予伤害
for (int i = 0; i < colliders.Length; i++)
{
//获取生命脚本组件,调用伤害函数
colliders[i].gameObject.GetComponent<Health>().GetDamage(_damage);
}
}
四、类似的Physics.OverlapBox OverlapCapsule
参考
https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.OverlapBox.html
https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.OverlapCapsule.html
小功能丨关于Unity Collider Physics.Overlap
unityOverlapBox如何正确的绘制出范围
Unity3D 学习笔记(八) 锁定目标与 Physics.OverlapBox
public static Collider[] OverlapBox (
Vector3 center,
Vector3 halfExtents,
Quaternion orientation= Quaternion.identity,
int layerMask= AllLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
参数
- center 盒体的中心。
- halfExtents 盒体各个维度大小的一半。
- orientation 盒体的旋转。
- layerMask 层遮罩,用于在投射射线时有选择地忽略碰撞体。
- queryTriggerInteraction 指定该查询是否应该命中触发器。
public static Collider[] OverlapCapsule (
Vector3 point0,
Vector3 point1,
float radius,
int layerMask= AllLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
参数
- point0 胶囊体在 start 处的球体中心。
- point1 胶囊体在 end 处的球体中心。
- radius 胶囊体的半径。
- layerMask 层遮罩,用于在投射胶囊体时有选择地忽略碰撞体。
- queryTriggerInteraction 指定该查询是否应该命中触发器。
五、Physics.SphereCast
https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.SphereCast.html
Physics.SphereCast 球形射线。相比Physics.Raycast,就是把射线的宽度给增加了。可以想象为把球向某个方向移动,在移动过程中去检测。这里有个坑就是:它不能检测到起点半径之内的物体,也就是说发出射线的时候就已经包含在球半径内的话是不能被检测到的,如果要检测半径内的使用Physics.OverlapSphere来进行检测。
public static bool SphereCast (
Vector3 origin,
float radius,
Vector3 direction,
out RaycastHit hitInfo,
float maxDistance= Mathf.Infinity,
int layerMask= DefaultRaycastLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
参数
origin 扫描开始处的球体中心。
radius 该球体的半径。
direction 扫描球体的方向。
hitInfo 如果返回 true,则 hitInfo 将包含有关碰撞体的撞击位置的更多信息(另请参阅:RaycastHit)。
maxDistance 投射的最大长度。
layerMask 层遮罩,用于在投射胶囊体时有选择地忽略碰撞体。
queryTriggerInteraction 指定该查询是否应该命中触发器。
返回
bool 当球体扫描与任何碰撞体交叠时为 true,否则为 false。
描述
沿射线投射球体并返回有关命中对象的详细信息。
当射线投射未提供足够的精度时,这很有用。例如,您可能只想知道某个具有特定大小的对象, 比如某个角色,能否在沿途不与任何对象发生碰撞的情况下到达某个地方。 可以将球体想象成一种“很厚”的射线投射。在这种情况下, 射线由起始矢量和方向指定。
注意:对于球体与碰撞体重叠的情况,SphereCast 不会检测到碰撞体。传递零作为半径会导致未定义的输出。
六、参考实例项目地址:Raycast - SouthBegonia
1.DEBUG手段
-
绘制线段:
-
DrawLine(startPos, endPos, color)
:绘制一条从startPos到endPos点、颜色为color的线段
-
-
绘制射线:
-
DrawRay(startPos, direction, color)
:绘制一条从startPos出发,指向direction的、颜色color的射线(默认长度为单位向量,再乘以倍率即可边长;在下一次绘制才会覆盖上一次的射线) -
Debug.DrawRay(startPos , direction, color, duration)
:同理绘制一定方向射线,但射线持续时间为duration :
-
-
Gimos.DrawXXX方法:
void OnDrawGizmos() { Gizmos.DrawCube(transform.position, transform.localScale );}
2.Check.unity
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//鼠标单击屏幕产生射线,设定distance检测碰撞的物体:
public class Player : MonoBehaviour
{
public static Player instance;
private Ray camerRay; //声明射线
private RaycastHit cameraHit; //声明射线检测
[Range(1, 20)]
public float RayDistance;
public Material material;
private Vector3 mousePos; //记录将鼠标
private void Awake()
{
instance = this;
}
void Update()
{
//当点击鼠标左键的时候,以鼠标在摄像机屏幕位置发射一个射线进行检测
if (Input.GetMouseButton(0))
{
//mousePosition坐标范围: 左下0,0 ~ 右上屏幕像素(width,height)
mousePos.x = Input.mousePosition.x;
mousePos.y = Input.mousePosition.y;
mousePos.z = 0;
//若相机为perspective模式,射线为发散形状;若为orthoGraphic,则为垂直与相机面的直线段
//此外,默认长度为单位向量
camerRay = Camera.main.ScreenPointToRay(mousePos);
//物理检测射线,out一个RaycastHit类型的 hitInfo 信息,float distance是射线长度,layerMask为可检测的Layer层
if (Physics.Raycast(camerRay, out cameraHit, RayDistance, LayerMask.GetMask("Anchor")))
{
Debug.Log(cameraHit.transform.gameObject.name);
cameraHit.transform.gameObject.GetComponent<MeshRenderer>().material = material;
}
}
//绘制射线
Debug.DrawRay(camerRay.origin, camerRay.direction * RayDistance, Color.red);
//Debug.DrawRay(camerRay.origin , camerRay.direction, Color.red, 1f);
}
}
如图,A和B的Layer都是Anchor,并且都添加了BoxCollider,isTrigger 都是false。但是默认值RayDistance为6时,点击B并不会变成绿色。RayDistance为8时,B就可以变成绿色了。
C并未添加BoxCollider,所以点击不会变成绿色。但是C上面绑定了脚本CheckBox.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CheckBox : MonoBehaviour
{
private bool IsOverlapAnyCollider;
void Update()
{
//自身的collider也会被检测到哈
IsOverlapAnyCollider = Physics.CheckBox(transform.position,
transform.localScale / 2, Quaternion.identity, LayerMask.GetMask("Anchor"));
Debug.Log("isOverlapAnyCollider? : " + IsOverlapAnyCollider);
}
}
3.Physics.CheckBox
https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.CheckBox.html
public static bool CheckBox (
Vector3 center,
Vector3 halfExtents,
Quaternion orientation= Quaternion.identity,
int layermask= DefaultRaycastLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
参数
- center 盒体的中心。
- halfExtents 盒体各个维度大小的一半。
- orientation 盒体的旋转。
- layermask 层遮罩,用于在投射射线时有选择地忽略碰撞体。
- queryTriggerInteraction 指定该查询是否应该命中触发器。
返回
- bool 如果盒体与任何碰撞体重叠,则返回 true。
参照官方的API,可以得知上面的CheckBox.cs脚本,是在检测自身是否有碰撞体。在Game视图做个测试,给A添加一个BoxCollider,看看打印结果,果然输出了True,并且点击它,也会变成绿色了。
4.OverlapAndCast.unity
在这个场景中,可以测试Overlap系列,以及SphereCast系列。比如在运行模式下,拖动这个球的位置,就能看到OverlapSphere的输出变化:
同样,也能观察SphereCast,在场景中将其激活,然后拖动Z轴方向,为什么拖Z轴可以参考代码:
//SphereCast方法:返回bool
Physics.SphereCast(this.transform.position, radius, Vector3.forward,
out RaycastHit, 1f, LayerMask.GetMask("Anchor"));
if (RaycastHit.collider != null)
Debug.Log("SphereCast Hit collider = " + RaycastHit.collider.gameObject.name);
可以看到填的方向:
//
// 摘要:
// Shorthand for writing Vector3(0, 0, 1).
public static Vector3 forward { get; }
并且,代码中填的距离是1f,所以移动到-1之后的距离,代码才会输出Debug.Log("SphereCast Hit collider...
CapsuleCast参数是类似的
5.NonAlloc.unity
这个场景主要是测NonAlloc系列的,参考https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.OverlapSphereNonAlloc.html
public static int OverlapSphereNonAlloc (
Vector3 position,
float radius,
Collider[] results,
int layerMask= AllLayers,
QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal
);
参数
- position 球体的中心。
- radius 球体的半径。
- results 用于存储结果的缓冲区。
- layerMask Layer mask 定义要在查询中包括哪些碰撞体层。
- queryTriggerInteraction 指定该查询是否应该命中触发器。
返回
- int 返回存储在 results 缓冲区中的碰撞体的数量。
如果缓冲区空间用尽,请勿尝试增大缓冲区。当缓冲区已满时,将返回缓冲区的长度。 与 Physics.OverlapSphere 类似,但不产生任何垃圾。
NonAlloc版本的函数要比非NonAlloc版本的函数在GC消耗上好一些。NonAlloc版本的函数是复用自己的数组,而非NoAlloc版本的函数每次都回去new 新的数组出来,如果在循环中调用的话,对比就会很明显。
此处引用网友 HONT的测试作为GC情况参考:
- 同方法下不同模型GC开销:Box < Sphere < Capsule
- 同模型下不同方法GC开销:CheckXXX < OverlapXXX < XXXCast