游戏开发经典样例 | web html5 吃豆人游戏

Posted 李泰山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏开发经典样例 | web html5 吃豆人游戏相关的知识,希望对你有一定的参考价值。

很早就知道canvas,当时一直没应用到,最近闲来无事就顺便写写看。吃豆游戏可以说是我们80,90后共同的回忆录,小时候常常在学习机上玩,所以也就有了强烈的欲望去写。为了写这个游戏,看了很多吃豆人游戏的相关信息,包括游戏规则,历史,其它相关游戏设计。又去了各个小游戏平台玩了下,找些灵感!!!就连最简单的配色,也是反复斟酌几个小时确定的方案……哈哈,不容易啊代码分两部分,游戏引擎和游戏主程序,引擎用来类的定义,管理动画场景,事件定义等等……游戏主程序主要组织业务逻辑。算面向对象编程吧,纯手工打造是男人就吃光所有豆子!!! 

项目结构

 因需要四个文件即可实现,index.html 游戏主页,index.js 主页控制,game.js 游戏控制,favicon.png 图标。代码简单,易懂。后续小伙伴们还可以自己加入音乐!可以部署到服务器上,也可用浏览器直接打开index.html玩耍!!!

index.html代码

<html>
	<head>
		<meta charset="utf8">
		<title>Pac-Man . 吃豆游戏</title>
		<link rel="shortcut icon" href="favicon.png">
		<style>
			bodybackground-color: #292929
			*padding:0;margin:0;
			.wrapper
				width: 960px;
				margin:0 auto;
				line-height:36px;
				text-align:center;
				color:#999;
			
			canvasdisplay:block;background: #000;
			.mod-botton
				height: 32px;
				padding: 15px 0;
				text-align: center;
			
		</style>
	</head>
	<body>
		<div class="wrapper">
			<canvas id="canvas" width="960" height="640">不支持画布</canvas>
			<p>按[空格]暂停或继续</p>		
		</div>
		<script src="game.js"></script>
		<script src="index.js"></script>
		
	</body>
</html>

index.js 代码

//主程序,业务逻辑
(function()
	var _DATA = [		//地图数据
		[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
		[1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1],
		[1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1],
		[1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1],
		[1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1],
		[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
		[1,0,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,0,1],
		[1,0,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,0,1],
		[1,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,1],
		[1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1],
		[1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1],
		[1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1],
		[1,1,1,1,1,1,0,1,1,0,1,1,1,2,2,1,1,1,0,1,1,0,1,1,1,1,1,1],
		[1,1,1,1,1,1,0,1,1,0,1,2,2,2,2,2,2,1,0,1,1,0,1,1,1,1,1,1],
		[0,0,0,0,0,0,0,0,0,0,1,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0],
		[1,1,1,1,1,1,0,1,1,0,1,2,2,2,2,2,2,1,0,1,1,0,1,1,1,1,1,1],
		[1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1],
		[1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1],
		[1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1],
		[1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1],
		[1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1],
		[1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1],
		[1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1],
		[1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1],
		[1,1,1,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,1],
		[1,1,1,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,1],
		[1,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,1],
		[1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1],
		[1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1],
		[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
		[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
	],
	_GOODS = 			//能量豆
		'1,3':1,
		'26,3':1,
		'1,23':1,
		'26,23':1
	,
	_COS = [1,0,-1,0],
	_SIN = [0,1,0,-1],
	_COLOR = ['#F00','#F93','#0CF','#F9C'],//红,橙,
	_LIFE = 3,
	_SCORE = 0;		//得分

	var game = new Game('canvas');
	//启动页
	(function()
		var stage = game.createStage();
		//logo
		stage.createItem(
			x:game.width/2,
			y:game.height*.45,
			width:100,
			height:100,
			frames:3,
			draw:function(context)
				var t = Math.abs(5-this.times%10);
				context.fillStyle = '#FFE600';
				context.beginPath();
				context.arc(this.x,this.y,this.width/2,t*.04*Math.PI,(2-t*.04)*Math.PI,false);
				context.lineTo(this.x,this.y);
				context.closePath();
				context.fill();
				context.fillStyle = '#000';
				context.beginPath();
				context.arc(this.x+5,this.y-27,7,0,2*Math.PI,false);
				context.closePath();
				context.fill();
			
		);
		//游戏名
		stage.createItem(
			x:game.width/2,
			y:game.height*.6,
			draw:function(context)
				context.font = 'bold 42px Helvetica';
				context.textAlign = 'center';
				context.textBaseline = 'middle';
				context.fillStyle = '#FFF';
				context.fillText('Pac-Man',this.x,this.y);
			
		);
		//版权信息
		stage.createItem(
			x:game.width-12,
			y:game.height-5,
			draw:function(context)
				context.font = '14px Helvetica';
				context.textAlign = 'right';
				context.textBaseline = 'bottom';
				context.fillStyle = '#AAA';
				context.fillText('© passer-by.com',this.x,this.y);
			
		);
		//事件绑定
		stage.bind('keydown',function(e)
			switch(e.keyCode)
				case 13:
				case 32:
				game.nextStage();
				break;
			
		);
	)();
	//游戏主程序
	(function()
		var stage,map,beans,player,times;
		stage = game.createStage(
			update:function()
				var stage = this;
				if(stage.status==1)								//场景正常运行
					items.forEach(function(item)
						if(map&&!map.get(item.coord.x,item.coord.y)&&!map.get(player.coord.x,player.coord.y))
							var dx = item.x-player.x;
							var dy = item.y-player.y;
							if(dx*dx+dy*dy<750&&item.status!=4)		//物体检测
								if(item.status==3)
									item.status = 4;
									_SCORE += 10;
								else
									stage.status = 3;
									stage.timeout = 30;
								
							
						
					);
					if(JSON.stringify(beans.data).indexOf(0)<0)	//当没有物品的时候,进入结束画面
						game.nextStage();
					
				else if(stage.status==3)		//场景临时状态
					if(!stage.timeout)
						_LIFE--;
						if(_LIFE)
							stage.resetItems();
						else
							game.nextStage();
							return false;
						
					
				
			
		);
		//绘制地图
		map = stage.createMap(
			x:60,
			y:10,
			data:_DATA,
			cache:true,
			draw:function(context)
				context.lineWidth = 2;
				for(var j=0; j<this.y_length; j++)
					for(var i=0; i<this.x_length; i++)
						var value = this.get(i,j);
						if(value)
							var code = [0,0,0,0];
							if(this.get(i+1,j)&&!(this.get(i+1,j-1)&&this.get(i+1,j+1)&&this.get(i,j-1)&&this.get(i,j+1)))
								code[0]=1;
							
							if(this.get(i,j+1)&&!(this.get(i-1,j+1)&&this.get(i+1,j+1)&&this.get(i-1,j)&&this.get(i+1,j)))
								code[1]=1;
							
							if(this.get(i-1,j)&&!(this.get(i-1,j-1)&&this.get(i-1,j+1)&&this.get(i,j-1)&&this.get(i,j+1)))
								code[2]=1;
							
							if(this.get(i,j-1)&&!(this.get(i-1,j-1)&&this.get(i+1,j-1)&&this.get(i-1,j)&&this.get(i+1,j)))
								code[3]=1;
							
							if(code.indexOf(1)>-1)
								context.strokeStyle=value==2?"#FFF":"#09C";
								var pos = this.coord2position(i,j);
								switch(code.join(''))
									case '1100':
										context.beginPath();
										context.arc(pos.x+this.size/2,pos.y+this.size/2,this.size/2,Math.PI,1.5*Math.PI,false);
										context.stroke();
										context.closePath();
										break;
									case '0110':
										context.beginPath();
										context.arc(pos.x-this.size/2,pos.y+this.size/2,this.size/2,1.5*Math.PI,2*Math.PI,false);
										context.stroke();
										context.closePath();
										break;
									case '0011':
										context.beginPath();
										context.arc(pos.x-this.size/2,pos.y-this.size/2,this.size/2,0,.5*Math.PI,false);
										context.stroke();
										context.closePath();
										break;
									case '1001':
										context.beginPath();
										context.arc(pos.x+this.size/2,pos.y-this.size/2,this.size/2,.5*Math.PI,1*Math.PI,false);
										context.stroke();
										context.closePath();
										break;
									default:
										var dist = this.size/2;
										code.forEach(function(v,index)
											if(v)
												context.beginPath();
												context.moveTo(pos.x,pos.y);
												context.lineTo(pos.x-_COS[index]*dist,pos.y-_SIN[index]*dist);
												context.stroke();
												context.closePath();							
											
										);
								
							
						
					
				
			
		);
		//物品地图
		beans = stage.createMap(
			x:60,
			y:10,
			data:_DATA,
			frames:8,
			draw:function(context)
				for(var j=0; j<this.y_length; j++)
					for(var i=0; i<this.x_length; i++)
						if(!this.get(i,j))
							var pos = this.coord2position(i,j);
							context.fillStyle = "#F5F5DC";
							if(_GOODS[i+','+j])
								context.beginPath();
								context.arc(pos.x,pos.y,3+this.times%2,0,2*Math.PI,true);
								context.fill();
								context.closePath();
							else
								context.fillRect(pos.x-2,pos.y-2,4,4);
							
						
					
				
			
		);
		//得分
		stage.createItem(
			x:690,
			y:100,
			draw:function(context)
				context.font = 'bold 28px Helvetica';
				context.textAlign = 'left';
				context.textBaseline = 'bottom';
				context.fillStyle = '#C33';
				context.fillText('SCORE',this.x,this.y);
				context.font = '28px Helvetica';
				context.textAlign = 'left';
				context.textBaseline = 'top';
				context.fillStyle = '#FFF';
				context.fillText(_SCORE,this.x+12,this.y);
			
		);
		//状态文字
		stage.createItem(
			x:690,
			y:320,
			frames:25,
			draw:function(context)
				if(stage.status==2&&this.times%2)
					context.font = '24px Helvetica';
					context.textAlign = 'left';
					context.textBaseline = 'center';
					context.fillStyle = '#09F';
					context.fillText('PAUSE',this.x,this.y);
				
			
		);
		//生命值
		stage.createItem(
			x:705,
			y:540,
			width:30,
			height:30,
			draw:function(context)
				for(var i=0;i<_LIFE-1;i++)
					var x=this.x+40*i,y=this.y;
					context.fillStyle = '#FFE600';
					context.beginPath();
					context.arc(x,y,this.width/2,.15*Math.PI,-.15*Math.PI,false);
					context.lineTo(x,y);
					context.closePath();
					context.fill();
				
			
		);
		//NPC
		for(var i=0;i<4;i++)
			stage.createItem(
				width:30,
				height:30,
				orientation:3,
				color:_COLOR[i],
				location:map,
				coord:x:12+i,y:14,
				vector:x:12+i,y:14,
				type:2,
				frames:10,
				speed:1,
				timeout:Math.floor(Math.random()*120),
				update:function()
					var new_map;
					if(this.status==3&&!this.timeout)
						this.status = 1;
					
					if(!this.coord.offset)			//到达坐标中心时计算
						if(this.status==1)
							if(!this.timeout)		//定时器
								new_map = JSON.parse(JSON.stringify(map.data).replace(/2/g,0));
								var id = this._id;
								items.forEach(function(item)
									if(item._id!=id&&item.status==1)	//NPC将其它所有还处于正常状态的NPC当成一堵墙
										new_map[item.coord.y][item.coord.x]=1;
									
								);
								this.path = map.finder(
									map:new_map,
									start:this.coord,
									end:player.coord
								);
								if(this.path.length)
									this.vector = this.path[0];
								
							
						else if(this.status==3)
							new_map = JSON.parse(JSON.stringify(map.data).replace(/2/g,0));
							var id = this._id;
							items.forEach(function(item)
								if(item._id!=id)
									new_map[item.coord.y][item.coord.x]=1;
								
							);
							this.path = map.finder(
								map:new_map,
								start:player.coord,
								end:this.coord,
								type:'next'
							);
							if(this.path.length)
								this.vector = this.path[Math.floor(Math.random()*this.path.length)];
							
						else if(this.status==4)
							new_map = JSON.parse(JSON.stringify(map.data).replace(/2/g,0));
							this.path = map.finder(
								map:new_map,
								start:this.coord,
								end:this._params.coord
							);
							if(this.path.length)
								this.vector = this.path[0];
							else
								this.status = 1;
							
						
						//是否转变方向
						if(this.vector.change)
							this.coord.x = this.vector.x;
							this.coord.y = this.vector.y;
							var pos = map.coord2position(this.coord.x,this.coord.y);
							this.x = pos.x;
							this.y = pos.y;
						
						//方向判定
						if(this.vector.x>this.coord.x)
							this.orientation = 0;
						else if(this.vector.x<this.coord.x)
							this.orientation = 2;
						else if(this.vector.y>this.coord.y)
							this.orientation = 1;
						else if(this.vector.y<this.coord.y)
							this.orientation = 3;
						
					
					this.x += this.speed*_COS[this.orientation];
					this.y += this.speed*_SIN[this.orientation];
				,
				draw:function(context)
					var isSick = false;
					if(this.status==3)
						isSick = this.timeout>80||this.times%2?true:false;
					
					if(this.status!=4)
						context.fillStyle = isSick?'#BABABA':this.color;
						context.beginPath();
						context.arc(this.x,this.y,this.width*.5,0,Math.PI,true);
						switch(this.times%2)
							case 0:
							context.lineTo(this.x-this.width*.5,this.y+this.height*.4);
							context.quadraticCurveTo(this.x-this.width*.4,this.y+this.height*.5,this.x-this.width*.2,this.y+this.height*.3);
							context.quadraticCurveTo(this.x,this.y+this.height*.5,this.x+this.width*.2,this.y+this.height*.3);
							context.quadraticCurveTo(this.x+this.width*.4,this.y+this.height*.5,this.x+this.width*.5,this.y+this.height*.4);
							break;
							case 1:
							context.lineTo(this.x-this.width*.5,this.y+this.height*.3);
							context.quadraticCurveTo(this.x-this.width*.25,this.y+this.height*.5,this.x,this.y+this.height*.3);
							context.quadraticCurveTo(this.x+this.width*.25,this.y+this.height*.5,this.x+this.width*.5,this.y+this.height*.3);
							break;
						
						context.fill();
						context.closePath();
					
					context.fillStyle = '#FFF';
					if(isSick)
						context.beginPath();
						context.arc(this.x-this.width*.15,this.y-this.height*.21,this.width*.08,0,2*Math.PI,false);
						context.arc(this.x+this.width*.15,this.y-this.height*.21,this.width*.08,0,2*Math.PI,false);
						context.fill();
						context.closePath();
					else
						context.beginPath();
						context.arc(this.x-this.width*.15,this.y-this.height*.21,this.width*.12,0,2*Math.PI,false);
						context.arc(this.x+this.width*.15,this.y-this.height*.21,this.width*.12,0,2*Math.PI,false);
						context.fill();
						context.closePath();
						context.fillStyle = '#000';
						context.beginPath();
						context.arc(this.x-this.width*(.15-.04*_COS[this.orientation]),this.y-this.height*(.21-.04*_SIN[this.orientation]),this.width*.07,0,2*Math.PI,false);
						context.arc(this.x+this.width*(.15+.04*_COS[this.orientation]),this.y-this.height*(.21-.04*_SIN[this.orientation]),this.width*.07,0,2*Math.PI,false);
						context.fill();
						context.closePath();
					
				
			);
		
		items = stage.getItemsByType(2);
		//主角
		player = stage.createItem(
			width:30,
			height:30,
			type:1,
			location:map,
			coord:x:13.5,y:23,
			orientation:2,
			speed:2,
			frames:10,
			update:function()
				var coord = this.coord;
				if(!coord.offset)
					if(this.control.orientation!='undefined')
						if(!map.get(coord.x+_COS[this.control.orientation],coord.y+_SIN[this.control.orientation]))
							this.orientation = this.control.orientation;
						
					
					this.control = ;
					var value = map.get(coord.x+_COS[this.orientation],coord.y+_SIN[this.orientation]);
					if(value==0)
						this.x += this.speed*_COS[this.orientation];
						this.y += this.speed*_SIN[this.orientation];
					else if(value<0)
						this.x -= map.size*(map.x_length-1)*_COS[this.orientation];
						this.y -= map.size*(map.y_length-1)*_SIN[this.orientation];
					
				else
					if(!beans.get(this.coord.x,this.coord.y))	//吃豆
						_SCORE++;
						beans.set(this.coord.x,this.coord.y,1);
						if(_GOODS[this.coord.x+','+this.coord.y])	//吃到能量豆
							items.forEach(function(item)
								if(item.status==1||item.status==3)	//如果NPC为正常状态,则置为临时状态
									item.timeout = 450;
									item.status = 3;
								
							);
						
					
					this.x += this.speed*_COS[this.orientation];
					this.y += this.speed*_SIN[this.orientation];
				
			,
			draw:function(context)
				context.fillStyle = '#FFE600';
				context.beginPath();
				if(stage.status!=3)	//玩家正常状态
					if(this.times%2)
						context.arc(this.x,this.y,this.width/2,(.5*this.orientation+.20)*Math.PI,(.5*this.orientation-.20)*Math.PI,false);
					else
						context.arc(this.x,this.y,this.width/2,(.5*this.orientation+.01)*Math.PI,(.5*this.orientation-.01)*Math.PI,false);
					
				else	//玩家被吃
					if(stage.timeout) 
						context.arc(this.x,this.y,this.width/2,(.5*this.orientation+1-.02*stage.timeout)*Math.PI,(.5*this.orientation-1+.02*stage.timeout)*Math.PI,false);
					
				
				context.lineTo(this.x,this.y);
				context.closePath();
				context.fill();
			
		);
		//事件绑定
		stage.bind('keydown',function(e)
			switch(e.keyCode)
				case 13: //回车
				case 32: //空格
				this.status = this.status==2?1:2;
				break;
				case 39: //右
				player.control = orientation:0;
				break;
				case 40: //下
				player.control = orientation:1;
				break;
				case 37: //左
				player.control = orientation:2;
				break;
				case 38: //上
				player.control = orientation:3;
				break;
			
		);
	)();
	//结束画面
	(function()
		var stage = game.createStage();
		//游戏结束
		stage.createItem(
			x:game.width/2,
			y:game.height*.35,
			draw:function(context)
				context.fillStyle = '#FFF';
				context.font = 'bold 48px Helvetica';
				context.textAlign = 'center';
				context.textBaseline = 'middle';
				context.fillText('GAME OVER',this.x,this.y);
			
		);
		//记分
		stage.createItem(
			x:game.width/2,
			y:game.height*.5,
			draw:function(context)
				context.fillStyle = '#FFF';
				context.font = '20px Helvetica';
				context.textAlign = 'center';
				context.textBaseline = 'middle';
				context.fillText('FINAL SCORE: '+(_SCORE+50*Math.max(_LIFE-1,0)),this.x,this.y);
			
		);
		//事件绑定
		stage.bind('keydown',function(e)
			switch(e.keyCode)
				case 13: //回车
				case 32: //空格
				_SCORE = 0;
				_LIFE = 3;
				var st = game.setStage(1);
				st.reset();
				break;
			
		);
	)();
	game.init();
)();

游戏截图

 

 操作说明 ,按键盘上下左右键移动 ,按键盘空格键暂停!

创作挑战赛 新人创作奖励来咯,坚持创作打卡瓜分现金大奖

以上是关于游戏开发经典样例 | web html5 吃豆人游戏的主要内容,如果未能解决你的问题,请参考以下文章

PS4游戏免费领,不要错过!

谷歌地图吃豆人[关闭]

吃豆人游戏:处理编程语言和ActionScript

蓝桥杯省赛真题26Scratch吃豆人 少儿编程scratch蓝桥杯省赛真题讲解

py小游戏系列吃豆人,儿时的回忆

py小游戏系列吃豆人,儿时的回忆