一直想自己做點小東西,直到最近看了本《HTML5遊戲開發》,才瞭解遊戲開發中的一點點入門知識。
本篇就針對學習的幾個樣例,自己動手實踐,做了個FlappyBird,原始碼共享在度盤 ;也可以參考github,裡面有更多的遊戲樣例。
遊戲截圖
HTML5之Canvas
Canvas是Html5中用於繪圖的元素,它可以繪製各種圖形,比如長方形,多邊形,圓形等等。如果想要了解Canvas的使用可以參考:
http://www.w3school.com.cn/tags/html_ref_canvas.asp
1 2 3 |
//如果想要使用canvas,首先需要獲得上下文物件: ctx = document.getElementById('canvas').getContext('2d'); //然後使用這個ctx繪製圖形 |
在cavas每個繪製都是獨立的操作。比如下圖的兩個繪製圖形,第二個會以覆蓋的形式繪製,因此繪製圖形的順序就顯得十分重要了。
canvas之drawImage()
本篇的遊戲開發中,主要使用的是依據圖片繪製的api:drawImage(),它有兩個基本的使用方法:
1 2 |
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座標實現。
上下的管子只會向左移動,為了簡單實現,遊戲中一個畫面僅僅會出現一對管子,這樣當管子移出左邊的背景框,就自動把管子放在最右邊!
1 2 3 4 5 6 7 8 9 10 11 |
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()中的時間共同作用,就能模擬重力。
1 2 |
ver2 = ver1+gravity; bird.by += (ver2+ver1)*0.5; |
碰撞檢測
遊戲中小鳥碰到管子或者地面都會算遊戲結束:
其中條件1上管道的檢測為:
1 2 |
((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下管道的檢測為:
1 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地面的檢測最簡單,為:
1 |
bird.by+bird.bheight>ground.bgy |
如果滿足這三個條件,就算遊戲結束,會清除迴圈以及提示遊戲結束資訊。
分數計算
分數的計算與碰撞檢測類似,設定一個開關,當管子重新出現時,設定為true。當分值加1時,設定為false。
小鳥的最左邊的x座標如果超出了管子的x+width,就認為成功通過。
1 2 3 4 5 6 7 |
if(isScore && bird.bx>up_pipe.px+up_pipe.pwidth){ score += 1; isScore = false; if(score>0 && score%10 === 0){ velocity++; } } |
通過後,分值加1,速度+1。
全部原始碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
<!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