日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

【教程】HTML5+JavaScript编写flappy bird

發(fā)布時(shí)間:2025/4/16 javascript 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【教程】HTML5+JavaScript编写flappy bird 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

? ? ?作者:?風(fēng)小銳 ? ? ?新浪微博ID:永遠(yuǎn)de風(fēng)小銳 ? ? ?QQ:547953539 ? ? ?轉(zhuǎn)載請(qǐng)注明出處

PS:新修復(fù)了兩個(gè)bug,已下載代碼的同學(xué)請(qǐng)查看一下

大學(xué)立即要畢業(yè)了。未來(lái)的公司為我們制定了在校學(xué)習(xí)計(jì)劃。希望我們能在畢業(yè)之前掌握一些技術(shù),當(dāng)中有一項(xiàng)就是使用HTML5+JavaScript編寫(xiě)flappy bird這個(gè)小游戲。

相信大家和我一樣,對(duì)這個(gè)小游戲還是非常熟悉的,控制小鳥(niǎo)跳過(guò)高矮不一的水管,并記錄下每局得到的分?jǐn)?shù),對(duì)于親手編寫(xiě)這個(gè)小游戲非常感興趣,立即開(kāi)始著手開(kāi)始編寫(xiě)。

學(xué)習(xí)JavaScript的時(shí)間并不久,看了《JavaScript語(yǔ)言精粹》和《HTML5游戲開(kāi)發(fā)》這兩本書(shū)。感覺(jué)都還不錯(cuò),推薦給想要學(xué)習(xí)HTML游戲開(kāi)發(fā)的朋友。

游戲的編寫(xiě)基本上用了兩個(gè)晚上,進(jìn)度還是比較快的,這里附上幾張完畢后的截圖。從左到右依次是開(kāi)始時(shí)、進(jìn)行時(shí)、結(jié)束后的截圖。

閑話說(shuō)完了,以下送上游戲的制作流程。給初學(xué)JavaScript的同學(xué)一個(gè)參考。

我將整個(gè)游戲的制作分為以下幾步:

一、游戲總體框架的搭建

這一部分包含html一些標(biāo)簽的設(shè)定,游戲循環(huán)框架的編寫(xiě)。

<body onLoad="init();"> <canvas id="canvas" width="384" height="512" style="margin-top: 8px;"> Your browser doesn't support the HTML5 element canvas. </canvas> </body>

canvas標(biāo)簽的設(shè)定,用于繪制圖像。

var fps=30; //游戲的幀數(shù),推薦在30~60之間 function init(){ctx=document.getElementById('canvas').getContext('2d'); ctx.lineWidth=2;canvas=document.getElementById("canvas");setInterval(run,1000/fps); }游戲主邏輯run()將會(huì)以每秒fps幀的速度執(zhí)行,這和后面繪制移動(dòng)物體有關(guān)。


另一些全局變量的設(shè)置,詳細(xì)含義后面還會(huì)提到
var boxx=0; var boxy=0; var boxwidth=384; var boxheight=512; var backgroundwidth=384; var backgroundheight=448; var groundwidth=18.5; var groundheight=64; var birdwidth=46; var birdheight=32; var birdx=192-birdwidth; var birdy=224-birdheight; var birdvy=0; //鳥(niǎo)初始的y軸速度 var gravity=1; //重力加速度 var jumpvelocity=11; //跳躍時(shí)獲得的向上速度 var pipewidth=69; //管道的寬度 var blankwidth=126; //上下管道之間的間隔 var pipeinterval=pipewidth+120; //兩個(gè)管道之間的間隔 var birdstate; var upbackground; var bottombackground; var birdimage; var pipeupimage; var pipedownimage; var pipenumber=0; //當(dāng)前已經(jīng)讀取管道高度的個(gè)數(shù) var fps=30; //游戲的幀數(shù),推薦在30~60之間 var gamestate=1; //游戲狀態(tài):0--未開(kāi)始,1--已開(kāi)始。2--已結(jié)束 var times; var canvas; var ctx; var i; var bottomstate; var pipeheight=[]; var pipeoncanvas=[ //要顯示在Canvas上的管道的location和height[0,0],[0,0],[0,0]];





二、游戲基本場(chǎng)景的繪制


游戲中的基本場(chǎng)景包含上方精巧的背景,下方移動(dòng)地面的繪制,以及管道的繪制。

首先是精巧的圖片,僅僅要用Image對(duì)象保存圖片地址后使用drawImage指定位置和大小即可了。

var backgroundwidth=384; var backgroundheight=448; var upbackground; function init(){ upbackground=new Image();upbackground.src="images/background.png";ctx.drawImage(upbackground,0,0,backgroundwidth,backgroundheight); }下方動(dòng)態(tài)的地面較為復(fù)雜,先貼出代碼

//繪制下方的動(dòng)態(tài)背景 function drawmovingscene(){if(bottomstate==1){for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*i,backgroundheight,groundwidth,groundheight);bottomstate=2;}else if(bottomstate==2){for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.25),backgroundheight,groundwidth,groundheight);bottomstate=3;}else if(bottomstate==3){for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.5),backgroundheight,groundwidth,groundheight);bottomstate=4;}else if(bottomstate==4){for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.75),backgroundheight,groundwidth,groundheight);bottomstate=1;} }我這里找到的地面圖片是這個(gè)樣子的。因此想要繪制以下的完整地須要先計(jì)算出多少條能將下部填滿,使用了
for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*i,backgroundheight,groundwidth,groundheight);就繪制出了下方地面的一幀圖像,想要讓地面動(dòng)起來(lái),我選擇每一幀都讓繪制的圖片向左移動(dòng)1/4寬度,這樣就能夠在游戲執(zhí)行時(shí)顯示地面在移動(dòng)。這里使用了一個(gè)bottomstate狀態(tài)量,以此來(lái)記錄當(dāng)前地面的繪制狀態(tài),每次加1。到4后下一幀變?yōu)?。

然后是移動(dòng)的管道。對(duì)于管道的繪制首先須要隨機(jī)生成若干個(gè)管道的高度,并將其并存放在一個(gè)pipeheight數(shù)組中待用

//隨機(jī)生成管道高度數(shù)據(jù) function initPipe(){for(i=0;i<200;i++)pipeheight[i]=Math.ceil(Math.random()*216)+56;//高度范圍從56~272for(i=0;i<3;i++){ pipeoncanvas[i][0]=boxwidth+i*pipeinterval;pipeoncanvas[i][1]=pipeheight[pipenumber];pipenumber++;} }鑒于管道在畫(huà)面中不會(huì)同一時(shí)候出現(xiàn)4個(gè),因此我首先取三個(gè)管道高度數(shù)據(jù)放入pipecanvas數(shù)組,并依據(jù)畫(huà)面的寬度和管道的間隔生成管道位置,為繪制管道作準(zhǔn)備,這是pipecanvas的結(jié)構(gòu)

var pipeoncanvas=[ //要顯示在Canvas上的管道的location和height[0,0],[0,0],[0,0]];

以下就要對(duì)管道進(jìn)行繪制了,先實(shí)現(xiàn)一根管道上下兩部分的繪制

//使用給定的高度和位置繪制上下兩根管道 function drawPipe(location,height){//繪制下方的管道ctx.drawImage(pipeupimage,0,0,pipewidth*2,height*2,location,boxheight-(height+groundheight),pipewidth,height);//繪制上方的管道ctx.drawImage(pipedownimage,0,793-(backgroundheight-height-blankwidth)*2,pipewidth*2,(backgroundheight-height-blankwidth)*2,location,0,pipewidth,backgroundheight-height-blankwidth); }函數(shù)比較簡(jiǎn)單不再贅述,在run函數(shù)中增加drawAllPipe函數(shù)。來(lái)繪制要顯示的三根管道

//繪制須要顯示的管道 function drawAllPipe(){for(i=0;i<3;i++){pipeoncanvas[i][0]=pipeoncanvas[i][0]-4.625;}if(pipeoncanvas[0][0]<=-pipewidth){pipeoncanvas[0][0]=pipeoncanvas[1][0];pipeoncanvas[0][1]=pipeoncanvas[1][1];pipeoncanvas[1][0]=pipeoncanvas[2][0];pipeoncanvas[1][1]=pipeoncanvas[2][1];pipeoncanvas[2][0]=pipeoncanvas[2][0]+pipeinterval;pipeoncanvas[2][1]=pipeheight[pipenumber];pipenumber++;}for(i=0;i<3;i++){drawPipe(pipeoncanvas[i][0],pipeoncanvas[i][1]);} }這里會(huì)先推斷第一根管道是否已經(jīng)移出畫(huà)布,假設(shè)移出了畫(huà)布則后面的管道數(shù)據(jù)向前順延,并將新的管道高度讀入第三根管道,處理完后按順序意思繪制三根管道。

基本場(chǎng)景繪制結(jié)束。






三、鳥(niǎo)的繪制

這里的鳥(niǎo)有一個(gè)扇翅膀的動(dòng)作,我拿到的圖片是這個(gè)樣子的,因此須要對(duì)圖片進(jìn)行裁剪。每次使用1/3,用狀態(tài)量須要記錄下鳥(niǎo)當(dāng)前的翅膀狀態(tài),并依據(jù)狀態(tài)決定下一幀的繪制。代碼例如以下:

function drawBird(){birdy=birdy+birdvy;if(birdstate==1||birdstate==2||birdstate==3){ctx.drawImage(birdimage,0,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;}else if(birdstate==4||birdstate==5||birdstate==6){ctx.drawImage(birdimage,92,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;}else if(birdstate==7||birdstate==8||birdstate==9){ctx.drawImage(birdimage,184,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;if(birdstate==9) birdstate=1;}//context.drawImage(img,0,0,swidth,sheight,x,y,width,height); }在重復(fù)嘗試后,這里我選擇3幀改變一次翅膀的位置,每幀狀態(tài)量加1。

這里有必要說(shuō)一下drawImage這個(gè)函數(shù),在使用9個(gè)參數(shù)的時(shí)候,第2-5個(gè)參數(shù)能夠指定位置和寬高對(duì)圖片進(jìn)行裁剪,有興趣的同學(xué)能夠去查一下相關(guān)的資料。

游戲開(kāi)始時(shí)須要設(shè)定鳥(niǎo)的初始位置。要讓鳥(niǎo)移動(dòng)起來(lái)。還要給鳥(niǎo)加入縱向的速度值,在游戲開(kāi)始時(shí)這個(gè)值會(huì)是0。


birdy=birdy+birdvy;birdvy=birdvy+gravity;每一幀鳥(niǎo)的位置都是由上一幀的位置加上速度決定的,在執(zhí)行過(guò)程中每一幀速度都會(huì)減去重力值(由我設(shè)定的),在檢測(cè)到用戶(hù)輸入會(huì)賦給鳥(niǎo)一個(gè)固定的速度(后面會(huì)提到)。形成了跳躍的動(dòng)作。

至此,我們?cè)谝粠幸呀?jīng)繪制了基本場(chǎng)景和鳥(niǎo),以下是碰撞檢測(cè)。




四、碰撞檢測(cè)

這里我們須要依次檢測(cè)鳥(niǎo)是否與管道以及地面發(fā)生碰撞。


function checkBird(){//先推斷第一組管道//假設(shè)鳥(niǎo)在x軸上與第一組管道重合if(birdx+birdwidth>pipeoncanvas[0][0]&&birdx+birdwidth<pipeoncanvas[0][0]+pipewidth+birdwidth){//假設(shè)鳥(niǎo)在y軸上與第一組管道上部或下部重合if(birdy<backgroundheight-pipeoncanvas[0][1]-blankwidth||birdy+birdheight>backgroundheight-pipeoncanvas[0][1])gamestate=2; //游戲結(jié)束}//推斷第二組管道//假設(shè)鳥(niǎo)在x軸上與第二組管道重合else if(birdx+birdwidth>pipeoncanvas[1][0]&&birdx+birdwidth<pipeoncanvas[1][0]+pipewidth+birdwidth){//假設(shè)鳥(niǎo)在y軸上與第二組管道上部或下部重合if(birdy<backgroundheight-pipeoncanvas[1][1]-blankwidth||birdy+birdheight>backgroundheight-pipeoncanvas[1][1])gamestate=2; //游戲結(jié)束}//推斷是否碰撞地面if(birdy+birdheight>backgroundheight)gamestate=2; //游戲結(jié)束 }這里的凝視比較具體,我簡(jiǎn)單解釋一下,推斷會(huì)先看鳥(niǎo)在x軸上是否與某一管道有重合,假設(shè)有則再檢測(cè)y軸上是否有重合,兩項(xiàng)都符合則游戲結(jié)束。地面則較為簡(jiǎn)單。





五、加入鍵盤(pán)和鼠標(biāo)控制

想要在HTML中讀取用戶(hù)輸入,須要在init中添加監(jiān)聽(tīng)事件

canvas.addEventListener("mousedown",mouseDown,false);window.addEventListener("keydown",keyDown,false);?mousedow字段監(jiān)聽(tīng)鼠標(biāo)按下事件并調(diào)用mouseDown函數(shù),keydown字段監(jiān)聽(tīng)按鍵事件并調(diào)用keyDown函數(shù)。

這兩個(gè)函數(shù)定義例如以下?

//處理鍵盤(pán)事件 function keyDown(){if(gamestate==0){playSound(swooshingsound,"sounds/swooshing.mp3");birdvy=-jumpvelocity;gamestate=1;}else if(gamestate==1){playSound(flysound,"sounds/wing.mp3");birdvy=-jumpvelocity;} }鍵盤(pán)不區(qū)分按下的鍵。會(huì)給將鳥(niǎo)的速度變?yōu)橐粋€(gè)設(shè)定的值(jumpvelocity)
function mouseDown(ev){var mx; //存儲(chǔ)鼠標(biāo)橫坐標(biāo)var my; //存儲(chǔ)鼠標(biāo)縱坐標(biāo)if ( ev.layerX || ev.layerX == 0) { // Firefoxmx= ev.layerX;my = ev.layerY;} else if (ev.offsetX || ev.offsetX == 0) { // Operamx = ev.offsetX;my = ev.offsetY;}if(gamestate==0){playSound(swooshingsound,"sounds/swooshing.mp3");birdvy=-jumpvelocity;gamestate=1;}else if(gamestate==1){playSound(flysound,"sounds/wing.mp3");birdvy=-jumpvelocity;}//游戲結(jié)束后推斷是否點(diǎn)擊了又一次開(kāi)始else if(gamestate==2){//ctx.fillRect(boardx+14,boardy+boardheight-40,75,40); //鼠標(biāo)是否在又一次開(kāi)始button上if(mx>boardx+14&&mx<boardx+89&&my>boardy+boardheight-40&&my<boardy+boardheight){playSound(swooshingsound,"sounds/swooshing.mp3");restart();}} }這里相比鍵盤(pán)多了鼠標(biāo)位置獲取和位置推斷,目的是在游戲結(jié)束后推斷是否點(diǎn)擊了又一次開(kāi)始button。


至此,我們實(shí)現(xiàn)了這個(gè)游戲的基本邏輯,已經(jīng)能窺見(jiàn)游戲的雛形了。這時(shí)的效果如flapp_1.html。





六、添加計(jì)分,加入開(kāi)始提示和結(jié)束積分板

計(jì)分的實(shí)現(xiàn)比較簡(jiǎn)單,使用一全局變量就可以,在每次通過(guò)管道時(shí)分?jǐn)?shù)加1,并依據(jù)全局變量的值將分?jǐn)?shù)繪制在畫(huà)布上。

var highscore=0; //得到過(guò)的最高分 var score=0 //眼下得到的分?jǐn)?shù)//通過(guò)了一根管道加一分if(birdx+birdwidth>pipeoncanvas[0][0]-movespeed/2&&birdx+birdwidth<pipeoncanvas[0][0]+movespeed/2||birdx+birdwidth>pipeoncanvas[1][0]-movespeed/2&&birdx+birdwidth<pipeoncanvas[1][0]+movespeed/2){playSound(scoresound,"sounds/point.mp3");score++;}function drawScore(){ctx.fillText(score,boxwidth/2-2,120); }在繪制文本之前須要先指定字體和顏色

ctx.font="bold 40px HarlemNights"; //設(shè)置繪制分?jǐn)?shù)的字體 ctx.fillStyle="#FFFFFF";開(kāi)始時(shí)的提示和結(jié)束的計(jì)分板都是普通的圖片,計(jì)分板上用兩個(gè)文本繪制了當(dāng)前分?jǐn)?shù)和得到的最高分?jǐn)?shù)

function drawTip(){ctx.drawImage(tipimage,birdx-57,birdy+birdheight+10,tipwidth,tipheight); }//繪制分?jǐn)?shù)板 function drawScoreBoard(){//繪制分?jǐn)?shù)板ctx.drawImage(boardimage,boardx,boardy,boardwidth,boardheight); //繪制當(dāng)前的得分ctx.fillText(score,boardx+140,boardheight/2+boardy-8);//132//繪制最高分ctx.fillText(highscore,boardx+140,boardheight/2+boardy+44);//184 }這里的最高分highscroe會(huì)在每次游戲結(jié)束時(shí)更新

//刷新最好成績(jī) function updateScore(){if(score>highscore)highscore=score; }


這時(shí)的游戲已經(jīng)比較完整了。執(zhí)行效果如flappybird_2.html版本號(hào)。但和原版比還是認(rèn)為差了什么。所以有了下一步







七、給鳥(niǎo)加入俯仰動(dòng)作。加入音效

在完畢了第二個(gè)版本號(hào)后,我察覺(jué)到鳥(niǎo)的動(dòng)作還不是很豐富。有必要給鳥(niǎo)加入上仰、俯沖的動(dòng)作使其更富動(dòng)感。

代碼例如以下:

function drawBird(){birdy=birdy+birdvy;if(gamestate==0){drawMovingBird();}//依據(jù)鳥(niǎo)的y軸速度來(lái)推斷鳥(niǎo)的朝向,僅僅在游戲進(jìn)行階段生效else if(gamestate==1){ctx.save();if(birdvy<=8){ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(-Math.PI/6);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); }if(birdvy>8&&birdvy<=12){ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(Math.PI/6);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); }if(birdvy>12&&birdvy<=16){ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(Math.PI/3);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); }if(birdvy>16){ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(Math.PI/2);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); }drawMovingBird();ctx.restore();}//游戲結(jié)束后鳥(niǎo)頭向下并停止活動(dòng)else if(gamestate==2){ctx.save();ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(Math.PI/2);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); ctx.drawImage(birdimage,0,0,92,64,birdx,birdy,birdwidth,birdheight);ctx.restore();} }這里使用了圖片的旋轉(zhuǎn)操作。過(guò)程是保存繪畫(huà)狀態(tài),將繪畫(huà)原點(diǎn)移到鳥(niǎo)的中心,旋轉(zhuǎn)一定的角度,將原點(diǎn)移回原位(防止影響其它物體的繪制),恢復(fù)繪畫(huà)狀態(tài):

ctx.save();ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(-Math.PI/6);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); ctx.restore();旋轉(zhuǎn)的角度我依據(jù)鳥(niǎo)當(dāng)前的速度來(lái)推斷,birdvy<=8時(shí)向上旋轉(zhuǎn)30度,8<birdvy<=12時(shí)向下旋轉(zhuǎn)30度。12<birdvy<=16時(shí)向下旋轉(zhuǎn)60度。birdvy>16時(shí)向下旋轉(zhuǎn)90度,在確定了旋轉(zhuǎn)角度后再使用之前的方法進(jìn)行鳥(niǎo)的繪制,這樣就同一時(shí)候?qū)崿F(xiàn)了鳥(niǎo)的俯仰和扇動(dòng)翅膀。在開(kāi)始和結(jié)束階段繪制方法并不一樣,感興趣的同學(xué)能夠細(xì)致看一看。

如今看看我們還差什么?

一個(gè)游戲怎么能缺少聲音能。優(yōu)秀的音樂(lè)和音效能為游戲添加很多的樂(lè)趣。提高玩家的代入感。

關(guān)于在HTML中使用音效,我查閱了很多資料,經(jīng)過(guò)重復(fù)試驗(yàn)后。排除了很多效果不佳的方法。終于選擇使用audio這個(gè)HTML標(biāo)簽來(lái)實(shí)現(xiàn)音效的播放。

要使用audio標(biāo)簽,首先要在HTML的body部分定義之

<audio id="flysound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <audio id="scoresound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <audio id="hitsound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <audio id="deadsound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <audio id="swooshingsound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio>為了時(shí)播放音效時(shí)不發(fā)生沖突,我為每一個(gè)音效定義了一個(gè)audio標(biāo)簽,這樣在使用中就不會(huì)出現(xiàn)故障。

然后將定義的變量與標(biāo)簽綁定:

//各種音效 var flysound; //飛翔的聲音 var scoresound; //得分的聲音 var hitsound; //撞到管道的聲音 var deadsound; //死亡的聲音 var swooshingsound; //切換界面時(shí)的聲音 function init(){flysound = document.getElementById('flysound');scoresound = document.getElementById('scoresound');hitsound = document.getElementById('hitsound');deadsound = document.getElementById('deadsound');swooshingsound = document.getElementById('swooshingsound'); }再定義用來(lái)播放音效的函數(shù)

function playSound(sound,src){if(src!='' && typeof src!=undefined){sound.src = src;} }函數(shù)的兩個(gè)參數(shù)分別指定了要使用的標(biāo)簽和聲音文件的路徑,接下來(lái)僅僅要在須要播放音效的地方調(diào)用這個(gè)函數(shù)并指定聲音文件即可了。比方

else if(gamestate==1){playSound(flysound,"sounds/wing.mp3");birdvy=-jumpvelocity;}這里在點(diǎn)擊鍵盤(pán)按鍵且游戲正在執(zhí)行的時(shí)候使鳥(niǎo)跳躍,并播放扇動(dòng)翅膀的音效。使用的地方非常多,這里不一一提到,用到的五種音效各自是界面切換、扇動(dòng)翅膀、撞上管道、鳥(niǎo)死亡、得分。


至此。整個(gè)游戲已經(jīng)所有完畢。達(dá)到了flappybird_3.html的效果(假設(shè)可能的話還能夠?qū)⒂?jì)分的數(shù)字由文本改為圖片,這里因?yàn)橘Y源不足沒(méi)有做這件事)。





八、源碼資源和感悟

在整個(gè)游戲的制作過(guò)程中,我學(xué)到了非常多技術(shù),積累了一些經(jīng)驗(yàn),掌握了一些主要的設(shè)計(jì)方法。

整個(gè)項(xiàng)目的源碼和資源我放在github倉(cāng)庫(kù)中

地址https://github.com/fengxiaorui/My-flappy-bird

點(diǎn)擊頁(yè)面右邊的download zipbutton就可以下載。

因?yàn)閯偨佑|JavaScript不久,難免經(jīng)驗(yàn)不足,對(duì)于代碼中的缺陷與不足,歡迎大家批評(píng)和指正。

我的新浪微博ID 永遠(yuǎn)de風(fēng)小銳,期待與大家討論各種問(wèn)題。

PS:今天發(fā)生了靈異事件。我編輯好的文章發(fā)表后后半段變成了全是代碼,希望不要再出問(wèn)題。

。。

最后附上終于版完整的源碼方便大家查看:


<html> <head> <title>My flappy bird</title> <script> //==================================================== // Name: flappybird_3.html // Des: flappy bird 的終于版本號(hào)。在第二版的基礎(chǔ)上加入了鳥(niǎo)的上下俯仰動(dòng)作, // 加入了飛翔得分碰撞等音效,重構(gòu)了部分代碼 // 2014年 4月26日 Create by 風(fēng)小銳 // 2015年 4月30日 modify by 風(fēng)小銳 // 1.改動(dòng)了checkBird方法中關(guān)于得分的推斷,如今不會(huì)在撞上管道左邊的情況下得分了。 // 2.將checkBird方法中關(guān)于得分的推斷放在了碰撞檢測(cè)之前,如今不會(huì)出現(xiàn)最高分比當(dāng)前得分高一分的情況了。 //==================================================== var boxx=0; var boxy=0; var boxwidth=384; var boxheight=512; var backgroundwidth=384; var backgroundheight=448; var groundwidth=18.5; var groundheight=64;var birdwidth=46; var birdheight=32; var birdx=192-birdwidth; var birdy=224-birdheight; var birdvy=0; //鳥(niǎo)初始的y軸速度 var birdimage; var gravity=1; //重力加速度 var jumpvelocity=11; //跳躍時(shí)獲得的向上速度 var birdstate;var upbackground; var bottombackground; var bottomstate; var pipeupimage; var pipedownimage; var pipewidth=69; //管道的寬度 var blankwidth=126; //上下管道之間的間隔 var pipeinterval=pipewidth+120; //兩個(gè)管道之間的間隔 var pipenumber=0; //當(dāng)前已經(jīng)讀取管道高度的個(gè)數(shù) var fps=30; //游戲的幀數(shù)。推薦在30~60之間 var gamestate=0; //游戲狀態(tài):0--未開(kāi)始。1--已開(kāi)始。2--已結(jié)束 var times; //地板圖片的條數(shù) Math.ceil(boxwidth/groundwidth)+1; var highscore=0; //得到過(guò)的最高分 var score=0 //眼下得到的分?jǐn)?shù) var movespeed=groundwidth/4; //場(chǎng)景向左移動(dòng)的速度,為底部場(chǎng)景的寬度的1/4var tipimage; //開(kāi)始的提示圖片 var tipwidth=168; var tipheight=136;var boardimage; //分?jǐn)?shù)板的圖片 var boardx; var boardy=140; var boardwidth=282; var boardheight=245;var canvas; var ctx; var i; var pipeheight=[]; //各種音效 var flysound; //飛翔的聲音 var scoresound; //得分的聲音 var hitsound; //撞到管道的聲音 var deadsound; //死亡的聲音 var swooshingsound; //切換界面時(shí)的聲音var pipeoncanvas=[ //要顯示在Canvas上的管道的location和height[0,0],[0,0],[0,0]];function init(){ctx=document.getElementById('canvas').getContext('2d'); flysound = document.getElementById('flysound');scoresound = document.getElementById('scoresound');hitsound = document.getElementById('hitsound');deadsound = document.getElementById('deadsound');swooshingsound = document.getElementById('swooshingsound');ctx.lineWidth=2;//ctx.font="bold 40px HarlemNights"; //設(shè)置繪制分?jǐn)?shù)的字體 Quartz Regular \HarlemNightsctx.font="bold 40px HirakakuProN-W6"; //繪制字體還原ctx.fillStyle="#FFFFFF";upbackground=new Image();upbackground.src="images/background.png";bottombackground=new Image();bottombackground.src="images/ground.png";bottomstate=1;birdimage=new Image();birdimage.src="images/bird.png";birdstate=1;tipimage=new Image();tipimage.src="images/space_tip.png";boardimage=new Image();boardimage.src="images/scoreboard.png";boardx=(backgroundwidth-boardwidth)/2;///pipeupimage=new Image();pipeupimage.src="images/pipeup.png";pipedownimage=new Image();pipedownimage.src="images/pipedown.png";/times=Math.ceil(boxwidth/groundwidth)+1;initPipe();canvas=document.getElementById("canvas");canvas.addEventListener("mousedown",mouseDown,false);window.addEventListener("keydown",keyDown,false);//window.addEventListener("keydown",getkeyAndMove,false);setInterval(run,1000/fps); }//隨機(jī)生成管道高度數(shù)據(jù) function initPipe(){for(i=0;i<200;i++)pipeheight[i]=Math.ceil(Math.random()*216)+56;//高度范圍從56~272for(i=0;i<3;i++){ pipeoncanvas[i][0]=boxwidth+i*pipeinterval;pipeoncanvas[i][1]=pipeheight[pipenumber];pipenumber++;} }//游戲的主要邏輯及繪制 function run(){//游戲未開(kāi)始if(gamestate==0){drawBeginScene(); //繪制開(kāi)始場(chǎng)景drawBird(); //繪制鳥(niǎo)drawTip(); //繪制提示}//游戲進(jìn)行中if(gamestate==1){birdvy=birdvy+gravity;drawScene(); //繪制場(chǎng)景drawBird(); //繪制鳥(niǎo)drawScore(); //繪制分?jǐn)?shù)checkBird(); //檢測(cè)鳥(niǎo)是否與物體發(fā)生碰撞}//游戲結(jié)束if(gamestate==2){if(birdy+birdheight<backgroundheight) //假設(shè)鳥(niǎo)沒(méi)有落地birdvy=birdvy+gravity;else {birdvy=0;birdy=backgroundheight-birdheight;}drawEndScene(); //繪制結(jié)束場(chǎng)景drawBird(); //繪制鳥(niǎo)drawScoreBoard(); //繪制分?jǐn)?shù)板//ctx.fillRect(boardx+14,boardy+boardheight-40,75,40); // 測(cè)試又一次開(kāi)始button的位置} }function drawTip(){ctx.drawImage(tipimage,birdx-57,birdy+birdheight+10,tipwidth,tipheight); }//繪制分?jǐn)?shù)板 function drawScoreBoard(){//繪制分?jǐn)?shù)板ctx.drawImage(boardimage,boardx,boardy,boardwidth,boardheight); //繪制當(dāng)前的得分ctx.fillText(score,boardx+140,boardheight/2+boardy-8);//132//繪制最高分ctx.fillText(highscore,boardx+140,boardheight/2+boardy+44);//184 } //繪制開(kāi)始場(chǎng)景(不包含管道) function drawBeginScene(){//清理畫(huà)布上上一楨的畫(huà)面ctx.clearRect(boxx,boxy,boxwidth,boxheight);//繪制上方靜態(tài)背景ctx.drawImage(upbackground,0,0,backgroundwidth,backgroundheight);//繪制下方的動(dòng)態(tài)背景drawmovingscene();//繪制邊框線ctx.strokeRect(boxx+1,boxy+1,boxwidth-2,boxheight-2); }//繪制場(chǎng)景 function drawScene(){ctx.clearRect(boxx,boxy,boxwidth,boxheight); //清理畫(huà)布上上一楨的畫(huà)面ctx.drawImage(upbackground,0,0,backgroundwidth,backgroundheight); //繪制上方靜態(tài)背景drawmovingscene(); //繪制下方的動(dòng)態(tài)背景drawAllPipe(); //繪制管道ctx.strokeRect(boxx+1,boxy+1,boxwidth-2,boxheight-2); //繪制邊框線 }//繪制結(jié)束場(chǎng)景(不包含管道) function drawEndScene(){ctx.clearRect(boxx,boxy,boxwidth,boxheight); //清理畫(huà)布上上一楨的畫(huà)面ctx.drawImage(upbackground,0,0,backgroundwidth,backgroundheight); //繪制上方靜態(tài)背景//繪制下方的靜態(tài)背景。依據(jù)bottomstate來(lái)推斷怎樣繪制靜態(tài)地面switch(bottomstate){case 1:for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.75),backgroundheight,groundwidth,groundheight);break;case 2:for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*i,backgroundheight,groundwidth,groundheight);break;case 3:for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.25),backgroundheight,groundwidth,groundheight);break;case 4:for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.5),backgroundheight,groundwidth,groundheight);}//繪制當(dāng)前的柱子for(i=0;i<3;i++){drawPipe(pipeoncanvas[i][0],pipeoncanvas[i][1]);}ctx.strokeRect(boxx+1,boxy+1,boxwidth-2,boxheight-2); //繪制邊框線 }//繪制下方的動(dòng)態(tài)背景 function drawmovingscene(){if(bottomstate==1){for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*i,backgroundheight,groundwidth,groundheight);bottomstate=2;}else if(bottomstate==2){for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.25),backgroundheight,groundwidth,groundheight);bottomstate=3;}else if(bottomstate==3){for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.5),backgroundheight,groundwidth,groundheight);bottomstate=4;}else if(bottomstate==4){for(i=0;i<times;i++)ctx.drawImage(bottombackground,groundwidth*(i-0.75),backgroundheight,groundwidth,groundheight);bottomstate=1;} }//使用給定的高度和位置繪制上下兩根管道 function drawPipe(location,height){//繪制下方的管道ctx.drawImage(pipeupimage,0,0,pipewidth*2,height*2,location,boxheight-(height+groundheight),pipewidth,height);//繪制上方的管道ctx.drawImage(pipedownimage,0,793-(backgroundheight-height-blankwidth)*2,pipewidth*2,(backgroundheight-height-blankwidth)*2,location,0,pipewidth,backgroundheight-height-blankwidth); }//繪制須要顯示的管道 function drawAllPipe(){for(i=0;i<3;i++){pipeoncanvas[i][0]=pipeoncanvas[i][0]-movespeed;}if(pipeoncanvas[0][0]<=-pipewidth){pipeoncanvas[0][0]=pipeoncanvas[1][0];pipeoncanvas[0][1]=pipeoncanvas[1][1];pipeoncanvas[1][0]=pipeoncanvas[2][0];pipeoncanvas[1][1]=pipeoncanvas[2][1];pipeoncanvas[2][0]=pipeoncanvas[2][0]+pipeinterval;pipeoncanvas[2][1]=pipeheight[pipenumber];pipenumber++;}for(i=0;i<3;i++){drawPipe(pipeoncanvas[i][0],pipeoncanvas[i][1]);} }function drawBird(){birdy=birdy+birdvy;if(gamestate==0){drawMovingBird();}//依據(jù)鳥(niǎo)的y軸速度來(lái)推斷鳥(niǎo)的朝向,僅僅在游戲進(jìn)行階段生效else if(gamestate==1){ctx.save();if(birdvy<=8){ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(-Math.PI/6);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); }if(birdvy>8&&birdvy<=12){ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(Math.PI/6);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); }if(birdvy>12&&birdvy<=16){ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(Math.PI/3);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); }if(birdvy>16){ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(Math.PI/2);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); }drawMovingBird();ctx.restore();}//游戲結(jié)束后鳥(niǎo)頭向下并停止活動(dòng)else if(gamestate==2){ctx.save();ctx.translate(birdx+birdwidth/2,birdy+birdheight/2);ctx.rotate(Math.PI/2);ctx.translate(-birdx-birdwidth/2,-birdy-birdheight/2); ctx.drawImage(birdimage,0,0,92,64,birdx,birdy,birdwidth,birdheight);ctx.restore();} } //繪制扇動(dòng)翅膀的鳥(niǎo) function drawMovingBird(){if(birdstate==1||birdstate==2||birdstate==3){ctx.drawImage(birdimage,0,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;}else if(birdstate==4||birdstate==5||birdstate==6){ctx.drawImage(birdimage,92,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;}else if(birdstate==7||birdstate==8||birdstate==9){ctx.drawImage(birdimage,184,0,92,64,birdx,birdy,birdwidth,birdheight);birdstate++;if(birdstate==9) birdstate=1;} }function drawScore(){ctx.fillText(score,boxwidth/2-2,120); }//檢查鳥(niǎo)是否與管道產(chǎn)生碰撞(不可能與第三組管道重合),以及鳥(niǎo)是否碰撞地面 function checkBird(){//通過(guò)了一根管道加一分if(birdx>pipeoncanvas[0][0]&&birdx<pipeoncanvas[0][0]+movespeed||birdx>pipeoncanvas[1][0]&&birdx<pipeoncanvas[1][0]+movespeed){playSound(scoresound,"sounds/point.mp3");score++;}//先推斷第一組管道//假設(shè)鳥(niǎo)在x軸上與第一組管道重合if(birdx+birdwidth>pipeoncanvas[0][0]&&birdx+birdwidth<pipeoncanvas[0][0]+pipewidth+birdwidth){//假設(shè)鳥(niǎo)在y軸上與第一組管道上部或下部重合if(birdy<backgroundheight-pipeoncanvas[0][1]-blankwidth||birdy+birdheight>backgroundheight-pipeoncanvas[0][1]){hitPipe();}}//推斷第二組管道//假設(shè)鳥(niǎo)在x軸上與第二組管道重合//這里我原本使用else if出現(xiàn)了問(wèn)題,但第一版中卻沒(méi)有問(wèn)題,對(duì)照代碼后發(fā)現(xiàn)原因是上方第一個(gè)if后沒(méi)有加大括號(hào),//這里的else無(wú)法區(qū)分相應(yīng)哪一個(gè)if。加上大括號(hào)后問(wèn)題解決,建議將if后的內(nèi)容都加上大括號(hào),養(yǎng)成良好的變成習(xí)慣else if(birdx+birdwidth>pipeoncanvas[1][0]&&birdx+birdwidth<pipeoncanvas[1][0]+pipewidth+birdwidth){//假設(shè)鳥(niǎo)在y軸上與第二組管道上部或下部重合if(birdy<backgroundheight-pipeoncanvas[1][1]-blankwidth||birdy+birdheight>backgroundheight-pipeoncanvas[1][1]){hitPipe();}}//推斷是否碰撞地面else if(birdy+birdheight>backgroundheight){hitPipe();} }//撞擊到管道或地面后的一些操作 function hitPipe(){ctx.font="bold 40px HirakakuProN-W6";//ctx.font="bold 35px HarlemNights"; ctx.fillStyle="#000000";playSound(hitsound,"sounds/hit.mp3");playSound(deadsound,"sounds/die.mp3");updateScore();gamestate=2; //游戲結(jié)束 }//刷新最好成績(jī) function updateScore(){if(score>highscore)highscore=score; }//處理鍵盤(pán)事件 function keyDown(){if(gamestate==0){playSound(swooshingsound,"sounds/swooshing.mp3");birdvy=-jumpvelocity;gamestate=1;}else if(gamestate==1){playSound(flysound,"sounds/wing.mp3");birdvy=-jumpvelocity;} }//處理鼠標(biāo)點(diǎn)擊事件,相比鍵盤(pán)多了位置推斷 function mouseDown(ev){var mx; //存儲(chǔ)鼠標(biāo)橫坐標(biāo)var my; //存儲(chǔ)鼠標(biāo)縱坐標(biāo)if ( ev.layerX || ev.layerX == 0) { // Firefoxmx= ev.layerX;my = ev.layerY;} else if (ev.offsetX || ev.offsetX == 0) { // Operamx = ev.offsetX;my = ev.offsetY;}if(gamestate==0){playSound(swooshingsound,"sounds/swooshing.mp3");birdvy=-jumpvelocity;gamestate=1;}else if(gamestate==1){playSound(flysound,"sounds/wing.mp3");birdvy=-jumpvelocity;}//游戲結(jié)束后推斷是否點(diǎn)擊了又一次開(kāi)始else if(gamestate==2){//ctx.fillRect(boardx+14,boardy+boardheight-40,75,40); //鼠標(biāo)是否在又一次開(kāi)始button上if(mx>boardx+14&&mx<boardx+89&&my>boardy+boardheight-40&&my<boardy+boardheight){playSound(swooshingsound,"sounds/swooshing.mp3");restart();}} }function restart(){gamestate=0; //回到未開(kāi)始狀態(tài)//ctx.font="bold 40px HarlemNights"; //繪制字體還原ctx.font="bold 40px HirakakuProN-W6"; //繪制字體還原ctx.fillStyle="#FFFFFF";score=0; //當(dāng)前分?jǐn)?shù)清零pipenumber=0; //讀取的管道數(shù)清零initPipe(); //又一次初始化水管高度birdx=192-birdwidth; //鳥(niǎo)的位置和速度回到初始值birdy=224-birdheight;birdvy=0; }function playSound(sound,src){if(src!='' && typeof src!=undefined){sound.src = src;} }</script> </head> <body onLoad="init();"> <audio id="flysound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <audio id="scoresound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <audio id="hitsound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <audio id="deadsound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <audio id="swooshingsound" playcount="1" autoplay="true" src=""> Your browser doesn't support the HTML5 element audio. </audio> <canvas id="canvas" width="384" height="512" style="margin-top: 8px;"> Your browser doesn't support the HTML5 element canvas. </canvas> </body> </html>

總結(jié)

以上是生活随笔為你收集整理的【教程】HTML5+JavaScript编写flappy bird的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。