1. 项目概述为什么AABB碰撞检测值得你花时间在游戏开发里碰撞检测是个绕不开的坎。无论是角色踩到地面、子弹击中敌人还是玩家捡起一个道具背后都离不开它。而轴对齐包围盒也就是我们常说的AABB可以说是碰撞检测家族里最基础、最高效的成员之一。它的“轴对齐”特性意味着它的边与坐标系的轴平行这带来了一个巨大的计算优势判断两个AABB是否相交只需要比较它们在X、Y、Z三个轴上的最小值和最大值区间是否有重叠。这个计算简单到几乎可以忽略不计因此在性能敏感的场合比如物理引擎的宽阶段检测或者需要处理海量静态物体的场景AABB是首选。但是简单不等于不会出错。我见过太多项目初期为了赶进度随手写个if (obj1.x obj2.x width ...)就了事。等到游戏物体旋转了、缩放了或者需要更精确的检测时bug就接踵而至角色卡进墙里、子弹穿模、触发检测时灵时不灵。这些问题排查起来极其耗时往往需要回头重构整个碰撞逻辑。所以今天我们不聊那些复杂的凸包碰撞或者连续碰撞检测就聚焦在这个最基础、最常用但也最容易埋坑的AABB上。我将分享一种被称为“Slabs Method”的思路它本质上是对AABB相交测试原理的一种清晰、可扩展的理解方式。掌握了它你不仅能快速在Unity或Unreal中写出健壮的AABB检测代码更能深刻理解碰撞检测的数学本质未来遇到更复杂的需求也能从容应对。无论你是刚入门的新手还是想巩固基础的熟手这篇指南都能帮你避开那些我亲自踩过的坑。2. Slabs Method核心思想拆解从“盒子”到“平板”Slabs Method直译过来是“平板法”或“ slabs 方法”。这个名字听起来有点抽象但它的思想非常直观。我们不要把它想成一个高深的算法而是看作一种理解AABB碰撞的思维方式。2.1 什么是“Slab”想象一下AABB一个方方正正的盒子。Slabs Method建议我们不要把它看作一个整体而是将它分解。怎么分解呢沿着每一个坐标轴的方向这个盒子都可以被看作是两个无限延伸的“平板”夹出来的空间。以3D空间为例我们有X、Y、Z三个轴。对于X轴AABB在X方向上的范围是[min.x, max.x]。我们可以想象两个无限大的平面一个位于x min.x另一个位于x max.x。这两个平面平行于YZ平面它们之间的空间就是物体在X轴上“占据”的区间。这一对平面夹出的无限延伸的空间带就是一个“X轴方向的Slab”。同理我们也有Y轴和Z轴方向的Slab。关键点来了一个AABB就是这三个相互垂直的SlabX-Slab, Y-Slab, Z-Slab的交集。只有当一个点同时位于这三个Slab的内部时它才在这个AABB内部。2.2 如何用Slab思想判断碰撞判断两个AABB我们叫它A和B是否相交Slabs Method告诉我们如果两个AABB在所有轴向上的Slab都相交那么它们整体就相交只要在任何一个轴向上它们的Slab不相交那么两个AABB就一定分离。这其实就是我们常说的“分离轴定理”在AABB这个特例上的简化体现。分离轴定理说如果能找到一个轴使得两个物体在该轴上的投影不重叠那么它们就没碰撞。对于AABB我们只需要检查三个标准的坐标轴X, Y, Z就够了。用数学表示就是对于每一个轴i(i 属于 {x, y, z})A的Slab区间是[A.min.i, A.max.i]B的Slab区间是[B.min.i, B.max.i]这两个区间相交的条件是A.min.i B.max.i且B.min.i A.max.i。这个条件比记忆“min小于max”那种公式更不容易出错因为它对称地处理了两个物体。注意这里有一个非常常见的“差一错误”陷阱。很多新手会写成A.max.i B.min.i来判断是否碰撞这看似正确但在边界情况刚好接触下不同游戏逻辑对“碰撞”的定义可能不同。是包含边界和还是不包含和Slabs Method清晰地要求你定义区间是开区间还是闭区间。在大多数物理引擎中包含边界的判断即允许刚好接触是更常用的因为它更稳定。我们在实现时必须明确这一点。2.3 Slabs Method的优势不止于相交检测理解了Slab是区间的交集它的威力就显现出来了。我们不仅能回答“是否碰撞”还能轻松地计算碰撞深度穿透向量如果碰撞了在每个轴上重叠的部分是多少取所有轴上重叠最小的那个轴其重叠量就是最短的分离距离方向指向需要将物体推离的方向。这对于碰撞响应把物体推开至关重要。计算接触时间用于连续检测在物体运动时我们可以计算它在每个轴上进入和离开另一个AABB的Slab的时间点。取最晚的进入时间和最早的离开时间就能得到实际发生碰撞的时间窗口。这是实现“子弹时间”或防止高速物体穿模的基础。扩展到OBB定向包围盒Slabs Method的思想可以推广。对于旋转了的盒子OBB我们不再使用世界坐标轴而是使用物体自身的局部坐标轴作为“Slab”的方向。虽然计算变复杂了但核心逻辑一脉相承。所以Slabs Method不仅仅是一个代码写法它提供了一个坚实的、可扩展的概念框架。接下来我们就基于这个框架在Unity和Unreal中实现它。3. 在Unity中实现Slabs Method驱动的AABB检测Unity提供了强大的Collider组件和物理引擎但有时我们需要更轻量、更定制化的碰撞逻辑比如用于AI感知范围、技能触发区域或者非物理对象的交互。这时手动实现AABB检测就派上用场了。3.1 基础数据结构与表示在Unity中一个AABB最自然的表示就是Bounds结构体。它包含了center中心点和size尺寸我们可以很容易地计算出min和max。public struct AABB { public Vector3 min; public Vector3 max; public AABB(Vector3 center, Vector3 size) { Vector3 extents size * 0.5f; min center - extents; max center extents; } // 也可以直接从Transform和MeshRenderer构建 public static AABB FromRenderer(MeshRenderer renderer) { Bounds bounds renderer.bounds; return new AABB(bounds.center, bounds.size); } }3.2 核心相交检测函数根据Slabs Method我们实现一个静态方法。这里我采用包含边界的判断和因为它更通用。public static bool Intersects(AABB a, AABB b) { // 检查X轴Slab是否相交 bool overlapX (a.min.x b.max.x) (b.min.x a.max.x); // 检查Y轴Slab是否相交 bool overlapY (a.min.y b.max.y) (b.min.y a.max.y); // 检查Z轴Slab是否相交 bool overlapZ (a.min.z b.max.z) (b.min.z a.max.z); // 所有轴Slab都相交则AABB相交 return overlapX overlapY overlapZ; }代码极其简单但这就是Slabs Method的核心。你可以看到它清晰地分离了三个轴上的判断逻辑一目了然。3.3 获取碰撞信息重叠深度与法线仅仅知道“撞了”还不够我们通常需要知道“撞了多少”和“从哪个方向撞的”以便做出响应比如把角色推开。public static bool Intersects(AABB a, AABB b, out Vector3 penetration) { penetration Vector3.zero; if (!Intersects(a, b)) return false; // 先用简单方法判断是否相交 // 计算在每个轴上的重叠深度穿透量 // 重叠深度 两个区间中较小的max - 较大的min float overlapX Mathf.Min(a.max.x, b.max.x) - Mathf.Max(a.min.x, b.min.x); float overlapY Mathf.Min(a.max.y, b.max.y) - Mathf.Max(a.min.y, b.min.y); float overlapZ Mathf.Min(a.max.z, b.max.z) - Mathf.Max(a.min.z, b.min.z); // 找出最小的重叠深度及其对应的轴 // 我们认为最小的重叠方向是最短的分离路径 float minOverlap Mathf.Min(Mathf.Min(overlapX, overlapY), overlapZ); if (Mathf.Approximately(minOverlap, overlapX)) { // X轴重叠最小分离方向沿X轴 penetration.x (a.center.x b.center.x) ? -overlapX : overlapX; // 方向由中心点相对位置决定 } else if (Mathf.Approximately(minOverlap, overlapY)) { // Y轴重叠最小分离方向沿Y轴 penetration.y (a.center.y b.center.y) ? -overlapY : overlapY; } else { // Z轴重叠最小分离方向沿Z轴 penetration.z (a.center.z b.center.z) ? -overlapZ : overlapZ; } return true; }这个函数返回一个penetration向量它的方向指出了将物体A从物体B中推离的方向长度就是需要推开的距离。这是实现简单物理响应如角色与墙壁碰撞的关键数据。实操心得在比较浮点数判断“最小重叠轴”时直接使用非常危险因为浮点数有精度误差。我强烈推荐使用Mathf.Approximately或者判断差值是否小于一个极小的阈值如1e-5f。否则在两个轴重叠深度极其接近的情况下可能会得到不稳定的碰撞法线导致物体在角落抖动。3.4 性能优化与批量检测当需要检测大量AABB时例如一个技能同时检测范围内所有敌人逐对检测的O(n²)复杂度是不可接受的。这时我们可以利用空间划分数据结构如四叉树2D或八叉树/网格3D。其思想依然是Slabs Method的延伸我们不是在全局范围比较所有物体而是先快速判断一个AABB与空间划分的“节点”的Slab是否相交。如果连节点都不相交那节点内的所有物体也都不可能相交。这里给出一个非常简化的基于网格的宽阶段检测思路将世界空间划分为均匀的网格。每个AABB根据其min和max坐标计算出它覆盖了哪些网格单元格。将AABB的引用注册到它覆盖的所有单元格中。当检测某个AABB时只需获取它所在单元格及相邻单元格内的其他AABB列表进行窄阶段精确的AABB相交检测即可。这极大地减少了需要两两比较的对象对数。Unity的Physics系统内部就采用了类似但更复杂的Broad-Phase算法。4. 在Unreal Engine中应用Slabs MethodUnreal EngineUE的碰撞系统非常庞大主要围绕PrimitiveComponent和其BodyInstance中的碰撞形状展开。手动实现AABB检测的场景与Unity类似多用于Gameplay逻辑而非物理模拟。4.1 使用FBox构建AABBUE中表示AABB的类是FBox。它可以直接从很多组件获取。// 从一个StaticMeshComponent获取其世界空间的AABB UStaticMeshComponent* MeshComp ...; FBoxSphereBounds Bounds MeshComp-CalcBounds(MeshComp-GetComponentTransform()); FBox AABB Bounds.GetBox(); // 这就是一个FBox // FBox提供了Min和Max成员变量 FVector Min AABB.Min; FVector Max AABB.Max;4.2 实现相交检测与信息获取在UE中我们可以用C编写一个工具函数其逻辑与Unity版本完全一致体现了Slabs Method的跨引擎通用性。bool AABBIntersection(const FBox BoxA, const FBox BoxB, FVector OutPenetration) { OutPenetration FVector::ZeroVector; // Slabs Method: 检查每个轴区间是否重叠 if (BoxA.Max.X BoxB.Min.X || BoxB.Max.X BoxA.Min.X) return false; if (BoxA.Max.Y BoxB.Min.Y || BoxB.Max.Y BoxA.Min.Y) return false; if (BoxA.Max.Z BoxB.Min.Z || BoxB.Max.Z BoxA.Min.Z) return false; // 计算重叠深度 float OverlapX FMath::Min(BoxA.Max.X, BoxB.Max.X) - FMath::Max(BoxA.Min.X, BoxB.Min.X); float OverlapY FMath::Min(BoxA.Max.Y, BoxB.Max.Y) - FMath::Max(BoxA.Min.Y, BoxB.Min.Y); float OverlapZ FMath::Min(BoxA.Max.Z, BoxB.Max.Z) - FMath::Max(BoxA.Min.Z, BoxB.Min.Z); // 寻找最小重叠轴 float MinOverlap FMath::Min3(OverlapX, OverlapY, OverlapZ); const float Tolerance 1e-5f; if (FMath::IsNearlyEqual(MinOverlap, OverlapX, Tolerance)) { OutPenetration.X (BoxA.GetCenter().X BoxB.GetCenter().X) ? -OverlapX : OverlapX; } else if (FMath::IsNearlyEqual(MinOverlap, OverlapY, Tolerance)) { OutPenetration.Y (BoxA.GetCenter().Y BoxB.GetCenter().Y) ? -OverlapY : OverlapY; } else { OutPenetration.Z (BoxA.GetCenter().Z BoxB.GetCenter().Z) ? -OverlapZ : OverlapZ; } return true; }4.3 与UE物理系统的协作大多数时候我们不需要手动实现这些。UE的碰撞查询系统功能强大Overlap查询使用UWorld::OverlapMultiBy...系列函数可以快速检测与某个形状重叠的所有物体。你可以指定一个FCollisionShape其中就包括Box。Sweep查询用于连续碰撞检测检测一个形状沿着一条线段移动时是否会撞到什么。这本质上是在多个时间点上进行Slab测试。碰撞预设与通道这是UE碰撞系统的精髓。通过合理设置对象的碰撞预设和响应通道你可以精细控制什么和什么能碰撞以及碰撞后是重叠、阻挡还是忽略。手动检测通常用于那些不适合或不想用物理引擎的Gameplay逻辑。一个经典的使用场景你的游戏有一个“警觉区域”当敌人进入这个扇形区域时会被触发。你可以用物理系统做一个大的球形碰撞体作为触发器简单但可能产生不必要的物理开销。手动用AABB做一个快速的宽阶段过滤先判断敌人是否在AABB包围盒内然后再进行精确的扇形检测计算角度和距离。这种方法更高效、更可控。这里的AABB检测就可以用我们上面实现的Slabs Method。5. 常见陷阱、优化策略与实战技巧即使理解了原理在实际项目中应用AABB碰撞仍然有很多细节需要注意。下面是我总结的一些“坑”和应对技巧。5.1 精度问题与浮点数误差这是3D编程的永恒之敌。在判断边界时直接使用或可能会因为浮点数误差导致在边界处时碰撞时而不碰撞。对策引入一个微小的容差EPSILON例如1e-5f。将判断条件改为A.max.x EPSILON B.min.x。但要注意这实际上略微放大了你的碰撞体。更稳健的做法是在设计游戏逻辑时就避免让游戏对象长期处于“刚好接触”的临界状态。5.2 动态物体的AABB更新对于移动、旋转、缩放的物体其世界空间的AABB需要每帧更新。低效做法每帧根据模型的所有顶点重新计算最小/最大值。高效做法在局部空间保存一个固定的AABB通常是模型的包围盒。每帧根据物体的变换矩阵Transform Matrix将这个局部AABB的8个顶点变换到世界空间。遍历这8个顶点快速计算出新的世界空间AABB的min和max。对于只有平移和均匀缩放的物体计算可以简化worldMin transform.localToWorldMatrix.MultiplyPoint(localMin)然后根据缩放系数调整size。但如果物体发生了旋转局部空间的AABB变换到世界空间后通常不再是一个轴对齐的盒子而是一个OBB。这时如果你仍需要AABB就必须计算顶点集的外包或者接受一个更大的、能包裹住旋转后物体的保守AABB。踩坑实录早期我曾试图用物体的position和固定的size来计算AABB忽略了物体的旋转。结果就是一个旋转了45度的长条物体其AABB还是一个正方向的盒子但实际模型已经斜着伸出盒子外了导致碰撞检测范围严重不准。切记世界空间的AABB必须考虑物体的完整变换旋转、缩放。5.3 缩放与非均匀缩放的影响如果物体被非均匀缩放例如X轴放大2倍Y轴不变其局部空间的AABB经过变换后形状会改变。手动计算时需要将缩放系数应用到局部AABB的extents半尺寸上再结合旋转和平移。在Unity中直接使用Renderer.bounds或Collider.bounds是最省事的它们已经处理好了这一切。在Unreal中使用CalcBounds函数。5.4 高速物体穿模问题这是AABB以及所有离散碰撞检测的固有缺陷。如果物体一帧移动的距离超过其自身尺寸或对方物体的尺寸它就可能从一端“瞬移”到另一端中间没有检测到碰撞。对策连续碰撞检测。思路是将运动过程视为一条线段从上一帧位置到当前帧位置检测这条线段与目标AABB是否相交。这可以通过对每个轴向上的Slab分别计算射线进入和离开的时间来实现最后取交集。这就是Slabs Method在时间维度上的延伸。虽然计算量稍大但对于子弹、高速移动的玩家角色来说是必要的。5.5 调试与可视化看不见的碰撞体是BUG的温床。务必为你的自定义AABB检测添加调试绘制功能。Unity在OnDrawGizmos或OnDrawGizmosSelected中使用Gizmos.DrawWireCube和Gizmos.color来绘制AABB的线框。Unreal使用DrawDebugBox函数可以在游戏运行时或编辑器中绘制出盒子的轮廓。 清晰的调试可视化能帮你快速确认AABB的大小、位置是否正确是节省调试时间的利器。6. 从AABB到更复杂形状的思维延伸掌握了Slabs Method你就掌握了分离轴定理的入门钥匙。当你需要处理更复杂的碰撞形状时比如OBB定向包围盒、凸包甚至三角形网格其核心思想是相通的找到一组潜在的分离轴对于OBB是每个盒子的3个本地轴对于凸包是每个面的法线以及边叉乘得到的轴。将两个物体投影到每一个轴上得到两个区间。如果存在任何一个轴使得这两个投影区间不重叠则物体分离。如果所有轴上的投影区间都重叠则物体相交。AABB只是这个通用算法的一个特例它的分离轴就是世界的X、Y、Z轴且计算极其简单。理解了这一点你再去看Unity的MeshCollider或Unreal的复杂碰撞体就不会再觉得神秘。它们无非是在更多、更复杂的轴上执行同样的区间重叠测试。所以花时间吃透AABB和Slabs Method绝对是一笔划算的投资。它建立起的思维模型能让你在游戏开发的道路上走得更稳、更远。下次当你需要写碰撞检测时不妨先在脑海里画一画那些“平板”理一理各个轴上的区间代码自然就会清晰、健壮起来。
AABB碰撞检测:Slabs Method原理与Unity/Unreal实战指南
1. 项目概述为什么AABB碰撞检测值得你花时间在游戏开发里碰撞检测是个绕不开的坎。无论是角色踩到地面、子弹击中敌人还是玩家捡起一个道具背后都离不开它。而轴对齐包围盒也就是我们常说的AABB可以说是碰撞检测家族里最基础、最高效的成员之一。它的“轴对齐”特性意味着它的边与坐标系的轴平行这带来了一个巨大的计算优势判断两个AABB是否相交只需要比较它们在X、Y、Z三个轴上的最小值和最大值区间是否有重叠。这个计算简单到几乎可以忽略不计因此在性能敏感的场合比如物理引擎的宽阶段检测或者需要处理海量静态物体的场景AABB是首选。但是简单不等于不会出错。我见过太多项目初期为了赶进度随手写个if (obj1.x obj2.x width ...)就了事。等到游戏物体旋转了、缩放了或者需要更精确的检测时bug就接踵而至角色卡进墙里、子弹穿模、触发检测时灵时不灵。这些问题排查起来极其耗时往往需要回头重构整个碰撞逻辑。所以今天我们不聊那些复杂的凸包碰撞或者连续碰撞检测就聚焦在这个最基础、最常用但也最容易埋坑的AABB上。我将分享一种被称为“Slabs Method”的思路它本质上是对AABB相交测试原理的一种清晰、可扩展的理解方式。掌握了它你不仅能快速在Unity或Unreal中写出健壮的AABB检测代码更能深刻理解碰撞检测的数学本质未来遇到更复杂的需求也能从容应对。无论你是刚入门的新手还是想巩固基础的熟手这篇指南都能帮你避开那些我亲自踩过的坑。2. Slabs Method核心思想拆解从“盒子”到“平板”Slabs Method直译过来是“平板法”或“ slabs 方法”。这个名字听起来有点抽象但它的思想非常直观。我们不要把它想成一个高深的算法而是看作一种理解AABB碰撞的思维方式。2.1 什么是“Slab”想象一下AABB一个方方正正的盒子。Slabs Method建议我们不要把它看作一个整体而是将它分解。怎么分解呢沿着每一个坐标轴的方向这个盒子都可以被看作是两个无限延伸的“平板”夹出来的空间。以3D空间为例我们有X、Y、Z三个轴。对于X轴AABB在X方向上的范围是[min.x, max.x]。我们可以想象两个无限大的平面一个位于x min.x另一个位于x max.x。这两个平面平行于YZ平面它们之间的空间就是物体在X轴上“占据”的区间。这一对平面夹出的无限延伸的空间带就是一个“X轴方向的Slab”。同理我们也有Y轴和Z轴方向的Slab。关键点来了一个AABB就是这三个相互垂直的SlabX-Slab, Y-Slab, Z-Slab的交集。只有当一个点同时位于这三个Slab的内部时它才在这个AABB内部。2.2 如何用Slab思想判断碰撞判断两个AABB我们叫它A和B是否相交Slabs Method告诉我们如果两个AABB在所有轴向上的Slab都相交那么它们整体就相交只要在任何一个轴向上它们的Slab不相交那么两个AABB就一定分离。这其实就是我们常说的“分离轴定理”在AABB这个特例上的简化体现。分离轴定理说如果能找到一个轴使得两个物体在该轴上的投影不重叠那么它们就没碰撞。对于AABB我们只需要检查三个标准的坐标轴X, Y, Z就够了。用数学表示就是对于每一个轴i(i 属于 {x, y, z})A的Slab区间是[A.min.i, A.max.i]B的Slab区间是[B.min.i, B.max.i]这两个区间相交的条件是A.min.i B.max.i且B.min.i A.max.i。这个条件比记忆“min小于max”那种公式更不容易出错因为它对称地处理了两个物体。注意这里有一个非常常见的“差一错误”陷阱。很多新手会写成A.max.i B.min.i来判断是否碰撞这看似正确但在边界情况刚好接触下不同游戏逻辑对“碰撞”的定义可能不同。是包含边界和还是不包含和Slabs Method清晰地要求你定义区间是开区间还是闭区间。在大多数物理引擎中包含边界的判断即允许刚好接触是更常用的因为它更稳定。我们在实现时必须明确这一点。2.3 Slabs Method的优势不止于相交检测理解了Slab是区间的交集它的威力就显现出来了。我们不仅能回答“是否碰撞”还能轻松地计算碰撞深度穿透向量如果碰撞了在每个轴上重叠的部分是多少取所有轴上重叠最小的那个轴其重叠量就是最短的分离距离方向指向需要将物体推离的方向。这对于碰撞响应把物体推开至关重要。计算接触时间用于连续检测在物体运动时我们可以计算它在每个轴上进入和离开另一个AABB的Slab的时间点。取最晚的进入时间和最早的离开时间就能得到实际发生碰撞的时间窗口。这是实现“子弹时间”或防止高速物体穿模的基础。扩展到OBB定向包围盒Slabs Method的思想可以推广。对于旋转了的盒子OBB我们不再使用世界坐标轴而是使用物体自身的局部坐标轴作为“Slab”的方向。虽然计算变复杂了但核心逻辑一脉相承。所以Slabs Method不仅仅是一个代码写法它提供了一个坚实的、可扩展的概念框架。接下来我们就基于这个框架在Unity和Unreal中实现它。3. 在Unity中实现Slabs Method驱动的AABB检测Unity提供了强大的Collider组件和物理引擎但有时我们需要更轻量、更定制化的碰撞逻辑比如用于AI感知范围、技能触发区域或者非物理对象的交互。这时手动实现AABB检测就派上用场了。3.1 基础数据结构与表示在Unity中一个AABB最自然的表示就是Bounds结构体。它包含了center中心点和size尺寸我们可以很容易地计算出min和max。public struct AABB { public Vector3 min; public Vector3 max; public AABB(Vector3 center, Vector3 size) { Vector3 extents size * 0.5f; min center - extents; max center extents; } // 也可以直接从Transform和MeshRenderer构建 public static AABB FromRenderer(MeshRenderer renderer) { Bounds bounds renderer.bounds; return new AABB(bounds.center, bounds.size); } }3.2 核心相交检测函数根据Slabs Method我们实现一个静态方法。这里我采用包含边界的判断和因为它更通用。public static bool Intersects(AABB a, AABB b) { // 检查X轴Slab是否相交 bool overlapX (a.min.x b.max.x) (b.min.x a.max.x); // 检查Y轴Slab是否相交 bool overlapY (a.min.y b.max.y) (b.min.y a.max.y); // 检查Z轴Slab是否相交 bool overlapZ (a.min.z b.max.z) (b.min.z a.max.z); // 所有轴Slab都相交则AABB相交 return overlapX overlapY overlapZ; }代码极其简单但这就是Slabs Method的核心。你可以看到它清晰地分离了三个轴上的判断逻辑一目了然。3.3 获取碰撞信息重叠深度与法线仅仅知道“撞了”还不够我们通常需要知道“撞了多少”和“从哪个方向撞的”以便做出响应比如把角色推开。public static bool Intersects(AABB a, AABB b, out Vector3 penetration) { penetration Vector3.zero; if (!Intersects(a, b)) return false; // 先用简单方法判断是否相交 // 计算在每个轴上的重叠深度穿透量 // 重叠深度 两个区间中较小的max - 较大的min float overlapX Mathf.Min(a.max.x, b.max.x) - Mathf.Max(a.min.x, b.min.x); float overlapY Mathf.Min(a.max.y, b.max.y) - Mathf.Max(a.min.y, b.min.y); float overlapZ Mathf.Min(a.max.z, b.max.z) - Mathf.Max(a.min.z, b.min.z); // 找出最小的重叠深度及其对应的轴 // 我们认为最小的重叠方向是最短的分离路径 float minOverlap Mathf.Min(Mathf.Min(overlapX, overlapY), overlapZ); if (Mathf.Approximately(minOverlap, overlapX)) { // X轴重叠最小分离方向沿X轴 penetration.x (a.center.x b.center.x) ? -overlapX : overlapX; // 方向由中心点相对位置决定 } else if (Mathf.Approximately(minOverlap, overlapY)) { // Y轴重叠最小分离方向沿Y轴 penetration.y (a.center.y b.center.y) ? -overlapY : overlapY; } else { // Z轴重叠最小分离方向沿Z轴 penetration.z (a.center.z b.center.z) ? -overlapZ : overlapZ; } return true; }这个函数返回一个penetration向量它的方向指出了将物体A从物体B中推离的方向长度就是需要推开的距离。这是实现简单物理响应如角色与墙壁碰撞的关键数据。实操心得在比较浮点数判断“最小重叠轴”时直接使用非常危险因为浮点数有精度误差。我强烈推荐使用Mathf.Approximately或者判断差值是否小于一个极小的阈值如1e-5f。否则在两个轴重叠深度极其接近的情况下可能会得到不稳定的碰撞法线导致物体在角落抖动。3.4 性能优化与批量检测当需要检测大量AABB时例如一个技能同时检测范围内所有敌人逐对检测的O(n²)复杂度是不可接受的。这时我们可以利用空间划分数据结构如四叉树2D或八叉树/网格3D。其思想依然是Slabs Method的延伸我们不是在全局范围比较所有物体而是先快速判断一个AABB与空间划分的“节点”的Slab是否相交。如果连节点都不相交那节点内的所有物体也都不可能相交。这里给出一个非常简化的基于网格的宽阶段检测思路将世界空间划分为均匀的网格。每个AABB根据其min和max坐标计算出它覆盖了哪些网格单元格。将AABB的引用注册到它覆盖的所有单元格中。当检测某个AABB时只需获取它所在单元格及相邻单元格内的其他AABB列表进行窄阶段精确的AABB相交检测即可。这极大地减少了需要两两比较的对象对数。Unity的Physics系统内部就采用了类似但更复杂的Broad-Phase算法。4. 在Unreal Engine中应用Slabs MethodUnreal EngineUE的碰撞系统非常庞大主要围绕PrimitiveComponent和其BodyInstance中的碰撞形状展开。手动实现AABB检测的场景与Unity类似多用于Gameplay逻辑而非物理模拟。4.1 使用FBox构建AABBUE中表示AABB的类是FBox。它可以直接从很多组件获取。// 从一个StaticMeshComponent获取其世界空间的AABB UStaticMeshComponent* MeshComp ...; FBoxSphereBounds Bounds MeshComp-CalcBounds(MeshComp-GetComponentTransform()); FBox AABB Bounds.GetBox(); // 这就是一个FBox // FBox提供了Min和Max成员变量 FVector Min AABB.Min; FVector Max AABB.Max;4.2 实现相交检测与信息获取在UE中我们可以用C编写一个工具函数其逻辑与Unity版本完全一致体现了Slabs Method的跨引擎通用性。bool AABBIntersection(const FBox BoxA, const FBox BoxB, FVector OutPenetration) { OutPenetration FVector::ZeroVector; // Slabs Method: 检查每个轴区间是否重叠 if (BoxA.Max.X BoxB.Min.X || BoxB.Max.X BoxA.Min.X) return false; if (BoxA.Max.Y BoxB.Min.Y || BoxB.Max.Y BoxA.Min.Y) return false; if (BoxA.Max.Z BoxB.Min.Z || BoxB.Max.Z BoxA.Min.Z) return false; // 计算重叠深度 float OverlapX FMath::Min(BoxA.Max.X, BoxB.Max.X) - FMath::Max(BoxA.Min.X, BoxB.Min.X); float OverlapY FMath::Min(BoxA.Max.Y, BoxB.Max.Y) - FMath::Max(BoxA.Min.Y, BoxB.Min.Y); float OverlapZ FMath::Min(BoxA.Max.Z, BoxB.Max.Z) - FMath::Max(BoxA.Min.Z, BoxB.Min.Z); // 寻找最小重叠轴 float MinOverlap FMath::Min3(OverlapX, OverlapY, OverlapZ); const float Tolerance 1e-5f; if (FMath::IsNearlyEqual(MinOverlap, OverlapX, Tolerance)) { OutPenetration.X (BoxA.GetCenter().X BoxB.GetCenter().X) ? -OverlapX : OverlapX; } else if (FMath::IsNearlyEqual(MinOverlap, OverlapY, Tolerance)) { OutPenetration.Y (BoxA.GetCenter().Y BoxB.GetCenter().Y) ? -OverlapY : OverlapY; } else { OutPenetration.Z (BoxA.GetCenter().Z BoxB.GetCenter().Z) ? -OverlapZ : OverlapZ; } return true; }4.3 与UE物理系统的协作大多数时候我们不需要手动实现这些。UE的碰撞查询系统功能强大Overlap查询使用UWorld::OverlapMultiBy...系列函数可以快速检测与某个形状重叠的所有物体。你可以指定一个FCollisionShape其中就包括Box。Sweep查询用于连续碰撞检测检测一个形状沿着一条线段移动时是否会撞到什么。这本质上是在多个时间点上进行Slab测试。碰撞预设与通道这是UE碰撞系统的精髓。通过合理设置对象的碰撞预设和响应通道你可以精细控制什么和什么能碰撞以及碰撞后是重叠、阻挡还是忽略。手动检测通常用于那些不适合或不想用物理引擎的Gameplay逻辑。一个经典的使用场景你的游戏有一个“警觉区域”当敌人进入这个扇形区域时会被触发。你可以用物理系统做一个大的球形碰撞体作为触发器简单但可能产生不必要的物理开销。手动用AABB做一个快速的宽阶段过滤先判断敌人是否在AABB包围盒内然后再进行精确的扇形检测计算角度和距离。这种方法更高效、更可控。这里的AABB检测就可以用我们上面实现的Slabs Method。5. 常见陷阱、优化策略与实战技巧即使理解了原理在实际项目中应用AABB碰撞仍然有很多细节需要注意。下面是我总结的一些“坑”和应对技巧。5.1 精度问题与浮点数误差这是3D编程的永恒之敌。在判断边界时直接使用或可能会因为浮点数误差导致在边界处时碰撞时而不碰撞。对策引入一个微小的容差EPSILON例如1e-5f。将判断条件改为A.max.x EPSILON B.min.x。但要注意这实际上略微放大了你的碰撞体。更稳健的做法是在设计游戏逻辑时就避免让游戏对象长期处于“刚好接触”的临界状态。5.2 动态物体的AABB更新对于移动、旋转、缩放的物体其世界空间的AABB需要每帧更新。低效做法每帧根据模型的所有顶点重新计算最小/最大值。高效做法在局部空间保存一个固定的AABB通常是模型的包围盒。每帧根据物体的变换矩阵Transform Matrix将这个局部AABB的8个顶点变换到世界空间。遍历这8个顶点快速计算出新的世界空间AABB的min和max。对于只有平移和均匀缩放的物体计算可以简化worldMin transform.localToWorldMatrix.MultiplyPoint(localMin)然后根据缩放系数调整size。但如果物体发生了旋转局部空间的AABB变换到世界空间后通常不再是一个轴对齐的盒子而是一个OBB。这时如果你仍需要AABB就必须计算顶点集的外包或者接受一个更大的、能包裹住旋转后物体的保守AABB。踩坑实录早期我曾试图用物体的position和固定的size来计算AABB忽略了物体的旋转。结果就是一个旋转了45度的长条物体其AABB还是一个正方向的盒子但实际模型已经斜着伸出盒子外了导致碰撞检测范围严重不准。切记世界空间的AABB必须考虑物体的完整变换旋转、缩放。5.3 缩放与非均匀缩放的影响如果物体被非均匀缩放例如X轴放大2倍Y轴不变其局部空间的AABB经过变换后形状会改变。手动计算时需要将缩放系数应用到局部AABB的extents半尺寸上再结合旋转和平移。在Unity中直接使用Renderer.bounds或Collider.bounds是最省事的它们已经处理好了这一切。在Unreal中使用CalcBounds函数。5.4 高速物体穿模问题这是AABB以及所有离散碰撞检测的固有缺陷。如果物体一帧移动的距离超过其自身尺寸或对方物体的尺寸它就可能从一端“瞬移”到另一端中间没有检测到碰撞。对策连续碰撞检测。思路是将运动过程视为一条线段从上一帧位置到当前帧位置检测这条线段与目标AABB是否相交。这可以通过对每个轴向上的Slab分别计算射线进入和离开的时间来实现最后取交集。这就是Slabs Method在时间维度上的延伸。虽然计算量稍大但对于子弹、高速移动的玩家角色来说是必要的。5.5 调试与可视化看不见的碰撞体是BUG的温床。务必为你的自定义AABB检测添加调试绘制功能。Unity在OnDrawGizmos或OnDrawGizmosSelected中使用Gizmos.DrawWireCube和Gizmos.color来绘制AABB的线框。Unreal使用DrawDebugBox函数可以在游戏运行时或编辑器中绘制出盒子的轮廓。 清晰的调试可视化能帮你快速确认AABB的大小、位置是否正确是节省调试时间的利器。6. 从AABB到更复杂形状的思维延伸掌握了Slabs Method你就掌握了分离轴定理的入门钥匙。当你需要处理更复杂的碰撞形状时比如OBB定向包围盒、凸包甚至三角形网格其核心思想是相通的找到一组潜在的分离轴对于OBB是每个盒子的3个本地轴对于凸包是每个面的法线以及边叉乘得到的轴。将两个物体投影到每一个轴上得到两个区间。如果存在任何一个轴使得这两个投影区间不重叠则物体分离。如果所有轴上的投影区间都重叠则物体相交。AABB只是这个通用算法的一个特例它的分离轴就是世界的X、Y、Z轴且计算极其简单。理解了这一点你再去看Unity的MeshCollider或Unreal的复杂碰撞体就不会再觉得神秘。它们无非是在更多、更复杂的轴上执行同样的区间重叠测试。所以花时间吃透AABB和Slabs Method绝对是一笔划算的投资。它建立起的思维模型能让你在游戏开发的道路上走得更稳、更远。下次当你需要写碰撞检测时不妨先在脑海里画一画那些“平板”理一理各个轴上的区间代码自然就会清晰、健壮起来。