在面对大规模交互性游戏,如割草游戏中出现的大量小怪时,高效的碰撞检测是关键。对于这种场景,我们有几种可能的碰撞检测方法:Sweep and Prune、四叉树(Quadtree)和八叉树(Octree)。
Sweep and Prune:高效的粗略筛选
Sweep and Prune是一种高效的碰撞检测方法,它通过在一维空间上扫描和剪枝物体,快速粗略地确定可能发生碰撞的物体对。这种方法的效率主要源于其算法复杂度相对较低。在最好的情况下,该方法的时间复杂度为O(n),其中n是物体的数量。这使得Sweep and Prune特别适合处理大规模、高密度的物体集,如割草游戏中的大量小怪。
四叉树与八叉树:空间划分的碰撞检测
相比之下,四叉树和八叉树是一种基于空间划分的碰撞检测方法。四叉树主要用于二维空间,而八叉树用于三维空间。这种方法的主要思想是将空间划分为更小的区域(节点),并将物体存储在它们所在的节点中。然而,四叉树和八叉树的效率往往依赖于物体在空间中的分布。如果物体分布不均,或者大量物体位于同一个节点中,四叉树和八叉树的效率可能会大大降低。
为何选择 Sweep and Prune?
在割草游戏中,我们通常会遇到大量的小怪,这些小怪可能会在游戏世界的各个地方出现,而且它们的数量可能会非常大。这就使得四叉树和八叉树的效率可能会大大降低,因为大量的小怪可能会被存储在同一个节点中,从而增加碰撞检测的复杂性。
相比之下,Sweep and Prune方法对物体的分布和数量并不敏感,它只关心物体在选定的一个轴上的位置。这使得Sweep and Prune在处理大规模、高密度的物体集时表现出更高的效率。
此外,Sweep and Prune方法也更易于实现和优化。与四叉树和八叉树相比,Sweep and Prune方法的数据结构和算法都相对简单,这使得它更易于理解和实现。同时,Sweep and Prune方法也更容易进行并行化和硬件加速,从而进一步提高其效率。
检测流程
Sweep and Prune
也有其局限性,由于它只考虑了物体在某个轴上的排序,因此可能会出现假阳性
的情况,即两个物体在某个轴上有重叠,但其他轴上没有重叠,因此并没有碰撞,因此我们可以将碰撞分为两个阶段:
- 粗略的碰撞筛选SAP: 快速找出可能碰撞的列表
- 精确碰撞检测SAT: 对可能碰撞的列表进行精确的碰撞
粗略的碰撞筛选:Sweep and Prune
"Sweep and Prune"是一种高效的粗略碰撞筛选方法。这种方法不是去确定哪些物体发生了碰撞,而是去确定哪些物体不可能发生碰撞,从而可以在后续的精确碰撞检测中排除这些物体。
这种方法的工作原理是:在一维空间(例如,x轴)上扫描所有的物体,并根据它们的位置进行排序。然后,我们检查每个物体的边界盒(一个紧紧包围物体的矩形或立方体)是否与其后的物体重叠。如果没有重叠,我们就可以“剪枝”,因为这两个物体不可能发生碰撞。
以下是一种可能的 TypeScript 实现方法:
class GameObject {
constructor(public id: number, public start: number, public end: number) {}
}
class SweepAndPrune {
static sortAndSweep(objects: GameObject[]): Set<[number, number]> {
objects.sort((a, b) => a.start - b.start || a.end - b.end);
let activeList: GameObject[] = [];
let potentialCollisions: Set<[number, number]> = new Set();
for (let object of objects) {
activeList = activeList.filter(activeObject => activeObject.end > object.start);
for (let activeObject of activeList) {
potentialCollisions.add([activeObject.id, object.id].sort() as [number, number]);
}
activeList.push(object);
}
return potentialCollisions;
}
}
在这个例子中,GameObject
类代表游戏中的物体,它有一个唯一的 id
,以及在选择的轴上的 start
和 end
值。SweepAndPrune
类的 sortAndSweep
方法实现了 "Sweep and Prune" 算法,它返回一个包含所有潜在碰撞对的集合。
精确的碰撞检测:Separating Axis Theorem (SAT)
在通过 "Sweep and Prune" 筛选出潜在的碰撞对后,我们需要使用一个精确的碰撞检测方法来确定这些物体是否真的发生了碰撞。这就是 "Separating Axis Theorem (SAT)" 的用途。
"SAT" 是一种用于检测两个凸多边形(在三维空间中是凸多面体)是否发生碰撞的方法。该方法的基本思想是,如果存在一个轴,使得两个物体在该轴上的投影不重叠,那么这两个物体就不会发生碰撞。
以下是一种可能的 TypeScript 实现方法:
class Vector {
constructor(public x: number, public y: number) {}
subtract(other: Vector): Vector {
return new Vector(this.x - other.x, this.y - other.y);
}
dot(other: Vector): number {
return this.x * other.x + this.y * other.y;
}
}
class SAT {
static checkCollision(object1: Vector[], object2: Vector[]): boolean {
let axes = [...object1, ...object2].map((point, i, array) => {
let nextPoint = array[i + 1] || array[0];
let edge = nextPoint.subtract(point);
return new Vector(-edge.y, edge.x);
});
for (let axis of axes) {
let [min1, max1] = this.project(object1, axis);
let [min2, max2] = this.project(object2, axis);
if (max1 < min2 || max2 < min1) return false;
}
return true;
}
private static project(object: Vector[], axis: Vector): [number, number] {
let dots = object.map(point => point.dot(axis));
return [Math.min(...dots), Math.max(...dots)];
}
}
在这个例子中,Vector
类表示二维空间中的一个点或一个向量,它有 x
和 y
两个属性。SAT
类的 checkCollision
方法实现了 "SAT" 算法,它接受两个表示物体的 Vector
数组作为参数,如果这两个物体发生了碰撞,就返回 true
。