Use canvas, javascript to make "Tank Battle" small game

game screenshot

This is what the game does:
insert image description here

Image resources used:
insert image description here
Here, all the images required for tank animation, terrain images, etc. are put in a picture, and you can use it by intercepting a part when using it.

game code

The following is the current code of the entire game. If you have any suggestions or don’t understand, you can comment. Anyway, I am also learning html canvas and javascript now.

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>坦克大战</title>
<script src="modernizr.js"></script>
<script type="text/javascript">
//当网页加载完毕后运行js代码
window.addEventListener('load', eventWindowLoaded, false);	
//运行canvasApp()函数
function eventWindowLoaded() {
    
    
	canvasApp();
}
function canvasSupport () {
    
    
  	return Modernizr.canvas;
}
function canvasApp(){
    
    
	if (!canvasSupport()) {
    
    
			 return;
  	}else{
    
    
	    var theCanvas = document.getElementById('canvas');	//将变量theCanvas与画布绑定
	    var context = theCanvas.getContext('2d');		//将变量context作为画布内容
	}
	var tileSheet=new Image();		//创建图片对象
	tileSheet.addEventListener('load', eventSheetLoaded , false);
	tileSheet.src="tanks_sheet.png";
	
	//地图属性
	var mapRows=10;
	var mapCols=10;
	var tileMap=[[32,31,31,31,1,31,31,31,31,32],
				 [1,1,1,1,1,1,1,1,1,1],
				 [32,1,1,26,1,1,1,26,1,32],
				 [32,1,26,26,26,1,26,26,1,32],
				 [32,1,1,26,1,1,1,1,1,32],
				 [32,1,1,1,1,1,26,26,1,32],
				 [32,1,26,1,26,1,26,1,1,32],
				 [1,1,26,1,26,1,1,1,1,1],
				 [32,1,1,1,1,1,1,1,1,32],
				 [32,31,31,31,1,31,31,31,31,32]];
				 
	//与坦克动画相关
	var animationFrames=[1,2,3,4,5,6,7,8];		//用来存储坦克移动动画的8张图片的下标
	var frameIndex=0;
	//与坦克属性有关,包括坐标,方向,是否移动
	var x=0;		//当前x坐标
	var y=32;		//当前y坐标
	var dx=4;		//x方向上的移动速度
	var dy=0;		//y方向上的移动速度
	var count=0;	//用于记录坦克动画执行的次数,到8后会清0,0代表着未执行动画,即没有移动
	var rotation=90;		//旋转角度
	var moveflag=false;		//是否移动
	var live=1;
	var win=false;
	
	//敌方坦克的属性
	var tanks=[];			//用于存储所有敌方坦克
	function Tanks(x,y){
    
    
		this.x=x;
		this.y=y;
		this.xspeed=0;
		this.yspeed=-4;
		this.count=0;
		this.rotation=0;
		this.moveflag=false;	//移动指令,为false时可以移动
		this.frameIndex=0;		//动画帧
	}
	tanks[0]=new Tanks(32,256);
	tanks[1]=new Tanks(224,256);
	//与子弹动画相关
	var bullets=[];		//用于存储子弹对象的数组
	//与子弹属性有关,如子弹的方向,飞行速度
	function Bullet(rotation,x,y,xspeed,yspeed,checkFlag){
    
    
		this.rotation=rotation;
		this.x=x;
		this.y=y;
		this.xspeed=xspeed;
		this.yspeed=yspeed;
		this.checkFlag=checkFlag;		//子弹是否碰撞的标志
	}
	
	//与爆炸动画有关
	var booms=[];
	//爆炸点的属性,主要是坐标值,爆炸动画由三张图片组成,需要记录当前帧播放的是哪张图片
	function Boom(x,y){
    
    
		this.x=x;
		this.y=y;
		this.nowpic=1;
	}
	
	//与游戏结束相关
	var gameover=false;		//游戏结束的标志
	var tt;			//运行动画的函数
	
	function eventSheetLoaded() {
    
    
		drawScreen();
	}
	
	function drawScreen() {
    
    
		//绘制背景
		context.fillStyle="#aaaaaa";
		context.fillRect(0,0,320,320);
		//绘制地图
		for(var rowCtr=0;rowCtr<mapRows;rowCtr++) {
    
     
			for(var colCtr=0;colCtr<mapCols;colCtr++){
    
     
				var tileId = tileMap[rowCtr][colCtr]-1;
				var sourceX = Math.floor(tileId % 8) *32;
				var sourceY = Math.floor(tileId / 8) *32;
				context.drawImage(tileSheet, sourceX,sourceY,32,32,colCtr*32,rowCtr*32,32,32);
			} 
		}
		
		//玩家的坦克动画
		if(moveflag==true){
    
    		//如果moveflag==true,则进行移动
			x=x+dx;
			y=y+dy;
			count++;		//每一步,count加1
			frameIndex++;	//	调用下一张图片
			if (frameIndex ==animationFrames.length) {
    
    
			   frameIndex=0;		//下标清零,重新开始播放动画
			}
		}
		if(count==8){
    
    		//当count==8时,一次移动完毕,即移动了32px
			count=0;		//将count清零
			moveflag=false;	//令moveflag=false,停止移动
		}
		context.save();		//保存当前变换状态
		context.translate(x+16,y+16);		//平移原点坐标
		context.rotate(rotation * Math.PI / 180);		//旋转整个坐标系
		var sourceX=Math.floor(animationFrames[frameIndex] % 8) *32;
		var sourceY=Math.floor(animationFrames[frameIndex] / 8) *32;
		context.drawImage(tileSheet, sourceX, sourceY,32,32,-16,-16,32,32);
		context.restore();	//读取保存的变换状态
		
		//敌方坦克动画
		var tankslength=tanks.length;
		for(var i=0;i<tankslength;i++){
    
    
			//敌方坦克检测到十字方向上有玩家坦克时,瞄准玩家开火
			if(tanks[i].count==0){
    
    
				tanksAttack(i);
			}
			//敌方坦克进行随机移动
			if(tanks[i].moveflag==true){
    
    
				tanks[i].x+=tanks[i].xspeed;
				tanks[i].y+=tanks[i].yspeed;
				tanks[i].count++;
				tanks[i].frameIndex++;
				if(tanks[i].frameIndex==animationFrames.length){
    
    
					tanks[i].frameIndex=0;
				}
			}else{
    
    
				tanksChange(i);
			}
			if(tanks[i].count==8){
    
    
				tanks[i].count=0;
				tanks[i].moveflag=false;
			}
			context.save();
			context.translate(tanks[i].x+16,tanks[i].y+16);
			context.rotate(tanks[i].rotation*Math.PI/180);
			sourceX=Math.floor(animationFrames[tanks[i].frameIndex]%8)*32;
			sourceY=Math.floor(animationFrames[tanks[i].frameIndex]/8+1)*32;
			context.drawImage(tileSheet,sourceX,sourceY,32,32,-16,-16,32,32);
			context.restore();
		}
		//子弹飞行动画
		var bulletCount=bullets.length;
		for(var i=0;i<bulletCount;i++){
    
    
			//检测子弹碰撞的效果
			checkChase(i,bullets[i].x,bullets[i].y);
			//绘制子弹
			if(bullets[i].checkFlag==false){
    
    
				context.save();
				context.translate(bullets[i].x+16,bullets[i].y+16);
				context.rotate(bullets[i].rotation*Math.PI/180);
				context.drawImage(tileSheet,128,64,32,32,-16,-16,32,32);
				context.restore();
				//子弹前进
				bullets[i].x+=bullets[i].xspeed;
				bullets[i].y+=bullets[i].yspeed;
			}else{
    
    
				createBoom(bullets[i].rotation,bullets[i].x,bullets[i].y);	//产生一个爆炸动画对象
			}
		}
		for(var i=0;i<bulletCount;i++){
    
    
			if(bullets[i].checkFlag==true){
    
    
				bullets.splice(i,1);	//将该子弹对象从数组中移除并重排数组
				i--;
				bulletCount--;
			}
		}
		//子弹爆炸动画
		var boomCount=booms.length;
		for(var i=0;i<boomCount;i++){
    
    
			context.save();
			context.translate(booms[i].x,booms[i].y);
			context.drawImage(tileSheet,32*booms[i].nowpic,64,32,32,0,0,32,32);
			context.restore();
			booms[i].nowpic++;
		}
		for(var i=0;i<boomCount;i++){
    
    
			if(booms[i].nowpic==4){
    
    
				booms.splice(i,1);
				i--;
				boomCount--;
			}
		}
		if(live==0||tanks.length==0){
    
    
			gameover=true;
			if(live==0){
    
    
				win=false;
			}
			if(tanks.length==0){
    
    
				win=true;
			}
		}
		if(gameover==true){
    
    
			clearInterval(tt);
			if(win==false)
			alert("你输了");
			else
			alert("你赢了");
			window.location=window.location;
		}
	}
	//键盘监听事件,用于改变玩家坦克的方向,同时修改坦克的一些数值
	function changeDirection(e){
    
    
		if(count==0){
    
    		//count==0代表着坦克的前一个动作执行完毕 
			var xindex=x/32;
			var yindex=y/32;
			switch(e.keyCode){
    
    
				case 37:
					if(tileMap[yindex][xindex-1]==1){
    
    
						moveflag=true;
						rotation=-90;
						dx=-4;
						dy=0;
					}else if(tileMap[yindex][xindex-1]==undefined){
    
    
						moveflag=true;
						rotation=-90;
						x=320;
						dx=-4;
						dy=0;
					}
					break;
				case 38:
					if(yindex-1==-1){
    
    
						moveflag=true;
						rotation=0;
						y=320;
						dx=0;
						dy=-4;
					}else if(tileMap[yindex-1][xindex]==1){
    
    
						moveflag=true;
						rotation=0;
						dx=0;
						dy=-4;
					}
					break;
				case 39:
					if(tileMap[yindex][xindex+1]==1){
    
    
						moveflag=true;
						rotation=90;
						dx=4;
						dy=0;
					}else if(tileMap[yindex][xindex+1]==undefined){
    
    
						moveflag=true;
						rotation=90;
						x=0;
						dx=4;
						dy=0;
					}
					break;
				case 40:
					if(yindex+1==10){
    
    
						moveflag=true;
						rotation=180;
						y=0;
						dx=0;
						dy=4;
					}else if(tileMap[yindex+1][xindex]==1){
    
    
						moveflag=true;
						rotation=180;
						dx=0;
						dy=4;
					}
			}
		}
	}
	//改变敌方坦克移动方向的函数
	function tanksChange(i){
    
    
		var randMath=Math.floor(Math.random()*4-1);
		var xindex=tanks[i].x/32;
		var yindex=tanks[i].y/32;
		switch(randMath){
    
    
			case -1:
				if(tileMap[yindex][xindex-1]==1){
    
    
						tanks[i].moveflag=true;
						tanks[i].rotation=-90;
						tanks[i].xspeed=-4;
						tanks[i].yspeed=0;
					}else if(tileMap[yindex][xindex-1]==undefined){
    
    
						tanks[i].moveflag=true;
						tanks[i].rotation=-90;
						tanks[i].x=320;
						tanks[i].xspeed=-4;
						tanks[i].yspeed=0;
					}
					break;
			case 0:
				if(yindex-1==-1){
    
    
						tanks[i].moveflag=true;
						tanks[i].rotation=0;
						tanks[i].y=320;
						tanks[i].xspeed=0;
						tanks[i].yspeed=-4;
					}else if(tileMap[yindex-1][xindex]==1){
    
    
						tanks[i].moveflag=true;
						tanks[i].rotation=0;
						tanks[i].xspeed=0;
						tanks[i].yspeed=-4;
					}
					break;
			case 1:
				if(tileMap[yindex][xindex+1]==1){
    
    
						tanks[i].moveflag=true;
						tanks[i].rotation=90;
						tanks[i].xspeed=4;
						tanks[i].yspeed=0;
					}else if(tileMap[yindex][xindex+1]==undefined){
    
    
						tanks[i].moveflag=true;
						tanks[i].rotation=90;
						tanks[i].x=0;
						tanks[i].xspeed=4;
						tanks[i].yspeed=0;
					}
					break;
			case 2:
				if(yindex+1==10){
    
    
						tanks[i].moveflag=true;
						tanks[i].rotation=180;
						tanks[i].y=0;
						tanks[i].xspeed=0;
						tanks[i].yspeed=4;
					}else if(tileMap[yindex+1][xindex]==1){
    
    
						tanks[i].moveflag=true;
						tanks[i].rotation=180;
						tanks[i].xspeed=0;
						tanks[i].yspeed=4;
					}
		}
	}
	//键盘监听事件,用于产生子弹,根据坦克当前方向和位置,产生子弹的方向,坐标,以及速度
	var time=0;
	function createBullet(e){
    
    
		if(count==0&&time==0){
    
    
			var newBullet;
			if(e.keyCode==32){
    
    
				switch(rotation){
    
    
					case -90:
						newBullet=new Bullet(-90,x-32,y,-32,0,false);
						break;
					case 0:
						newBullet=new Bullet(0,x,y-32,0,-32,false);
						break;
					case 90:
						newBullet=new Bullet(90,x+32,y,32,0,false);
						break;
					case 180:
						newBullet=new Bullet(180,x,y+32,0,32,false);
				}
				bullets.push(newBullet);
				time=1;
				setTimeout(retime,400);		//设置延时是为了不让子弹快速连发,快速连发会破坏游戏体验
				function retime(){
    
    
					time=0;
				}
			}
		}
	}
	//敌方坦克检测十字方向上是否有玩家坦克,若有,则开火
	function tanksAttack(i){
    
    
		if(tanks[i].x==x&&!checkBlocky(i)){
    
    
			if(tanks[i].y>y){
    
    
				bullets.push(new Bullet(0,tanks[i].x,tanks[i].y-32,0,-32,false));
			}
			if(tanks[i].y<y){
    
    
				bullets.push(new Bullet(180,tanks[i].x,tanks[i].y+32,0,32,false));
			}
		}
		if(tanks[i].y==y&&!checkBlockx(i)){
    
    
			if(tanks[i].x>x){
    
    
				bullets.push(new Bullet(-90,tanks[i].x-32,tanks[i].y,-32,0,false));
			}
			if(tanks[i].x<x){
    
    
				bullets.push(new Bullet(90,tanks[i].x+32,tanks[i].y,32,0,false));
			}
		}
		
		function checkBlocky(i){
    
    
			var y1=Math.min(Math.floor(y/32),tanks[i].y/32);
			var y2=Math.max(Math.floor(y/32),tanks[i].y/32);
			var x_=tanks[i].x/32;
			for(var i=y1;i<y2;i++){
    
    
				if(tileMap[i][x_]!=1){
    
    
					return true;
				}
			}
			return false;
		}
		function checkBlockx(i){
    
    
			var x1=Math.min(Math.floor(x/32),tanks[i].x/32);
			var x2=Math.max(Math.floor(x/32),tanks[i].x/32);
			var y_=tanks[i].y/32;
			for(var i=x1;i<x2;i++){
    
    
				if(tileMap[y_][i]!=1){
    
    
					return true;
				}
			}
			return false;
		}
	}
	
	//根据地图数组来检测子弹的碰撞,若子弹所在格子不为1,将播放子弹爆炸效果
	function checkChase(a,b,c){
    
    
		if(c/32>0&&c/32<9){
    
    
			if(tileMap[c/32][b/32]!=1){
    
    
				bullets[a].checkFlag=true;
			}else if(x-16<=b&&b<=x+16&&y-16<=c&&c<=y+16){
    
    
				bullets[a].checkFlag=true;
				live=false;
			}else if(attackTanks(b,c)==true){
    
    
				bullets[a].checkFlag=true;
			}
		}else{
    
    
			bullets[a].checkFlag=true;
		}
	}
	//判定子弹是否打中了敌方坦克
	function attackTanks(b,c){
    
    
		var aa=tanks.length;
		for(var i=0;i<aa;i++){
    
    
			if(tanks[i].x-16<=b&&b<=tanks[i].x+16&&tanks[i].y-16<=c&&c<=tanks[i].y+16){
    
    
				tanks.splice(i,1);
				return true;
			}
		}
	}
	//产生一个爆炸动画,并根据子弹方向调整爆炸动画的坐标
	function createBoom(a,b,c){
    
    
		var newBoom;
		switch(a){
    
    
			case -90:
				newBoom=new Boom(b+16,c);
				break;
			case 0:
				newBoom=new Boom(b,c+16);
				break;
			case 90:
				newBoom=new Boom(b-16,c);
				break;
			case 180:
				newBoom=new Boom(b,c-16);
		}
		booms.push(newBoom);
	}
	//开始游戏按钮点击事件
	var button=document.getElementById("start");		//将参数button与id="start"的按钮绑定
	button.addEventListener("click",startgame);			//按钮点击事件
	
	//开始游戏
	var tt;				//用于保存播放动画函数,在游戏结束时需要删除动画(即clearInterval(tt)),才能刷新页面
	function startgame(){
    
    
		button.style.visibility="hidden";		//将开始游戏按钮隐藏
		document.getElementById("mask").style.visibility="hidden";
		tt=setInterval(drawScreen, 50);
		window.addEventListener("keydown",changeDirection,true);	//添加键盘监听事件(键盘按下)——用于改变坦克运动方向
		window.addEventListener("keyup",createBullet,true);		//添加键盘监听事件(键盘松开)——用于产生子弹
	}
}

</script> 
</head>
<body>
<div style="position: absolute; top: 50px; left: 50px;">
<canvas id="canvas" width="320" height="320">
 Your browser does not support the HTML 5 Canvas. 
</canvas>
<div id="mask" style="position:absolute;top:0;left:0;background-color:black;opacity:0.4;">
	<table width="320" height="320"></table>
</div>
<button id="start" style="position:absolute;left:80px;top:150px;width:150px;height:40px;">开始游戏</button>
</div>

<span style="position:absolute;left:50px;top:400px;color:blue">使用上下左右方向键移动,空格键开火</span>
</body>
</html>

Guess you like

Origin blog.csdn.net/zhulong16/article/details/102922137