學習html5的canvas第三天,覺得還沒過癮,轉眼就忘,于是趁著有空,準備弄個小游戲來玩!游戲應該需要注意性能,還有一些邏輯需要斟酌,我想還需要用戶可修改性,也就是用戶配置。好,開始我們簡單但有趣的旅程吧——
想先看效果的,先跳轉試玩一下吧!
第一步,當然需要一張畫布
1 <canvas id="canvas" width="300" height="400">你的瀏覽器不支持Canvas</canvas>
JavaScript的總體結構如下:
var定義一些變量 planeMoveTimer飛機移動監(jiān)聽/計時器 attackEnemyTimer發(fā)射炮彈計時器 onkeydown監(jiān)聽 onkeyup監(jiān)聽 drawPlane畫飛機 drawEnemy畫敵人
首先預定義一些變量
var _keyA = _keyD = _keyW = _keyS = 0, // 存儲按鍵狀態(tài) step = 8, // 飛機移動速率 w = 34, h = 44, // 飛機實際大小 planeX = 133, planeY = 356, // 飛機目前位置 planeSrc = "feiji.png", // 飛機素材位置 imgW = 177, imgH = 220, // 飛機源圖尺寸 cw = 300, ch = 400, // 畫布大小 canvas = document.getElementById("canvas"), ctx = canvas.getContext("2d");
本游戲只用到一個外部資源,就是那張圖片,獲取地址請訪問本文底部給出的項目GitHub位置鏈接。跳轉
先看畫圖的兩個方法
function drawPlane(x, y) { var img = new Image(); img.src = planeSrc; // 飛機源圖地址 img.onload = function() { ctx.drawImage(img, 0, 0, imgW, imgH, planeX, planeY, w, h); } } function drawEnemy(){ for(var a=0;a<cw;a+=10) { for(var b=0;b<ch-300;b+=10) { ctx.beginPath(); ctx.fillStyle = "orange"; ctx.strokeStyle = "black"; ctx.strokeRect(a, b, 10 ,10); ctx.fillRect(a, b, 10, 10); ctx.closePath(); } } }
飛機的圖片一定要在onload()方法里才能把飛機畫出來,目前的敵人還是一堆橙色的不會動的磚頭,通過遍歷在畫布的頂端把它們畫出來。
接著,看兩個鍵盤事件:
window.document.onkeydown = function(evt){ evt = (evt) ? evt : window.event; switch(evt.keyCode) { case 65: // A _keyA = 1; break; case 68: // D _keyD = 1; break; case 87: // W _keyW = 1; break; case 83: // S _keyS = 1; break; } } window.document.onkeyup = function(evt){ evt = (evt) ? evt : window.event; switch(evt.keyCode) { case 65: // A _keyA = 0; break; case 68: // D _keyD = 0; break; case 87: // W _keyW = 0; break; case 83: // S _keyS = 0; break; } }
至于為什么要在兩個事件里設置變量_keyA、_keyD、_keyW、_keyS,而不是直接觸發(fā)畫圖事件,原因是——同時長按兩個鍵時,無法同時觸發(fā)事件,先按者只觸發(fā)一次,只有后按者在按著鍵的時候才能一直觸發(fā)事件,簡單點來說,如果我想要向左上角移動,同時按下A和W,假設A比W慢了一點點,即時很微小,那么飛機的移動路徑是先上移一步然后一直向左移動,這顯然不是我想要的,我用4個變量來存儲按鍵的狀態(tài),按下鍵的時候,設置其狀態(tài)為1,在按鍵起來的時候,設置其狀態(tài)為0,然后我們用計時器來不斷地讀這些鍵的狀態(tài)并及時更新飛機的狀態(tài)。
飛機移動計時器如下:
var planeMoveTimer = window.setInterval(function(){ if(_keyA||_keyD||_keyW||_keyS){ ctx.clearRect(planeX, planeY, w, h); _keyA && (planeX-=step); _keyD && (planeX+=step); _keyW && (planeY-=step); _keyS && (planeY+=step); planeX>=0 || (planeX=0); planeX<=(cw-w) || (planeX=cw-w); planeY>=0 || (planeY=0); planeY<=(ch-h) || (planeY=ch-h); drawPlane(planeX, planeY); } }, 10);
ctx.clearRect()用來清除原來位置的飛機,為繪制飛機的下一狀態(tài)做準備,但是有一個很大的問題,它是直接清除整塊的,要是游戲有背景,有交疊,那不是要把這些不是飛機的東西也一并清空了?恕我愚昧,暫時不考慮這個問題。
通過判斷按鍵狀態(tài),每次移動的步距為預先設置的step,并做好邊界處理。
然后是攻擊計時器:
var attackEnemyTimer = window.setInterval(function(){ var cannonX = planeX+Math.floor(w/2); // 炮口X軸位置 var cannonY = planeY; // 炮口Y軸位置 var tmpTimer = window.setInterval(function(){ // 每顆炮彈的移動計時器 ctx.clearRect(cannonX-1, cannonY-3, 2, 3); cannonY -= 3; // 炮彈步距 if(cannonY<0){ // 炮彈的貫透效果 ctx.beginPath(); ctx.moveTo(cannonX, 100); // 100為enemy的最低位置 ctx.strokeStyle = "white"; ctx.lineWidth = "4"; // 模擬不規(guī)則毀壞,暫時尚未沒實現(xiàn) ctx.lineTo(cannonX, 0); ctx.stroke(); ctx.closePath(); window.clearInterval(tmpTimer); // 清除該炮彈的計時器 } ctx.clearRect(cannonX-1, cannonY-3, 2, 3); ctx.beginPath(); ctx.fillStyle="red"; ctx.fillRect(cannonX-1, cannonY-3, 2, 3); // 炮彈大?。?X3 ctx.closePath(); }, 0); }, 500);
每0.5s發(fā)射一顆炮彈,每顆炮彈又單獨設置一個計時器,以便控制,炮彈的移動我也是采用先擦后畫的方式,由于炮彈移動也有步距,所謂炮彈貫透效果就是直接畫一條顏色跟背景色一樣的直線而已。試著修改炮彈步距可以調(diào)節(jié)炮彈的移動速度,也可以調(diào)節(jié)炮彈的尺寸。
最后一步了,還差點什么,一開始就要畫好敵人和飛機啦!
1 drawPlane(); 2 drawEnemy();
大功告成!還想繼續(xù)優(yōu)化增加可玩性的,但實在是沒時間弄了,還有很多其他的東西要學,所以這個游戲就先這樣啦!是不是很簡單!哈哈,囧了吧,標題太誘惑人,沒辦法!