周一沒有看聖誕大戰,這幾天比較忙也沒有看賽後的報道,今天就先不扯NBA,隨便扯扯自己。昨天在電腦里找東西的時候翻到以前兼職健身教練時的照片,思緒一下子回到學生時代,腦子久久換不過來。現在深深覺得健身和寫代碼真的是兩個極端,一個往死里做(做動作),一個往死里坐(坐椅子),現在的體重和巔峰時期足足差了6 ...
周一沒有看聖誕大戰,這幾天比較忙也沒有看賽後的報道,今天就先不扯NBA,隨便扯扯自己。昨天在電腦里找東西的時候翻到以前兼職健身教練時的照片,思緒一下子回到學生時代,腦子久久換不過來。現在深深覺得健身和寫代碼真的是兩個極端,一個往死里做(做動作),一個往死里坐(坐椅子),現在的體重和巔峰時期足足差了6公斤,以前80公斤5*10的卧推變成了現在5*20的下斜俯卧撐,唉...掉了的塊和體重里可都藏著我寫過的代碼啊!這裡給大家送點小福利,珍藏版的衛平-布萊恩特奉上: 好了不扯,接著上一篇H5坦克大戰之【玩家控制坦克移動】(http://www.cnblogs.com/zhouhuan/p/H5_tankgame2.html),今天我們來繼續看玩家怎麼控制坦克,今天主要修複兩處bug,第一個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(); } };
(PS:一個人的力量畢竟有限,如在閱讀的過程中發現有描述不當或者錯誤的地方歡迎隨時指正,筆者不勝感激!)