[初学]H5+PHP 在线坦克开发日记

来源:互联网 发布:melt it down 编辑:程序博客网 时间:2024/05/22 08:09

      着手开始写原计划php小组项目的网络坦克对战。当时决定做游戏的原因是猫哥有h5开发能力,但现在自己做,自己要去学h5的东西。现在记录下学习和开发的过程。

2016.7.26

      实现了坦克在空白地图上的移动。

      途中遇到的问题:

          坦克图片空白位置覆盖地图:

      解决方案:通过美图秀秀抠图功能将坦克图抠下,并保存为png图片(如保存为jpg,则空白部分会变成白色,效果跟上图一样,原因是jpg没有alpha通道)

      这个问题困扰了我两天,一开始以为是绘图方式的问题,学习了两张绘图方式,之后发现,并不是。然后才在图片上找原因。

目前完成的代码:

main.js

var canvas_width = 1280,canvas_height = 900;var key_up = 38,key_down = 40,key_left = 37,key_right = 39;var canvas, stage;var txt;var tank;window.onload = function() {creatcanvas();stage = new createjs.Stage(canvas)tank = new tankObj();txt = new createjs.Text("", "20px Arial", "#ff7700");stage.addChild(tank.now);createjs.Ticker.setFPS(60);createjs.Ticker.addEventListener("tick", tick);}document.onkeydown = function(e) {e = !e ? window.event : e;console.log(e.keyCode);switch (e.keyCode) {case key_up:tank.now = tank.up;tank.ctrlary[0] = true;break;case key_down:tank.now = tank.down;tank.ctrlary[1] = true;break;case key_left:tank.now = tank.left;tank.ctrlary[2] = true;break;case key_right:tank.now = tank.right;tank.ctrlary[3] = true;break;return;}}document.onkeyup = function(e) {e = !e ? window.event : e;console.log(e.keyCode);switch (e.keyCode) {case key_up:tank.ctrlary[0] = false;break;case key_down:tank.ctrlary[1] = false;break;case key_left:tank.ctrlary[2] = false;break;case key_right:tank.ctrlary[3] = false;break;}}function tick(e) {if (tank.ctrlary[0] == true) {if(tank.now.y - tank.speed > 0)tank.now.y = (tank.y -= tank.speed);elsetank.now.y = tank.y;tank.now.x = tank.x;console.log(tank.now.x, tank.now.y);}if (tank.ctrlary[1] == true) {if(tank.now.y + tank.speed < canvas_height - 60)tank.now.y = (tank.y += tank.speed);elsetank.now.y = tank.y;tank.now.x = tank.x;console.log(tank.now.x, tank.now.y);}if (tank.ctrlary[2] == true) {if (tank.now.x - tank.speed > 0)tank.now.x = (tank.x -= tank.speed);elsetank.now.x = tank.x;tank.now.y = tank.y;console.log(tank.now.x, tank.now.y);}if (tank.ctrlary[3] == true) {if (tank.now.x + tank.speed < canvas_width - 60)tank.now.x = (tank.x += tank.speed);elsetank.now.x = tank.x;tank.now.y = tank.y;console.log(tank.now.x, tank.now.x);}stage.removeChildAt(0);stage.addChild(tank.now);stage.update();}function timepass(e) {console.log(tank.now.x);var time = new Date();txt.text = "Now Time : " + time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds() + "!";stage.update();}


tank.js
var tankObj = function(){this.x = 0; //坦克x坐标this.y = 0; //坦克y坐标this.speed = 10;this.up = new createjs.Bitmap("img/tank-up.png");this.left = new createjs.Bitmap("img/tank-left.png");this.right = new createjs.Bitmap("img/tank-right.png");this.down = new createjs.Bitmap("img/tank-down.png");this.now = this.down; //坦克朝向this.up.scaleX = this.up.scaleY =this.down.scaleX = this.down.scaleY = this.left.scaleX = this.left.scaleY =this.right.scaleX = this.right.scaleY =0.3;this.ctrlary = [false,false,false,false]; //按键数组,true为按下,0123分别对应上下左右};


7.30

      27号出了不朽三,滚去玩了几天的dota。当然!我不可能单纯只玩dota!因为月末了嘛,炉石低保还是要拿的,这个月前半个月都在期末复习培训什么的,炉石天梯都没打,又花了些时间!不过!这几天空闲的时候我还是有想一些问题的。早上出去拿物流,下午才开工,7点完成。那么进入正题。

      今天完成了单机攻击AI坦克,主要实现了发射炮弹,炮弹判定(击中,出界),以及AI坦克。

首先是发射炮弹:

首先tank类添加了一些属性

增加:count(填弹计时器) ProV(填弹速度),bulV(炮弹速度),boomtime(爆炸状态)以及AIcontime(AI当前状态时间,在AI里说明)

修改:新增参数tanktpye,传递坦克模型。扩增ctrlyary数组空间,代表开火状态,即空格键状态

var tankObj = function(tanktpye) {this.x = 0; //坦克x坐标this.y = 0; //坦克y坐标this.speed = 5;this.count = 0; //填弹计时器this.ProV = 20; //填弹速度this.bulV = 8;this.AIcontime = 0; //AI保持当前状态时间this.boomtime = 0; //爆炸状态this.up = new createjs.Bitmap("img/" + tanktpye + "-up.png");this.left = new createjs.Bitmap("img/" + tanktpye + "-left.png");this.right = new createjs.Bitmap("img/" + tanktpye + "-right.png");this.down = new createjs.Bitmap("img/" + tanktpye + "-down.png");this.now = this.down; //坦克朝向this.up.scaleX = this.up.scaleY = this.down.scaleX = this.down.scaleY = this.left.scaleX = this.left.scaleY = this.right.scaleX = this.right.scaleY = 0.3;this.ctrlary = [false, false, false, false, false]; //按键数组,true为按下,0123分别对应上下左右,4代表开火状态};

整合函数:将控制坦克函数封装入tank.js内

tankObj.prototype.movement = function() {if(this.boomtime != 0) return;if (this.ctrlary[0] == true) {if (this.now.y - this.speed > 0)this.now.y = (this.y -= this.speed);elsethis.now.y = this.y;this.now.x = this.x;}if (this.ctrlary[1] == true) {if (this.now.y + this.speed < canvas_height - 60)this.now.y = (this.y += this.speed);elsethis.now.y = this.y;this.now.x = this.x;}if (this.ctrlary[2] == true) {if (this.now.x - this.speed > 0)this.now.x = (this.x -= this.speed);elsethis.now.x = this.x;this.now.y = this.y;}if (this.ctrlary[3] == true) {if (this.now.x + this.speed < canvas_width - 60)this.now.x = (this.x += this.speed);elsethis.now.x = this.x;this.now.y = this.y;}}
新增爆炸函数用于处理坦克集中后动画及移除
tankObj.prototype.boom = function(){if(this.boomtime >= 20)return ;if(this.boomtime % 2 == 0){stage2.removeChild(this.now);this.now = new createjs.Bitmap("img/boom_"+this.boomtime/2+".png");this.now.x = this.x;this.now.y = this.y;this.now.scaleX = this.now.scaleY = (0.6 - 0.1*this.boomtime);stage2.addChild(this.now);}this.boomtime++;}
爆炸图片编号为0-9,原本写boomtime上限是10即每一帧刷新换一张爆炸图片,效果不够明显,就换成2帧换一张,每一帧更新一次缩放,效果我说不清,就跟烟花一样,爆炸后向左上飘散,等我什么时候心情好了做个gif吧,下面附上爆炸每帧的原图


新增炮弹类

bullet.js

var bulletObj = function() {this.bullet = new createjs.Bitmap("img/bullet.png");this.bullet.scaleX = this.bullet.scaleY = 0.3;this.faceX = 0;this.faceY = 0;}bulletObj.prototype.movement = function(i) {this.bullet.x += this.faceX;this.bullet.y += this.faceY;if (bullet[i].bullet.x < 0 || bullet[i].bullet.y < 0 || bullet[i].bullet.x > canvas_width || bullet[i].bullet.y > canvas_height) {stage2.removeChild(bullet[i].bullet);bullet.splice(i, 1);}}
其中faceX,faceY记录炮弹方向,faceX=-1代表向上发射

bullet为子弹数组定义在main.js中,movement函数每一次刷新都会调用,用于子弹移动已经出界判断

tank.js新增开火函数

tankObj.prototype.onfire = function() {if (this.boomtime != 0)return;if (this.count == 0) {if (this.ctrlary[4] == false)return;var len = bullet.length;bullet[len] = new bulletObj();switch (this.now) {case this.up:bullet[len].bullet.x = this.x + 27;bullet[len].bullet.y = this.y;bullet[len].faceY = -this.bulV;break;case this.down:bullet[len].bullet.x = this.x + 27;bullet[len].bullet.y = this.y + 60;bullet[len].faceY = this.bulV;break;case this.left:bullet[len].bullet.x = this.x;bullet[len].bullet.y = this.y + 20;bullet[len].faceX = -this.bulV;break;case this.right:bullet[len].bullet.x = this.x + 60;bullet[len].bullet.y = this.y + 20;bullet[len].faceX = this.bulV;break;}stage2.addChild(bullet[len].bullet);this.count = this.ProV;} else {this.count--;}}
每一次刷新都会调用一次开火函数,只有当填弹完成(count=0)并且空格被按下(ctrlary[4]=true)才会触发开火,如果填弹未完成则填弹(count--),开火是通过判断坦克朝向设置子弹初始位置(bullet.x,bullet.y),已经子弹方向(faceX,faceY)

修改test.js

新增:敌人坦克数组entanks,爆炸中坦克数组boomtanks,子弹数组bullet,攻击判断函数AttackJudge,全局变量canvas2和stage2

修改:canvas创建函数createcanvas,帧刷新函数tick,主函数onload以及用于检测开火按键(空格)状态的对应函数

createcanvas函数

function creatcanvas() {canvas = document.createElement("canvas");canvas.id = "canvas";canvas.width = canvas_width;canvas.height = canvas_height;canvas.style.background = "green";document.body.appendChild(canvas);canvas2 = document.createElement("canvas");canvas2.id = "canvas2";canvas2.width = canvas_width;canvas2.height = canvas_height;canvas.style.position = canvas2.style.position = "absolute";canvas.style.buttom = canvas.style.left = canvas2.style.buttom = canvas2.style.left = 0;document.body.appendChild(canvas2);}
增加第二个canvas标签创建,并设置两个canvas等大小,设置position为absolute,同时获取舞台2stag2,之后子弹,敌对坦克都在stage2刷新

attackjudge函数

function AttackJudge() {for (var i = 0; i < bullet.length; i++)for (var j = 0; j < entanks.length; j++) {if (bullet[i].bullet.x > entanks[j].x + 60)continue;if (bullet[i].bullet.y > entanks[j].y + 60)continue;if (bullet[i].bullet.x + 5 < entanks[j].x)continue;if (bullet[i].bullet.y + 5 < entanks[j].y)continue;stage2.removeChild(bullet[i].bullet);//stage2.removeChild(entanks[j].now);bullet.splice(i, 1);boomtanks[boomtanks.length] = entanks[j];entanks[j].boom();entanks.splice(j, 1);}}<strong></strong>

遍历所有子弹,判定子弹与所有敌对坦克的位置关系,一旦有重合则将子弹移除数组,敌对坦克移动到爆炸中坦克数组,并执行爆炸函数

onload函数

window.onload = function() {creatcanvas();stage = new createjs.Stage(canvas);stage2 = new createjs.Stage(canvas2);tank = new tankObj("tank");for (var i = 0; i < 3; i++) {entanks[i] = new tankObj("tank2");entanks[i].x = entanks[i].now.x = Math.random() * (canvas_width - 60);entanks[i].y = entanks[i].now.y = Math.random() * (canvas_height - 60);stage.addChild(entanks[i].now);}stage.addChild(tank.now);createjs.Ticker.setFPS(60);createjs.Ticker.addEventListener("tick", tick);}
添加敌对(AI)坦克创建循环

tick函数
function tick(e) {tank.movement();tank.onfire();if (bullet.length != 0)for (var i = 0; i < bullet.length; i++) {bullet[i].movement(i);}stage.removeAllChildren();for(var i = 0; i<entanks.length;i++){entanks[i].AImove();entanks[i].movement();stage.addChild(entanks[i].now);}stage.addChild(tank.now);AttackJudge();for(var i = 0; i < boomtanks.length; i++){if(boomtanks[i].boomtime >= 20 ){stage2.removeChild(boomtanks[i].now);boomtanks.splice(i,1);continue;}boomtanks[i].boom();}stage.update();stage2.update();}
封装坦克移动函数以tank.js,tick调用坦克移动函数movement,以及开火函数onfire。当屏幕中有子弹时遍历子弹数组调用子弹移动函数movement。新增循环用于调用AI坦克移动函数,新增循环用于爆炸中坦克动画处理。

坦克AI移动

tankObj.prototype.AImove = function() {if(this.AIcontime != 0){this.AIcontime--;return;}var x = parseInt(Math.random() * 5);for (var i = 0; i < 5; i++)this.ctrlary[i] = false;switch(x){case 0:this.now = this.up;this.ctrlary[0] = true;break;case 1:this.now = this.down;this.ctrlary[1] = true;break;case 2:this.now = this.left;this.ctrlary[2] = true;break;case 3:this.now = this.right;this.ctrlary[3] = true;break;case 4:this.ctrlary[4] = true;return;return;}this.AIcontime = 20;}

AI坦克将保持移动动作20帧,随后从新随机选择一个方向移动,并重置AIcontime用于计算当前动作保持帧数,这里坦克移动方向随机,AI还没编写攻击性,只用于攻击判断attackjudge函数实验,计划中并不准备编写攻击性,毕竟要做成联网的。

7.31

在媳妇儿的监督下,效率提升,没几个小时就完成了昨天计划今天完成的东西。开始今天的正题。

今天完成了,地图,包括基础地形,特殊地形(坦克无法通行并且炮弹无法通行,坦克无法通行但炮台能通过)。写完这些还有半天以上的时间,本来想把坦克与坦克直接的碰撞也些,不过想了些细节的东西,因为涉及到AI要改很多地方,而且以后做成联网对战的还要不能用,就没去写。准备写完博客帮媳妇儿看算法。

新增地形类

landform.js

var landformObj = function() {this.landary = []; //地形数组this.tankland = []; //坦克通行数组,0为可通过this.bulletland = []; //炮弹通行数组,0为可通过this.landform = []; //地形图片this.background = []; //背景图片}
地形数组landary中存放图片编号,,坦克通行数组tankland用于存放相对于tank的地形,炮弹通行数组bulletland用于存放相对于炮弹的地形

定义地图生成函数buildlandform

landformObj.prototype.buildlandform = function() {//this.readlandform() //预留,从服务器上获取地形数组for (var i = 0; i < this.landary.length; i++) {this.landform[i] = new Array();this.background[i] = new Array();for (var j = 0; j < this.landary[i].length; j++) {this.background[i][j] = new createjs.Bitmap("img/land_0.png");this.background[i][j].x = j * 64;this.background[i][j].y = i * 60;stage2.addChild(this.background[i][j]);if (this.landary[i][j] != 0) {this.landform[i][j] = new createjs.Bitmap("img/land_" + this.landary[i][j] + ".png");this.landform[i][j].x = j * 64;this.landform[i][j].y = i * 60;stage2.addChild(this.landform[i][j]);}}}}

每个地形尺寸都为64*60px。

附上地形素材:

草地(坦克以及炮弹都可通行)

河流(坦克不可通行但炮弹可通行)


岩石(坦克以及炮弹都不可通行)

这里需要补充,因为地形的原因,我修改了main.js中createcanvas函数

function creatcanvas() {canvas = document.createElement("canvas");canvas.id = "canvas";canvas.width = canvas_width;canvas.height = canvas_height;//canvas.style.background = "green";canvas2 = document.createElement("canvas");canvas2.id = "canvas2";canvas2.width = canvas_width;canvas2.height = canvas_height;canvas.style.position = canvas2.style.position = "absolute";canvas.style.buttom = canvas.style.left = canvas2.style.buttom = canvas2.style.left = 0;//canvas.style.z - index = 1;//canvas2.style.z - index = 0;document.body.appendChild(canvas2);document.body.appendChild(canvas);}
这里修改了下两个canvas的插入顺序,让stag2的canvas先插入,这样stag1就会在stag2上面,不这样做的,位于stag2的地形图片会覆盖位于stag1上的坦克图片


定义坦克通行判断函数tankJudge以及炮台通行判断函数bulletJudge

landformObj.prototype.tankJudge = function(x, y) {var ruj = Math.floor(x / 64); //右上角X坐标所在格子j坐标var rui = Math.floor(y / 60); //右上角Y坐标所在格子i坐标var ldj = Math.floor((x + 50) / 64); //左下角X坐标所在格子j坐标var ldi = Math.floor((y + 50) / 60); //左下角Y坐标所在格子i坐标if (rui < 0)rui = 0;if (ruj < 0)ruj = 0;if (ldi > 14)ldi = 14;if (ldj > 19)ldj = 19;if (this.tankland[rui][ruj] != 0) {console.log("rui,ruj");console.log(rui, ruj);return false;}if (this.tankland[rui][ldj] != 0) {console.log("rui,ldj");console.log(rui, ldj);return false;}if (this.tankland[ldi][ruj] != 0) {console.log("ldi,ruj");console.log(ldi, ruj);return false;}if (this.tankland[ldi][ldj] != 0) {console.log("ldi,ldj");console.log(ldi, ldj);return false;}return true;}landformObj.prototype.bulletJudge =function(x,y){var ruj = Math.floor(x / 64); //右上角X坐标所在格子j坐标var rui = Math.floor(y / 60); //右上角Y坐标所在格子i坐标var ldj = Math.floor((x + 5) / 64); //左下角X坐标所在格子j坐标var ldi = Math.floor((y + 5) / 60); //左下角Y坐标所在格子i坐标if (rui < 0)rui = 0;if (ruj < 0)ruj = 0;if (ldi > 14)ldi = 14;if (ldj > 19)ldj = 19;if (this.bulletland[rui][ruj] != 0) {console.log("rui,ruj");console.log(rui, ruj);return false;}if (this.bulletland[rui][ldj] != 0) {console.log("rui,ldj");console.log(rui, ldj);return false;}if (this.bulletland[ldi][ruj] != 0) {console.log("ldi,ruj");console.log(ldi, ruj);return false;}if (this.bulletland[ldi][ldj] != 0) {console.log("ldi,ldj");console.log(ldi, ldj);return false;}return true;}
修改坦克移动函数

tankObj.prototype.movement = function() {if (this.boomtime != 0)return;if (this.ctrlary[0] == true) {if (this.now.y - this.speed > 0)this.now.y = (this.y - this.speed);elsethis.now.y = this.y;this.now.x = this.x;}if (this.ctrlary[1] == true) {if (this.now.y + this.speed < canvas_height - 60)this.now.y = (this.y + this.speed);elsethis.now.y = this.y;this.now.x = this.x;}if (this.ctrlary[2] == true) {if (this.now.x - this.speed > 0)this.now.x = (this.x - this.speed);elsethis.now.x = this.x;this.now.y = this.y;}if (this.ctrlary[3] == true) {if (this.now.x + this.speed < canvas_width - 60)this.now.x = (this.x + this.speed);elsethis.now.x = this.x;this.now.y = this.y;}if (landform.tankJudge(this.now.x, this.now.y) == false) {this.now.x = this.x;this.now.y = this.y;} else {this.x = this.now.x;this.y = this.now.y;}}
每次完成移动后,不刷新而是进入tankJudge函数判断新的坐标是否处于可通行的位置,如果不可则返回原来的坐标,可通行则刷新。

修改炮弹移动函数

bulletObj.prototype.movement = function(i) {this.bullet.x += this.faceX;this.bullet.y += this.faceY;if (bullet[i].bullet.x < 0 || bullet[i].bullet.y < 0 || bullet[i].bullet.x > canvas_width || bullet[i].bullet.y > canvas_height || landform.bulletJudge(bullet[i].bullet.x, bullet[i].bullet.y) == false) {stage2.removeChild(bullet[i].bullet);bullet.splice(i, 1);}}
出界或遇到不可通行地形时皆消失。

附目前运行截图


7.31晚

跟媳妇儿闲聊的时候有了新的想法,只要定义一个函数就可以进行完成坦克碰撞判断,其他地方都不需要修改,下面是新定义的坦克碰撞判断函数covertank

covertank函数

tankObj.prototype.covertank = function(x, y) {for (var i = 0; i < entanks.length; i++) {if (entanks[i] == this)continue;if (entanks[i].x >= x + 50)continue;if (entanks[i].x + 50 <= x)continue;if (entanks[i].y >= y + 50)continue;if (entanks[i].y + 50 <= y)continue;return false;}if (this != tank) {if (tank.x >= x + 50)return true;if (tank.x + 50 <= x)return true;if (tank.y >= y + 50)return true;if (tank.y + 50 <= y)return true;return false;}return true;}
这个地形通行判断也可以用这个算法,可以提高判断精度,不过地形的数组比较大会影响效率。

然后在坦克移动函数movement里调用这个函数就好了,方式和坦克通行判断函数tankJudge一样。

1 0
原创粉丝点击