着手开始写原计划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);
else
tank.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);
else
tank.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);
else
tank.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);
else
tank.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();
}
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);
else
this.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);
else
this.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);
else
this.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);
else
this.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);
else
this.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);
else
this.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);
else
this.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);
else
this.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一样。