canvas動(dòng)畫(huà)包教不包會(huì):邊界與摩擦力

2018-06-19 15:23 更新
      在前面的幾章中,我們已經(jīng)介紹了如何實(shí)現(xiàn)用戶交互、利用三角函數(shù)實(shí)現(xiàn)物體旋轉(zhuǎn)、給物體加上速度和加速度,利用這些知識(shí),我們已經(jīng)能夠?qū)崿F(xiàn)很豐富的動(dòng)畫(huà)效果,不過(guò),似乎動(dòng)畫(huà)還不夠真實(shí),比如:物體可以無(wú)限的向左或向右移動(dòng)、運(yùn)動(dòng)沒(méi)有阻力,這就是這一章要處理的問(wèn)題:邊界和摩擦力。

1、邊界
在一個(gè)游戲中,很少會(huì)讓物體可以無(wú)限的向左或向右的移動(dòng),這就出現(xiàn)了“活動(dòng)范圍”這一詞,我們?cè)谶@里稱(chēng)為“邊界”,邊界也可以認(rèn)為是我們用墻將物體圍住,限制它的移動(dòng)位置。

在canvas中,我們一般會(huì)設(shè)置三種邊界:
  • 整個(gè)canvas元素
  • 大于canvas的區(qū)域,比如有一張大地圖,物體可以在里面任意移動(dòng),移到邊界時(shí)地圖也跟著移動(dòng)變化
  • 小于canvas的區(qū)域,比如給物體設(shè)置了一個(gè)小房間,限制它的活動(dòng)范圍

1.1 設(shè)置邊界
設(shè)置邊界其實(shí)也是一種碰撞檢測(cè),只不過(guò)物體的碰撞對(duì)象變成了邊界。

當(dāng)我們將整個(gè)canvas大小作為邊界時(shí),我們可以很容易檢測(cè):

if(ball.x > canvas.width){

  console.log('超出了右邊界');

}else if(ball.x < 0){

  console.log('超出了左邊界');

};

if(ball.y > canvas.height){

  console.log('超出了下邊界');

}else if(ball.y < 0){

  console.log('超出了上邊界');

};

這里為什么是 ball.y < 0 是超出了上邊界,請(qǐng)回想一下canvas的坐標(biāo)是怎樣的。

那么,如果不是基于整個(gè)canvas元素內(nèi),怎么做呢?一般以兩個(gè)點(diǎn)作為范圍點(diǎn),看下圖:

如何檢測(cè)物體在這個(gè)范圍內(nèi),代碼如下:

if( ball.x < x1 ){

  console.log('物體超出了左邊界');

}else if( ball.x > x2){

  console.log('物體超出了右邊界');

};


if( ball.y < y1 ){

  console.log('物體超出了上邊界');

}else if( ball.y > y2){

  console.log('物體超出了下邊界');

};

當(dāng)然,上面這段代碼是檢測(cè)物體的中心點(diǎn)是否越界,如果要檢測(cè)是否完全越界,就需要加或減物體的高寬了:比如檢測(cè)物體是否完全越出了左邊界:

if( ball.x < (x1 - ball.radius)){

  console.log('物體完全越出了左邊界');

};


大多數(shù)情況下,我們不是單純的檢測(cè)物體是否越界,而是為了在物體越界后進(jìn)行某些操作,當(dāng)然,你也可以在物體越界后不做任何操作,不過(guò)這不是我們所推薦的。


當(dāng)物體越界時(shí),一般我們會(huì)進(jìn)行以下4中選擇操作:

  • 移除物體
  • 重置物體,也就是讓物體所有狀態(tài)恢復(fù)到原始位置
  • 屏幕環(huán)繞:讓同一個(gè)物體出現(xiàn)在邊界內(nèi)的另一個(gè)位置
  • 物體反彈,也就是向反方向運(yùn)動(dòng)


1.2 移除物體

移除物體多用在多個(gè)物體在canvas上移動(dòng)時(shí),這時(shí),我們一般將它們的引用保存到一個(gè)數(shù)組中,再通過(guò)遍歷整個(gè)數(shù)組的來(lái)移動(dòng)它們(前面的例子,我都是采取這種方式),這樣,我們就可以使用 Array.splice 方法來(lái)移除數(shù)組中的某個(gè)物體了。

var balls = [];  // 存放多個(gè)物體的數(shù)組


var ball = balls[i];

if(ball.x < x1 || ball.x > x2 || ball.y < y1 || ball.y > y2){   

  balls.splice(balls.indexof(ball),1);

  i -= 1;

}

上面的檢測(cè)越界條件是和檢測(cè)在邊界內(nèi)的條件是不一樣的。


注意:當(dāng)你使用 Array.splice 方法在循環(huán)中移除元素后,需要加上 i -= 1,不然后續(xù)循環(huán)會(huì)出問(wèn)題。當(dāng)然,你也可以使用反向遍歷,就不會(huì)存在這問(wèn)題:

var i = balls.length;

while( i-- ){

   if(ball.x < x1 || ball.x > x2 || ball.y < y1 || ball.y > y2){      

    balls.splice(balls.indexof(ball),1);   

  }

}


1.3 重置物體

重置物體其實(shí)就是重新設(shè)置物體的位置坐標(biāo)。

在下面的例子,你會(huì)看到一個(gè)物體從上向下落下,當(dāng)它離開(kāi)canvas后,又有一個(gè)物體在同一個(gè)位置開(kāi)始從上向下落下,看起來(lái)是不同的物體,其實(shí)是同一個(gè),只不過(guò)每次它離開(kāi)canvas后,都將它的 ball.y 設(shè)置為原始值,在這里是0。



1.4 屏幕環(huán)繞

屏幕環(huán)繞的意思是當(dāng)物體從屏幕左邊移出,它就會(huì)在屏幕右邊再次出現(xiàn);當(dāng)物體從屏幕上方移出,它又會(huì)出現(xiàn)在屏幕下方,反之亦然。


屏幕環(huán)繞和重置物體類(lèi)似,都遵循著同一個(gè)物體的原則,只不過(guò)屏幕環(huán)繞是讓其從一邊出再?gòu)南喾吹囊贿呥M(jìn)而已。


1.5 反彈

在讓物體反彈之前,你需要檢測(cè)物體何時(shí)離開(kāi)屏幕,當(dāng)它剛要離開(kāi)時(shí),要保持它的位置不變而僅改變它的速度向量,也可以說(shuō)是速度值取反。


在檢測(cè)何時(shí)反彈時(shí),有一點(diǎn)需要注意,我們不能等到物體完全移出canvas才開(kāi)始反彈,這顯然和現(xiàn)實(shí)不符合,不知道你有沒(méi)有玩過(guò)足球,當(dāng)你將足球踢向墻壁時(shí),你會(huì)看到球在撞墻后,停在那里并很快反彈回來(lái)。


當(dāng)物體移到如下圖位置,物體就要開(kāi)始反彈:



if( ball.x <= (x1 + ball.radius)){

  ball.x = x1 + ball.width;

  ball.speed.x *= -1;

}

在上面的代碼中,我們將 -1 作為反彈系數(shù),不過(guò)在現(xiàn)實(shí)中,反彈的速度總是會(huì)有所減小,這是因?yàn)槟芰繐p失,所以為了模擬更真實(shí)的動(dòng)畫(huà),你可以將 -1 乘以一個(gè)百分比來(lái)實(shí)現(xiàn)能量損耗的效果。

ball.speed.x *= -0.8;


反彈的步驟如下:

  • 檢測(cè)物體是否越界
  • 如果發(fā)生越界,立即將物體置回邊界
  • 反轉(zhuǎn)物體的速度向量的方向,也可以說(shuō)是速度取反。


簡(jiǎn)單例子:




2、摩擦力(friction)

摩擦力,又一物理概念,也可稱(chēng)為阻力,指兩個(gè)互相接觸的物體,當(dāng)它們要發(fā)生或已經(jīng)發(fā)生相對(duì)運(yùn)動(dòng)時(shí),就會(huì)在接觸面上產(chǎn)生一種阻礙相對(duì)運(yùn)動(dòng)的力。


上面是概念式的說(shuō)法,簡(jiǎn)單的講,摩擦力就是阻止你運(yùn)動(dòng)的力,它并不會(huì)改變你運(yùn)動(dòng)的方向,而只會(huì)讓你慢慢減速,直至速度為0。


如果想讓動(dòng)畫(huà)更加真實(shí),很多時(shí)候我們都需要考慮摩擦力,那如何用代碼實(shí)現(xiàn)呢?


(1)精確方法

上面也說(shuō)到,摩擦力是阻止你運(yùn)動(dòng)的力,這就意味著,可以用速度向量減去摩擦力。更準(zhǔn)確地說(shuō),只能沿著速度向量的方向減去與摩擦力相等的大小,而不能分別在x、y軸上減小速度向量,也可以這樣理解,摩擦力必須與合速度相減,然后再根據(jù)減后的合速度分別求出x、y軸上的最終速度。


如下方式:

var v = Math.sqrt( vx * vx + vy * vy );

var angle = Math.atan2(vy,vx);


if(v > f){

  v -= f;

}else{

  v = 0;

};


vx = Math.cos(angle) * v;

vy = Math.sin(angle) * v;


(2)約等方法

約等方法是指將x、y軸上的速度向量乘以一個(gè)百分?jǐn)?shù),一個(gè)接近0.9的系數(shù)能很好的模擬出摩擦力的效果。

vx *= f;

vy *= f;

使用這種方法的好處就是不必去做條件判斷,但它只能無(wú)限接近于0,不過(guò)由于JavaScript的精度約束,最后的結(jié)果也會(huì)變?yōu)?。


在上面的反彈中,反彈系數(shù)也是用這種方法。


總結(jié)

  • 介紹了如何檢測(cè)是否越界
  • 越界后的處理方式:移除物體、重置物體、屏幕環(huán)繞、反彈
  • 實(shí)現(xiàn)摩擦力的兩種方法:精度方法、約等方法


附錄


重要公式:

(1)檢測(cè)是否越界

if( object.x - object.width / 2 > right ||

    object.x + object.width / 2 < left ||

    object.y - object.height / 2 > bottom ||

    object.y + object.height / 2 < top){}


(2)摩擦力(精度方法)

var v = Math.sqrt( vx * vx + vy * vy );

var angle = Math.atan2(vy,vx);


if(v > f){

  v -= f;

}else{

  v = 0;

};


vx = Math.cos(angle) * v;

vy = Math.sin(angle) * v;


(3)摩擦力(約等方法)

vx *= friction;

vy *= friction;


下一章:移動(dòng)物體


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)