博客專欄

EEPW首頁 > 博客 > HTML5+Canvas實(shí)現(xiàn)酷炫小游戲案例及源碼

HTML5+Canvas實(shí)現(xiàn)酷炫小游戲案例及源碼

發(fā)布人:扣丁學(xué)習(xí) 時(shí)間:2020-11-05 來源:工程師 發(fā)布文章

  首先,一個(gè)游戲最重要的就是動畫,怎么讓元素動起來呢?一幀一幀的來渲染這個(gè)元素,而且這個(gè)元素每一幀的位置都不一樣,我們的眼睛看到的就是動畫了。OK,先來介紹requestAnimationFrame這個(gè)函數(shù)。

  簡單舉個(gè)例子吧:setInterval(myFun,1);意思是隔一毫秒執(zhí)行一個(gè)myFun函數(shù),但是這樣就有一個(gè)問題了,比如我myFun函數(shù)里面繪制的東西比較耗時(shí),而1ms之內(nèi)還沒有完全繪制出來,但是這段代碼強(qiáng)制1ms之后又開始繪制下一幀了,所以就會出現(xiàn)丟幀的問題,而如果時(shí)間設(shè)置太長,就會出現(xiàn)視覺卡頓的問題。


  requestAnimationFrame(myFun);如果我們這樣寫,又是什么意思呢?意思是根據(jù)一定的時(shí)間間隔,會自動執(zhí)行myFun函數(shù)來進(jìn)行繪制。這個(gè)”一定的時(shí)間間隔”就是根據(jù)瀏覽器的性能或者網(wǎng)速快慢來決定了,總之,它會保證你繪制完這一幀,才會繪制下一幀,保證性能的同時(shí),也保證動畫的流暢。


  html文件


  <divclass="page">


  <divclass="content"id="main">


  <canvasid="canvas1"width="800"height="600">


  </canvas>


  <canvasid="canvas2"width="800"height="600">


  </canvas>


  </div>


  </div>


  定義兩個(gè)畫布,分別在畫布上繪制相應(yīng)的物體;


  canvas2上繪制,背景、???、果實(shí);


  canvas1上繪制,大魚、小魚、顯示文字、圓圈特效;


  js文件


  functioninit(){


  can1=document.getElementById('canvas1');//畫布


  ctx1=can1.getContext('2d');//畫筆


  can2=document.getElementById('canvas2');


  ctx2=can2.getContext('2d');//下面的canvas


  }


  functiongameloop(){


  requestAnimFrame(gameLoop);


  //繪制物體...


  }


  function(callback,element){


  returnwindow.setTimeout(callback,1000/60);


  };


  })();


  init函數(shù)初始化一些變量,比如??麑ο?,大魚、小魚對象等等。


  gameloop函數(shù)用于繪制每一幀的頁面。下面所介紹的所有繪制函數(shù)都是在這里執(zhí)行。


  requestAnimFrame函數(shù)是為了兼容所有瀏覽器。


  下面我們就開始繪制游戲中出現(xiàn)的東西,順便看看都用到了哪些有趣的API函數(shù)。go!go!go!


  繪制背景和海葵


  背景是一張圖,而海葵是一個(gè)類,它有x坐標(biāo),y坐標(biāo),個(gè)數(shù)等等屬性,有初始化init和draw方法。


  drawImage(image,x,y,width,height)


  ctx2.save();


  ctx2.globalAlpha=0.7;


  ctx2.lineWidth=20;


  ctx2.lineCap='round';


  ctx2.strokeStyle='#3b154e';


  ctx2.beginPath();


  ctx2.moveTo(this.rootx[i],canHei);//起始點(diǎn)


  ctx2.lineTo(this.rootx[i],canHei-220,);//結(jié)束點(diǎn)ctx2.stroke();


  ctx2.restore();


  ctx2.drawImage(image,x,y,width,height)//x,y代表坐標(biāo),width和height代表寬高


  ctx2.save();//定義作用空間


  ctx2.globalAlpha=0.7;//定義線的透明度


  ctx2.lineWidth=20;//寬度


  ctx2.lineCap=‘round’;//圓角


  ctx2.strokeStyle=‘#3b154e’;//定義繪制線條的顏色


  ctx2.beginPath();//開始路徑


  ctx2.moveTo(x,y);//線的起點(diǎn),x,y代表坐標(biāo)(坐標(biāo)原點(diǎn)在左上角)


  ctx2.lineTo(x,y);//線條從起點(diǎn)連接到這個(gè)點(diǎn)


  ctx2.stroke();//開始繪制線條


  ctx2.restore();//作用空間結(jié)束


  海葵產(chǎn)生果實(shí)


  果實(shí)也是一個(gè)類,他的屬性有:坐標(biāo)、類型(黃色和藍(lán)色)、大小、狀態(tài)(顯示還是隱藏)、速度(向上漂浮的速度)等等屬性;他的方法有:初始化init、出生born和繪制draw。


  draw方法:


  for(vari=0;i<this.num;i++){


  if(this.alive[i]){


  //findanane,grow,flyup...


  if(this.size[i]<=16){//長大狀態(tài)


  this.grow[i]=false;


  this.size[i]+=this.speed[i]*diffframetime*0.8;


  }else{//已經(jīng)長大,向上漂浮


  this.grow[i]=true;


  this.y[i]-=this.speed[i]*5*diffframetime;


  }


  varpic=this.orange;


  if(this.type[i]=='blue')pic=this.blue;


  ctx2.drawImage(pic,this.x[i]-this.size[i]*0.5,this.y[i]-this.size[i]*0.5,this.size[i],this.size[i]);


  if(this.y[i]<8){


  this.alive[i]=false;


  }


  }


  }


  born方法:隨機(jī)找到一個(gè)??淖鴺?biāo),在??淖鴺?biāo)上出生一個(gè)果實(shí)。


  繪制大魚和小魚


  大魚和小魚都是一個(gè)類,它的屬性有:坐標(biāo)、旋轉(zhuǎn)角度、尾巴擺動時(shí)間間隔、眨眼睛時(shí)間間隔、身體圖片數(shù)組….等等


  先把大魚繪制出來,用canvas的drawImage方法。


  比較難的是大魚的動畫,大魚會隨著鼠標(biāo)移動而移動的動畫,這里定義了兩個(gè)函數(shù):


  functionlerpAngle(a,b,t){//計(jì)算每一幀旋轉(zhuǎn)的角度


  vard=b-a;


  if(d>Math.PI)d=d-2*Math.PI;


  if(d<-Math.PI)d=d+2*Math.PI;


  returna+d*t;


  }


  functionlerpDistance(aim,cur,ratio){//aim:目標(biāo)cur:當(dāng)前ratio:百分比計(jì)算每一幀趨近的距離


  vardelta=cur-aim;


  returnaim+delta*ratio


  }


  this.momTailTimer+=diffframetime;


  if(this.momTailTimer>50){


  this.momTailIndex=(this.momTailIndex+1)%8;//根據(jù)時(shí)間間隔改變尾巴圖片


  this.momTailTimer%=50;


  }


  lerpDistance是計(jì)算每一幀大魚趨緊到鼠標(biāo)的距離。


  lerpAngle用來計(jì)算大魚每一幀向鼠標(biāo)旋轉(zhuǎn)的角度。定義這兩個(gè)函數(shù),讓大魚動起來比較平滑。


  獲得了一個(gè)角度之后,怎么讓大魚旋轉(zhuǎn)起來呢?這里又需要用到幾個(gè)API了。


  ctx1.save();//建議每次繪制都使用save和restore,可以避免定義樣式,發(fā)生沖突。


  ctx1.translate(this.x,this.y);//把原點(diǎn)變成(this.x,this.y);


  ctx1.rotate(this.angle);//根據(jù)原點(diǎn)順時(shí)針旋轉(zhuǎn)一個(gè)角度


  繪制小魚跟大魚是一樣的,不做詳述。但是需要注意的是繪制小魚的時(shí)候有個(gè)判斷,當(dāng)小魚的顏色變白的時(shí)候,游戲結(jié)束。


  this.babyBodyTimer+=diffframetime;


  if(this.babyBodyTimer>550){//身體圖片變化的計(jì)數(shù)器>550ms


  this.babyBodyIndex+=1;//身體圖片變淡


  this.babyBodyTimer%=550;


  scoreOb.strength=((20-this.babyBodyIndex)/2).toFixed(0);


  if(this.babyBodyIndex>19){//如果身體變成白色,gameover;


  this.babyBodyIndex=19;


  scoreOb.gameOver=true;


  can1.style.cursor="pointer";


  }


  }


  大魚吃果實(shí)


  大魚吃果實(shí)是根據(jù)距離來判斷定的,如果大魚和果實(shí)的距離小于30,則讓果實(shí)消失,并且出現(xiàn)白色圓環(huán),并且分值有一定的變化。


  jzk.momEatFruit=function(){//判斷果實(shí)和大魚之間的距離,小于30說明被吃掉


  for(vari=0;i<fruitOb.num;i++){


  if(fruitOb.alive[i]&&fruitOb.grow[i]){


  varlen=calLength2(fruitOb.x[i],fruitOb.y[i],momOb.x,momOb.y);


  if(len<30){


  fruitOb.dead(i);//如果距離小于30,則被吃掉


  waveOb.born(i);//吃掉的時(shí)候,產(chǎn)生圓圈


  scoreOb.fruitNum++;//吃到的果實(shí)數(shù)量+1


  momOb.momBodyIndex=momOb.momBodyIndex==7?momOb.momBodyIndex:(momOb.momBodyIndex+1);//大魚的身體顏色紅


  if(fruitOb.type[i]=='blue'){


  scoreOb.doubleNum++;//吃到藍(lán)色果實(shí),倍數(shù)+1


  }


  }


  }


  }


  }


  其中有一個(gè)calLength2函數(shù),使用來計(jì)算兩個(gè)點(diǎn)之間的距離的。


  functioncalLength2(x1,y1,x2,y2){//計(jì)算兩個(gè)點(diǎn)之間的距離,,,先求平方和,再開平方


  returnMath.sqrt(Math.pow(x1-x2,2)+Math.pow(y1-y2,2));


  }


  大魚吃到果實(shí)的時(shí)候,會產(chǎn)生一個(gè)白色的圓圈,這個(gè)效果怎么實(shí)現(xiàn)呢?


  首先,我們定義一個(gè)waveObject類,它的屬性有:坐標(biāo)、數(shù)量、半徑、使用狀態(tài)。它的方法有:初始化、繪制和出生。


  我們來看一下繪制圓圈的方法:


  for(vari=0;i<this.num;i++){


  if(this.status[i]){//如果圓圈是使用狀態(tài),則繪制圓圈


  this.r[i]+=diffframetime*0.04;


  if(this.r[i]>60){


  this.status[i]=false;


  returnfalse;


  }


  varalpha=1-this.r[i]/60;


  ctx1.strokeStyle="rgba(255,255,255,"+alpha+")";


  ctx1.beginPath();


  ctx1.arc(this.x[i],this.y[i],this.r[i],0,2*Math.PI);//畫圓,


  ctx1.stroke();


  }


  }


  一幀一幀的畫每一個(gè)圓,圓的半徑逐漸增大,透明度逐漸減小,直到半徑大于60的時(shí)候,把狀態(tài)設(shè)為false,讓其回歸物體池中。


  這里又用到了一個(gè)新的方法:ctx1.arc(x,y,r,deg);//畫圓,x,y是中心圓點(diǎn),r是半徑,deg是角度,360度就是一個(gè)整圓。


  再來看一下出生的方法:


  for(vari=0;i<this.num;i++){


  if(!this.status[i]){


  this.status[i]=true;//把圓圈狀態(tài)設(shè)為使用狀態(tài)


  this.x[i]=fruitOb.x[index];


  this.y[i]=fruitOb.y[index];


  this.r[i]=10;


  returnfalse;//找到一個(gè)未使用的圓圈,就結(jié)束。


  }


  }


  圓圈出生的坐標(biāo)就是被吃果實(shí)的坐標(biāo)。


  大魚喂小魚


  大魚喂小魚同上,不再詳述,這里喂小魚之后,大魚身體變白,小魚隨果實(shí)數(shù)量相應(yīng)增多,另外需要注意的是,此時(shí)產(chǎn)生圓圈的坐標(biāo)是小魚的坐標(biāo)。


  游戲分值計(jì)算


  定義一個(gè)數(shù)據(jù)類,它的屬性有:吃到的果實(shí)數(shù)量、倍數(shù)、總分、力量值、游戲狀態(tài)(是否結(jié)束)等;方法有:初始化、繪制分?jǐn)?shù)。


  這里我們需要在畫布上繪制文字,又用到了新的API:


  ctx1.save();


  ctx1.font=‘40pxverdana’;定義文字的大小和字體;


  ctx1.shadowBlur=10;定義文字的陰影寬度


  ctx1.shadowColor=“white”;定義文字陰影的顏色;


  ctx1.fillStyle=“rgba(255,255,255,“+this.alpha+”)”;定義文字的顏色(rgba,a代表透明度)


  ctx1.fillText(“GAMEOVER”,canWid0.5,canHei0.5-25);繪制文字,第一個(gè)參數(shù)是字符串,支持表達(dá)式,后兩個(gè)參數(shù)是坐標(biāo)值。


  ctx1.font=‘25pxverdana’;


  ctx1.fillText(“CLICKTORESTART”,canWid0.5,canHei0.5+25);


  ctx1.restore();


  以上就是關(guān)于扣丁學(xué)堂HTML5視頻教程之HTML5+Canvas實(shí)現(xiàn)酷炫小游戲案例及源碼的詳細(xì)介紹,最后想要工作不累就要不斷的提升自己的技能,請關(guān)注扣丁學(xué)堂官網(wǎng)、微信等平臺,扣丁學(xué)堂IT職業(yè)在線學(xué)習(xí)教育平臺為您提供權(quán)威的HTML5培訓(xùn)視頻教程系統(tǒng),通過千鋒扣丁學(xué)堂金牌講師在線錄制的第一套自適應(yīng)HTML5在線視頻課程系統(tǒng),讓你快速掌握HTML5從入門到精通開發(fā)實(shí)戰(zhàn)技能??鄱W(xué)堂H5技術(shù)交流群:751662650。

*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請聯(lián)系工作人員刪除。



關(guān)鍵詞:

相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉