H5 canvas控制坦克移动2
接着上一篇(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(); } };