function getBound(body){
return {
x: (body.x - body.radius),
y: (body.y - body.radius),
width: body.radius * 2,
height: body.radius * 2
};
}
tool.intersects = function(bodyA,bodyB){
return !(bodyA.x + bodyA.width < bodyB.x ||
bodyB.x + bodyB.width < bodyA.x ||
bodyA.y + bodyA.height < bodyB.y ||
bodyB.y + bodyB.height < bodyA.y);
};
if(tool.intersects(objectA,objectB)){
console.log('撞上了');
}
if(activeRect !== rect && tool.intersects(activeRect, rect)) {
activeRect.y = rect.y - activeRect.height;
activeRect = createRect();
};
tool.containsPoint = function(body, x, y){
return !(x < body.x || x > (body.x + body.width)
|| y < body.y || y > (body.y + body.height));
};
比如,要檢測點(50,50)是否在一個矩形內(nèi):
if(tool.containsPoint(body,50,50)){
console.log('在矩形內(nèi)');
}
tool.intesects()和tool.containsPoint()方法都會遇到精確問題,對矩形最精確,越不規(guī)則,精確率就越小。大多數(shù)情況下,都會采取這兩種方法。當(dāng)然,如果你要對不規(guī)則圖形采取更精確的方法,那你就要寫更多的代碼去執(zhí)行精確的檢測了。
2、基于距離的碰撞檢測
距離就是指兩個物體間的距離,當(dāng)然,物體總是有高寬的,這就還要考慮高寬。一般我們會先確定兩個物體的最小距離,然后計算當(dāng)前距離,最后進(jìn)行比較,如果當(dāng)前距離比最小距離小,那肯定發(fā)生了碰撞。
這種距離檢測法,對圓來說是最精確的,而對于其他圖形,或多或少會有一些精確問題。
2.1 基于距離的簡單碰撞檢測
基于距離的碰撞檢測的最理想的情況是:有兩個正圓形要進(jìn)行碰撞檢測,從圓的中心點開始計算。
要檢測兩個圓是否碰撞,其實就是比較兩個圓的中心點的距離與兩個圓的半徑和的大小關(guān)系。
dx = ballB.x - ballA.x;
dy = ballB.y - ballA.y;
dist = Math.sqrt(dx * dx + dy * dy);
if(dist < ballA.radius + ballB.radius){
console.log('碰撞了');
}
實例:
在上面的例子中,碰撞距離就是一個球的半徑加上另一個球的半徑,也是碰撞的最小距離,而兩者真正的距離就是圓心與圓心的距離。
var dx = ballB.x - ballA.x;
var dy = ballB.y - ballA.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if(ball != ballB && dist < ballA.radius + ballB.radius){
ctx.strokeStyle = 'red';
var txt = '你壓著我了';
var tx = ballA.x - ctx.measureText(txt).width / 2;
ctx.font = '30px Arial'
ctx.strokeText(txt,tx,ballA.y);
};
2.2 彈性碰撞
就像2.1節(jié)里的例子一樣,當(dāng)兩個球碰撞時,我們加入了文字提示,當(dāng)然,我們還可以做更多操作,比如這節(jié)要講的彈性碰撞。
實例:
首先我們加入一個放在canvas中心的圓球ballA,然后加入多個隨機大小和隨機速度的圓球,讓它們做勻速運動,遇到墻就反彈,最后在每一幀使用基于距離的方法檢測小球是否與中央的圓球ballA發(fā)生了碰撞,如果發(fā)生了碰撞,則計算彈動目標(biāo)點和兩球間的最小距離來避免小球完全撞上圓球ballA。
對于小球和圓球ballA的碰撞,我們可以這樣理解,我們在ballA外設(shè)置了目標(biāo)點,然后讓小球向目標(biāo)點彈動,一旦小球到達(dá)目標(biāo)點,就不再繼續(xù)碰撞,彈性運動就結(jié)束了,繼續(xù)做勻速運動。
下面的效果就像一群小氣泡在大氣泡上反彈,小氣泡撞入大氣泡一點距離,這個距離取決于小氣泡的速度,然后被彈出來。
如果你看不懂它如何反彈的,那你就要回到上一章看看《緩動和彈動》是如何實現(xiàn)的了。
3、多物體的碰撞檢測策略
這一節(jié)并不會介紹新的碰撞檢測方法,而是介紹如何優(yōu)化多物體碰撞代碼。
如果你用過二維數(shù)組,那么你肯定知道如何去遍歷數(shù)組元素,通常的方法是使用兩個循環(huán)函數(shù),而多物體的碰撞檢測,也類似二維數(shù)組:
for(var i = 0; i < objects.length; i++){
var objectA = objects[i];
for(var j = 0; j < objects.length; j++){
var objectB = objects[j];
if(tool.intersects(objectA,objectB){}
}
};
上面的方法的語法是沒錯的,不過這段代碼有兩個效率問題:
(1)多余的自身碰撞檢測
它檢測了同一個物體是否自身碰撞,比如:第一個物體(i=0)是objects[0],在第二次循環(huán)中,第一個物體(j=0)也是objects[0],是不是完全沒必要的檢測,我們可以這樣避免:
if(i != j && tool.intersects(objectA,objectB){}
這樣會節(jié)省了i次碰撞檢測
(2)重復(fù)碰撞檢測
第一次(i=0)循環(huán)時,我們檢測了objects[0](i=0)和objects[1](j=1)的碰撞;第二次(i=1)循環(huán)時,代碼似乎又檢測了objects[1](i=1)和objects[0](j=0)的碰撞,這豈不是多余的嗎?
我們應(yīng)該做如下的避免:
for(var i = 0; i < objects.length; i++){
var objectA = objects[i];
for(var j = i + 1; j < objects.length; j++){
var objectB = objects[j];
if(tool.intersects(objectA,objectB){}
}
};
這樣處理后,不僅避免了自身碰撞檢測,而且減少了重復(fù)碰撞檢測。
實例:
在上面的例子中,兩個球在碰撞后的彈動代碼并沒有太大的區(qū)別,只不過這里將ballB當(dāng)成了中央位置的圓球而已:
function checkCollision(ballA, ballB) {
var dx = ballA.x - ballB.x;
var dy = ballA.y - ballB.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var min_dist = ballB.radius + ballA.radius;
if(dist < min_dist) {
var angle = Math.atan2(dy, dx);
var tx = ballB.x + Math.cos(angle) * min_dist;
var ty = ballB.y + Math.sin(angle) * min_dist;
var ax = (tx - ballA.x) * spring * 0.5;
var ay = (ty - ballA.y) * spring * 0.5;
ballA.vx += ax;
ballA.vy += ay;
ballB.vx += (-ax);
ballB.vy += (-ay);
};
};
上面代碼最后四行的意思是:不僅ballB要從ballA彈開,而且ballA要從ballB彈出,它們的加速度的絕對值是相同的,方向相反。
不知道你有沒有注意到,ax和ay的計算都乘以0.5,這是因為當(dāng)ballA移動ax時,ballB也反向移動ax,那么就造成了 ax 變成 2ax ,所以要乘以0.5,才是真正的加速度。當(dāng)然,你也可以將spring減小成原來的一半。
總結(jié)
碰撞檢測是很多動畫中必不可少的,你必須掌握基于幾何圖形的碰撞檢測、基于距離的碰撞檢測方法,以及如何更有效的的檢測多物體間的碰撞。
下一章:坐標(biāo)旋轉(zhuǎn)和斜面反彈
附錄
重要公式:
(1)矩形邊界碰撞檢測
tool.intersects = function(bodyA,bodyB){
return !(bodyA.x + bodyA.width < bodyB.x ||
bodyB.x + bodyB.width < bodyA.x ||
bodyA.y + bodyA.height < bodyB.y ||
bodyB.y + bodyB.height < bodyA.y);
};
(2)基于距離的碰撞檢測
dx = objectB.x - objectA.x;
dy = objectB.y - objectA.y;
dist = Math.sqrt(dx * dx + dy * dy);
if(dist < objectA.radius + objectB.radius){}
(3)多物體碰撞檢測
for(var i = 0; i < objects.length; i++){
var objectA = objects[i];
for(var j = i + 1; j < objects.length; j++){
var objectB = objects[j];
if(tool.intersects(objectA,objectB){}
}
};
更多建議: