這篇是學習和回顧canvas系列筆記的第六篇,完整筆記詳見:canvas核心技術。javascript
在上一篇canvas核心技術-如何實現複雜的動畫筆記中,咱們詳細討論了在製做複雜動畫時,須要考慮時間因素,物理因素等,同時還回顧瞭如何使用緩動函數來扭曲時間軸實現非線性運動,好比常見的緩入,緩出,緩入緩出等。在遊戲或者動畫中,運動的物體在變化的過程當中,它們是有可能碰撞在一塊兒的,那麼這一篇咱們就來詳細學習下如何進行碰撞檢測。html
最簡單的檢測手段就是邊界值檢測了,就是對一個運動的物體的某些屬性進行條件判斷,若是達到了這個條件,則說明發生了碰撞。例如在上一篇中的示例,小球自由下落,當在檢測小球是否與地面發生碰撞時,咱們是檢測小球下落的高度fh是否達到了小球自己距離地面的高度dh,若是fh>dh,則說明小球與地面發生了碰撞。java
let distance = ball.currentSpeed * t;
if (ball.offset + distance > ball.verticalHeight) {
//落到地面了,發生了碰撞
// ...
} else {
// 尚未落到地面,沒有發生碰撞
ball.offset += distance;
}
複製代碼
這種檢測方式很是的簡單且準確,在針對相似業務開發時,咱們能夠簡化成邊界值檢測。可是當咱們開發較爲複雜遊戲時,邊界值檢測一般不能很好的實現,爲了更加真實,它一般與其餘檢測方法一塊兒使用。github
在canvas遊戲中,對於不規則的物體,好比運動的小人等,咱們能夠經過抽象成一個矩形,使得這個矩形剛好能夠包裹這個物體,在進行碰撞檢測時,就可使用這個矩形來代替實際的物體。這種方法,實際上就是經過抽象,將複雜簡單化,對於精確度不是那麼高的動畫或者遊戲,咱們直接使用這種外接圖形來檢測就能夠了。在抽象圖形的時候,咱們要根據具體的物體,好比小人能夠抽象成矩形,太陽就要抽象成圓了,把具體的物體抽象的跟它類似的形狀,這樣在檢測時就會更加準確。typescript
進行了圖形抽象以後,咱們在檢測就只需對圖形進行檢測了。對於兩個圖形是否發生碰撞,咱們只須要判斷它們是否存在相交的部分,若是存在相交的部分,那麼則能夠認爲是發生了碰撞,不然就沒有。下面,咱們分別來學習矩形和矩形的碰撞檢測,圓和圓的碰撞檢測,矩形和圓的碰撞檢測。canvas
矩形與矩形碰撞狀況,ide
這裏列舉兩個矩形發生碰撞的全部狀況,在canvas中具體代碼實現以下,函數
/* 判斷是否兩個矩形發生碰撞 */
private didRectCollide(sprite: RectSprite, otherSprite: RectSprite) {
let horizontal = sprite.left + sprite.width > otherSprite.left && sprite.left < otherSprite.left + otherSprite.width;
let vertical = sprite.top < otherSprite.top + otherSprite.height && sprite.top + sprite.height > otherSprite.top;
return horizontal && vertical;
}
複製代碼
其實就是分別在水平方向和垂直方向判斷這兩個矩形是否發生重疊。post
圓和圓碰撞狀況,
判斷兩個圓是否發生碰撞,就是判斷兩個圓的圓心之間的距離是否小於它們的半徑之和,若是小於半徑之和,則發生碰撞,不然就沒有發生碰撞。主要就是計算兩個圓心之間的距離,能夠根據座標系中兩點之間距離公式獲得,
在canvas中具體代碼實現以下,
/* 判斷是否兩個圓發生碰撞 */
private didCircleCollide(sprite: CircleSprite, otherSprite: CircleSprite) {
return distance(sprite.x, sprite.y, otherSprite.x, otherSprite.y) < sprite.radius + otherSprite.radius;
}
複製代碼
矩形和圓碰撞狀況,
這種狀況,就是判斷圓形到矩形上最近的一點的距離是否小於圓的半徑,若是小於圓的半徑,則發生碰撞,不然就沒有發生碰撞。咱們首先要找到圓距離矩形上最近的點的座標,這種就要考慮圓心在矩形左側,圓心在矩形上面,圓心在矩形右側,圓心在矩形下面,圓心在矩形裏面這五種狀況。若是圓心在矩形裏面,那麼必定是碰撞的。其餘四種狀況根據每一種狀況來計算獲得矩形上離圓心最近的一點,下面舉例其中一種狀況,其餘狀況原理相似,好比圓心在矩形左側,
這種狀況下,最近一點的X軸座標跟矩形左上角座標的X軸座標相等,跟圓心Y軸座標相等,這樣就能夠得出來了,。在canvas中具體代碼實現以下,
/* 判斷是否矩形和圓形發生碰撞 */
private didRectWidthCircleCollide(rectSprite: RectSprite, circleSprite: CircleSprite) {
let closePoint = { x: undefined, y: undefined };
if (circleSprite.x < rectSprite.left) {
closePoint.x = rectSprite.left;
} else if (circleSprite.x < rectSprite.left + rectSprite.width) {
closePoint.x = circleSprite.x;
} else {
closePoint.x = rectSprite.left + rectSprite.width;
}
if (circleSprite.y < rectSprite.top) {
closePoint.y = rectSprite.top;
} else if (circleSprite.y < rectSprite.top + rectSprite.height) {
closePoint.y = circleSprite.y;
} else {
closePoint.y = rectSprite.top + rectSprite.height;
}
return distance(circleSprite.x, circleSprite.y, closePoint.x, closePoint.y) < circleSprite.radius;
}
複製代碼
光線投射法:畫一條與物體的速度向量相重合的線,而後再從另一個待檢測物體出發,繪製第二條線,根據兩條線的交點位置來斷定是否發生碰撞。
光線投射法通常還會結合邊界值檢測來進行嚴格準確的判斷,這種方法要求咱們在動畫更新中,不斷計算出兩個速度向量的交點座標,根據交點座標判斷是否知足碰撞條件,交點知足了條件,咱們還要運用邊界值檢測方法來檢測運動物體是否知足邊界值條件,只有同時知足才判斷爲發生碰撞。這種檢測,準確度通常比較高,特別是適用於運動速度快的物體。以小球投桶示例,檢測代碼以下,
/* 是否發生碰撞 */
public didCollide(ball: CircleSprite, bucket: ImageSprite) {
let k1 = ball.verticalVelocity / ball.horizontalVelocity;
let b1 = ball.y - k1 * ball.x;
let inertSectionY = bucket.mockTop; //計算交點Y座標
let insertSectionX = (inertSectionY - b1) / k1; //計算交點X座標
return (
insertSectionX > bucket.mockLeft &&
insertSectionX < bucket.mockLeft + bucket.mockWidth &&
ball.x > bucket.mockLeft &&
ball.x < bucket.mockLeft + bucket.mockWidth &&
ball.y > bucket.mockTop &&
ball.y < bucket.mockTop + bucket.mockHeight
);
}
}
複製代碼
在判斷凸多邊形的碰撞檢測時,咱們可使用分離軸方法。在學習分離軸檢測以前,咱們須要先熟悉向量的一些基礎知識。
向量基礎知識:
圖中能夠看到,,。多餘凸多邊形的每一個頂點,咱們能夠用向量來表示。
分離軸檢測思路,
/* 判斷是否發生碰撞 */
public didCollide(sprite: Sprite, otherSprite: Sprite) {
let axes1 = sprite.type === 'circle' ? (sprite as Circle).getAxes(otherSprite as Polygon) : (sprite as Polygon).getAxes();
let axes2 = otherSprite.type === 'circle' ? (otherSprite as Circle).getAxes(sprite as Polygon) : (otherSprite as Polygon).getAxes();
// 第一步:獲取全部的投影軸
// 第二步:獲取多邊形在各個投影軸的投影
// 第三步:判斷是否存在一條投影軸上,多邊形的投影不相交,若是存在不相交的投影則直接返回false,若是有所的投影軸上的投影都存在相交,則說明相碰了。
let axes = [...axes1, ...axes2];
for (let axis of axes) {
let projections1 = sprite.getProjection(axis);
let projections2 = otherSprite.getProjection(axis);
if (!projections1.overlaps(projections2)) {
return false;
}
}
return true;
}
}
複製代碼
下面咱們就按照這三個步驟來,一步一步實現分離軸檢測方法。
獲取投影軸
在多邊形中,咱們是以邊來創建邊向量的,邊向量的法向量,就是這條邊的投影軸了。對於投影軸,咱們只需它的方向,因此通常會把它格式化爲單位向量。
//獲取凸多邊形的投影軸
public getAxes() {
let points = this.points;
let axes = [];
for (let i = 0, j = points.length - 1; i < j; i++) {
let v1 = new Vector(points[i].x, points[i].y);
let v2 = new Vector(points[i + 1].x, points[i + 1].y);
axes.push(
v1
.subtract(v2)
.perpendicular()
.normalize(),
);
}
let firstPoint = points[0];
let lastPoint = points[points.length - 1];
let v1 = new Vector(lastPoint.x, lastPoint.y);
let v2 = new Vector(firstPoint.x, firstPoint.y);
axes.push(
v1
.subtract(v2)
.perpendicular()
.normalize(),
);
return axes;
}
複製代碼
獲取了待檢測圖形的投影軸以後,咱們就須要計算圖形在每條投影軸上的投影
public getProjection(v: Vector) {
let min = Number.MAX_SAFE_INTEGER;
let max = Number.MIN_SAFE_INTEGER;
for (let point of this.points) {
let p = new Vector(point.x, point.y);
let dotProduct = p.dotProduct(v);
min = Math.min(min, dotProduct);
max = Math.max(max, dotProduct);
}
return new Projection(min, max);
}
複製代碼
最後判斷投影是否重疊
/* 投影是否重疊 */
overlaps(p: Projection) {
return this.max > p.min && p.max > this.min;
}
複製代碼
其中,若是是一個圓形與一個凸多邊形的檢測時,在計算圓對應的投影軸時比較特殊,圓只有一條投影軸,就是圓心與它距離多邊形最近頂點的向量,
//獲取圓的投影軸
public getAxes(polygon: Polygon) {
// 對於圓來講,獲取其投影軸就是將圓心與他距離多邊形最近頂點的連線
let { x, y } = this;
let nearestPoint = null;
let nearestDistance = Number.MAX_SAFE_INTEGER;
for (let [index, point] of polygon.points.entries()) {
let d = distance(x, y, point.x, point.y);
if (d < nearestDistance) {
nearestDistance = d;
nearestPoint = point;
}
}
let v1 = new Vector(x, y);
let v2 = new Vector(nearestPoint.x, nearestPoint.y);
return [v1.subtract(v2).normalize()];
}
複製代碼
這篇筆記詳細記錄了2d圖形中碰撞檢測的方法,比較簡單的方法是外接圖形法和邊界值檢測法,它們相對不是那麼精確,比較複雜和精確的方法有光線投射法和分離軸法。根據不一樣的場景和精確度要求,咱們選擇不一樣的方法。其餘,除了上面幾種,還有像素檢測等方法也能夠實現碰撞檢測,像素檢測是以像素爲單位來檢測,若是存在不透明的像素在同一個座標上重疊,則說明發生了碰撞,具體實現能夠查看Pixel accurate collision detection with Javascript and Canvas。
對於這幾種檢測方法,強力建議熟悉掌握分離軸法,由於它使用的範圍最爲普遍,對於任意的凸多邊形,它均可以較精確的檢測出來。因爲分離軸檢測法計算量通常比較大,因此在檢測以前,咱們先過濾掉那些根本不可能發生碰撞的圖形,通常方法是空間分隔法,或者過濾可視區間不可見的圖形等,而後再對較小的一部分可能發生碰撞的圖形來進行計算檢測,這樣能夠提高檢測的速度。