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, ...) 牢记用半长尺寸
相关推荐
楼田莉子1 小时前
C++算法题目分享:二叉搜索树相关的习题
数据结构·c++·学习·算法·leetcode·面试
三千道应用题1 小时前
WPF&C#超市管理系统(6)订单详情、顾客注册、商品销售排行查询和库存提示、LiveChat报表
开发语言·c#·wpf
奶黄小甜包2 小时前
C语言零基础第18讲:自定义类型—结构体
c语言·数据结构·笔记·学习
rannn_1115 小时前
【MySQL学习|黑马笔记|Day7】触发器和锁(全局锁、表级锁、行级锁、)
笔记·后端·学习·mysql
喜欢吃燃面5 小时前
C++算法竞赛:位运算
开发语言·c++·学习·算法
传奇开心果编程5 小时前
【传奇开心果系列】Flet框架实现的家庭记账本示例自定义模板
python·学习·ui·前端框架·自动化
唐青枫6 小时前
别滥用 Task.Run:C# 异步并发实操指南
c#·.net
_Kayo_12 小时前
node.js 学习笔记3 HTTP
笔记·学习
我好喜欢你~13 小时前
C#---StopWatch类
开发语言·c#
CCCC131016315 小时前
嵌入式学习(day 28)线程
jvm·学习