最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

JS和H5编写推箱子游戏

来源:动视网 责编:小采 时间:2020-11-27 19:59:09
文档

JS和H5编写推箱子游戏

JS和H5编写推箱子游戏:推箱子小游戏是一款多年前很流行的小游戏(即使现在也有很多人玩),游戏目的很简单,就是人推箱子,把所有的箱子推到目的地,就游戏成功:看似跟简单的逻辑,其实还是有一定难度的,我也是依靠了别人的帮助才完成的,现在开始介绍如何用js,html5编写该游戏
推荐度:
导读JS和H5编写推箱子游戏:推箱子小游戏是一款多年前很流行的小游戏(即使现在也有很多人玩),游戏目的很简单,就是人推箱子,把所有的箱子推到目的地,就游戏成功:看似跟简单的逻辑,其实还是有一定难度的,我也是依靠了别人的帮助才完成的,现在开始介绍如何用js,html5编写该游戏
 推箱子小游戏是一款多年前很流行的小游戏(即使现在也有很多人玩),游戏目的很简单,就是人推箱子,把所有的箱子推到目的地,就游戏成功:看似跟简单的逻辑,其实还是有一定难度的,我也是依靠了别人的帮助才完成的,现在开始介绍如何用js,html5编写该游戏(方便起见我们把人用一个粉圆形代替):

一.能力要求

JavaScript,HTML画布,面向对象的基本思想,合理的编程逻辑。

二.编写顺序

1.pushBox.html文件

2.pojo.js文件(用来存放所有的对象)

3.paint.js文件(用来写画画的语句)

4.game.js文件(用来写运行逻辑部分)

5.allLevels.js文件(用来存放关卡)

*注:这是我的书写习惯,按内容和功能将个各类分开,如有更好地写法欢迎评论

三.开始编写

1.建立基本的pushBox.html文件:

内容很简单,<body>里面仅仅需要<canvas>标签,设置id,其后的<audio>文件在逻辑部分做完后再加进去:

<body>
	<canvas id='can1' width=1536 height=733></canvas>
	<audio id="walk" autoplay></audio>
	<audio id="push" autoplay></audio>
	<audio id="win" autoplay></audio>
</body>

<script>有引入其他四个.js文件,还有一个调用run()方法:

<script type="text/javascript" src="pojo.js"></script>
<script type="text/javascript" src="game.js"></script>
<script type="text/javascript" src="paint.js"></script>
<script type="text/javascript" src="allLevels.js"></script>
<script>
	window.onload = function(){
	run();
	}
</script>

2.写pojo类:

首先我们需要知道总共有这些类:

人,箱子,目标点,砖块和围墙,很简单,所有的类都有color(颜色),size(边长/半径),x(横坐标),y(纵坐标)这些属性。然后我们要记得,人和砖块可能和目标点重合,所以在箱子和人的类里面要加上isOnTarget(是否和目标点重合)属性,这样就完成了:

//人类
function Person(x, y){
	this.color = 'pink';
	this.size = 20;
	this.x = x;
	this.y = y;
	//判断这个人是否在目标点上
	this.isOnTarget = false;
}
//箱子类
function Box(x, y){
	this.color = 'yellow';
	this.size = 40;
	this.x = x;
	this.y = y;
	//判断某个箱子是否在目标点上
	this.isOnTarget = false;
}
//目标点类
function Target(x, y){
	this.size = 12;
	this.x = x;
	this.y = y;
	this.color = 'lime';
}
//地砖类
function Brick(x, y){
	this.x = x;
	this.y = y;
	this.size = 40;
}
//围墙类
function Wall(x, y){
	this.x = x;
	this.y = y;
	this.size = 40;
}

3.编写paint.js类

我们需要对刚刚在pojo.js类中写的所有类写出画的方法:

需要注意一点很重要:我们如果使用确定的x,y坐标,比如说要画箱子:

ctx.fillRect(x, y, size, size);

如果如下面的方式调用的话,我们在allLevels里面这样画这个箱子:

ctx.fillRect(500, 500, 40, 40);

我们就无法确定这个箱子的旁边是什么,不好判断,于是没有办法写逻辑层。

所以,我们这样构思:我们用一个二维数组来构造这个关卡,每一个物块(箱子,人,目标点,砖块或者墙)都放到这个数组当中,arr1[][],像这样:

var arr1 = [
	['','wall','wall','wall','wall','wall','',''],
	['','wall','brick','person','wall','wall','wall',''],
	['','wall','brick','box','brick','brick','wall',''],
	['wall','wall','wall','brick','wall','brick','wall','wall'],
	['wall','target','wall','brick','wall','brick','brick','wall'],
	['wall','target','box','brick','brick','wall','brick','wall'],
	['wall','target','brick','brick','brick','box','brick','wall'],
	['wall','wall','wall','wall','wall','wall','wall','wall']
];

比如说arr1[0][1]就是墙了,这样一来就能写逻辑层了。而且还有一个优点,就是我们在之后创建新关卡的时候很方便,只需要按照坐标顺序在数组里写出来即可。

所以,我们在paint.js里面这样写,每个方法里面都把x和y进行一些运算,使它能正确地在网页中画出:

//首先还是清屏
function clearScreen(ctx){
	ctx.clearRect(0,0,1536,750);
}
//画人
function paintPerson(ctx, x, y, size,color){
	ctx.beginPath();
	ctx.fillStyle = color;
	//我们在内部就写好该往哪里画,传过来的x,y坐标值就可以直接在里面计算了
	ctx.arc(530+x*size*2+20, 180+y*size*2+20, size, 0, 2*Math.PI);
	ctx.fill();
}
//画箱子
function paintBox(ctx, x, y, size,color){
	ctx.beginPath();
	ctx.fillStyle = color;	
	ctx.fillRect(530+x*size, 180+y*size, size, size);
	ctx.strokeStyle = 'black';
	ctx.lineWidth = 2;
	ctx.moveTo(530+x*size, 180+y*size);
	ctx.lineTo(530+x*size+size, 180+y*size+size);
	ctx.stroke();
	ctx.beginPath();
	ctx.moveTo(530+x*size+size, 180+y*size);
	ctx.lineTo(530+x*size, 180+y*size+size);
	ctx.stroke();
	ctx.strokeRect(530+x*size, 180+y*size, size, size);
}
//画目标点
function paintTarget(ctx, x, y, size,color){
	ctx.beginPath();
	ctx.fillStyle = color;
	ctx.arc(530+x*size*4+20, 180+y*size*4+20, size, 0, 2*Math.PI);
	ctx.fill();
}
//画地砖,其中用了for循环简化了代码量
function paintBrick(ctx, x, y, size){
	ctx.beginPath();
	ctx.fillStyle = 'blue';
	ctx.fillRect(530+x*size, 180+y*size, size, size);
	ctx.strokeStyle = 'lightblue';
	for(var i = 0; i <= 3; i++){
	ctx.beginPath();
	ctx.moveTo(530+x*size, 180+y*size+0.25*(i+1)*size);
	ctx.lineTo(530+x*size+size, 180+y*size+0.25*(i+1)*size);
	ctx.stroke();
	}
	for(var i = 0; i < 4; i++){
	ctx.beginPath();
	if(i%2 == 0){
	ctx.moveTo(530+x*size+0.5*size, 180+y*size+0.25*i*size);
	ctx.lineTo(530+x*size+0.5*size, 180+y*size+0.25*(i+1)*size);
	ctx.stroke();
	}else{
	ctx.moveTo(530+x*size+0.25*size, 180+y*size+0.25*i*size);
	ctx.lineTo(530+x*size+0.25*size, 180+y*size+0.25*(i+1)*size);
	ctx.stroke();
	ctx.beginPath();
	ctx.moveTo(530+x*size+0.75*size, 180+y*size+0.25*i*size);
	ctx.lineTo(530+x*size+0.75*size, 180+y*size+0.25*(i+1)*size);
	ctx.stroke();
	}
	}
}
//画围墙
function paintWall(ctx, x, y, size){
	ctx.beginPath();
	ctx.fillStyle = 'gray';
	ctx.fillRect(530+x*size, 180+y*size, size, size);
	ctx.strokeStyle = 'white';
	ctx.beginPath();
	ctx.moveTo(530+x*size+0.5*size, 180+y*size);
	ctx.lineTo(530+x*size+0.5*size, 180+y*size+size);
	ctx.stroke();
	ctx.beginPath();
	ctx.moveTo(530+x*size, 180+y*size+0.5*size);
	ctx.lineTo(530+x*size+size, 180+y*size+0.5*size);
	ctx.stroke();
}

然后,我们在调用画的方法时候这样调用,按照每个物块在数组中的位置进行画画:

unction getMap(ctx, person){
	clearScreen(ctx);
	
	for(var i = 0; i<arr1.length; i++){
	for(var j = 0; j<arr1[0].length; j++){
	//画初始图
	if(arr1[i][j] == 'wall'){
	//这样我们就把坐标作为参数,引入到那些paint方法里,经过计算,在网页中正确显示了
	paintWall(ctx,j,i,40);
	}else if(arr1[i][j] == 'brick'){
	paintBrick(ctx, j, i,40);
	}else if(arr1[i][j] == 'target'){
	paintBrick(ctx, j, i,40);
	paintTarget(ctx, j, i,10,'lime');
	}else if(arr1[i][j] == 'box'){
	paintBrick(ctx, j, i,40);
	var index = getBoxIndex(boxes,i,j);
	if(boxes[index].isOnTarget){
	paintBox(ctx, j, i,40,'red');
	}else{
	paintBox(ctx, j, i,40,'yellow');
	}
	}else if(arr1[i][j] == 'person'){
	paintBrick(ctx, j, i,40);
	paintPerson(ctx, j, i,20,'pink');
	}
	
	}
	}
	
}

在game.js里面的run()调用getMap()方法后,效果就是这样:

4.game.js类:

首先我们还是需要创建人和箱子的对象:

var person = new Person(0,0);
var boxLevel1Count = 3;
var boxes = new Array(boxLevel1Count);
for(var i = 0;i<boxLevel1Count;i++){
	boxes[i] = new Box(0,0);
}

然后我们要知道人在二维数组中的位置(坐标),以及三个箱子在其中的坐标需要以下两个方法:

//找到人的坐标
function findPerson(){
	for (var i = 0; i < arr1.length; i++) {
 var tmp = arr1[i];
 for (var j = 0; j < tmp.length; j++) {
 if (arr1[i][j] == 'person') {
	//使用json传变量
 return {personX:i,personY:j};
 }
 }
 }
}

//找箱子的坐标,并把他们放到数组里
function findBox(){
	var count = 0;
	for (var i = 0; i < arr1.length; i++) {
 var tmp = arr1[i];
 for (var j = 0; j < tmp.length; j++) {
 if (arr1[i][j] == 'box') {
	boxes[count].x = i;
	boxes[count].y = j;
	count++;
 }
 }
 }
	return boxes;
}

找到人的坐标之后,我们要把person对象的x,y和二维数组里的i,j关联起来,就是:

//接收person的坐标
	var position = findPerson();
	//i是person的横坐标
	
	var i = position.personX;
	//j是person的纵坐标
	var j = position.personY;
	//使对象的属性和人在二维数组的坐标关联
	person.x = i;
	person.y = j;

然后我们就可以开始写逻辑了,比如说用户按方向键左,要判断左边是什么,如果是箱子的话,还要判断箱子的左边是什么:

正确的逻辑如下所示(我们就拿人往左移动为例,然后上下右都是一样的):

具体的语法很简单:比如说,按照第一个为例,左边是砖块,并且人踩的不是目标点:

if(arr1[i][j-1] == 'brick'){
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'brick';
	Audio1.src = '走路emm.wav';
}

如果我们下一步,人踩到了目标点,我们就要把person.isOnTarget 设置为true,当人移开时候,我们把这个属性设置为false

当人左边是箱子的时候,比较麻烦,首先必须明白有一点:我们到底推的是哪个箱子?之前已经有了一个存放所有箱子的数组了,所以现在需要一个方法,可以让我们知道我们推的是哪个箱子:

function getBoxIndex(boxes, i,j){
	var index = 0;
	for(var k = 0;k<boxes.length;k++){
	if(boxes[k].x == i && boxes[k].y == j){
	//找到了这个箱子的下标
	index = k;
	}
	}
	return index;
}

在实际调用中,里面的参数(i,j)就写下一步要走的那个位置,比如说向左走,就是

var index = getBoxIndex(boxes, i,j-1);

这个index就是我们要找的第i个箱子了,接下来就很好办了,我们按照刚才的逻辑一步一步写,一堆的if、else,只需注意两点,当人踩到目标点时,把person.isOnTarget = true,移开之后false;箱子踩到目标点时boxes[index].isOnTarget = true,移开之后false,然后再整理一下,简化代码量,就是:

//玩家操作
document.onkeydown = function(ev){
	var oCan = document.getElementById('can1');
	var ctx = oCan.getContext('2d');
	var oEvent = ev || event;
	var Audio1 = document.getElementById('walk');
	var Audio2 = document.getElementById('push');
	
	//接收person的坐标
	var position = findPerson();
	//i是person的横坐标
	
	var i = position.personX;
	//j是person的纵坐标
	var j = position.personY;
	//使对象的属性和人在二维数组的坐标关联
	person.x = i;
	person.y = j;
	if(oEvent.keyCode == 37){
	if(person.isOnTarget){
	if(arr1[i][j-1] == 'brick'){
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'target';
	person.isOnTarget = false;
	}else if(arr1[i][j-1] == 'box' && arr1[i][j-2] != 'wall' && arr1[i][j-2] != 'box'){
	var index = getBoxIndex(boxes, i,j-1);
	if(!boxes[index].isOnTarget){
	if(arr1[i][j-2] == 'brick'){
	arr1[i][j-2] = 'box';
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'target';
	person.isOnTarget = false;
	}else if(arr1[i][j-2] == 'target'){
	arr1[i][j-2] = 'box';
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'target';
	person.isOnTarget = false;	
	boxes[index].isOnTarget = true;
	}
	}else if(boxes[index].isOnTarget){
	if(arr1[i][j-2] == 'brick'){
	arr1[i][j-2] = 'box';
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'target';
	boxes[index].isOnTarget = false;
	}else if(arr1[i][j-2] == 'target'){
	arr1[i][j-2] = 'box';
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'target';	
	}
	}
	boxes[index].y--;
	}else if(arr1[i][j-1] == 'target'){
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'target';
	}
	}else if(!person.isOnTarget){
	if(arr1[i][j-1] == 'brick'){
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'brick';
	}else if(arr1[i][j-1] == 'box' && arr1[i][j-2] != 'wall' && arr1[i][j-2] != 'box'){
	var index = getBoxIndex(boxes, i,j-1);
	//箱子踩的不是目标点
	if(!boxes[index].isOnTarget){
	//箱子左边是地面
	if(arr1[i][j-2] == 'brick'){
	arr1[i][j-2] = 'box';
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'brick';	
	}//箱子左边是目标点
	else if(arr1[i][j-2] == 'target'){
	arr1[i][j-2] = 'box';
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'brick';
	boxes[index].isOnTarget = true;
	}
	}else if(boxes[index].isOnTarget){
	if(arr1[i][j-2] == 'brick'){
	arr1[i][j-2] = 'box';
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'brick';
	boxes[index].isOnTarget = false;
	person.isOnTarget = true;
	}else if(arr1[i][j-2] == 'target'){
	arr1[i][j-2] = 'box';
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'brick';
	person.isOnTarget = true;
	}
	}
	boxes[index].y--;
	}else if(arr1[i][j-1] == 'target'){
	arr1[i][j-1] = 'person';
	arr1[i][j] = 'brick';
	person.isOnTarget = true;
	}
	}
	}

这样,向左走的所有逻辑就完成了,然后是上,右,下,接着else if 就行,照猫画虎,把里面的i和j一更换就完成了。

所有逻辑写完,验证无误后,我们要判断通关条件,这个很简单,当所有的箱子都isOnTarget时候,成功,当然是在每次按完方向键之后都要判断:

function judgeWin(boxes){
	var count = 0;
	for(var p = 0;p<boxes.length;p++){
	if(boxes[p].isOnTarget)
	count++;
	}if(count == boxes.length){
	var Audio3 = document.getElementById('win');
	Audio3.src = '鼓掌.mp3';
	alert('You Win! 一共走了'+countStep+'步');
	}
}

通关的画面如下:

最后,我们加入音效,主要划分成以下几类:人走到砖块的,人推箱子的,人碰到墙的(和人推箱子碰到墙的),成功后的掌声。

总结:

1.我开始以为这个很简单,和我之前做的那个flappy bird 差不多,没想到这里面的逻辑其实很复杂,我的flappy bird链接如下:点击打开链接。所以,一定要在刚开始的时候要构思好大局,别越写越麻烦,容易产生放弃心里。

2.利用二维数组存放地图,然后在paint()方法里面写画的位置,大小等,调用的时候paint()里面就填坐标,这样有两个好处:(1)可以知道每个物块的上下左右都是什么,易于判断;(2)新建关卡的时候易于创建,只需要按照坐标位置,把wall,brick,person,target,box放进去即可

3.在创建人的对象后,我们需要在地图数组中把人的坐标找出来,然后将对象的x,y属性和坐标关联;在创建箱子的对象数组后,我们需要getIndex()方法,找到人到底推的是哪个箱子,才能使这个箱子的isOnTarget改成true或者false,这两点很关键。

文档

JS和H5编写推箱子游戏

JS和H5编写推箱子游戏:推箱子小游戏是一款多年前很流行的小游戏(即使现在也有很多人玩),游戏目的很简单,就是人推箱子,把所有的箱子推到目的地,就游戏成功:看似跟简单的逻辑,其实还是有一定难度的,我也是依靠了别人的帮助才完成的,现在开始介绍如何用js,html5编写该游戏
推荐度:
标签: 游戏 实现 js
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top