Unity Physics.Raycast

参考
Unity - 射线检测
https://docs.unity.cn/cn/2019.4/ScriptReference/Physics.html
Unity 基础 之 Ray 射线简单介绍和使用忽略碰撞层的时候的注意事项(记得添加距离,不然 layer mask 可能无效)

一、射线检测图解

参考
浅析射线检测 raycast 的使用 !Cocos Creator 3D !

首先,我们看到的视角是这样子的。假设我们点击其中屏幕中的一个位置(图中的红点点)。


image.png

因为这个视角是摄像机提供的,我们就把这个点点和摄像机组合一条射线。


image.png

接着,检查这条射线穿过了那些物体,这些物体中可能就有我们点击的对象。

也可以这么理解,你用眼睛看着一块区域,伸出手指。你可以看到手指头挡住了一点视线,从你的视线做经过手指这个点画一条射线,这个射线穿过的物体,就刚好是你想要点击的物体。

二、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
image.png

设置物体的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】及其技巧

image.png
//代码
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);
    }
}

image.png

如图,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
image.png

在这个场景中,可以测试Overlap系列,以及SphereCast系列。比如在运行模式下,拖动这个球的位置,就能看到OverlapSphere的输出变化:


image.png

同样,也能观察SphereCast,在场景中将其激活,然后拖动Z轴方向,为什么拖Z轴可以参考代码:


image.png
//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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容