当前位置: 首页 > ai >正文

Unity基础-范围检测

Unity基础-范围检测

七、范围检测

概述

范围检测(Overlap Detection)是Unity物理系统中的一种常用技术,用于在不产生实际物理碰撞或不依赖刚体的情况下,判断指定区域内是否存在或有哪些碰撞器。它常用于游戏中瞬时的攻击范围判断、技能效果判定、区域触发等场景,例如:

  • 玩家在前方5米处释放一个范围,检测此范围内的对象是否受到伤害。
  • 玩家攻击,在前方1米圆形范围内检测对象是否受到伤害。
  • 类似这种并没有实体物体,只是想检测在指定某一范围是否让敌方受到伤害时便可以使用。

简而言之,在指定位置进行范围判断,我们可以得到处于指定范围内的对象,目的是对这些对象进行处理(比如受伤、减血等)。

必备条件与注意点

  • 必备条件:想要被范围检测到的对象,必须具备碰撞器(Collider)!
  • 注意点
    1. 范围检测相关API是瞬时的,只有当执行该代码时才进行一次范围检测。
    2. 范围检测相关API并不会真正产生一个碰撞器,它仅仅是进行碰撞判断计算。

层级 (LayerMask)

在进行范围检测时,我们通常需要检测指定层级(Layer)上的物体,以避免检测到不相关的对象,提高效率和精确性。Unity使用位运算来表示层级信息。

  • 通过名字得到层级编号:LayerMask.NameToLayer("LayerName")
  • 层级编号是0~31,刚好32位,是一个int数。
  • 每一个编号都代表二进制的一位。通过将1左移对应层级编号,可以得到该层级的二进制表示。
    • 0 层:1 << 00000 0000 0000 0000 0000 0000 0000 0001 (= 1)
    • 1 层:1 << 10000 0000 0000 0000 0000 0000 0000 0010 (= 2)
    • 2 层:1 << 20000 0000 0000 0000 0000 0000 0000 0100 (= 4)
    • 3 层:1 << 30000 0000 0000 0000 0000 0000 0000 1000 (= 8)
    • 4 层:1 << 40000 0000 0000 0000 0000 0000 0001 0000 (= 16)
  • 好处:一个int就可以表示所有想要检测的层级信息。我们可以通过位运算来选择想要检测的层级。
    • 检测单层:1 << LayerMask.NameToLayer("Default")
    • 检测多层:(1 << LayerMask.NameToLayer("Layer1")) | (1 << LayerMask.NameToLayer("Layer2"))
    • 检测所有层(默认):不传入层级参数,或传入-1

范围检测API

Unity Physics 类提供了一系列范围检测的API。它们通常分为两类:

  • 直接返回Collider[]数组的方法(有GC开销):例如 Physics.OverlapBox,每次调用会创建一个新的数组。
  • 非分配式方法(NonAlloc,无GC开销):例如 Physics.OverlapBoxNonAlloc,需要传入一个预先分配好的Collider[]数组来存储结果,推荐在频繁调用时使用。
1. 盒形范围检测 (Box Overlap)

在指定的一个立方体范围内检测所有碰撞体。

  • Physics.OverlapBox(center, halfExtents, orientation, layerMask)

    • center:立方体的中心点(世界坐标)。
    • halfExtents:立方体各边一半的长度(Vector3)。
    • orientation:立方体的旋转(Quaternion)。
    • layerMask:可选参数,要检测的层级(int)。
    • 返回值Collider[]数组,包含在该范围内的所有碰撞器。
  • Physics.OverlapBoxNonAlloc(center, halfExtents, results, orientation, layerMask)

    • results:预先分配好的Collider[]数组,用于存储检测结果。
    • 返回值int,表示实际检测到的碰撞器数量。

示例代码:

// 声明一个Collider数组用于NonAlloc方法
Collider[] boxColliders = new Collider[10]; // 使用OverlapBox(会产生GC)
Collider[] detectedColliders = Physics.OverlapBox(transform.position + Vector3.forward * 1, Vector3.one / 2, Quaternion.identity, 1 << LayerMask.NameToLayer("Default"));
if (detectedColliders.Length > 0)
{Debug.Log($"OverlapBox检测到 {detectedColliders.Length} 个碰撞体。");
}// 使用OverlapBoxNonAlloc(无GC)
int numColliders = Physics.OverlapBoxNonAlloc(transform.position + Vector3.forward * 1, Vector3.one / 2, boxColliders, Quaternion.identity, 1 << LayerMask.NameToLayer("Default"));
if (numColliders > 0)
{Debug.Log($"OverlapBoxNonAlloc检测到 {numColliders} 个碰撞体。");for (int i = 0; i < numColliders; i++){Debug.Log($"  - {boxColliders[i].name}");}
}
2. 球形范围检测 (Sphere Overlap)

在指定的一个球形范围内检测所有碰撞体。

  • Physics.OverlapSphere(position, radius, layerMask)

    • position:球体的中心点(世界坐标)。
    • radius:球体的半径。
    • layerMask:可选参数,要检测的层级(int)。
    • 返回值Collider[]数组,包含在该范围内的所有碰撞器。
  • Physics.OverlapSphereNonAlloc(position, radius, results, layerMask)

    • results:预先分配好的Collider[]数组。
    • 返回值int,表示实际检测到的碰撞器数量。

示例代码:

// 声明一个Collider数组用于NonAlloc方法
Collider[] sphereColliders = new Collider[10];// 使用OverlapSphere(会产生GC)
Collider[] detectedSphereColliders = Physics.OverlapSphere(transform.position + Vector3.down * 1, 10f, 1 << LayerMask.NameToLayer("Default"));
if (detectedSphereColliders.Length > 0)
{Debug.Log($"OverlapSphere检测到 {detectedSphereColliders.Length} 个碰撞体。");
}// 使用OverlapSphereNonAlloc(无GC)
int numSphereColliders = Physics.OverlapSphereNonAlloc(transform.position + Vector3.down * 1, 10f, sphereColliders, 1 << LayerMask.NameToLayer("Default"));
if (numSphereColliders > 0)
{Debug.Log($"OverlapSphereNonAlloc检测到 {numSphereColliders} 个碰撞体。");for (int i = 0; i < numSphereColliders; i++){Debug.Log($"  - {sphereColliders[i].name}");}
}
3. 胶囊范围检测 (Capsule Overlap)

在指定的一个胶囊体范围内检测所有碰撞体。

  • Physics.OverlapCapsule(point1, point2, radius, layerMask)

    • point1:胶囊体底部半球的中心点(世界坐标)。
    • point2:胶囊体顶部半球的中心点(世界坐标)。
    • radius:胶囊体的半径。
    • layerMask:可选参数,要检测的层级(int)。
    • 返回值Collider[]数组,包含在该范围内的所有碰撞器。
  • Physics.OverlapCapsuleNonAlloc(point1, point2, radius, results, layerMask)

    • results:预先分配好的Collider[]数组。
    • 返回值int,表示实际检测到的碰撞器数量。

示例代码:

// 声明一个Collider数组用于NonAlloc方法
Collider[] capsuleColliders = new Collider[10];// 使用OverlapCapsule(会产生GC)
Collider[] detectedCapsuleColliders = Physics.OverlapCapsule(transform.position, transform.position + Vector3.forward * 5, 0.5f, 1 << LayerMask.NameToLayer("Default"));
if (detectedCapsuleColliders.Length > 0)
{Debug.Log($"OverlapCapsule检测到 {detectedCapsuleColliders.Length} 个碰撞体。");
}// 使用OverlapCapsuleNonAlloc(无GC)
int numCapsuleColliders = Physics.OverlapCapsuleNonAlloc(transform.position, transform.position + Vector3.forward * 5, 0.5f, capsuleColliders, 1 << LayerMask.NameToLayer("Default"));
if (numCapsuleColliders > 0)
{Debug.Log($"OverlapCapsuleNonAlloc检测到 {numCapsuleColliders} 个碰撞体。");for (int i = 0; i < numCapsuleColliders; i++){Debug.Log($"  - {capsuleColliders[i].name}");}
}

实际应用示例:WASD移动与多类型范围检测

这个示例提供一个PlayController脚本,用于控制一个游戏对象的移动,并实现三种不同形状的碰撞范围检测:盒形、胶囊体和球形。移动通过Transform.Translate实现,碰撞检测则利用Unity的Physics.Overlap系列方法。

核心思想:

  • WASD 移动控制:通过获取水平和垂直输入,使用Transform.Translate方法实现游戏对象的移动。
  • 多种碰撞检测
    • Physics.OverlapBox:用于检测一个指定盒子范围内的所有碰撞体。
    • Physics.OverlapCapsule:用于检测一个指定胶囊体范围内的所有碰撞体。
    • Physics.OverlapSphere:用于检测一个指定球形范围内的所有碰撞体。
  • LayerMask (层掩码):用于过滤碰撞检测,只检测指定层上的对象,提高效率和精度。

代码实现 (PlayController.cs)

点击展开/折叠 PlayController.cs 代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayController : MonoBehaviour
{// 用于存储移动向量private Vector3 movement;// 移动速度,可在Inspector面板中调整public float moveSpeed = 2f; // 用于存储碰撞检测结果的Collider数组,预设大小为10个,避免频繁GCprivate Collider[] colliders = new Collider[10];// Update是MonoBehaviour的生命周期函数,每帧调用一次void Update(){// 处理对象的移动逻辑HandleMovement();// 处理对象的碰撞检测逻辑HandleOverLap();}/// <summary>/// 处理碰撞检测逻辑。/// 根据按键(J、K、L)执行不同类型的范围检测。/// </summary>void HandleOverLap(){// J键按下时执行盒形(Box)范围检测if(Input.GetKeyDown(KeyCode.J)){// Physics.OverlapBox:检测一个立方体范围内的所有碰撞体// transform.position + transform.forward * 1:检测中心点在物体前方1个单位// Vector3.one:检测盒子的尺寸为1x1x1// transform.rotation:检测盒子的旋转与物体当前旋转一致// 1 << LayerMask.NameToLayer("Default"):只检测"Default"层上的物体int numDetected = Physics.OverlapBoxNonAlloc(transform.position + transform.forward * 1, Vector3.one / 2, // 半尺寸,所以实际尺寸是Vector3.onecolliders, transform.rotation, 1 << LayerMask.NameToLayer("Default"));// 清空数组中未检测到的部分for (int i = numDetected; i < colliders.Length; i++){colliders[i] = null;}if (numDetected > 0){Debug.Log($"J键:盒形检测到 {numDetected} 个碰撞体!");}else{Debug.Log("J键:盒形检测未发现碰撞体。");}}// K键按下时执行胶囊体(Capsule)范围检测if(Input.GetKeyDown(KeyCode.K)){// Physics.OverlapCapsule:检测一个胶囊体范围内的所有碰撞体// transform.position:胶囊体的底部中心点// transform.position + transform.forward * 5:胶囊体的顶部中心点,在物体前方5个单位// 0.5f:胶囊体的半径// 1 << LayerMask.NameToLayer("Default"):只检测"Default"层上的物体int numDetected = Physics.OverlapCapsuleNonAlloc(transform.position, transform.position + transform.forward * 5, 0.5f, colliders, 1 << LayerMask.NameToLayer("Default"));// 清空数组中未检测到的部分for (int i = numDetected; i < colliders.Length; i++){colliders[i] = null;}if (numDetected > 0){Debug.Log($"K键:胶囊形检测到 {numDetected} 个碰撞体!");}else{Debug.Log("K键:胶囊形检测未发现碰撞体。");}}// L键按下时执行球形(Sphere)范围检测if(Input.GetKeyDown(KeyCode.L)){// Physics.OverlapSphere:检测一个球形范围内的所有碰撞体// transform.position + Vector3.down * 1:球体的中心点在物体下方1个单位// 10f:球体的半径为10个单位// 1 << LayerMask.NameToLayer("Default"):只检测"Default"层上的物体int numDetected = Physics.OverlapSphereNonAlloc(transform.position + Vector3.down * 1, 10f, colliders, 1 << LayerMask.NameToLayer("Default"));// 清空数组中未检测到的部分for (int i = numDetected; i < colliders.Length; i++){colliders[i] = null;}if (numDetected > 0){Debug.Log($"L键:球形检测到 {numDetected} 个碰撞体!");}else{Debug.Log("L键:球形检测未发现碰撞体。");}}// 如果检测到碰撞体,则遍历并打印所有检测到的碰撞体名称// 这里使用循环来确保只处理实际检测到的数量for(int i = 0; i < colliders.Length; i++){// 确保当前元素不为空才打印if (colliders[i] != null) {Debug.Log($"检测到碰撞体: {colliders[i].name}"); }}}/// <summary>/// 处理移动逻辑。/// 根据WASD键的输入控制物体的移动和旋转。/// </summary>void HandleMovement(){// 获取水平轴(A/D键或左右箭头)的输入float horizontal = Input.GetAxis("Horizontal");// 获取垂直轴(W/S键或上下箭头)的输入float vertical = Input.GetAxis("Vertical");// 基于垂直输入前后移动// Time.deltaTime 用于使移动速度与帧率无关transform.Translate(Vector3.forward * vertical * moveSpeed * Time.deltaTime);// 基于水平输入左右旋转// 旋转速度可以根据moveSpeed调整,或者单独设置一个rotateSpeedtransform.Rotate(Vector3.up * horizontal * moveSpeed * 50 * Time.deltaTime); // 乘以一个系数使旋转更明显}
}

使用建议

  1. 性能优化:在频繁进行范围检测的场景,优先使用NonAlloc系列方法(如OverlapBoxNonAlloc),以避免每次调用都产生新的Collider[]数组,从而减少GC(垃圾回收)开销。
  2. 精确控制:善用LayerMask参数来精确控制只检测特定层级的对象,提高检测效率和准确性。
  3. 调试可视化:在开发过程中,可以使用Debug.DrawBoxDebug.DrawLine等方法来可视化检测范围,帮助理解和调试。
  4. 触发器设置:范围检测通常用于获取范围内的对象信息,而不是模拟物理碰撞。确保被检测对象上的碰撞器如果设置为Is Trigger,则它们不会产生物理交互。
http://www.xdnf.cn/news/14332.html

相关文章:

  • Redis全面深入学习目录
  • 求数组中最长单调不降连续子数组的长度
  • stm32 f103c8t6仿真 串口收发测试
  • 用AI配合MCP快速生成n8n工作流
  • 【Linux服务器】-安装zabbix-负载环境(故障自动切换场景)
  • HarmonyOS Grid 网格拖拽完全指南
  • 设备健康管理系统搭建全技术解析:从架构设计到智能运维实践
  • Linux 忘记root密码如何解决-linux025
  • 理解 package.json 中的版本控制:“nuxt“: “3.16.0“ vs “nuxt“: “^3.16.0“ 的深层差异
  • DependencyMatcher + ML Reranking 策略设计实践
  • Qt3d中的材质--PBR材质
  • vue中computed和watch区别
  • jxWebUI--简单易用的webUI库
  • 大模型微调(Fine-tuning)概览
  • 算法导论第七章:快速排序的艺术与科学
  • 使用axios及和spirng boot 交互
  • @SpringBootTest 详解
  • Day32
  • 《Vuejs设计与实现》第 9 章(简单 diff 算法)
  • NISP-PTE基础实操——SQL注入
  • [蓝桥杯 2025 国 B] 斐波那契字符串一一题解
  • 论文笔记 <交通灯> <多智能体>DERLight双重经验回放灯机制
  • HTML5+JS实现一个简单的SVG 贝塞尔曲线可视化设计器,通过几个点移动位置,控制曲线的方向
  • 路由器端口映射怎么设置?本地固定内网IP给外面网络连接访问
  • [深度学习]目标检测YOLO v3
  • AI视野:视频处理AI排行榜Top10 | 2025年05月
  • 解决电脑第一排按键功能失效的问题
  • 多维数据透视分析应用案例与深度解析
  • Micro-F1分数(多选)
  • 基于Python爬虫的房价可视化