用canvas+javascript制作的贪食蛇小游戏

这是一个用html5的canvas(画布)结合javascript制作的贪食蛇小游戏,完成了基本的游戏功能,但是有一些地方还未完善。使用的javascript资源和代码可以点以下链接进行下载:用canvas制作的贪食蛇小游戏代码资源

总体思路

贪食蛇虽然是个简单小游戏,但是要再画布上实现还是需要考虑很多东西的,我把整个游戏分成了几个部分去分别解决,最终完成了这个游戏。具体功能模块如下所示。

需要的功能

(1)关于蛇:

  1. 贪食蛇的绘制:我将贪食蛇的颜色绘制为蓝色(#0000ff),蛇头处又画了一个蛇眼为黑色(#000000),然后创建了一个对象snake,在snake中放入了和贪食蛇有关的数据,比如蛇头的坐标,蛇尾的坐标,蛇身转折点(蛇在转弯时会产生转折点),蛇头/蛇尾当前移动方向,蛇头上一帧方向。
  2. 贪食蛇的移动:用画布做贪食蛇小游戏自然不是真的让贪食蛇进行前进,而是在每一帧进行重绘。根据对象的snake.fangxiang1(蛇头移动方向)属性,每一帧对蛇头的坐标进行修改,同理蛇尾的坐标修改根据snake.fangxiang2属性。
  3. 贪食蛇吃到自己时:贪食蛇每次移动完毕后,都会探索蛇头前方和左右位置的像素颜色,若预测到某个方向的像素为蓝色时,即代表着下一帧若往这个方向走将会吃到自己,即游戏失败。所以我创建了一个aa数组,用来存储会吃到蛇身的方向。当进入下一帧后,会根据当前方向和aa数组进行对比,若aa数组中存有当前方向,则判定为吃到自己,游戏结束,弹出确认框,然后刷新页面。
  4. 贪食蛇吃到食物:这个和吃到自己其实是差不多的,只是换了一种颜色而已(食物为红色),只不过吃到自己是游戏失败,而吃到食物是清除食物,并在一个新的的位置产生食物,然后贪食蛇变长。
  5. 贪食蛇如何变长:这里我设置了一个变量gogo,如果gogo==false,则代表着没吃到食物,蛇照常前进,当gogo=false时,蛇头前进,蛇尾不前进,那么蛇的总体长度就被拉长了。
  6. 转折点的作用:当用户按下键盘上的方向键时,会把当前蛇头坐标存入转折点数组中,蛇头继续根据snake.fangxiang1前进,而蛇尾则需要根据蛇尾和第一个转折点之间的差值来计算,应该往哪个方向移动,当蛇尾到达第一个转折点的位置时,删除该转折点,此时第二个转折点就代表了“第一个”转折点了。
    (2)关于画布:
    画布是整体的游戏界面,主要由绿色的背景(草地),红色的圆(食物),蓝色的折线(蛇)组成,大小为300x300,画布中央还有一个按钮为开始游戏按钮,点击按钮后按钮消失,开始游戏。
    (3)关于食物:
    食物在被蛇头碰到后会消失,具体做法就是在当前食物的坐标处画一个绿色的圆,半径大小和食物一样,这样就能把食物覆盖了,然后产生两个随机整数(5的倍数),作为食物的新坐标,在下一帧食物就会被重新绘制了。

全部代码

思路已经讲完了,下面就是完整版的代码了,注释已经很完整了,我就不把代码拆开一部分一部分讲了。当然直接复制粘帖肯定是运行不了的,因为缺少一个modernizr.js文件,要把这个文件和代码文件放在同一个文件夹下才能运行。文章开头的链接可以下载代码文件和modernizr.js文件。

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>画布练习</title>
<script src="modernizr.js"></script>
<script type="text/javascript">
	window.addEventListener("load", eventWindowLoaded, false);	
	function eventWindowLoaded() {
    
    
		canvasApp();
	}
	function canvasSupport () {
    
    
		return Modernizr.canvas;
	}
	function canvasApp(){
    
    
		if (!canvasSupport()) {
    
    
			return;
		}	
		var theCanvas=document.getElementById("canvas1");	
		var cxt=theCanvas.getContext("2d");
		var food={
    
    
			foodx:100,		//食物的x坐标
			foody:100,		//食物的y坐标
		};
		var snake={
    
    
			headx:150,		//蛇头x坐标
			heady:50,		//蛇头y坐标
			woundx:[],		//转折点x坐标
			woundy:[],		//转折点y坐标
			endx:100,		//蛇尾x坐标
			endy:50,		//蛇尾y坐标
			fangxiang1:"right",	//蛇头方向
			fangxiang2:"right",	//蛇尾方向
		};
		var gameover=false;			//游戏结束的标志
		var aa=[];		//记录下一帧会吃到自己的方向
		var bb;		//记录下一帧会吃到食物的方向
		var gogo=false;		//若gogo=false,则代表没吃到食物,蛇尾需要前进。若gogo=true,则代表吃到食物,蛇尾不需要前进
		drawScreen();
		function drawScreen(){
    
    
			//背景绘制
			cxt.fillStyle="#00ff00";		//设置背景颜色为绿色
			cxt.fillRect(0,0,300,300);		//绘制背景,一个左上角在(0,0),右下角在(600,600)的矩形
			
			//绘制食物
			cxt.beginPath();			//开始绘制
			cxt.fillStyle="#ff0000";		//设置食物颜色为红色
			cxt.arc(food.foodx,food.foody,9,(Math.PI/180)*0,(Math.PI/180)*360,false);		//在(100,100)的位置画一个半径为5的圆
			cxt.fill();			//立即以填充颜色的方式绘制
			cxt.closePath();	//结束绘制
			
			//绘制贪食蛇
			cxt.beginPath();	//开始绘制
			cxt.strokeStyle="#0000ff";		//设置贪食蛇颜色为蓝色
			cxt.lineCap="round";		//设置线段两端为圆角
			cxt.lineWidth=10;		//设置线段宽度为10
			cxt.moveTo(snake.endx,snake.endy);		//设置线段的开始点为蛇尾(snake.endx,snake.endy)
			for(var i=0;i<snake.woundx.length;i++){
    
    
				cxt.lineTo(snake.woundx[i],snake.woundy[i]);		//根据蛇的转折点数量,来连接蛇身和蛇尾
			}
			cxt.lineTo(snake.headx,snake.heady);			//再将蛇头连接
			cxt.stroke();			//立即以描边的方式绘制
			cxt.closePath();		//结束绘制
			
			//绘制贪食蛇的眼睛
			cxt.beginPath();		//开始绘制
			cxt.fillStyle="#000000";		//设置蛇眼为黑色
			cxt.arc(snake.headx,snake.heady,3,(Math.PI/180)*0,(Math.PI/180)*360,false);		//在(snake.headx,snake.heady)的位置画一个半径为3的圆
			cxt.fill();			//以填充颜色的方式立即绘制
			cxt.closePath();		//结束绘制	
			
			//判断游戏是否结束
			if(snake.headx>297||snake.headx<0||snake.heady>297||snake.heady<0){
    
    
				gameover=true;
			}
			for(var i=0;i<aa.length;i++){
    
    
				if(aa[i]==snake.fangxiang1){
    
    
					gameover=true;
					break;
				}
			}
			if(gameover==true){
    
    
				var t=confirm("你输了");		//弹出确认框,并返回一个值t,t代表用户点了确认键/取消键
				if(t==true){
    
    
					clearInterval(tt);	//清除动画
					location = location;		//刷新页面
				}else{
    
    
					clearInterval(tt);
					location = location;
				}
			}
			aa.length=0;		//清空数组
			//判断是否吃到了食物
			if(bb==snake.fangxiang1){
    
    
				cxt.beginPath();
				fillStyle="#00ff00";		//设置颜色为背景色
				cxt.arc(food.foodx,food.foody,9,(Math.PI/180)*0,(Math.PI/180)*360,false);		//清除该食物
				cxt.fill();
				cxt.closePath();
				food.foodx=Math.floor(Math.random()*56)*5+10;		//重新设置食物x坐标
				food.foody=Math.floor(Math.random()*56)*5+10;		//重新设置食物y坐标
				gogo=true;			//表示吃到了食物
			}
			bb="";		//清除bb
			
			//根据snake.fangxiang1来判断蛇头的前进方向,同时根据前进后的蛇头位置,来预判下一步是否会吃到自己或者吃到食物
			switch(snake.fangxiang1){
    
    
				case "right":
					snake.headx+=5;
					var cc=cxt.getImageData(snake.headx+5,snake.heady,1,1);
					setAB(cc,"right");
					cc=cxt.getImageData(snake.headx,snake.heady-5,1,1);
					setAB(cc,"top");
					cc=cxt.getImageData(snake.headx,snake.heady+5,1,1);
					setAB(cc,"bottom");
					break;
				case "left":
					snake.headx-=5;
					var cc=cxt.getImageData(snake.headx-5,snake.heady,1,1);
					setAB(cc,"left");
					cc=cxt.getImageData(snake.headx,snake.heady-5,1,1);
					setAB(cc,"top");
					cc=cxt.getImageData(snake.headx,snake.heady+5,1,1);
					setAB(cc,"bottom");
					break;
				case "top":
					snake.heady-=5;
					var cc=cxt.getImageData(snake.headx+5,snake.heady,1,1);
					setAB(cc,"right");
					cc=cxt.getImageData(snake.headx-5,snake.heady,1,1);
					setAB(cc,"left");
					cc=cxt.getImageData(snake.headx,snake.heady-5,1,1);
					setAB(cc,"top");
					break;
				case "bottom":
					snake.heady+=5;
					var cc=cxt.getImageData(snake.headx+5,snake.heady,1,1);
					setAB(cc,"right");
					cc=cxt.getImageData(snake.headx-5,snake.heady,1,1);
					setAB(cc,"left");
					cc=cxt.getImageData(snake.headx,snake.heady+5,1,1);
					setAB(cc,"bottom");
					break;
			}
			function setAB(e,f){
    
    
				if(e.data[2]==255){
    
    
					aa.push(f);
				}
				if(e.data[0]==255){
    
    
					bb=f;
				}
			}
			
			//根据gogo的值判断蛇尾是否需要前进,用来实现蛇身变长
			if(gogo==false){
    
    
				//以下是蛇尾前进的方法
				if(snake.woundx.length!=0){
    
    
					//若转折点数组不为空,则根据蛇尾坐标和转折点数组中的第一个坐标的差值,判断蛇尾的前进方向
					if(snake.woundx[0]-snake.endx>0){
    
    
						snake.fangxiang2="right";
					}
					if(snake.woundx[0]-snake.endx<0){
    
    
						snake.fangxiang2="left";
					}
					if(snake.woundy[0]-snake.endy>0){
    
    
						snake.fangxiang2="bottom";
					}
					if(snake.woundy[0]-snake.endy<0){
    
    
						snake.fangxiang2="top";
					}
				}else{
    
    
					//若转折点数组为空,则蛇尾与蛇头进行比较
					if(snake.headx-snake.endx>0){
    
    
						snake.fangxiang2="right";
					}
					if(snake.headx-snake.endx<0){
    
    
						snake.fangxiang2="left";
					}
					if(snake.heady-snake.endy>0){
    
    
						snake.fangxiang2="bottom";
					}
					if(snake.heady-snake.endy<0){
    
    
						snake.fangxiang2="top";
					}
				}
			
				//根据蛇尾的前进方向(snake.fanxgiang2)来决定蛇尾的坐标值变化
				switch(snake.fangxiang2){
    
    
					case "right":
						snake.endx+=5;
						break;
					case "left":
						snake.endx-=5;
						break;
					case "top":
						snake.endy-=5;
						break;
					case "bottom":
						snake.endy+=5;
						break;
				}
			}else{
    
    
				gogo=false;
			}
			//当蛇尾到达第一个转折点时,移除该转折点
			if(snake.endx==snake.woundx[0]&&snake.endy==snake.woundy[0]){
    
    
				snake.woundx.shift();		//shift()函数时移除数组中第一个值,并将数组中剩余的值重新设置下标
				snake.woundy.shift();
			}
		}
		
		//键盘监听事件
		function eventKeyDown(event){
    
    
			switch(event.keyCode){
    
    
				case 37:		//37代表了键盘的向左键,同理38为上,39为右,40为下。
					if(snake.fangxiang1!="right"){
    
    
						snake.fangxiang1="left";
						snake.woundx.push(snake.headx);		//将蛇头x坐标存入转折点x坐标数组里
						snake.woundy.push(snake.heady);		//将蛇头y坐标存入转折点y坐标数组里
						break;
					}else{
    
    
						break;
					}
				case 38:
					if(snake.fangxiang1!="bottom"){
    
    
						snake.fangxiang1="top";
						snake.woundx.push(snake.headx);
						snake.woundy.push(snake.heady);
						break;
					}else{
    
    
						break;
					}
				case 39:
					if(snake.fangxiang1!="left"){
    
    
						snake.fangxiang1="right";
						snake.woundx.push(snake.headx);
						snake.woundy.push(snake.heady);
						break;
					}else{
    
    
						break;
					}
				case 40:
					if(snake.fangxiang1!="top"){
    
    
						snake.fangxiang1="bottom";
						snake.woundx.push(snake.headx);
						snake.woundy.push(snake.heady);
						break;
					}else{
    
    
						break;
					}
			}
		}
		
		//开始游戏按钮点击事件
		var button=document.getElementById("start");		//将参数button与id="start"的按钮绑定
		button.addEventListener("click",startgame);			//按钮点击事件
		
		//开始游戏
		var tt;				//用于保存播放动画函数,在游戏结束时需要删除动画(即clearInterval(tt)),才能刷新页面
		function startgame(){
    
    
			button.style.visibility="hidden";		//将开始游戏按钮隐藏
			window.addEventListener("keyup",eventKeyDown,true);	//添加键盘监听事件
			tt=setInterval(drawScreen,70);			//每40毫秒运行一次drawScreen()函数
		}
	}
</script>
</head>
<body>
	<div>
		<canvas id="canvas1" width="300" height="300"></canvas>
		<button id="start" style="position:absolute;left:80px;top:150px;width:150px;height:40px;">开始游戏</button>
		<br>
		<span>使用键盘的上下左右键来控制蛇的移动方向</span>
	</div>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/zhulong16/article/details/102727334