文章目录
前言
在Unity游戏开发中,碰撞检测是至关重要的功能。为了优化性能,开发者通常使用包围盒(Bounding Box)技术作为初步的碰撞检测手段。本文将深入探讨两种核心包围盒技术:AABB(轴对齐包围盒)和OBB(有向包围盒)。
一、AABB(轴对齐包围盒)
1、基本概念
AABB(Axis-Aligned Bounding Box)是最简单的包围盒形式,其特点是:
始终与世界坐标系的X/Y/Z轴对齐
不随物体旋转而改变方向
计算简单,性能高效
2、数学表示
一个AABB可以用两个点表示:
最小点(min):包含X/Y/Z的最小值
最大点(max):包含X/Y/Z的最大值
或者用中心点和半尺寸表示:
中心点(center)
半尺寸(extents):从中心到各边界的距离
3、Unity中的实现
Unity内置了Bounds结构体来处理AABB:
// 创建AABB
Bounds bounds = new Bounds(center, size);
// 检测AABB相交
bool isIntersecting = bounds.Intersects(otherBounds);
4、实际应用示例
// 获取预制体的AABB包围盒
Bounds GetPrefabBounds(GameObject prefab)
{
// 优先使用渲染器包围盒
Renderer renderer = prefab.GetComponentInChildren<Renderer>();
if (renderer != null) return renderer.bounds;
// 其次尝试碰撞器
Collider collider = prefab.GetComponentInChildren<Collider>();
if (collider != null) return collider.bounds;
// 默认返回单位大小
return new Bounds(Vector3.zero, Vector3.one);
}
/// <summary>
/// AABB碰撞检测,检查新物体是否与已放置物体发生重叠
/// </summary>
/// <param name="position">新物体的中心位置</param>
/// <param name="halfExtents">新物体的半尺寸(从中心到各边界的距离)</param>
/// <param name="existingBounds">已放置物体的包围盒列表</param>
/// <returns>true表示有重叠,false表示无重叠</returns>
bool CheckAABBOverlap(Vector3 position, Vector3 halfExtents, List<Bounds> existingBounds)
{
// 1. 创建新物体的包围盒
// Bounds构造函数需要中心点和完整尺寸,所以将halfExtents乘以2
Bounds newBounds = new Bounds(position, halfExtents * 2);
// 2. 遍历所有已放置物体的包围盒
foreach (var bounds in existingBounds)
{
// 3. 检查新物体与当前已放置物体是否相交
if (bounds.Intersects(newBounds))
{
// 4. 如果相交则立即返回true(发生重叠)
return true;
}
}
// 5. 遍历结束未发现重叠,返回false
return false;
}
调用
// 存储已放置物体的位置和大小(用于碰撞检测)
List<Bounds> placedBounds = new List<Bounds>();
//预制体
public GameObiect prefab;
Vector3 position = 预制体准备放置的位置;
// 获取预制体大小
Bounds bounds = GetPrefabBounds(prefab);
//获取包围盒(Bounds)的半尺寸
halfExtents = bounds.extents;
// 检查是否与其他物体重叠
if (CheckOverlap(position, halfExtents, placedBounds)) return;
// 实例化预制体
GameObject propInstance = Instantiate(prefab, position, Quaternion.identity);
// 添加到已放置列表
placedBounds.Add(new Bounds(position, halfExtents * 2));
它没有考虑物体的旋转。当物体旋转后,使用 Bounds.Intersects() 进行轴对齐包围盒检测会导致误判。
二、OBB(有向包围盒)
1、Physics.ComputePenetration (Unity 物理引擎)
1.1 基本概念
Physics.ComputePenetration 是 Unity 物理引擎提供的一个方法,用于精确计算两个碰撞体之间的穿透情况。它比简单的 Bounds.Intersects 或 Collider.bounds 检测更准确,特别是对于旋转后的物体或非轴对齐的碰撞体。
1.2 Unity中的实现
public static bool Physics.ComputePenetration(
Collider colliderA, // 第一个碰撞体
Vector3 positionA, // 第一个碰撞体的位置
Quaternion rotationA, // 第一个碰撞体的旋转
Collider colliderB, // 第二个碰撞体
Vector3 positionB, // 第二个碰撞体的位置
Quaternion rotationB, // 第二个碰撞体的旋转
out Vector3 direction, // 穿透方向(从A指向B)
out float distance // 穿透深度
);
返回值
true → 两个碰撞体发生穿透
false → 两个碰撞体没有穿透
1.3 实际应用示例
bool CheckOverlap(Vector3 position, Quaternion rotation, Vector3 size, List<GameObject> placedObjects)
{
// 创建一个临时 BoxCollider 用于检测
GameObject tempObj = new GameObject("TempCollider");
BoxCollider testCollider = tempObj.AddComponent<BoxCollider>();
testCollider.size = size; // 注意:size 是完整尺寸(不是 halfExtents)
// 检查与所有已放置物体的碰撞
foreach (GameObject obj in placedObjects)
{
Collider[] objColliders = obj.GetComponentsInChildren<Collider>();
foreach (Collider col in objColliders)
{
Vector3 dir;
float dist;
if (Physics.ComputePenetration(
testCollider, position, rotation,
col, col.transform.position, col.transform.rotation,
out dir, out dist))
{
Destroy(tempObj);
return true; // 发生重叠
}
}
}
Destroy(tempObj);
return false; // 无重叠
}
调用
// 修改为存储实际物体
List<GameObject> placedObjects = new List<GameObject>();
if (CheckOverlap(position, rotation, halfExtents * 2, placedObjects)) return;
GameObject propInstance = Instantiate(prefab, position, rotation);
// 实例化后添加
placedObjects.Add(propInstance);
2、OBB (SAT) 手动实现
2.1 基本概念
OBB(Oriented Bounding Box)是更精确的包围盒:
可以随物体旋转/缩放
方向与物体的本地坐标轴一致
使用分离轴定理(SAT)进行相交检测
2.2 数学表示
一个OBB需要存储:
中心点(center)
半尺寸(extents)
旋转(rotation)
2.3 Unity中的实现
Unity没有直接提供OBB结构,但可以自定义实现:
using UnityEngine;
/// <summary>
/// 有向包围盒(Oriented Bounding Box)结构体
/// </summary>
public struct OBB
{
public Vector3 center; // 包围盒中心点
public Vector3 extents; // 包围盒半尺寸(从中心到各边的距离)
public Quaternion rotation; // 包围盒旋转
/// <summary>
/// 检测两个OBB是否相交
/// </summary>
/// <param name="other">另一个OBB</param>
/// <returns>true表示相交,false表示不相交</returns>
public bool Intersects(OBB other)
{
// 使用分离轴定理(SAT)进行相交检测
return SATIntersection(this, other);
}
/// <summary>
/// 分离轴定理(SAT)实现
/// </summary>
private static bool SATIntersection(OBB a, OBB b)
{
// 获取两个OBB的旋转矩阵
Matrix4x4 aRot = Matrix4x4.Rotate(a.rotation);
Matrix4x4 bRot = Matrix4x4.Rotate(b.rotation);
// 需要测试的15条分离轴:
// 6条来自两个OBB的局部坐标轴
// 9条来自两两轴的叉积
Vector3[] axes = new Vector3[15];
// A的3个局部轴(X/Y/Z)
axes[0] = aRot.GetColumn(0).normalized; // A的X轴
axes[1] = aRot.GetColumn(1).normalized; // A的Y轴
axes[2] = aRot.GetColumn(2).normalized; // A的Z轴
// B的3个局部轴(X/Y/Z)
axes[3] = bRot.GetColumn(0).normalized; // B的X轴
axes[4] = bRot.GetColumn(1).normalized; // B的Y轴
axes[5] = bRot.GetColumn(2).normalized; // B的Z轴
// 计算A和B各轴的叉积(9条轴)
// 这些轴是两个OBB各边平面法线的方向
axes[6] = Vector3.Cross(axes[0], axes[3]).normalized; // A.X × B.X
axes[7] = Vector3.Cross(axes[0], axes[4]).normalized; // A.X × B.Y
axes[8] = Vector3.Cross(axes[0], axes[5]).normalized; // A.X × B.Z
axes[9] = Vector3.Cross(axes[1], axes[3]).normalized; // A.Y × B.X
axes[10] = Vector3.Cross(axes[1], axes[4]).normalized; // A.Y × B.Y
axes[11] = Vector3.Cross(axes[1], axes[5]).normalized; // A.Y × B.Z
axes[12] = Vector3.Cross(axes[2], axes[3]).normalized; // A.Z × B.X
axes[13] = Vector3.Cross(axes[2], axes[4]).normalized; // A.Z × B.Y
axes[14] = Vector3.Cross(axes[2], axes[5]).normalized; // A.Z × B.Z
// 在每条分离轴上做投影测试
foreach (var axis in axes)
{
// 忽略长度接近0的无效轴(叉积结果可能是零向量)
if (axis.sqrMagnitude < 0.001f) continue;
// 如果在当前轴上投影不重叠,则存在分离轴,OBB不相交
if (!OverlapOnAxis(a, b, axis))
return false;
}
// 所有轴上都重叠,则OBB相交
return true;
}
/// <summary>
/// 检测两个OBB在指定轴上的投影是否重叠
/// </summary>
private static bool OverlapOnAxis(OBB a, OBB b, Vector3 axis)
{
// 计算两个OBB在当前轴上的投影半径
float aProj = GetProjectionRadius(a, axis);
float bProj = GetProjectionRadius(b, axis);
// 计算两个中心点在当前轴上的距离
float centerDistance = Mathf.Abs(Vector3.Dot(axis, b.center - a.center));
// 如果中心距离小于投影半径之和,则投影重叠
return centerDistance <= (aProj + bProj);
}
/// <summary>
/// 计算OBB在指定轴上的投影半径
/// </summary>
private static float GetProjectionRadius(OBB obb, Vector3 axis)
{
// 获取OBB的旋转矩阵
Matrix4x4 rot = Matrix4x4.Rotate(obb.rotation);
// 计算OBB三个轴向的向量(考虑半尺寸)
Vector3 xAxis = rot.GetColumn(0) * obb.extents.x;
Vector3 yAxis = rot.GetColumn(1) * obb.extents.y;
Vector3 zAxis = rot.GetColumn(2) * obb.extents.z;
// 投影半径 = 各轴在测试轴上投影长度的绝对值之和
return Mathf.Abs(Vector3.Dot(xAxis, axis))
+ Mathf.Abs(Vector3.Dot(yAxis, axis))
+ Mathf.Abs(Vector3.Dot(zAxis, axis));
}
}
2.4 实际应用示例
/// <summary>
/// 检查新物体是否与已放置物体发生OBB重叠
/// </summary>
/// <param name="position">新物体的中心位置</param>
/// <param name="halfExtents">新物体的半尺寸</param>
/// <param name="rotation">新物体的旋转</param>
/// <param name="existingOBBs">已放置物体的OBB列表</param>
/// <returns>true表示有重叠,false表示无重叠</returns>
bool CheckOverlap(Vector3 position, Vector3 halfExtents, Quaternion rotation, List<OBB> existingOBBs)
{
// 创建新物体的OBB
OBB newOBB = new OBB()
{
center = position, // 设置中心点
extents = halfExtents, // 设置半尺寸
rotation = rotation // 设置旋转
};
// 遍历所有已放置物体的OBB
foreach (var obb in existingOBBs)
{
// 如果与任一已放置物体相交,返回true
if (newOBB.Intersects(obb))
return true;
}
// 没有发现重叠
return false;
}
调用
// 存储已放置物体的OBB列表
List<OBB> placedOBBs = new List<OBB>();
// 尝试在指定位置放置物体
if (!CheckOverlap(position, halfExtents, rotation, placedOBBs))
{
// 如果没有重叠,则实例化新物体
Instantiate(prop.prefab, position, rotation);
// 将新物体的OBB添加到已放置列表
placedOBBs.Add(new OBB()
{
center = position, // 记录中心位置
extents = halfExtents, // 记录半尺寸
rotation = rotation // 记录旋转
});
}
3、如何选择?
✅ 使用 OBB (SAT) 的情况
需要检测 大量简单形状(如地牢墙壁、道具摆放)。
在 非物理系统 中使用(如自定义碰撞逻辑)。
追求 极致性能(如子弹碰撞检测)。
✅ 使用 Physics.ComputePenetration 的情况
需要检测 复杂形状(如 MeshCollider)。
需要 穿透信息(如角色控制器解决卡顿)。
已在使用 Unity 物理系统(如 Rigidbody 物体)。
三、选择建议:
对静态物体或简单碰撞使用AABB
对动态旋转物体使用OBB
可采用两阶段检测:先用AABB快速筛选剔除绝对不相交的物体,再用OBB精确判断
专栏推荐
完结
好了,我是向宇
,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!