2D 碰撞檢測
2D 遊戲中碰撞檢測的演算法取決於可能發生碰撞的形狀型別(例如,矩形與矩形、矩形與圓形、圓形與圓形)。通常,你會有一個簡單的通用形狀來覆蓋實體,稱為“碰撞盒”,因此即使碰撞可能不是畫素級的精確,它看起來也足夠好,並且在多個實體上效能良好。本文回顧了在 2D 遊戲中提供碰撞檢測的最常用技術。
引擎程式碼
此頁面上的演示不依賴任何外部庫,因此我們自己實現了所有編排,包括渲染、處理使用者輸入以及呼叫每個實體的行為。程式碼如下所示(每個示例都不會重複)。
html
<div id="container"></div>
css
.entity {
display: inline-block;
position: absolute;
height: 20px;
width: 20px;
background-color: blue;
}
.movable {
left: 50px;
top: 50px;
background-color: red;
}
.collision-state {
background-color: green !important;
}
js
const collider = {
moveableEntity: null,
staticEntities: [],
checkCollision() {
// Important: the isCollidingWith method is what we are implementing
const isColliding = this.staticEntities.some((staticEntity) =>
this.moveableEntity.isCollidingWith(staticEntity),
);
this.moveableEntity.setCollisionState(isColliding);
},
};
const container = document.getElementById("container");
class BaseEntity {
ref;
position;
constructor(position) {
this.position = position;
this.ref = document.createElement("div");
this.ref.classList.add("entity");
this.ref.style.left = `${this.position.x}px`;
this.ref.style.top = `${this.position.y}px`;
container.appendChild(this.ref);
}
shiftPosition(dx, dy) {
this.position.x += dx;
this.position.y += dy;
this.redraw();
}
redraw() {
this.ref.style.left = `${this.position.x}px`;
this.ref.style.top = `${this.position.y}px`;
}
setCollisionState(isColliding) {
if (isColliding && !this.ref.classList.contains("collision-state")) {
this.ref.classList.add("collision-state");
} else if (!isColliding) {
this.ref.classList.remove("collision-state");
}
}
isCollidingWith(other) {
throw new Error("isCollidingWith must be implemented in subclasses");
}
}
document.addEventListener("keydown", (e) => {
e.preventDefault();
switch (e.key) {
case "ArrowLeft":
collider.moveableEntity.shiftPosition(-5, 0);
break;
case "ArrowUp":
collider.moveableEntity.shiftPosition(0, -5);
break;
case "ArrowRight":
collider.moveableEntity.shiftPosition(5, 0);
break;
case "ArrowDown":
collider.moveableEntity.shiftPosition(0, 5);
break;
}
collider.checkCollision();
});
軸對齊邊界框
碰撞檢測最簡單的形式之一是兩個軸對齊的矩形之間的碰撞。軸對齊意味著沒有旋轉。該演算法透過確保矩形的 4 個邊之間沒有間隙來工作。任何間隙都意味著不存在碰撞。
js
class BoxEntity extends BaseEntity {
width = 20;
height = 20;
isCollidingWith(other) {
return (
this.position.x < other.position.x + other.width &&
this.position.x + this.width > other.position.x &&
this.position.y < other.position.y + other.height &&
this.position.y + this.height > other.position.y
);
}
}
圓形碰撞
碰撞檢測的另一個簡單形狀是兩個圓形之間的碰撞。該演算法透過獲取兩個圓的中心點,並確保中心點之間的距離小於兩個半徑之和來工作。
css
.entity {
border-radius: 50%;
}
js
class CircleEntity extends BaseEntity {
radius = 10;
isCollidingWith(other) {
const dx =
this.position.x + this.radius - (other.position.x + other.radius);
const dy =
this.position.y + this.radius - (other.position.y + other.radius);
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < this.radius + other.radius;
}
}
注意:圓的 x 和 y 座標指的是它們的左上角,因此我們需要加上半徑來比較它們的中心。
分離軸定理
這是一種碰撞演算法,可以檢測任何兩個凸多邊形之間的碰撞。它比上述方法更復雜,但功能更強大。像這樣的演算法的複雜性意味著我們需要考慮效能最佳化,這將在下一節中介紹。
實現 SAT 超出了本頁的範圍,請參閱下面的推薦教程。
碰撞效能
雖然其中一些碰撞檢測演算法的計算足夠簡單,但將每個實體與其他所有實體進行測試可能會浪費 CPU 週期。通常,遊戲會將碰撞分為兩個階段:粗略階段和精確階段。
粗略階段
粗略階段應該為您提供一個可能發生碰撞的實體列表。這可以透過空間資料結構來實現,該結構可以粗略瞭解實體的位置以及其周圍存在的事物。空間資料結構的一些示例包括四叉樹、R 樹或空間雜湊表。
精確階段
當您有一小部分實體需要檢查時,您將希望使用精確階段演算法(如上所列)來提供一個確切的答案,說明是否存在碰撞。