Unity基础学习(十二)Unity 物理系统之范围检测

目录

一、关于范围检测的主要API:

[1. 盒状范围检测 Physics.OverlapBox](#1. 盒状范围检测 Physics.OverlapBox)

[2. 球形范围检测 Physics.OverlapSphere](#2. 球形范围检测 Physics.OverlapSphere)

[3. 胶囊范围检测 Physics.OverlapCapsule](#3. 胶囊范围检测 Physics.OverlapCapsule)

[4. 盒状检测 NonAlloc 版](#4. 盒状检测 NonAlloc 版)

[5. 球形检测 NonAlloc 版](#5. 球形检测 NonAlloc 版)

[6. 胶囊检测 NonAlloc 版](#6. 胶囊检测 NonAlloc 版)

二、关于API中的两个重点参数

[QueryTriggerInteraction 参数详解](#QueryTriggerInteraction 参数详解)

[1. QueryTriggerInteraction.UseGlobal](#1. QueryTriggerInteraction.UseGlobal)

[2. QueryTriggerInteraction.Collide](#2. QueryTriggerInteraction.Collide)

[3. QueryTriggerInteraction.Ignore](#3. QueryTriggerInteraction.Ignore)

[LayerMask 在范围检测中的深度解析](#LayerMask 在范围检测中的深度解析)

[1. 层级系统基础](#1. 层级系统基础)

[2. LayerMask 本质](#2. LayerMask 本质)

层级选择:精准定位目标层

基础选择方法

高级选择方式

层级合并:组合检测

基本合并技巧

层级排除:精准过滤

常见错误:

[1. 层名拼写错误(静默失败)](#1. 层名拼写错误(静默失败))

[2. 位运算优先级错误](#2. 位运算优先级错误)

[3. 掩码值为0(不检测任何层)](#3. 掩码值为0(不检测任何层))

[4. 混淆层索引和掩码值 这个最常见](#4. 混淆层索引和掩码值 这个最常见)

三、总结

基本范围检测函数

[高效 NonAlloc 版本](#高效 NonAlloc 版本)

关键参数详解表

通用参数(所有检测函数)

盒状检测特有参数

球形检测特有参数

胶囊检测特有参数

[NonAlloc 版本特有参数](#NonAlloc 版本特有参数)

[LayerMask 操作指南](#LayerMask 操作指南)

高频错误及解决方案


在前面的内容中,我们学习了关于碰撞的检测相关,今天我们来看看指定范围的检测,这个检测是什么呢?就是瞬时检测指定空间内的碰撞器对象(无实体碰撞效果),适用于技能攻击、区域触发等场景。

那么被检测的对象需要具备些什么条件呢?被检测对象 必须挂载碰撞器(Collider)(无需 Rigidbody)。

一、关于范围检测的主要API:

1. 盒状范围检测 Physics.OverlapBox

参数相关:

cpp 复制代码
Collider[] Physics.OverlapBox(
    Vector3 center,                 // 盒子中心点(世界坐标)
    Vector3 halfExtents,            // 盒子三边尺寸(半长,非全尺寸)
    Quaternion orientation,         // 盒子旋转角度
    int layerMask = AllLayers,      // 层级掩码(默认所有层)
    QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal
)

返回值:检测范围内的所有碰撞器组成的数组(无碰撞时返回空数组)

例如:

cpp 复制代码
void CheckAttackRange()
{
    // 在玩家前方1米处创建2x2x2的检测盒
    Vector3 center = transform.position + transform.forward;
    Vector3 size = new Vector3(2, 2, 2);
    int enemyLayer = 1 << LayerMask.NameToLayer("Enemy");
    
    Collider[] hits = Physics.OverlapBox(
        center,
        size / 2,  // 注意:参数是半长尺寸
        transform.rotation,
        enemyLayer,
        QueryTriggerInteraction.Ignore
    );
    
    foreach (Collider col in hits)
    {
        Enemy enemy = col.GetComponent<Enemy>();
        if(enemy != null) enemy.TakeDamage(10);
    }
    
    // 调试绘制
    Debug.DrawLine(transform.position, center, Color.red, 0.5f);
}

实际上你看不到他的检测范围的,你要自己想象。

2. 球形范围检测 Physics.OverlapSphere

参数相关:

cpp 复制代码
Collider[] Physics.OverlapSphere(
    Vector3 position,               // 球心位置(世界坐标)
    float radius,                   // 球体半径
    int layerMask = AllLayers,      // 层级掩码
    QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal
)

返回值:球体内的所有碰撞器数组

cpp 复制代码
void DetectNearbyItems()
{
    // 检测周围5米内的可收集物品
    int itemLayer = LayerMask.GetMask("Collectibles");
    float detectRadius = 5f;
    
    Collider[] items = Physics.OverlapSphere(
        transform.position,
        detectRadius,
        itemLayer
    );
    
    foreach (Collider item in items)
    {
        item.GetComponent<Collectible>().Highlight();
    }
    
    // 调试绘制
    Gizmos.color = Color.cyan;
    Gizmos.DrawWireSphere(transform.position, detectRadius);
}

这个球形的和上面的一样,就只是参数不同而已,形状不同,效果都是碰撞检测,还有下面的胶囊检测也是如此。

3. 胶囊范围检测 Physics.OverlapCapsule

参数相关:

cs 复制代码
Collider[] Physics.OverlapCapsule(
    Vector3 point0,                 // 胶囊体底部球心
    Vector3 point1,                 // 胶囊体顶部球心
    float radius,                   // 胶囊半径
    int layerMask = AllLayers,      // 层级掩码
    QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal
)

例如:

cs 复制代码
void CheckCharacterHit()
{
    // 检测玩家角色碰撞(高度2米,半径0.5米)
    Vector3 feetPos = transform.position;
    Vector3 headPos = feetPos + Vector3.up * 2;
    float charRadius = 0.5f;
    int playerLayer = LayerMask.GetMask("Player");
    
    Collider[] hits = Physics.OverlapCapsule(
        feetPos,
        headPos,
        charRadius,
        playerLayer
    );
    
    if (hits.Length > 0)
    {
        Debug.Log("Player character hit detected!");
    }
}

上面的三个API只是最基本的范围检测,他们还有好兄弟。你不需要将返回值碰撞器数组存下来,可以直接在外部创建一个数组,然后装入这个数组中即可:

4. 盒状检测 NonAlloc 版

cpp 复制代码
int Physics.OverlapBoxNonAlloc(
    Vector3 center,                 // 盒子中心点
    Vector3 halfExtents,            // 盒子半尺寸(长/宽/高各一半)
    Collider[] results,             // 结果存储数组(预分配)
    Quaternion orientation = Quaternion.identity, // 旋转角度
    int layerMask = AllLayers,      // 层级掩码
    QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal
)

返回值:实际检测到的碰撞器数量(非数组长度)

5. 球形检测 NonAlloc 版

cpp 复制代码
int Physics.OverlapSphereNonAlloc(
    Vector3 position,               // 球心位置
    float radius,                   // 球体半径
    Collider[] results,             // 结果存储数组
    int layerMask = AllLayers,      // 层级掩码
    QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal
)

返回值:实际检测到的碰撞器数量

6. 胶囊检测 NonAlloc 版

cpp 复制代码
int Physics.OverlapCapsuleNonAlloc(
    Vector3 point0,                 // 胶囊底部球心
    Vector3 point1,                 // 胶囊顶部球心
    float radius,                   // 胶囊半径
    Collider[] results,             // 结果存储数组
    int layerMask = AllLayers,      // 层级掩码
    QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal
)

返回值:实际检测到的碰撞器数量

使用手段都是和前三个差不多的

NonAlloc 方法核心优势

特性 基础方法 (OverlapX) NonAlloc 方法 (OverlapXNonAlloc)
内存分配 每次调用创建新数组 使用预分配数组,无GC开销
性能影响 高频调用导致GC压力 零内存分配,性能稳定
使用场景 低频/单次检测 高频检测(如Update中)
返回值 Collider[] 数组 int (实际检测数量)

二、关于API中的两个重点参数

QueryTriggerInteraction 参数详解

QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal 是 Unity 物理检测 API 中的一个重要参数,用于控制物理检测如何处理触发器(Trigger)。实际上一般默认的就OK

这个参数决定了在物理检测(如范围检测、射线检测等)中是否应该包括触发器碰撞器:

  1. 控制检测行为:指定是否将触发器视为有效的碰撞对象

  2. 避免意外结果:防止触发器干扰正常的物理检测逻辑

  3. 灵活配置:可以覆盖项目的全局设置

1. QueryTriggerInteraction.UseGlobal

行为:使用项目的全局物理设置

说明:检测行为取决于 "Edit > Project Settings > Physics" 中的 "Queries Hit Triggers" 设置

推荐场景:

希望检测行为与项目全局设置保持一致时

不特别关注触发器处理方式时

2. QueryTriggerInteraction.Collide

行为:包含触发器在检测结果中

说明:无论项目全局设置如何,本次检测都会将触发器视为有效碰撞体

推荐场景:

需要检测区域触发器时(如进入安全区、收集区域)

需要响应触发器事件时

3. QueryTriggerInteraction.Ignore

行为:忽略所有触发器

说明:无论项目全局设置如何,本次检测都会跳过所有触发器

推荐场景:

只关心实体碰撞时(如攻击检测、物理碰撞)

避免触发器干扰检测结果时

示例:不同参数的效果

注: :这种写法叫做可选参数。

cpp 复制代码
// 检测所有碰撞体(包括普通碰撞器和触发器)
Collider[] allHits = Physics.OverlapSphere(
    position: transform.position,
    radius: 5f,
    layerMask: LayerMask.GetMask("Enemy", "Trap", "SafeZone"),
    queryTriggerInteraction: QueryTriggerInteraction.Collide
);
// 结果:包含敌人、陷阱区域、安全区

// 只检测实体碰撞体(忽略所有触发器)
Collider[] physicalHits = Physics.OverlapSphere(
    position: transform.position,
    radius: 5f,
    layerMask: LayerMask.GetMask("Enemy", "Trap", "SafeZone"),
    queryTriggerInteraction: QueryTriggerInteraction.Ignore
);
// 结果:只包含敌人(陷阱区域和安全区是触发器,被忽略)

// 使用全局设置检测
Collider[] globalSettingHits = Physics.OverlapSphere(
    position: transform.position,
    radius: 5f,
    layerMask: LayerMask.GetMask("Enemy", "Trap", "SafeZone"),
    queryTriggerInteraction: QueryTriggerInteraction.UseGlobal
);
// 结果:取决于项目设置中的"Queries Hit Triggers"选项

LayerMask 在范围检测中的深度解析

LayerMask 核心概念

1. 层级系统基础

Unity 提供 32 个层级(0-31)

每个游戏对象分配到一个层级

层级用于逻辑分组(如:玩家、敌人、环境、UI等)

2. LayerMask 本质

32 位位掩码(bitmask)

每个位对应一个层级(1=包含,0=排除)

示例:00000000 00000000 00000000 00000101 表示包含第0层和第2层

可以在这里进行创建层级

通过代码来获取层级:

cpp 复制代码
// 单层级
int enemyLayer = LayerMask.NameToLayer("Enemy");
LayerMask enemyMask = 1 << enemyLayer;

// 多层级组合
LayerMask enemyAndObstacleMask = 
    (1 << LayerMask.NameToLayer("Enemy")) | 
    (1 << LayerMask.NameToLayer("Obstacle"));

// 排除特定层
LayerMask allExceptUI = ~(1 << LayerMask.NameToLayer("UI"));

//或者
// 包含多个层级
LayerMask mask = LayerMask.GetMask("Enemy", "Projectile", "Destructible");

// 等效于:
// (1 << LayerMask.NameToLayer("Enemy")) |
// (1 << LayerMask.NameToLayer("Projectile")) |
// (1 << LayerMask.NameToLayer("Destructible"))

接下来咱们讲讲为什么它会这么进行设计,和如何进行层级的选择,合并,排除。

因为位运算很快,而且非常的适合做状态合并与排除。你只需要将对应位置的数置0即排除,置1就合并了。利用,位与,位或进行操作。

层级选择:精准定位目标层

基础选择方法

cs 复制代码
// 选择单个层级
LayerMask enemyMask = 1 << LayerMask.NameToLayer("Enemy");

// 选择多个层级
LayerMask combatMask = (1 << LayerMask.NameToLayer("Enemy")) | 
                     (1 << LayerMask.NameToLayer("Boss"));

高级选择方式

cpp 复制代码
// 使用 GetMask 更简洁
LayerMask environmentMask = LayerMask.GetMask("Ground", "Water", "Wall");

层级合并:组合检测

基本合并技巧

cpp 复制代码
// 创建基础掩码
LayerMask baseMask = LayerMask.GetMask("Player", "Enemy");

// 动态添加层级
void AddLayerToMask(ref LayerMask mask, string layerName)
{
    int layer = LayerMask.NameToLayer(layerName);
    if (layer != -1) 
    {
        mask |= (1 << layer);  // 按位或操作
    }
}

// 使用
AddLayerToMask(ref baseMask, "Projectile");

多掩码组合

cpp 复制代码
// 定义不同类别的掩码
LayerMask characterMask = LayerMask.GetMask("Player", "NPC", "Enemy");
LayerMask environmentMask = LayerMask.GetMask("Ground", "Wall", "Water");
LayerMask interactableMask = LayerMask.GetMask("Chest", "Door", "Lever");

// 组合掩码
LayerMask fullDetectionMask = characterMask | environmentMask | interactableMask;

层级排除:精准过滤

cpp 复制代码
// 所有层
LayerMask allLayers = ~0; // 或 Physics.AllLayers

// 排除 UI 层
int uiLayer = LayerMask.NameToLayer("UI");
LayerMask noUIMask = allLayers & ~(1 << uiLayer);

// 排除多个层
int ignoreLayer1 = LayerMask.NameToLayer("IgnoreRaycast");
int ignoreLayer2 = LayerMask.NameToLayer("Water");
LayerMask filteredMask = allLayers & ~((1 << ignoreLayer1) | (1 << ignoreLayer2));

假设你要排除的层级:00000000 00000000 00000000 00001001

取反后 11111111 11111111 11111111 11110110

全层级 11111111 11111111 11111111 11111111

相与后,便排除了指定层级

常见错误:

1. 层名拼写错误(静默失败)

cs 复制代码
// 错误:层名拼错无提示
LayerMask mask = 1 << LayerMask.NameToLayer("Enemi"); // 返回 -1

// 解决方案:验证层名
int GetLayerSafe(string layerName)
{
    int layer = LayerMask.NameToLayer(layerName);
    if (layer == -1)
    {
        Debug.LogError($"Layer '{layerName}' does not exist!");
        return 0; // 默认层
    }
    return layer;
}

2. 位运算优先级错误

cs 复制代码
// 错误:运算优先级问题
LayerMask wrongMask = 1 << 8 | 1 << 9; // 实际是 (1 << 8) | (9)!

// 正确:使用括号明确优先级
LayerMask correctMask = (1 << 8) | (1 << 9);

3. 掩码值为0(不检测任何层)

cpp 复制代码
// 常见于动态构建掩码失败
LayerMask emptyMask = 0; // 不检测任何层

// 防护:添加默认层
if (mask == 0)
{
    mask = 1 << 0; // 至少包含默认层
    Debug.LogWarning("Empty layer mask, using default layer");
}

4. 混淆层索引和掩码值 这个最常见

cpp 复制代码
// 错误:传入层索引而非掩码
int enemyLayer = LayerMask.NameToLayer("Enemy");
Physics.Raycast(..., enemyLayer); // 应该传入 1 << enemyLayer

// 正确:始终使用位掩码
Physics.Raycast(..., 1 << enemyLayer);

三、总结

基本范围检测函数
函数名 返回值 核心功能
Physics.OverlapBox Collider[] 检测盒状区域内的碰撞器
Physics.OverlapSphere Collider[] 检测球形区域内的碰撞器
Physics.OverlapCapsule Collider[] 检测胶囊区域内的碰撞器
高效 NonAlloc 版本
函数名 返回值 核心优势
Physics.OverlapBoxNonAlloc int 零GC分配,适合高频调用
Physics.OverlapSphereNonAlloc int 预分配数组,性能稳定
Physics.OverlapCapsuleNonAlloc int 返回实际碰撞数量而非数组

关键参数详解表

通用参数(所有检测函数)
参数名 类型 说明
layerMask int 层级掩码(位运算值),决定检测哪些层
queryTriggerInteraction enum 触发器处理方式: UseGlobal-用项目设置 Collide-包含触发器 Ignore-忽略触发器
盒状检测特有参数
参数名 类型 说明 注意事项
center Vector3 盒子中心点(世界坐标)
halfExtents Vector3 三边尺寸的一半​(非全尺寸) 例:2x2x2盒子需传入(1,1,1)
orientation Quaternion 盒子的旋转角度 默认Quaternion.identity
球形检测特有参数
参数名 类型 说明
position Vector3 球心位置(世界坐标)
radius float 球体半径
胶囊检测特有参数
参数名 类型 说明
point0 Vector3 胶囊底部球心(世界坐标)
point1 Vector3 胶囊顶部球心(世界坐标)
radius float 胶囊半径
NonAlloc 版本特有参数
参数名 类型 说明
results Collider[] 预分配的碰撞器数组(避免GC分配)

LayerMask 操作指南

操作类型 代码示例 说明
单层选择 1 << LayerMask.NameToLayer("Enemy") 位左移构建掩码
多层合并 LayerMask.GetMask("Enemy", "Boss") 官方推荐的多层获取方式
动态添加层 `mask = (1 << LayerMask.NameToLayer("Projectile"))`
排除特定层 LayerMask filteredMask = ~0 & ~(1 << LayerMask.NameToLayer("UI")) 取反+位与实现层排除
所有层 Physics.AllLayers~0 32位全1掩码

高频错误及解决方案

错误类型 错误示例 正确写法 解决方案
层名拼写错误 NameToLayer("Enemi") 验证层名是否存在 添加层名检查逻辑
位运算优先级错误 `1 << 8 1 << 9` `(1 << 8)
空掩码 LayerMask mask = 0 添加默认层兜底 mask = mask==0 ? 1<<0 : mask
混淆层索引与掩码 Physics.Raycast(..., enemyLayer) Physics.Raycast(..., 1<<enemyLayer) 始终使用位掩码格式
盒状尺寸参数错误 OverlapBox(center, fullSize, ...) OverlapBox(center, fullSize/2, ...) 牢记用半长尺寸
相关推荐
AgilityBaby26 分钟前
UE5蓝图暴露变量,在游戏运行时修改变量实时变化、看向目标跟随目标Find Look at Rotation、修改玩家自身弹簧臂
笔记·游戏·ue5·游戏引擎·蓝图
蓝婷儿26 分钟前
6个月Python学习计划 Day 10 - 模块与标准库入门
java·python·学习
一叶知秋秋35 分钟前
python学习day34
开发语言·python·学习
爱凤的小光39 分钟前
C#数字图像处理(一)
开发语言·c#
一只鱼^_39 分钟前
用JS实现植物大战僵尸(前端作业)
javascript·css·vscode·游戏引擎·游戏程序·html5·动画
流年viv6 小时前
数据分析学习笔记——A/B测试
笔记·学习·数据分析·统计原理
真的想上岸啊9 小时前
学习STC51单片机22(芯片为STC89C52RCRC)
单片机·嵌入式硬件·学习
木子.李34710 小时前
数据结构-算法学习C++(入门)
数据库·c++·学习·算法
小白写代码hh10 小时前
Git入门到精通:30分钟掌握核心技巧
git·学习
shangjg310 小时前
Kafka 如何保证顺序消费
c#·linq