AABB是Axis-Aligned Bounding Box的缩写,中文叫轴对齐包围盒。简单说,就是一个矩形的四条边永远和坐标系的X轴、Y轴保持平行,不会旋转。正因为有这个约束,判断两个AABB是否重叠根本不需要复杂的几何运算,只做几次坐标大小的比较就够了。这篇文章会把原理、代码和优化技巧一次讲透。

一、AABB碰撞判定的核心原理
先从直觉上理解:两个矩形什么时候算“碰撞”?当它们在投影到X轴上的区间有重叠,并且投影到Y轴上的区间也有重叠时,矩形本身才重叠。只要有一个轴上的投影完全分开,两个矩形就一定不相交。这就是著名的分离轴定理在轴对齐情况下的特例。
假设矩形A的横坐标范围是[xMinA, xMaxA],矩形B的横坐标范围是[xMinB, xMaxB]。那么X轴方向重叠的条件是:A的右边界大于B的左边界,同时A的左边界小于B的右边界。用公式表达就是xMaxA >= xMinB && xMinA <= xMaxB。Y轴方向同理。两个方向同时满足,碰撞发生。
这里有个容易踩的坑:到底用>还是>=?如果两个矩形恰好边缘贴边缘,比如A的右边界正好等于B的左边界,这算不算碰撞?在大多数游戏场景里,贴边不算碰撞,用严格大于号;但在某些物理引擎中,恰好接触也算碰撞,用等号。写代码前要想清楚你的业务需求,这个细节会直接影响手感的判断。
二、基础实现:基于最小最大坐标的结构体
最常见的做法是用两个点表示矩形:左上角加右下角(屏幕坐标系Y向下),或者最小点加最大点(数学坐标系Y向上)。下面给出一个完整的C++实现,结构清晰,适合直接嵌入项目。
#include <iostream>
#include <algorithm>
struct AABB {
float minX, minY; // 左边界、下边界
float maxX, maxY; // 右边界、上边界
};
// 判断两个AABB是否相交(贴边不算碰撞)
bool isColliding(const AABB& a, const AABB& b) {
if (a.maxX <= b.minX) return false; // A在B的左侧
if (a.minX >= b.maxX) return false; // A在B的右侧
if (a.maxY <= b.minY) return false; // A在B的下方
if (a.minY >= b.maxY) return false; // A在B的上方
return true; // 四个方向都不满足分离条件,说明重叠
}
int main() {
AABB box1{0.0f, 0.0f, 10.0f, 10.0f};
AABB box2{5.0f, 5.0f, 15.0f, 15.0f};
if (isColliding(box1, box2)) {
std::cout << "发生碰撞" << std::endl;
} else {
std::cout << "未碰撞" << std::endl;
}
return 0;
}
这种写法逻辑上很好理解:只要矩形B完全位于A的某一侧外部,二者就分离。四个条件依次排除上下左右四种分离情况,剩下的自然就是重叠。它的优点是判断顺序直观,缺点是浮点比较没有处理精度问题,后面会专门讲。
还有一种等价的“短路”写法,把四个条件合并成一个返回语句,代码更紧凑:return !(a.maxX <= b.minX || a.minX >= b.maxX || a.maxY <= b.minY || a.minY >= b.maxY);。编译器优化后两种写法的机器码几乎一样,选哪种主要看团队代码风格。
三、中心点表示法与判定的另一种思路
游戏引擎里经常用“中心点加半宽高”的方式存储AABB,比如角色的位置就是包围盒中心。这种表示法下判定公式要换一种推导:两个矩形在X轴上重叠,等价于两个中心点的X距离小于两个半宽之和。用数学式表达就是|cxA - cxB| < halfWA + halfWB,Y轴同理。
struct AABBCenter {
float cx, cy; // 中心点坐标
float hw, hh; // 半宽、半高
};
bool isCollidingCenter(const AABBCenter& a, const AABBCenter& b) {
float dx = std::abs(a.cx - b.cx);
float dy = std::abs(a.cy - b.cy);
return dx < (a.hw + b.hw) && dy < (a.hh + b.hh);
}
这种写法的好处是求距离时可以用平方比较避开开方,不过对于AABB本身来说没有开方运算,优势不明显。它真正的价值在于和圆形碰撞检测、球形检测的公式形式统一,方便封装成模板或泛型接口。另外,中心点表示法在物体移动时更新包围盒更方便,只需要改中心坐标,不用重新计算两个角点。
两种表示法可以互相转换:已知min和max,中心点就是二者之和除以二,半宽就是差值除以二。反过来也成立。工程上建议根据数据来源选择:如果碰撞数据来自地图编辑器导出的矩形区域,用min-max更自然;如果来自移动物体,用中心点表示更顺手。
四、性能优化与工程实践细节
单次AABB判断快到可以忽略不计,但真实场景中往往是几百上千个物体两两检测,复杂度是O(n²)。这时除了用空间划分(网格、四叉树)减少检测对数量,还可以在单次判断层面抠细节。比如把参数标记为const引用避免拷贝,用[[nodiscard]]提示调用者不要忽略返回值,以及在循环密集的场景下开启编译器优化。
浮点精度是另一个实际问题。如果坐标经过多帧累加运算,两个“理论上相等”的边界可能差出一个极小值,导致判定结果抖动。解决办法是引入一个小的容差值epsilon,把比较写成a.maxX >= b.minX - epsilon的形式。epsilon的取值要看场景尺度,一般取坐标量级的百万分之一即可,不要无脑用一个固定值。
最后提醒一点:AABB只适合不旋转的物体。如果物体带旋转角度,轴对齐包围盒会随着旋转变得松散,产生大量“假碰撞”。这种情况下要么把旋转物体近似成多个小AABB拼接,要么直接换成OBB(有向包围盒)检测。另外在3D场景中,AABB判定只需扩展一个Z轴维度,把四条件变六条件即可,公式完全同理,这也是它被广泛用作物理引擎粗筛阶段的原因。
五、总结
AABB碰撞检测的本质就一句话:两个轴上的投影区间同时重叠,矩形就重叠。实现上从分离条件的反面入手,排除所有分离情况,剩下的就是碰撞。掌握min-max表示和中心点表示两种写法,注意贴边语义和浮点容差这两个细节,再配合空间划分降低检测规模,就足以应对绝大多数2D游戏和3D粗筛场景。理解了这套坐标判定思路,后面学习圆形碰撞、OBB乃至完整的物理引擎都会轻松很多。