首頁 >web前端 >js教程 >JS和H5編寫推箱子遊戲

JS和H5編寫推箱子遊戲

小云云
小云云原創
2018-03-17 16:24:492087瀏覽


推盒子小遊戲是一款多年前很流行的小遊戲(即使現在也有很多人玩),遊戲目的很簡單,就是人推箱子,把所有的箱子推到目的地,就遊戲成功:看似跟簡單的邏輯,其實還是有一定難度的,我也是依靠了別人的幫助才完成的,現在開始介紹如何用js,html5編寫該遊戲(方便起見我們把人用一個粉圓形代替):

一.能力要求

#JavaScript,HTML畫布,物件導向的基本思想,合理的編程邏輯。

二.寫順序

#1.pushBox.html檔

#2.pojo.js檔案(用來存放所有的物件)

#3.paint.js檔案(用來寫畫畫的語句)

4.game.js檔案(用來寫運行邏輯部分)

5.allLevels.js檔案(用來存放關卡)

*註:這是我的書寫習慣,按內容和功能將個各類別分開,如有更好地寫法歡迎評論

三.開始寫

1.建立基本的pushBox .html檔:

內容很簡單,裡面只要標籤,設定id,其後的

<body>
	<canvas id=&#39;can1&#39; 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>

<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 = &#39;pink&#39;;
	this.size = 20;
	this.x = x;
	this.y = y;
	//判断这个人是否在目标点上
	this.isOnTarget = false;
}
//箱子类
function Box(x, y){
	this.color = &#39;yellow&#39;;
	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 = &#39;lime&#39;;
}
//地砖类
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 = [
	[&#39;&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;&#39;,&#39;&#39;],
	[&#39;&#39;,&#39;wall&#39;,&#39;brick&#39;,&#39;person&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;&#39;],
	[&#39;&#39;,&#39;wall&#39;,&#39;brick&#39;,&#39;box&#39;,&#39;brick&#39;,&#39;brick&#39;,&#39;wall&#39;,&#39;&#39;],
	[&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;brick&#39;,&#39;wall&#39;,&#39;brick&#39;,&#39;wall&#39;,&#39;wall&#39;],
	[&#39;wall&#39;,&#39;target&#39;,&#39;wall&#39;,&#39;brick&#39;,&#39;wall&#39;,&#39;brick&#39;,&#39;brick&#39;,&#39;wall&#39;],
	[&#39;wall&#39;,&#39;target&#39;,&#39;box&#39;,&#39;brick&#39;,&#39;brick&#39;,&#39;wall&#39;,&#39;brick&#39;,&#39;wall&#39;],
	[&#39;wall&#39;,&#39;target&#39;,&#39;brick&#39;,&#39;brick&#39;,&#39;brick&#39;,&#39;box&#39;,&#39;brick&#39;,&#39;wall&#39;],
	[&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;,&#39;wall&#39;]
];

比如說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 = &#39;black&#39;;
	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 = &#39;blue&#39;;
	ctx.fillRect(530+x*size, 180+y*size, size, size);
	ctx.strokeStyle = &#39;lightblue&#39;;
	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 = &#39;gray&#39;;
	ctx.fillRect(530+x*size, 180+y*size, size, size);
	ctx.strokeStyle = &#39;white&#39;;
	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] == &#39;wall&#39;){
				//这样我们就把坐标作为参数,引入到那些paint方法里,经过计算,在网页中正确显示了
				paintWall(ctx,j,i,40);
			}else if(arr1[i][j] == &#39;brick&#39;){
				paintBrick(ctx, j, i,40);
			}else if(arr1[i][j] == &#39;target&#39;){
				paintBrick(ctx, j, i,40);
				paintTarget(ctx, j, i,10,&#39;lime&#39;);
			}else if(arr1[i][j] == &#39;box&#39;){
				paintBrick(ctx, j, i,40);
				var index = getBoxIndex(boxes,i,j);
				if(boxes[index].isOnTarget){
					paintBox(ctx, j, i,40,&#39;red&#39;);
				}else{
					paintBox(ctx, j, i,40,&#39;yellow&#39;);
				}
			}else if(arr1[i][j] == &#39;person&#39;){
				paintBrick(ctx, j, i,40);
				paintPerson(ctx, j, i,20,&#39;pink&#39;);
			}
			
		}
	}
	
}

在game.js裡面的run()調用getMap()方法後,效果就是這樣:

JS和H5編寫推箱子遊戲

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] == &#39;person&#39;) {
		//使用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] == &#39;box&#39;) {
				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;
<br/>

然後我們就可以開始寫邏輯了,比如說用戶按方向鍵左,要判斷左邊是什麼,如果是箱子的話,還要判斷箱子的左邊是什麼:

正確的邏輯如下所示(我們就拿人往左移動為例,然後上下右都是一樣的):

JS和H5編寫推箱子遊戲

具體的語法很簡單:比如說,按照第一個為例,左邊是磚塊,而人踩的不是目標點:

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

如果我們下一步,人踩到了目標點,我們就要把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(&#39;can1&#39;);
	var ctx = oCan.getContext(&#39;2d&#39;);
	var oEvent = ev || event;
	var Audio1 = document.getElementById(&#39;walk&#39;);
	var Audio2 = document.getElementById(&#39;push&#39;);
	
	//接收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] == &#39;brick&#39;){
				arr1[i][j-1] = &#39;person&#39;;
				arr1[i][j] = &#39;target&#39;;
				person.isOnTarget = false;
			}else if(arr1[i][j-1] == &#39;box&#39; && arr1[i][j-2] != &#39;wall&#39; && arr1[i][j-2] != &#39;box&#39;){
				var index = getBoxIndex(boxes, i,j-1);
				if(!boxes[index].isOnTarget){
					if(arr1[i][j-2] == &#39;brick&#39;){
						arr1[i][j-2] = &#39;box&#39;;
						arr1[i][j-1] = &#39;person&#39;;
						arr1[i][j] = &#39;target&#39;;
						person.isOnTarget = false;
					}else if(arr1[i][j-2] == &#39;target&#39;){
						arr1[i][j-2] = &#39;box&#39;;
						arr1[i][j-1] = &#39;person&#39;;
						arr1[i][j] = &#39;target&#39;;
						person.isOnTarget = false;					
						boxes[index].isOnTarget = true;
					}
				}else if(boxes[index].isOnTarget){
					if(arr1[i][j-2] == &#39;brick&#39;){
						arr1[i][j-2] = &#39;box&#39;;
						arr1[i][j-1] = &#39;person&#39;;
						arr1[i][j] = &#39;target&#39;;
						boxes[index].isOnTarget = false;
					}else if(arr1[i][j-2] == &#39;target&#39;){
						arr1[i][j-2] = &#39;box&#39;;
						arr1[i][j-1] = &#39;person&#39;;
						arr1[i][j] = &#39;target&#39;;		
					}
				}
				boxes[index].y--;
			}else if(arr1[i][j-1] == &#39;target&#39;){
				arr1[i][j-1] = &#39;person&#39;;
				arr1[i][j] = &#39;target&#39;;
			}
		}else if(!person.isOnTarget){
			if(arr1[i][j-1] == &#39;brick&#39;){
				arr1[i][j-1] = &#39;person&#39;;
				arr1[i][j] = &#39;brick&#39;;
			}else if(arr1[i][j-1] == &#39;box&#39; && arr1[i][j-2] != &#39;wall&#39; && arr1[i][j-2] != &#39;box&#39;){
				var index = getBoxIndex(boxes, i,j-1);
				//箱子踩的不是目标点
				if(!boxes[index].isOnTarget){
					//箱子左边是地面
					if(arr1[i][j-2] == &#39;brick&#39;){
						arr1[i][j-2] = &#39;box&#39;;
						arr1[i][j-1] = &#39;person&#39;;
						arr1[i][j] = &#39;brick&#39;;					
					}//箱子左边是目标点
					else if(arr1[i][j-2] == &#39;target&#39;){
						arr1[i][j-2] = &#39;box&#39;;
						arr1[i][j-1] = &#39;person&#39;;
						arr1[i][j] = &#39;brick&#39;;
						boxes[index].isOnTarget = true;
					}
				}else if(boxes[index].isOnTarget){
					if(arr1[i][j-2] == &#39;brick&#39;){
						arr1[i][j-2] = &#39;box&#39;;
						arr1[i][j-1] = &#39;person&#39;;
						arr1[i][j] = &#39;brick&#39;;
						boxes[index].isOnTarget = false;
						person.isOnTarget = true;
					}else if(arr1[i][j-2] == &#39;target&#39;){
						arr1[i][j-2] = &#39;box&#39;;
						arr1[i][j-1] = &#39;person&#39;;
						arr1[i][j] = &#39;brick&#39;;
						person.isOnTarget = true;
					}
				}
				boxes[index].y--;
			}else if(arr1[i][j-1] == &#39;target&#39;){
				arr1[i][j-1] = &#39;person&#39;;
				arr1[i][j] = &#39;brick&#39;;
				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(&#39;win&#39;);
		Audio3.src = &#39;鼓掌.mp3&#39;;
		alert(&#39;You Win! 一共走了&#39;+countStep+&#39;步&#39;);
	}
}

通关的画面如下:

JS和H5編寫推箱子遊戲

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

总结:

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

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

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

以上是JS和H5編寫推箱子遊戲的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn