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));
};
比如,要檢測點(diǎn)(50,50)是否在一個(gè)矩形內(nèi):
if(tool.containsPoint(body,50,50)){
console.log('在矩形內(nèi)');
}
tool.intesects()和tool.containsPoint()方法都會(huì)遇到精確問題,對(duì)矩形最精確,越不規(guī)則,精確率就越小。大多數(shù)情況下,都會(huì)采取這兩種方法。當(dāng)然,如果你要對(duì)不規(guī)則圖形采取更精確的方法,那你就要寫更多的代碼去執(zhí)行精確的檢測了。
2、基于距離的碰撞檢測
距離就是指兩個(gè)物體間的距離,當(dāng)然,物體總是有高寬的,這就還要考慮高寬。一般我們會(huì)先確定兩個(gè)物體的最小距離,然后計(jì)算當(dāng)前距離,最后進(jìn)行比較,如果當(dāng)前距離比最小距離小,那肯定發(fā)生了碰撞。
這種距離檢測法,對(duì)圓來說是最精確的,而對(duì)于其他圖形,或多或少會(huì)有一些精確問題。
2.1 基于距離的簡單碰撞檢測
基于距離的碰撞檢測的最理想的情況是:有兩個(gè)正圓形要進(jìn)行碰撞檢測,從圓的中心點(diǎn)開始計(jì)算。
要檢測兩個(gè)圓是否碰撞,其實(shí)就是比較兩個(gè)圓的中心點(diǎn)的距離與兩個(gè)圓的半徑和的大小關(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('碰撞了');
}
實(shí)例:
在上面的例子中,碰撞距離就是一個(gè)球的半徑加上另一個(gè)球的半徑,也是碰撞的最小距離,而兩者真正的距離就是圓心與圓心的距離。
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)兩個(gè)球碰撞時(shí),我們加入了文字提示,當(dāng)然,我們還可以做更多操作,比如這節(jié)要講的彈性碰撞。
實(shí)例:
首先我們加入一個(gè)放在canvas中心的圓球ballA,然后加入多個(gè)隨機(jī)大小和隨機(jī)速度的圓球,讓它們做勻速運(yùn)動(dòng),遇到墻就反彈,最后在每一幀使用基于距離的方法檢測小球是否與中央的圓球ballA發(fā)生了碰撞,如果發(fā)生了碰撞,則計(jì)算彈動(dòng)目標(biāo)點(diǎn)和兩球間的最小距離來避免小球完全撞上圓球ballA。
對(duì)于小球和圓球ballA的碰撞,我們可以這樣理解,我們?cè)赽allA外設(shè)置了目標(biāo)點(diǎn),然后讓小球向目標(biāo)點(diǎn)彈動(dòng),一旦小球到達(dá)目標(biāo)點(diǎn),就不再繼續(xù)碰撞,彈性運(yùn)動(dòng)就結(jié)束了,繼續(xù)做勻速運(yùn)動(dòng)。
下面的效果就像一群小氣泡在大氣泡上反彈,小氣泡撞入大氣泡一點(diǎn)距離,這個(gè)距離取決于小氣泡的速度,然后被彈出來。
如果你看不懂它如何反彈的,那你就要回到上一章看看《緩動(dòng)和彈動(dòng)》是如何實(shí)現(xiàn)的了。
3、多物體的碰撞檢測策略
這一節(jié)并不會(huì)介紹新的碰撞檢測方法,而是介紹如何優(yōu)化多物體碰撞代碼。
如果你用過二維數(shù)組,那么你肯定知道如何去遍歷數(shù)組元素,通常的方法是使用兩個(gè)循環(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){}
}
};
上面的方法的語法是沒錯(cuò)的,不過這段代碼有兩個(gè)效率問題:
(1)多余的自身碰撞檢測
它檢測了同一個(gè)物體是否自身碰撞,比如:第一個(gè)物體(i=0)是objects[0],在第二次循環(huán)中,第一個(gè)物體(j=0)也是objects[0],是不是完全沒必要的檢測,我們可以這樣避免:
if(i != j && tool.intersects(objectA,objectB){}
這樣會(huì)節(jié)省了i次碰撞檢測
(2)重復(fù)碰撞檢測
第一次(i=0)循環(huán)時(shí),我們檢測了objects[0](i=0)和objects[1](j=1)的碰撞;第二次(i=1)循環(huán)時(shí),代碼似乎又檢測了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ù)碰撞檢測。
實(shí)例:
在上面的例子中,兩個(gè)球在碰撞后的彈動(dòng)代碼并沒有太大的區(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彈出,它們的加速度的絕對(duì)值是相同的,方向相反。
不知道你有沒有注意到,ax和ay的計(jì)算都乘以0.5,這是因?yàn)楫?dāng)ballA移動(dòng)ax時(shí),ballB也反向移動(dòng)ax,那么就造成了 ax 變成 2ax ,所以要乘以0.5,才是真正的加速度。當(dāng)然,你也可以將spring減小成原來的一半。
總結(jié)
碰撞檢測是很多動(dòng)畫中必不可少的,你必須掌握基于幾何圖形的碰撞檢測、基于距離的碰撞檢測方法,以及如何更有效的的檢測多物體間的碰撞。
下一章:坐標(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){}
}
};
更多建議: