一直想自己做點小東西,直到最近看了本《HTML5游戲開發》,才了解游戲開發中的一點點入門知識。
本篇就針對學習的幾個樣例,自己動手實踐,做了個FlappyBird,源碼共享在度盤 ;也可以參考github,里面有更多的游戲樣例。
游戲截圖
HTML5之Canvas
Canvas是Html5中用於繪圖的元素,它可以繪制各種圖形,比如長方形,多邊形,圓形等等。如果想要了解Canvas的使用可以參考:
http://www.w3school.com.cn/tags/html_ref_canvas.asp
//如果想要使用canvas,首先需要獲得上下文對象: ctx = document.getElementById('canvas').getContext('2d'); //然后使用這個ctx繪制圖形
在cavas每個繪制都是獨立的操作。比如下圖的兩個繪制圖形,第二個會以覆蓋的形式繪制,因此繪制圖形的順序就顯得十分重要了。
canvas之drawImage()
本篇的游戲開發中,主要使用的是依據圖片繪制的api:drawImage(),它有兩個基本的使用方法:
ctx.drawImage(image,this.bx,this.by,this.bwidth,this.bheight); ctx.drawImage(image,x,y,width,height,this.px,this.py,this.pwidth,this.pheight);
第一個api中,指定Image對象,然后給出繪制圖片的x,y坐標以及寬度和高度即可。
第二個api中,第一組x,y,width,height則指定了裁剪圖片的坐標尺寸,這在使用多元素的矢量圖時很常用。比如:
上面的圖片中為了減少圖片資源的請求數量,把很多的元素放在了一個圖片中,此時就需要通過裁剪的方式,獲取指定的圖片元素。
FlappyBird原理解析
其實這個游戲很簡單,一張圖就可以看懂其中的奧妙:
其中背景和地面是不動的。
小鳥只有上和下兩個動作,可以通過控制小鳥的y坐標實現。
上下的管子只會向左移動,為了簡單實現,游戲中一個畫面僅僅會出現一對管子,這樣當管子移出左邊的背景框,就自動把管子放在最右邊!
if(up_pipe.px+up_pipe.pwidth>0){ up_pipe.px -= velocity; down_pipe.px -= velocity; }else{ up_pipe.px = 400; down_pipe.px = 400; up_pipe.pheight = 100+Math.random()*200; down_pipe.py = up_pipe.pheight+pipe_height; down_pipe.pheight = 600-down_pipe.py; isScore = true; }
很簡單吧!
由於該游戲一共就這幾個元素,因此把他們都放入一個Objects數組中,通過setInteral()方法,在一定間隔時間內,執行一次重繪。
重繪的時候會先清除畫面中的所有元素,然后按照新的元素的坐標一次繪制圖形,這樣就會出現移動的效果。
模擬小鳥重力
由於這個游戲不涉及小鳥橫向的運動,因此只要模擬出小鳥下落的動作以及上升的動作就可以了。
上升:這個很簡單,只要把小鳥的y坐標減去一定的值就可以了
下落:其實重力不需要使用gt^2來模擬,可以簡單的指定兩個變量,v1和gravity,這兩個變量與setInterval()中的時間共同作用,就能模擬重力。
ver2 = ver1+gravity;
bird.by += (ver2+ver1)*0.5;
碰撞檢測
游戲中小鳥碰到管子或者地面都會算游戲結束:
其中條件1上管道的檢測為:
((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&(bird.by<up_pipe.py+up_pipe.pheight))||
((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&(bird.by<up_pipe.py+up_pipe.pheight))
條件2下管道的檢測為:
((bird.bx>down_pipe.px)&&(bird.by>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by<down_pipe.py+down_pipe.pheight))||
((bird.bx>down_pipe.px)&&(bird.by+bird.bheight>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by+bird.bheight<down_pipe.py+down_pipe.pheight))
條件3地面的檢測最簡單,為:
bird.by+bird.bheight>ground.bgy
如果滿足這三個條件,就算游戲結束,會清除循環以及提示游戲結束信息。
分數計算
分數的計算與碰撞檢測類似,設置一個開關,當管子重新出現時,設置為true。當分值加1時,設置為false。
小鳥的最左邊的x坐標如果超出了管子的x+width,就認為成功通過。
if(isScore && bird.bx>up_pipe.px+up_pipe.pwidth){ score += 1; isScore = false; if(score>0 && score%10 === 0){ velocity++; } }
通過后,分值加1,速度+1。
全部源碼

<!DOCTYPE html> <html> <head> <title>Flappy Bird</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script type="text/javascript"> // Edit by xingoo // Fork on my github:https://github.com/xinghalo/CodeJS/tree/master/HTML5 var ctx; var cwidth = 400; var cheight = 600; var objects = []; var birdIndex = 0; var ver1 = 10; var ver2; var gravity = 2; var pipe_height = 200; var velocity = 10; var tid; var score = 0; var isScore = false; var birds = ["./images/0.gif","./images/1.gif","./images/2.gif"]; var back = new Background(0,0,400,600,"./images/bg.png"); var up_pipe = new UpPipe(0,0,100,200,"./images/pipe.png"); var down_pipe = new DownPipe(0,400,100,200,"./images/pipe.png"); var ground = new Background(0,550,400,200,"./images/ground.png"); var bird = new Bird(80,300,40,40,birds); objects.push(back); objects.push(up_pipe); objects.push(down_pipe); objects.push(ground); objects.push(bird); function UpPipe(x,y,width,height,img_src){ this.px = x; this.py = y; this.pwidth = width; this.pheight = height; this.img_src = img_src; this.draw = drawUpPipe; } function DownPipe(x,y,width,height,img_src){ this.px = x; this.py = y; this.pwidth = width; this.pheight = height; this.img_src = img_src; this.draw = drawDownPipe; } function drawUpPipe(){ var image = new Image(); image.src = this.img_src; ctx.drawImage(image,150,500,150,800,this.px,this.py,this.pwidth,this.pheight); } function drawDownPipe(){ var image = new Image(); image.src = this.img_src; ctx.drawImage(image,0,500,150,500,this.px,this.py,this.pwidth,this.pheight); } function Background(x,y,width,height,img_src){ this.bgx = x; this.bgy = y; this.bgwidth = width; this.bgheight = height; var image = new Image(); image.src = img_src; this.img = image; this.draw = drawbg; } function drawbg(){ ctx.drawImage(this.img,this.bgx,this.bgy,this.bgwidth,this.bgheight); } function Bird(x,y,width,height,img_srcs){ this.bx = x; this.by = y; this.bwidth = width; this.bheight = height; this.imgs = img_srcs; this.draw = drawbird; } function drawbird(){ birdIndex++; var image = new Image(); image.src = this.imgs[birdIndex%3]; ctx.drawImage(image,this.bx,this.by,this.bwidth,this.bheight); } function calculator(){ if(bird.by+bird.bheight>ground.bgy || ((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&( bird.by<up_pipe.py+up_pipe.pheight))|| ((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&( bird.by<up_pipe.py+up_pipe.pheight))|| ((bird.bx>down_pipe.px)&&(bird.by>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by<down_pipe.py+down_pipe.pheight))|| ((bird.bx>down_pipe.px)&&(bird.by+bird.bheight>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by+bird.bheight<down_pipe.py+down_pipe.pheight))){ clearInterval(tid); ctx.fillStyle = "rgb(255,255,255)"; ctx.font = "30px Accent"; ctx.fillText("You got "+score+"!",110,100) return; } ver2 = ver1+gravity; bird.by += (ver2+ver1)*0.5; if(up_pipe.px+up_pipe.pwidth>0){ up_pipe.px -= velocity; down_pipe.px -= velocity; }else{ up_pipe.px = 400; down_pipe.px = 400; up_pipe.pheight = 100+Math.random()*200; down_pipe.py = up_pipe.pheight+pipe_height; down_pipe.pheight = 600-down_pipe.py; isScore = true; } if(isScore && bird.bx>up_pipe.px+up_pipe.pwidth){ score += 1; isScore = false; if(score>0 && score%10 === 0){ velocity++; } } ctx.fillStyle = "rgb(255,255,255)"; ctx.font = "30px Accent"; if(score>0){ score%10!==0?ctx.fillText(score,180,100):ctx.fillText("Great!"+score,120,100); } } function drawall(){ ctx.clearRect(0,0,cwidth,cheight); var i; for(i=0;i<objects.length;i++){ objects[i].draw(); } calculator(); } function keyup(e){ var e = e||event; var currKey = e.keyCode||e.which||e.charCode; switch (currKey){ case 32: bird.by -= 80; break; } } function init(){ ctx = document.getElementById('canvas').getContext('2d'); document.onkeyup = keyup; drawall(); tid = setInterval(drawall,80); } </script> </head> <body onLoad="init();"> <canvas id="canvas" width="400" height="600" style="margin-left:200px;"> Your browser is not support canvas! </canvas> </body> </html>
總結
在學習游戲開發的時候,我突然懷念起大學的物理。當時很納悶,學計算機學什么物理,后來再接觸游戲開發才知道,沒有一定的物理知識,根本無法模擬游戲中的各個場景。
而通過這個簡單的小游戲,也撿起來了很多舊知識。
參考
【1】:Canvas參考手冊
【2】:《HTML5游戲開發》
【3】:EdisonChou的FlappyBird