<canvas>+js实现简单的贪吃蛇小游戏

贪吃蛇作为一个经典的游戏,直到现在还有不少人在玩,但是比起玩别人的游戏,还是玩自己的游戏比较有成就感,本篇博文就将告诉大家如何用<canvas>+js实现简单的贪吃蛇小游戏,如果对canvas的API不了解的同学请先学习canvas的基础知识再看本文。

本人还只是入坑前端不久,如有哪些做的不好,还请各位大神指出。

首先,在写游戏开始,我们要先写一个简单的html文件,这里不考虑美观的设置(主要是本小白审美实在太差。。),所以只用一个canvas标签,不考虑用css进行布局。为了能在js中调用canvas标签,在这里我为canvas添加了一个id="canvas"。

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>贪吃蛇小游戏</title>
</head>
<body>
	<canvas id="canvas"></canvas>
	<script type="text/javascript" src="js/Snake.js"></script>
</body>
</html>

写好一个简单的html文件后,我们开始编写核心的js文件,首先,我们要给各变量赋初值。

var canvas=document.getElementById('canvas');
var context=canvas.getContext('2d');
canvas.width=600;
canvas.height=600;
//初始化按键
var left=37;
var up=38;
var right=39;
var down=40;
var numbers=new Array(10);
for(var i=0;i<numbers.length;i++)//用于记录canvas中代表东西的代数
{
	numbers[i]=new Array(10);
	for(var j=0;j<numbers[i].length;j++)
	{
		numbers[i][j]=0;
	}
}
//记录蛇长度的数组
var snake=[
	{x:6,y:5},
	{x:5,y:5},
	{x:4,y:5}
];
//记录蛇的方向,速度
var snakeState={d:null,v:500};
//定义每个方格宽度,蛇每个小球半径,苹果半径
var perLength=50;
var perSnake=20;
var appleR=18;

后面控制小蛇方向移动时要使用document.onkeydown,所以这里的方向键使用了对应的ASCII码,也方便了以后按键的更改。小蛇的长度用数组保存,snake[0]作为小蛇的头,x,y对应蛇头蛇身所在的位置。这个游戏主要要通过判断数组内的数字在对应的位置上画上小蛇和果实,当为0时不填充内容,所以这里先把数组全部初始化为0。

初始化完各变量,我们需要把容纳小蛇的方块做出来,这里写了一个简单的方法绘制方格,传入5个参数分别代表方块左上角的x,y坐标,方块的边长,方块圆角的弧的半径,方块内填充的颜色。

//画出游戏所在的界面方格
drawRct(0,0,520,10,'rgb(204,204,204)');//游戏外方格
drawRct(10,10,500,0,'rgba(0,0,0,0.8)');//游戏内方格
//画出圆角正方形
function drawRct(x,y,l,r,color)
{
		context.beginPath();
		context.moveTo(x+r,y);
		context.lineTo(x+l-r,y);
		context.arcTo(x+l,y,x+l,y+r,r);
		context.lineTo(x+l,y+l-r);
		context.arcTo(x+l,y+l,x+l-r,y+l,r);
		context.lineTo(x+r,y+l);
		context.arcTo(x,y+l,x,y+l-r,r);
		context.lineTo(x,y+r);
		context.arcTo(x,y,x+r,y,r);
		context.fillStyle=color;
		context.closePath();
		context.fill();
		context.stroke();
}

方格绘制完成,那么接下来就轮到我们的主角贪吃蛇出场了。

//标记小蛇
function markSnake(){
	for(var i=0;i<snake.length;i++)
	{
		if(i==0)
			numbers[snake[i].y][snake[i].x]=2;//标记已被小蛇蛇头占用的位置
		else
			numbers[snake[i].y][snake[i].x]=3;//标记已被小蛇蛇身占用的位置
	}
}
//标记随机生成果实
function markApple(){
	var appleX=Math.floor(Math.random()*10);
	var appleY=Math.floor(Math.random()*10);
	if(!(numbers[appleY][appleX]>0))
	{
		numbers[appleY][appleX]=4;
	}
	else
		markApple();
}
//画出小蛇,苹果
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]==2)
			{
				context.arc(10+(j+0.5)*perLength,10+(i+0.5)*perLength,perSnake,0,2*Math.PI);
				context.fillStyle='rgb(0,0,255)';
			}
			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)';
			}
			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();
		}
	}
}
markSnake();
markApple();
drawPic();

为了在画布上显示小蛇,编写一个方法markSnake()用于标记小蛇所在的坐标,遍历储存小蛇位置的数组,当i为0时即为小蛇的头部,在记录位置的数组的对应位置上标记为2,在蛇身所在的位置标记为3。编写果实时随机生成果实的x,y坐标,当所在的坐标上对应的位置数组记录的数不大于0时,将该位置标记为4,若该位置上对应记录的数大于0时,即该地方有小蛇存在,重新调用该方法,递归直到找到没有被小蛇占用的位置,将该位置标记为4。

标记之后,编写drawPic()方法在对应的位置画上对应的内容,当为0时,不进行绘制;为2时,画上蛇的头部;为3时,画上蛇身(这里使用不同颜色来区别蛇的头部和身体);为4时,画上果实。(因为在我的电脑上写了1对应的是障碍物,这里只是写了最简单的贪吃蛇地图即无障碍物的地图,所以没有用到1,大家可以自行用这种方式绘制出不同的地图)

写到这里,游戏的主要内容都出现了,接下来就是让小蛇动起来了。因为整个游戏用到的是让数组储存小蛇和果实所在的位置,再通过读取位置绘制小蛇和果实,所以要使小蛇移动,我们要做的就是改变位置数组里储存的数字,还要在小蛇吃到果实的时候使小蛇的长度变长,这部分内容我觉得算是这个游戏的核心了,大家可以先想想这部分怎么做再看看我的代码。

//小蛇每一步的移动
function snakePerMove(x,y)//x,y分别表示小蛇下一步在x,y坐标上的位移单位
{
	if(snake[0].y+y>=0&&snake[0].y+y<=9&&snake[0].x+x>=0&&snake[0].x+x<=9)//判断小蛇是否撞到墙壁
	{
		if(numbers[snake[0].y+y][snake[0].x+x]==4)//判断小蛇下一步是否会吃到果实
		{
			markApple();//重新生成果实
			snake[snake.length]={x:'',y:''};//增加小蛇的长度
		}
		else
			numbers[snake[snake.length-1].y][snake[snake.length-1].x]=0;
		for(var i=snake.length-2;i>=0;i--)
		{
			snake[i+1].x=snake[i].x;
			snake[i+1].y=snake[i].y;
		}
		snake[0].x=snake[0].x+x;
		snake[0].y=snake[0].y+y;
	}
}

首先我们判断小蛇会否撞到墙壁,在不会的情况下,如果小蛇的下一步是果实,我们则重新生成果实,并增长小蛇的长度,否则我们将小蛇数组蛇身最后的位置标记为0,即将这个位置变为空。然后从小蛇的数组后面开始遍历,让数组中后一个记录的坐标等于前一个记录的坐标,然后再将snake[0]的坐标移到下一个位置,这样就能实现整条小蛇的移动。举个例子,假如小蛇现在的数组为snake[{x:6,y:5},{x:5,y:5},{x:4,y:5}],(蛇头坐标为(6,5))在下一步没吃到果实的情况下,按下右键,snake[2]=snake[1],snake[1]=snake[0],snake[0]在x上加一,数组就变为snake[{x:7,y:5},{x:6,y:5},{x:5,y:5}],实现了小蛇的右移。

最后就是添加键盘事件控制小蛇移动的方向了。

//重置游戏内方块
function resetDraw(){
	context.clearRect(0,0,canvas.width,canvas.height);
	drawRct(0,0,520,10,'rgb(204,204,204)');
	drawRct(10,10,500,0,'rgba(0,0,0,0.8)');
	markSnake();
	drawPic();
}
var judgeFalse=0;//判断是否已经提示失败
//小蛇的移动
function snakeMove(){
	document.onkeydown=function(){//键盘事件
		var e=event||window.event;
		if(e.keyCode==left)
		{
			if(snakeState.d!='right'&&snakeState.d!='left')
			{
				snakeState.d='left';
				setLeft=setTimeout(function(){snakeMoveLeft()},snakeState.v);
			}
		}
		if(e.keyCode==right)
		{
			if(snakeState.d!='right'&&snakeState.d!='left')
			{
				snakeState.d='right';
				setRight=setTimeout(function(){snakeMoveRight()},snakeState.v);
			}
		}
		if(e.keyCode==up)
		{
			if(snakeState.d!='down'&&snakeState.d!='up')
			{
				snakeState.d='up';
				setUp=setTimeout(function(){snakeMoveUp()},snakeState.v);
			}
		}
		if(e.keyCode==down)
		{
			if(snakeState.d!='down'&&snakeState.d!='up')
			{
				snakeState.d='down';
				setDown=setTimeout(function(){snakeMoveDown()},snakeState.v);
			}
		}
	}
}
//上移
function snakeMoveUp(){
	if(snakeState.d=='up'){
		setUp=setTimeout(function(){snakeMoveUp()},snakeState.v);
	}
	if(snake[0].y==0||numbers[snake[0].y-1][snake[0].x]==3)
	{
		clearTimeout(setUp);
		clearTimeout(setRight);
		clearTimeout(setDown);
		clearTimeout(setLeft);
		if(judgeFalse==0){
			judgeFalse=1;
			alert('game over');
		}
		document.onkeydown=null;//无效化键盘事件
	}
	snakePerMove(0,-1);
	resetRct();
}
//下移
function snakeMoveDown(){
	if(snakeState.d=='down'){
		setDown=setTimeout(function(){snakeMoveDown()},snakeState.v);
	}
	if(snake[0].y==9||numbers[snake[0].y+1][snake[0].x]==3)
	{
		clearTimeout(setUp);
		clearTimeout(setRight);
		clearTimeout(setDown);
		clearTimeout(setLeft);
		if(judgeFalse==0){
			judgeFalse=1;
			alert('game over');
		}
		document.onkeydown=null;//无效化键盘事件
	}
	snakePerMove(0,1);
	resetRct();
}
//右移
function snakeMoveRight(){
	if(snakeState.d=='right'){
		setRight=setTimeout(function(){snakeMoveRight()},snakeState.v);
	}
	if(snake[0].x==9||numbers[snake[0].y][snake[0].x+1]==3)
	{
		clearTimeout(setUp);
		clearTimeout(setRight);
		clearTimeout(setDown);
		clearTimeout(setLeft);
		if(judgeFalse==0){
			judgeFalse=1;
			alert('game over');
		}
		document.onkeydown=null;//无效化键盘事件
	}
	snakePerMove(1,0);
	resetRct();
}
//左移
function snakeMoveLeft(){
	if(snakeState.d=='left'){
		setLeft=setTimeout(function(){snakeMoveLeft()},snakeState.v);
	}
	if(snake[0].x==0||numbers[snake[0].y][snake[0].x-1]==3)
	{
		clearTimeout(setUp);
		clearTimeout(setRight);
		clearTimeout(setDown);
		clearTimeout(setLeft);
		if(judgeFalse==0){
			judgeFalse=1;
			alert('game over');
		}
		document.onkeydown=null;//无效化键盘事件
	}
	snakePerMove(-1,0);
	resetRct();
}
snakeMove();

因为在每次移动中都需要重新绘制方格和标记小蛇,果实,为了方便,将这几个方法放入同一个方法resetDraw()中。

在按下按键后,首先判断按下的方向是否在小蛇当前移动的方向(snakeState.d)或其反方向上,假如是的话,不改变小蛇的移动状态,否则调用对应方向上的方法,改变小蛇的方向。如果小蛇的下一步会撞到墙壁或者自己的身体,则游戏失败,弹出game over的警告窗口,并无效化键盘事件。

这里由于当在四个角上同时按下两个方向键会弹出两个警告窗口,所以我定义了一个judgeFalse判断是否已经弹出警告窗口,使失败时弹出窗口只有一个。

到这里内容基本上就结束了,当然也可以通过下面这个方法,使小蛇在长度达到一定值时速度变快,具体什么时候加快,加快多少,就由大家自行编写了。

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

希望本篇博文能对大家有所帮助,也希望大神能指出我的不足,大家一起进步。

猜你喜欢

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