范围检测

只要是让某指定范围内触发某种效果,比如让敌人受到伤害等,都可以使用范围检测。

比如:

  1. 玩家在前方5m出释放了一个地刺魔法,在此处范围内的对象将会受到地刺伤害。
  2. 玩家释放魔法,在前方1m圆形范围内所有对象都受到伤害

Layer和LayerMask层级相关

Layer:Unity中使用int32来表示32个Layer层。int32表示二进制一共有32位。

在Unity中每个GameObject都有Layer属性。

LayerMask:实际上是一个位码操作。

LayerMask实际上是用Int32的32个位与Inspector窗口中的32个Layer一一对应,当这个位为1时表示使用这个层,为0时表示不用这个层。

每一个编号 代表的 都是二进制的一位
0—— 1 << 0——0000 0000 0000 0000 0000 0000 0000 0001 = 1
1—— 1 << 1——0000 0000 0000 0000 0000 0000 0000 0010 = 2
2—— 1 << 2——0000 0000 0000 0000 0000 0000 0000 0100 = 4
3—— 1 << 3——0000 0000 0000 0000 0000 0000 0000 1000 = 8
4—— 1 << 4——0000 0000 0000 0000 0000 0000 0001 0000 = 16
5—— 1 << 5——0000 0000 0000 0000 0000 0000 0010 0000 = 32

开启第几层

LayerMask mask = 1 << 3; // 开启第三层

LayerMask mask1 = (1<<16) | (1<<8); // 开启第16层和8层

关闭第几层

LayerMask mask = 0 << 8; // 表示关闭Layer8层

除了第几层之外的所有层

LayerMask mask = ~(1 << 16) // 打开除了Layer16的层之外的层。

LayerMask mask = ~(1 << 0) // 打开所有的层。

相关API

public static int GetMask (params string[] layerNames);

public static string LayerToName (int layer);

// 根据层名字转换为层编号
public static int NameToLayer (string layerName);

如何进行范围检测

必备条件:被检测到的对象,必须具备碰撞体。

1. 盒状范围检测

void Start()
{
    // 参数一:立方体中心点
    // 参数二:立方体三边大小
    // 参数三:立方体角度
    // 参数四:检测指定层级(不填检测所有层)
    // 参数五:是否忽略触发器 UserGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器,不填使用UserBlobal
    // 返回值:在该范围内的触发器(得到对象触发器就可以得到对象的所有信息)
    Collider[] colliders = Physics.OverlapBox(Vector3.zero, Vector3.one, Quaternion.AngleAxis(45, Vector3.up),
        1 << LayerMask.NameToLayer("UI") | 1 << LayerMask.NameToLayer("Default"),
        QueryTriggerInteraction.UseGlobal);
    for (int i = 0; i < colliders.Length; i++)
    {
        print(colliders[i].gameObject.name);
    }

  
    //参数:传入一个数组进行存储
    //返回值:碰撞到的碰撞器数量
    if(Physics.OverlapBoxNonAlloc(Vector3.zero, Vector3.one, colliders) != 0)
    {
 
    }
}

2. 球形范围检测

刀的攻击、拳击,都可以使用范围检测。

例子:在人物的正前方一米进行球形范围检测,球的半径1m,必要要检测到怪物层级

注意:一般都是在动画帧中触发事件

public void KnifeEvent()
{
    // 1. 检测刀的正前方有没有敌人,进行范围检测
    // 参数一:立方体中心点
    // 参数二:球半径
    // 参数三:检测指定层级(不填检测所有层)
    // 参数四:是否忽略触发器 UserGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器,不填使用UserBlobal
    // 返回值:在该范围内的触发器(得到对象触发器就可以得到对象的所有信息)
    Collider[] collider = Physics.OverlapSphere(transform.position + transform.forward + transform.up, 1,
        1 << LayerMask.NameToLayer("Monster"));
    for(int i = 0; i < collider.Length; i++)
    {
        print("检测到的敌人");
        // TODO:敌人受到伤害等操作
    }
}

3. 胶囊范围检测

private void Start()
{
    Collider[] colliders;
    //参数一:半圆一中心点
    //参数二:半圆二中心点
    //参数三:半圆半径
    //参数四:检测指定层级(不填检测所有层)
    //参数五:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
    //返回值:在该范围内的触发器(得到了对象触发器就可以得到对象的所有信息)
    colliders = Physics.OverlapCapsule(Vector3.zero, Vector3.up, 1, 
1 << LayerMask.NameToLayer("UI"), QueryTriggerInteraction.UseGlobal);

    //另一个API 
    //返回值:碰撞到的碰撞器数量
    //参数:传入一个数组进行存储
    //Physics.OverlapCapsuleNonAlloc
    if (Physics.OverlapCapsuleNonAlloc(Vector3.zero, Vector3.up, 1, colliders) != 0)
    {

    }
}

射线检测

物理射线

从某个初始点开始,沿着特定的方向发射一条不可见且无限长的射线,通过此射线检测是否有任何模型添加了Collider碰撞器组件。一旦检测到碰撞,停止射线发射。

1. 射线对象

射线就是:一个点和一个方向

// 参数一:起点
// 参数二:方向(一定记住 不是两点决定射线方向,第二个参数 直接就代表方向向量)
// 目前只是申明了一个射线对象 对于我们来说 没有任何的用处
Ray r = new Ray(Vector3.right, Vector3.forward);

摄像机发射出来的射线

// 得到一条从屏幕位置作为起点
// 摄像机视口方向为 方向的射线
Ray r2 = Camera.main.ScreenPointToRay(Input.mousePosition);

2. 碰撞检测函数

2.1 最原始的射线检测

// 准备一条射线
Ray r3 = new Ray(Vector3.zero, Vector3.forward);
// 进行射线检测 如果碰撞到对象 返回true
//参数一:射线
//参数二: 检测的最大距离 超出这个距离不检测
//参数三:检测指定层级(不填检测所有层)
//参数四:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
//返回值:bool 当碰撞到对象时 返回 true 没有 返回false

if (Physics.Raycast(r3, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
{
    print("碰撞到了对象");
}

//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
//就是把 第一个参数射线 变成了 射线的 两个点 一个起点 一个方向
if (Physics.Raycast(Vector3.zero, Vector3.forward, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
{
    print("碰撞到了对象2");
}

2.2 获取相交的单个物体信息

//物体信息类 RaycastHit
RaycastHit hitInfo;
//参数一:射线
//参数二:RaycastHit是结构体 是值类型 Unity会通过out 关键在 在函数内部处理后 得到碰撞数据后返回到该参数中
//参数三:距离
//参数四:检测指定层级(不填检测所有层)
//参数五:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
if (Physics.Raycast(r3, out hitInfo, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
{
    print("碰撞到了物体 得到了信息");

    //碰撞器信息
    print("碰撞到物体的名字" + hitInfo.collider.gameObject.name);
    //碰撞到的点
    print(hitInfo.point);
    //法线信息
    print(hitInfo.normal);

    //得到碰撞到对象的位置
    print(hitInfo.transform.position);

    //得到碰撞到对象 离自己的距离
    print(hitInfo.distance);

    //RaycastHit 该类 对于我们的意义
    //它不仅可以得到我们碰撞到的对象信息
    //还可以得到一些 碰撞的点 距离 法线等等的信息
}

//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
if (Physics.Raycast(Vector3.zero, Vector3.forward, out hitInfo, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal))
{

}

2.3 获取相交的多个物体

//可以得到碰撞到的多个对象
//如果没有 就是容量为0的数组
//参数一:射线
//参数二:距离
//参数三:检测指定层级(不填检测所有层)
//参数四:是否忽略触发器 UseGlobal-使用全局设置 Collide-检测触发器 Ignore-忽略触发器 不填使用UseGlobal
RaycastHit[] hits = Physics.RaycastAll(r3, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal);
for (int i = 0; i < hits.Length; i++)
{
    print("碰到的所有物体 名字分别是" + hits[i].collider.gameObject.name);
}

//还有一种重载 不用传入 射线 直接传入起点 和 方向 也可以用于判断
//之前的参数一射线 通过两个点传入
hits = Physics.RaycastAll(Vector3.zero, Vector3.forward, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal);

//还有一种函数 返回的碰撞的数量 通过out得到数据
if (Physics.RaycastNonAlloc(r3, hits, 1000, 1 << LayerMask.NameToLayer("Monster"), QueryTriggerInteraction.UseGlobal) > 0)
{

}

图形射线

  • 图形射线用于UI控件响应
  • 可以响应图形射线的UI组件,RayCast Target有Text、Image、RawImage,并且所有可以交互的UI组件都包含RayCast Target
  • UI控件事件响应基于EventSystem

案例与练习

案例:射击墙壁

using UnityEngine;

/// <summary>
/// 射线检测Demo - 小功能
/// 
/// 作者: DY
/// 创建日期: 2025-01-01
/// 版本: 1.0
/// 
/// 详细描述: 1. 子弹打在墙上的效果
/// </summary>
public class 射线检测Demo : MonoBehaviour
{
    RaycastHit hit;
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit, 1000, 1 << LayerMask.NameToLayer("Wall"), QueryTriggerInteraction.UseGlobal))
            {
                GameObject go = Resources.Load("Effect/FireImpactSmall") as GameObject;
                go = GameObjectPool.instance.CreateObject("bulleteffect", go, hit.point + hit.normal * 0.2f, Quaternion.identity);
                go.transform.rotation = Quaternion.LookRotation(hit.normal);
                GameObjectPool.instance.CollectObject(go, 1f);

                GameObject go1 = Resources.Load("Effect/FireProjectileSmall") as GameObject;
                go1 = GameObjectPool.instance.CreateObject("walleffect", go1, hit.point + hit.normal * 0.5f, Quaternion.identity);
                go1.transform.rotation = Quaternion.LookRotation(hit.normal);
                GameObjectPool.instance.CollectObject(go1, 5f);
            }
        }
    }
}

案例:找到敌人

需求:当射线射到敌人,敌人的颜色就会变成红色,射到好人,好人会变绿

jiance

思路:为敌人和好人添加Tag标签,用于去找到敌人和好人

using UnityEngine;
// 射线检测
public class TestDemo1 : MonoBehaviour
{
    // 碰撞信息对象
    RaycastHit hit;
    public LayerMask layer;
    private void Update()
    {
        bool flag = Physics.Raycast(transform.position, transform.forward, out hit, 100, layer);
        if (flag && hit.collider.tag == "Enemy")
        {
            hit.collider.GetComponent<Renderer>().material.color = Color.red;
            Debug.DrawLine(transform.position, hit.collider.transform.position, Color.red);
        } else if(flag && hit.collider.tag == "Friends")
        {
            hit.collider.GetComponent<Renderer>().material.color = Color.green;
            Debug.DrawLine(transform.position, hit.collider.transform.position, Color.green);
        }
    }

}

案例2:拖拽3d物品

【需求】:场景上有一个平面,有一个立方体,当鼠标点击选中立方体时,长按鼠标左键,可以拖拽立方体,在屏幕上移动,点击鼠标右键取消选中。

using UnityEngine;

/// <summary>
/// 拖拽物品 - 类描述
/// 
/// 作者: DY
/// 创建日期: 2025-01-01
/// 版本: 1.0
/// 
/// 详细描述: 功能描述
/// </summary>
public class 拖拽物品 : MonoBehaviour
{
    private GameObject currentSelectedObj;
    RaycastHit hit;

    private void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit, 100, 1 << LayerMask.NameToLayer("Player")))
            {
                currentSelectedObj = hit.collider.gameObject;
                currentSelectedObj.GetComponent<MeshRenderer>().material.color = Color.red;
            }
        }
        if(Input.GetMouseButtonDown(1))
        {
            if(currentSelectedObj != null)
            {
                currentSelectedObj.GetComponent<MeshRenderer>().material.color = Color.white;
            }
            currentSelectedObj = null;
        }

        if(Input.GetMouseButton(0) && currentSelectedObj)
        {
            Vector3 pos = Input.mousePosition;
            pos.z = 10;
            currentSelectedObj.transform.position = Camera.main.ScreenToWorldPoint(pos);
        }
    }

}