Unity基础-范围检测
Unity基础-范围检测
七、范围检测
概述
范围检测(Overlap Detection)是Unity物理系统中的一种常用技术,用于在不产生实际物理碰撞或不依赖刚体的情况下,判断指定区域内是否存在或有哪些碰撞器。它常用于游戏中瞬时的攻击范围判断、技能效果判定、区域触发等场景,例如:
- 玩家在前方5米处释放一个范围,检测此范围内的对象是否受到伤害。
- 玩家攻击,在前方1米圆形范围内检测对象是否受到伤害。
- 类似这种并没有实体物体,只是想检测在指定某一范围是否让敌方受到伤害时便可以使用。
简而言之,在指定位置进行范围判断,我们可以得到处于指定范围内的对象,目的是对这些对象进行处理(比如受伤、减血等)。
必备条件与注意点
- 必备条件:想要被范围检测到的对象,必须具备碰撞器(Collider)!
- 注意点:
- 范围检测相关API是瞬时的,只有当执行该代码时才进行一次范围检测。
- 范围检测相关API并不会真正产生一个碰撞器,它仅仅是进行碰撞判断计算。
层级 (LayerMask)
在进行范围检测时,我们通常需要检测指定层级(Layer)上的物体,以避免检测到不相关的对象,提高效率和精确性。Unity使用位运算来表示层级信息。
- 通过名字得到层级编号:
LayerMask.NameToLayer("LayerName")
- 层级编号是0~31,刚好32位,是一个
int
数。 - 每一个编号都代表二进制的一位。通过将1左移对应层级编号,可以得到该层级的二进制表示。
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
)- …
- 好处:一个
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); // 乘以一个系数使旋转更明显}
}
使用建议
- 性能优化:在频繁进行范围检测的场景,优先使用
NonAlloc
系列方法(如OverlapBoxNonAlloc
),以避免每次调用都产生新的Collider[]
数组,从而减少GC(垃圾回收)开销。 - 精确控制:善用
LayerMask
参数来精确控制只检测特定层级的对象,提高检测效率和准确性。 - 调试可视化:在开发过程中,可以使用
Debug.DrawBox
、Debug.DrawLine
等方法来可视化检测范围,帮助理解和调试。 - 触发器设置:范围检测通常用于获取范围内的对象信息,而不是模拟物理碰撞。确保被检测对象上的碰撞器如果设置为
Is Trigger
,则它们不会产生物理交互。