<canvas>+js实现简单的贪吃蛇小游戏(进阶篇)

通过在<canvas>+js实现简单的贪吃蛇小游戏这篇博文中,我们实现了基本的贪吃蛇游戏,让贪吃蛇得以移动和吃到果实,但是贪吃蛇现在在很多版本中都添加了或多或少的内容,今天我将以上一篇博文为基础,为贪吃蛇游戏添加几个新的内容。分别为穿透容器,障碍物的设置,道具的设置。

首先,上一篇博文的长宽为10*10的数组,为了添加更多内容,这里先将数组扩增为20*20。

var numbers=new Array(20);
for(var i=0;i<numbers.length;i++)//用于记录canvas中代表东西的代数
{
	numbers[i]=new Array(20);
	for(var j=0;j<numbers[i].length;j++)
	{
		numbers[i][j]=0;
	}
}

之前实现的贪吃蛇游戏中,贪吃蛇在撞上四个边后就会失败,但在很多贪吃蛇游戏中,其实是可以通过一边在反方向出现的,为了实现这一效果,又为了满足不同人的喜好,即在游戏中设置撞上边会失败和会到反方向两种模式,首先在html中添加对应的选择按钮。(这里可以自行设置一个游戏首页,在各个设置和游戏开始界面之间跳转,这里不写出跳转过程的js代码)

<div class="codeSetting" id="codeSetting">
    <button class="hide start">游戏开始</button>
    <button class="back">回到首页</button>
	<form>
		<input type="radio" name="mode" checked="checked"><label>围墙模式</label>//撞上边会失败
		<input type="radio" name="mode"><label>穿透模式</label>
	</form>
</div>
<div class="gamePlay hide" id="gamePlay">
	<canvas id="canvas">当前浏览器不支持canvas,请换取较高级浏览器尝试该游戏</canvas>
	<span id="myScore">0</span>
</div>

在这里,添加了在游戏界面中显示当前的分数,这将在下文实现另一个效果。

然后,我们将通过判断选中哪个单选按钮来判断当前的模式。重写小蛇每一步移动的方法。

//初始化游戏等级
var rank=0;
//小蛇每一步的移动
function snakePerMove(x,y)
{
	if((snake[0].y+y>=0&&snake[0].y+y<=19&&snake[0].x+x>=0&&snake[0].x+x<=19)||(document.getElementsByTagName('input')[1].checked))
	{
		numbers[snake[snake.length-1].y][snake[snake.length-1].x]=0;
		if(snake[0].y+y>=0&&snake[0].y+y<=19&&snake[0].x+x>=0&&snake[0].x+x<=19){
			if(numbers[snake[0].y+y][snake[0].x+x]==4)
			{
				markApple();
				rank++;
				score+=10;
				myScore.innerHTML=score;
				snake[snake.length]={x:'',y:''};
			}
		for(var i=snake.length-2;i>=0;i--)
		{
			snake[i+1].x=snake[i].x;
			snake[i+1].y=snake[i].y;
		}
		if((document.getElementsByTagName('input')[1].checked)&&!(snake[0].y+y>=0&&snake[0].y+y<=19&&snake[0].x+x>=0&&snake[0].x+x<=19)){//小蛇到达容器边缘且已选择穿透模式
			if(snake[0].y+y>19)
			{
				snake[0].y=0;
			}
			if(snake[0].y+y<0)
			{
				snake[0].y=19;
			}
			if(snake[0].x+x>19)
			{
				snake[0].x=0;
			}
			if(snake[0].x+x<0)
			{
				snake[0].x=19;
			}
		}
		else{
			snake[0].x=snake[0].x+x;
			snake[0].y=snake[0].y+y;
		}
	}
}

在这里通过document.getElementsByTagName('input')[1].checked来判断是否选择穿透模式,若是,则返回true,否则返回false。在原来只有在小蛇的下一步在容器内时才允许向下一步移动的条件外,添加一个或者已选择穿透模式。即当小蛇下一步会走出容器时,如果已选择穿透模式,仍可走下一步。然后,在接下来判断在穿透模式下小蛇的头部所在位置是否已超出容器,若是,则从容器另一边出现,这样就完成了从一边的边缘穿到另一边的功能。

在这里,我在每次吃到果实时使分数加10,因为我要在后面对蛇的长度进行改变,所以不再将蛇的长度作为速度改变的判断标准,改用rank作为速度改变的判断标准,并重写速度改变的方法。

//改变小蛇的速度
function changeV(){
	if(rank==6)
		snakeState.v=400;
	else if(rank==10)
		snakeState.v=350;
	else if(rank==15)
		snakeState.v=300;
}

接下来我们就着手编写障碍物的出现,与果实,蛇的出现一样,我们要先在数组中标出障碍物的位置,然后再在canvas画布中画出来。首先,为了能选择几张地图,我先将地图中的障碍物的位置保存在数组中。

var barriers=[
	[],//无障碍物
	[
		{x:8,y:10},
		{x:9,y:10},
		{x:10,y:10},
		{x:11,y:10},
		{x:12,y:10},
		{x:10,y:8},
		{x:10,y:9},
		{x:10,y:11},
		{x:10,y:12}
	],
	[
		{x:6,y:10},
		{x:7,y:10},
		{x:8,y:10},
		{x:9,y:10},
		{x:10,y:10},
		{x:11,y:10},
		{x:12,y:10},
		{x:13,y:10},
		{x:14,y:10},
		{x:6,y:6},
		{x:7,y:6},
		{x:8,y:6},
		{x:9,y:6},
		{x:10,y:6},
		{x:11,y:6},
		{x:12,y:6},
		{x:13,y:6},
		{x:14,y:6},
		{x:6,y:14},
		{x:7,y:14},
		{x:8,y:14},
		{x:9,y:14},
		{x:10,y:14},
		{x:11,y:14},
		{x:12,y:14},
		{x:13,y:14},
		{x:14,y:14}
	]
];

在数组中保存障碍物的x,y坐标,然后通过遍历数组,将对应的位置标记为1,在绘画方法中将对应数组为1时画出的内容写出。并在小蛇移动中判断失败里添加当撞上障碍物失败的条件,即判断数组下一个位置的数字是否为1。这里将失败后的处理封装在一个方法中。

//标记障碍物
function markBarrier(index){
	for(var i=0;i<barriers[index].length;i++)
	{
		numbers[barriers[index][i].x][barriers[index][i].y]=1;
	}
}
//画出小蛇,苹果,障碍物,道具
function drawPic(){
	for(var i=0;i<numbers.length;i++)
	{
		for(var j=0;j<numbers[i].length;j++)
		{
			context.beginPath();
			if(numbers[i][j]==1)//画出障碍物
			{
				drawRct((j+0.5)*perLength,(i+0.5)*perLength,perLength,5,'rgb(102,102,102)',context)
			}
			else if(numbers[i][j]==2)//画出蛇头
			{
				context.arc(10+(j+0.5)*perLength,10+(i+0.5)*perLength,perSnake,0,2*Math.PI);
				context.fillStyle='rgb(0,0,255)';
			}
			else if(numbers[i][j]==3)//画出蛇身
			{
				context.arc(10+(j+0.5)*perLength,10+(i+0.5)*perLength,perSnake,0,2*Math.PI);
				context.fillStyle='rgb(0,102,255)';
			}
			else if(numbers[i][j]==4)//画出苹果
			{
				context.arc(10+(j+0.5)*perLength,10+(i+0.5)*perLength,appleR,0,2*Math.PI);
				context.fillStyle='rgb(255,0,0)';
			}
			context.fill();
			context.closePath();
		}
	}
}
//失败处理
function falseHandle(){
	clearTimeout(setUp);
	clearTimeout(setRight);
	clearTimeout(setDown);
	clearTimeout(setLeft);
	if(judgeFalse==0){
		judgeFalse=1;
		alert('game over');
	}
	document.onkeydown=null;
}
//蛇的移动
function snakeMoveUp(){//上移
	if(snakeState.d=='up'){
		setUp=setTimeout(function(){snakeMoveUp()},snakeState.v);
	}
	if(snake[0].y-1>0){
		if((snake[0].y==0&&document.getElementsByTagName('input')[0].checked)||numbers[snake[0].y-1][snake[0].x]==3||numbers[snake[0].y-1][snake[0].x]==1)
		{
			falseHandle();
		}
	}
	if((snake[0].y-1<0)&&(document.getElementsByTagName('input')[0].checked))
	{
		falseHandle();
	}
	snakePerMove(0,-1);
	resetRct();
}
function snakeMoveDown(){//下移
	if(snakeState.d=='down'){
		setDown=setTimeout(function(){snakeMoveDown()},snakeState.v);
	}
	if(snake[0].y+1<19){
		if((snake[0].y==19&&document.getElementsByTagName('input')[0].checked)||numbers[snake[0].y+1][snake[0].x]==3||numbers[snake[0].y+1][snake[0].x]==1)
		{
			falseHandle();
		}
	}
	if((snake[0].y+1>19)&&(document.getElementsByTagName('input')[0].checked))
	{
		falseHandle();
	}
	snakePerMove(0,1);
	resetRct();
}
function snakeMoveRight(){右移
	if(snakeState.d=='right'){
		setRight=setTimeout(function(){snakeMoveRight()},snakeState.v);
	}
	if(snake[0].x+1<19){
		if((snake[0].x==19&&document.getElementsByTagName('input')[0].checked)||numbers[snake[0].y][snake[0].x+1]==3||numbers[snake[0].y][snake[0].x+1]==1)
		{
			falseHandle();
		}
	}
	if((snake[0].x+1>19)&&(document.getElementsByTagName('input')[0].checked))
	{
		falseHandle();
	}
	snakePerMove(1,0);
	resetRct();
}
function snakeMoveLeft(){左移
	if(snakeState.d=='left'){
		setLeft=setTimeout(function(){snakeMoveLeft()},snakeState.v);
	}
	if(snake[0].x-1>0){
		if((snake[0].x==0&&document.getElementsByTagName('input')[0].checked)||numbers[snake[0].y][snake[0].x-1]==3||numbers[snake[0].y][snake[0].x-1]==1)
		{
			falseHandle();
		}
	}
	if((snake[0].x-1<0)&&(document.getElementsByTagName('input')[0].checked))
	{
		falseHandle();
	}
	snakePerMove(-1,0);
	resetRct();
}

接下来,为了选择地图,我们要在html添加对应的内容,但是,如果每添加一个新的地图,就要重新添加一张图片的话,一旦改变了地图中某个障碍物的位置,就得重新绘制整张图片,为了避免这种麻烦,我们在html中添加一个div来用于向里面添加canvas标签,通过用js遍历障碍物的数组,向该div中添加各个地图对应的canvas标签,并添加事件选择对应的canvas标签时使用对应的地图。

<div class="mapChoose" id="mapChoose">
	<div class="canvasContainer">
			
	</div>
	<button class="start">游戏开始</button>
	<button class="back">回到首页</button>
	<br>
</div>
//初始化地图选择
var picChoose=0;
//设置小地图每单位宽度
var minLength=5;
//标记障碍物
markBarrier(picChoose);
//创建小地图
function setMaps(){
	var txt=document.createTextNode('当前浏览器不支持canvas,请换取较高级浏览器尝试该游戏');
	var canvasArray=[];
	var contextArray=[];
	for(var i=0;i<barriers.length;i++)
	{
		canvasArray[i]=document.createElement('canvas');
		canvasArray[i].appendChild(txt);
		getElementsByClassName(mapc,'canvasContainer')[0].appendChild(canvasArray[i]);
		canvasArray[i].width=100;
		canvasArray[i].height=100;
		contextArray[i]=canvasArray[i].getContext('2d');
		drawRct(0,0,100,2,'rgb(0,0,0,0.8)',contextArray[i]);
		for(var j=0;j<barriers[i].length;j++)
		{
			drawRct(barriers[i][j].y*minLength,barriers[i][j].x*minLength,minLength,2,'rgb(102,102,102)',contextArray[i]);
		}
	}
}
//创建小地图
setMaps();

var mapc=document.getElementById('mapChoose');
//改变地图
for(var i=0;i<mapc.getElementsByTagName('canvas').length;i++)
{
	(function(i){
		mapc.getElementsByTagName('canvas')[i].onclick=function(){
			picChoose=i;
			for(var j=0;j<mapc.getElementsByTagName('canvas').length;j++)
			{
				mapc.getElementsByTagName('canvas')[j].style.filter='brightness(100%)';
				mapc.getElementsByTagName('canvas')[j].style.border='none';
			}
			this.style.border='2px solid rgb(255,0,0)';
			this.style.filter='brightness(200%)';
		}
	})(i);
}

最后,我们来完成道具的设置。我在这里设置了几个比较简单实现的道具,加速,减速,长度变短,下一个吃到的果实分数加倍。首先要使道具随机出现,将四个道具对应的数组中的数字设置为5,6,7,8,标记道具时使用随机数Math.floor(Math.random()*4)+5来随机获取5-8的数字,并在数组中对应的位置上标记该数字。然后要判断什么时候使道具出现,在这里我通过判断rank是否为5的倍数来判断是否要添加道具,然后在小蛇移动中添加下一步为5-8时对应的处理方式。速度的增减通过改变snakeState.v的值来达到效果;长度变短先将变短后多余的长度所在的位置对应的数组标记为0,再改变蛇的数组长度;下一个果实的分数加倍,我通过用一个布尔类型变量来判断是否吃下该道具,若是,则下一次吃到的果实分数加倍,且将该布尔型变量重置。

//初始化加分道具设置
var scoreMul=false;
//标记道具字体
function markProp(propRandom){
	if(propRandom=='')
		propRandom=Math.floor(Math.random()*4)+5;
	var propX=Math.floor(Math.random()*20);
	var propY=Math.floor(Math.random()*20);
	if(!(numbers[propY][propX]>0))
	{
		numbers[propY][propX]=propRandom;
		setTimeout(function(){
			if(numbers[propY][propX]==propRandom)
				numbers[propY][propX]=0;
		},10000);//让道具在10秒后消失
	}
	else
		markProp(propRandom);
}
//描绘道具
function drawProp(Alpha,color,i,j){//Alpha为道具上的字体
	context.arc(10+(j+0.5)*perLength,10+(i+0.5)*perLength,appleR,0,2*Math.PI);
	context.fillStyle=color;
	context.fill();
	context.closePath();
	context.beginPath();
	context.fillStyle='rgb(0,0,0)';//设置字体颜色
	context.textAlign='center';
	context.textBaseline='middle';
	context.font='18px Adobe Ming Std';
	context.fillText(Alpha,10+(j+0.5)*perLength,10+(i+0.5)*perLength);
}
//画出小蛇,苹果,障碍物,道具
function drawPic(){
	for(var i=0;i<numbers.length;i++)
	{
		for(var j=0;j<numbers[i].length;j++)
		{
			context.beginPath();
			if(numbers[i][j]==1)//画出障碍物
			{
				drawRct((j+0.5)*perLength,(i+0.5)*perLength,perLength,5,'rgb(102,102,102)',context)
			}
			else if(numbers[i][j]==2)//画出蛇头
			{
				context.arc(10+(j+0.5)*perLength,10+(i+0.5)*perLength,perSnake,0,2*Math.PI);
				context.fillStyle='rgb(0,0,255)';
			}
			else if(numbers[i][j]==3)//画出蛇身
			{
				context.arc(10+(j+0.5)*perLength,10+(i+0.5)*perLength,perSnake,0,2*Math.PI);
				context.fillStyle='rgb(0,102,255)';
			}
			else if(numbers[i][j]==4)//画出苹果
			{
				context.arc(10+(j+0.5)*perLength,10+(i+0.5)*perLength,appleR,0,2*Math.PI);
				context.fillStyle='rgb(255,0,0)';
			}
			else if(numbers[i][j]==5)//减速
			{
				drawProp('S','rgb(255,204,51)',i,j);
			}
			else if(numbers[i][j]==6)//加速
			{
				drawProp('F','rgb(255,255,255)',i,j);
			}			
			else if(numbers[i][j]==7)//长度缩短
			{
				drawProp('L','rgb(0,255,0)',i,j);
			}
			else if(numbers[i][j]==8)//下一次吃到的果实分数翻5倍
			{
				drawProp('D','rgb(153,0,204)',i,j);
			}
			context.fill();
			context.closePath();
		}
	}
}
//小蛇每一步的移动
function snakePerMove(x,y)
{
	if((snake[0].y+y>=0&&snake[0].y+y<=19&&snake[0].x+x>=0&&snake[0].x+x<=19)||(document.getElementsByTagName('input')[1].checked))
	{
		numbers[snake[snake.length-1].y][snake[snake.length-1].x]=0;
		if(snake[0].y+y>=0&&snake[0].y+y<=19&&snake[0].x+x>=0&&snake[0].x+x<=19){
			if(numbers[snake[0].y+y][snake[0].x+x]==4)
			{
				markApple();
				if(rank%5==0&&rank!=0)//当等级达到7的倍数时生成道具
				{
					markProp('');
				}
				if(!scoreMul)//判断当前是否有道具加成
					score+=10;
				else
				{
					score+=50;
					scoreMul=false;
				}
				rank++;
				snake[snake.length]={x:'',y:''};
				myScore.innerHTML=score;
			}
			else if(numbers[snake[0].y+y][snake[0].x+x]==5)
			{
				snakeState.v=snakeState.v*1.25;//将蛇的速度变为原来的0.8倍
			}
			else if(numbers[snake[0].y+y][snake[0].x+x]==6)
			{
				snakeState.v=snakeState.v*0.8;//将蛇的速度变为原来的1.25倍
			}
			else if(numbers[snake[0].y+y][snake[0].x+x]==7)
			{
				for(var i=Math.ceil(snake.length/2);i<snake.length;i++)
				{
					numbers[snake[i].y][snake[i].x]=0;//将蛇长度一半后所在的位置全部重置为0
				}
				numbers[snake[Math.ceil(snake.length/2)-1].y][snake[Math.ceil(snake.length/2)-1].x]=0;
				snake.length=Math.ceil(snake.length/2);//将蛇的长度变为原来的一半
			}
			else if(numbers[snake[0].y+y][snake[0].x+x]==8)
			{
				scoreMul=true;//将判断分数是否加倍变为true
			}
		}
		for(var i=snake.length-2;i>=0;i--)
		{
			snake[i+1].x=snake[i].x;
			snake[i+1].y=snake[i].y;
		}
		if((document.getElementsByTagName('input')[1].checked)&&!(snake[0].y+y>=0&&snake[0].y+y<=19&&snake[0].x+x>=0&&snake[0].x+x<=19)){
			if(snake[0].y+y>19)
			{
				snake[0].y=0;
			}
			if(snake[0].y+y<0)
			{
				snake[0].y=19;
			}
			if(snake[0].x+x>19)
			{
				snake[0].x=0;
			}
			if(snake[0].x+x<0)
			{
				snake[0].x=19;
			}
		}
		else{
			snake[0].x=snake[0].x+x;
			snake[0].y=snake[0].y+y;
		}
	}
}

到这里三个功能就已经完成了,希望这篇博文能对大家有所帮助,如果文章中出现什么错误,万望各位大神告知本小白,十分感谢。

猜你喜欢

转载自blog.csdn.net/zemprogram/article/details/81805803