接着上一篇(
http://www.cnblogs.com/zhouhuan/p/H5_tankgame2.html),這篇主要修復兩個bug,第一,玩家按下方向鍵時,坦克的炮筒應該指向相應的方向,並向該方向移動,第二,坦克不能開出邊界,上一節的代碼坦克是可以開出邊界的,這樣顯然是不行的。
1. 修復第一個bug
我們的思路是,給造坦克的函數里再傳一個方向的參數,我們讓"u", "d", "l", "r"分別表示上下左右,封裝這樣一個可以傳方向的函數之后,我們在用戶按下不同的鍵時傳不同的參數進去,由於整個地板每隔100毫秒會刷新一次,那么這個函數就能以肉眼分辨不出來的速度,在用戶按下鍵的一瞬間相應地生產出不同方向的坦克了。
如下:
//封裝一個畫坦克的函數,傳兩個參數x,y,分別代表左上角的橫縱坐標 //再增加一個參數dir來表示方向 上下左右分別傳"u" "d" "l" "r" function drawTank(x,y,dir){ var cxt = getCxt(); switch(dir){ case "u": //此時造一個向上的坦克 cxt.fillStyle = "#542174"; cxt.fillRect(x,y,20,65); cxt.fillRect(x+70,y,20,65); cxt.fillRect(x+23,y+10,44,50); cxt.fillStyle = "#FCB827"; cxt.beginPath(); cxt.arc(x+45,y+35,16,0,2*Math.PI,false); cxt.closePath(); cxt.fill(); cxt.strokeStyle = "#FCB827"; cxt.lineWidth = "8.0"; cxt.moveTo(x+45,y+35); cxt.lineTo(x+45,y-25); cxt.stroke(); break; case "d": //此時造向下的坦克 cxt.fillStyle = "#542174"; cxt.fillRect(x,y,20,65); cxt.fillRect(x+70,y,20,65); cxt.fillRect(x+23,y+10,44,50); cxt.fillStyle = "#FCB827"; cxt.beginPath(); cxt.arc(x+45,y+35,16,0,2*Math.PI,false); cxt.closePath(); cxt.fill(); cxt.strokeStyle = "#FCB827"; cxt.lineWidth = "8.0"; cxt.moveTo(x+45,y+35); cxt.lineTo(x+45,y+95); //和向上造相比,只有炮筒需要改變 cxt.stroke(); break; case "l": //此時造向左的坦克 cxt.fillStyle = "#542174"; cxt.fillRect(x,y,65,20); //和向上造坦克相比,畫第一個矩形時長寬互換即可 cxt.fillRect(x,y+70,65,20); //向左的坦克,注意坐標之間的轉換即可,以下類似不再一一解釋 cxt.fillRect(x+10,y+23,50,44); cxt.fillStyle = "#FCB827"; cxt.beginPath(); cxt.arc(x+35,y+45,16,0,2*Math.PI,false); cxt.closePath(); cxt.fill(); cxt.strokeStyle = "#FCB827"; cxt.lineWidth = "8.0"; cxt.moveTo(x+35,y+45); cxt.lineTo(x-25,y+45); cxt.stroke(); break; case "r": cxt.fillStyle = "#542174"; cxt.fillRect(x,y,65,20); //和造向左的坦克類似,只要改動炮筒即可向右 cxt.fillRect(x,y+70,65,20); cxt.fillRect(x+10,y+23,50,44); cxt.fillStyle = "#FCB827"; cxt.beginPath(); cxt.arc(x+35,y+45,16,0,2*Math.PI,false); cxt.closePath(); cxt.fill(); cxt.strokeStyle = "#FCB827"; cxt.lineWidth = "8.0"; cxt.moveTo(x+35,y+45); cxt.lineTo(x+95,y+45); cxt.stroke(); } }
和之前不同的是,我們增加了一個switch語句,用來判斷傳進來的方向,然后將不同的畫坦克的動作放進不同的case分支里。看起來代碼量很大,但其實都是從向上畫的代碼改過來的,難度不大,具體一些的說明都寫在了注釋里。
接下來我們給myTank這個對象增加一個屬性direction,用來確定坦克的方向,初始的時候我們讓它等於"u",畫一個向上的坦克。
相應地,造坦克的時候多傳一個參數進去就可以了:
drawTank(myTank.x,myTank.y,myTank.direction);
再接着我們要做的就是在玩家按下不同的鍵時響應的改變方向就可以了:
window.onkeydown = function(eve){ switch(eve.keyCode){ case 38: case 87: myTank.y -= myTank.step; //Y坐標減小向上移動 myTank.direction = "u"; //改變成向上的方向 break; case 40: case 83: myTank.y += myTank.step; //Y坐標增加向下移動 myTank.direction = "d"; //改變為向下的方向 break; case 37: case 65: myTank.x -= myTank.step; //X坐標減小向左移動 myTank.direction = "l"; //改變為向左的方向 break; case 39: case 68: myTank.x += myTank.step; //X坐標增加向右移動 myTank.direction = "r"; //改變為向右的方向 } };
不過,我們最好再做進一步的封裝,首先給myTank對象增加一些方法:
myTank.turnUp = function(){ myTank.y -= myTank.step; myTank.direction = "u"; }; myTank.turnDown = function(){ myTank.y += myTank.step; myTank.direction = "d"; }; myTank.turnLeft = function(){ myTank.x -= myTank.step; myTank.direction = "l"; }; myTank.turnRight = function(){ myTank.x += myTank.step; myTank.direction = "r"; };
再根據玩家的操作進行相應地調用:
window.onkeydown = function(eve){ switch(eve.keyCode){ case 38: case 87: myTank.turnUp(); break; case 40: case 83: myTank.turnDown(); break; case 37: case 65: myTank.turnLeft(); break; case 39: case 68: myTank.turnRight(); } };
給myTank對象添加的屬性和方法多了之后這樣看着很煩,可讀性也比較差,我們有必要對它進行改動:
var myTank = { x : 350, y : 400, step : 3, direction : "u", turnUp : function(){ myTank.y -= myTank.step; myTank.direction = "u"; }, turnDown : function(){ myTank.y += myTank.step; myTank.direction = "d"; }, turnLeft : function(){ myTank.x -= myTank.step; myTank.direction = "l"; }, turnRight : function(){ myTank.x += myTank.step; myTank.direction = "r"; } };
嗯,這樣看着舒服多了。
2.解決第二個bug
我們的思路是,重新封裝一下turnUp turnDown turnLeft turnRight這幾個方法,給里面加上判斷條件,如果判斷為將要出界,那么不再執行改變坐標的代碼,這樣,坦克就只能在可視區內運動了。具體判斷方法如下:
坦克如果將要開出上面的邊界,那么開出去之前它一定是向上的,此時(myTank.x, myTank.y)點是坦克左邊履帶左上角的點,我們暫且將這個點稱為原點,再回頭看一下向上畫坦克時的代碼:cxt.arc(x+45,y+35,16,0,2*Math.PI,false); 可知圓蓋中心點(也就是炮筒的起點)和原點之間的縱向距離為35,又易知炮筒的總長度為60(cxt.moveTo(x+45,y+35); cxt.lineTo(x+45,y-25);),那么顯然炮筒的起點與原點的縱向距離就是25,所以我們就可以這樣判斷,坦克的y坐標減去25大於等於0的時候我們再讓坦克向上動起來,轉化成代碼就是:
var myTank = { turnUp : function(){ if((myTank.y-25) >= 0){ myTank.y -= myTank.step; myTank.direction = "u"; } } };
這時候,坦克就不會再超出上面的邊界了。
坦克向下移動的時候,我們把坦克履帶的長度考慮進去就可以了,如下:
var myTank = { turnDown : function(){ if((myTank.y+90) <= 500){ myTank.y += myTank.step; myTank.direction = "d"; } } };
同理坦克向左和向右移動時如下:
var myTank = { turnLeft : function(){ if((myTank.x-25) >= 0){ myTank.x -= myTank.step; myTank.direction = "l"; } }, turnRight : function(){ if((myTank.x+90) <= 800){ myTank.x += myTank.step; myTank.direction = "r"; } } };
3. 補充說明
上一節的代碼其實存在一定的問題,就是每一次更新戰場的時候都會去getCxt()一下,清理了戰場之后后面又會drawTank(), drawTank()里面又有getCxt(),這樣就會重復不斷地獲取同一個節點,而且更新戰場的函數每100毫秒執行一次,雖然不會影響功能,但是會影響到游戲的性能,我們可以定義一個變量專門用來存放獲取到的繪圖環境,后面需要的時候直接用就好了。
4. 最終代碼
//封裝一個獲取繪圖環境的函數 function getCxt(){ var myCanvas = document.getElementById('floor'), myContext = myCanvas.getContext('2d'); return myContext; } //為了防止重復地獲取節點影響性能,我們將獲取到的繪圖環境(也就是畫筆對象)存起來 var oCxt = getCxt(); //封裝一個畫坦克的函數,傳兩個參數x,y,分別代表左上角的橫縱坐標 //再增加一個參數dir來表示方向 上下左右分別傳"u" "d" "l" "r" function drawTank(x,y,dir){ switch(dir){ case "u": //此時造一個向上的坦克 oCxt.fillStyle = "#542174"; oCxt.fillRect(x,y,20,65); oCxt.fillRect(x+70,y,20,65); oCxt.fillRect(x+23,y+10,44,50); oCxt.fillStyle = "#FCB827"; oCxt.beginPath(); oCxt.arc(x+45,y+35,16,0,2*Math.PI,false); oCxt.closePath(); oCxt.fill(); oCxt.strokeStyle = "#FCB827"; oCxt.lineWidth = "8.0"; oCxt.moveTo(x+45,y+35); oCxt.lineTo(x+45,y-25); oCxt.stroke(); break; case "d": //此時造向下的坦克 oCxt.fillStyle = "#542174"; oCxt.fillRect(x,y,20,65); oCxt.fillRect(x+70,y,20,65); oCxt.fillRect(x+23,y+10,44,50); oCxt.fillStyle = "#FCB827"; oCxt.beginPath(); oCxt.arc(x+45,y+35,16,0,2*Math.PI,false); oCxt.closePath(); oCxt.fill(); oCxt.strokeStyle = "#FCB827"; oCxt.lineWidth = "8.0"; oCxt.moveTo(x+45,y+35); oCxt.lineTo(x+45,y+95); //和向上造相比,只有炮筒需要改變 oCxt.stroke(); break; case "l": //此時造向左的坦克 oCxt.fillStyle = "#542174"; oCxt.fillRect(x,y,65,20); //和向上造坦克相比,畫第一個矩形時長寬互換即可 oCxt.fillRect(x,y+70,65,20); //向左的坦克,注意坐標之間的轉換即可,以下類似不再一一解釋 oCxt.fillRect(x+10,y+23,50,44); oCxt.fillStyle = "#FCB827"; oCxt.beginPath(); oCxt.arc(x+35,y+45,16,0,2*Math.PI,false); oCxt.closePath(); oCxt.fill(); oCxt.strokeStyle = "#FCB827"; oCxt.lineWidth = "8.0"; oCxt.moveTo(x+35,y+45); oCxt.lineTo(x-25,y+45); oCxt.stroke(); break; case "r": oCxt.fillStyle = "#542174"; oCxt.fillRect(x,y,65,20); //和造向左的坦克類似,只要改動炮筒即可向右 oCxt.fillRect(x,y+70,65,20); oCxt.fillRect(x+10,y+23,50,44); oCxt.fillStyle = "#FCB827"; oCxt.beginPath(); oCxt.arc(x+35,y+45,16,0,2*Math.PI,false); oCxt.closePath(); oCxt.fill(); oCxt.strokeStyle = "#FCB827"; oCxt.lineWidth = "8.0"; oCxt.moveTo(x+35,y+45); oCxt.lineTo(x+95,y+45); oCxt.stroke(); } } //初始化一個對象myTank,用來存儲一些屬性和方法 var myTank = { x : 350, y : 400, step : 3, direction : "u", turnUp : function(){ if((myTank.y-25) >= 0){ //加判斷條件防止開出邊界 myTank.y -= myTank.step; myTank.direction = "u"; } }, turnDown : function(){ if((myTank.y+90) <= 500){ myTank.y += myTank.step; myTank.direction = "d"; } }, turnLeft : function(){ if((myTank.x-25) >= 0){ myTank.x -= myTank.step; myTank.direction = "l"; } }, turnRight : function(){ if((myTank.x+90) <= 800){ myTank.x += myTank.step; myTank.direction = "r"; } } }; //先畫一個坦克出來 drawTank(myTank.x,myTank.y,myTank.direction); //一開始先造一個向上的出來 //封裝一個更新戰場的函數 function updateFloor(){ oCxt.clearRect(0,0,800,500); //更新之前先清除畫布 drawTank(myTank.x,myTank.y,myTank.direction); //清除完之后重新造坦克,坦克要移動就必須實時地根據坐標重新來造 } //設置一個間歇調用的函數,每隔100ms更新一下戰場 setInterval(function(){ updateFloor(); },100); //響應玩家的操作指令 window.onkeydown = function(eve){ switch(eve.keyCode){ case 38: case 87: myTank.turnUp(); break; case 40: case 83: myTank.turnDown(); break; case 37: case 65: myTank.turnLeft(); break; case 39: case 68: myTank.turnRight(); } };