最近没什么事情,我的一个亲戚在学校学习PHP,课程中老师让他们编写一个javascript版本的推箱子小游戏,他没什么头绪,就来问我,我当时很闲,就随口答应他包在我身上。结果真正写的时候还是花了点时间,最后写出来的成品也有各种问题,在这里希望大家能一起探讨学习!(大神们请屏蔽鄙人的粗糙简单)
首先看一下最终的效果图,,不好意思,只是做了个简化版本,图中黄色的块是我们控制来推动的盒子,粉红色的块是被推的盒子,红色的块表示最终要被推到的位置,黑色的快表示墙,盒子不能穿过墙,游戏的方向控制使用wasd四个按键,分别表示上下左右,玩起来没有任何意思,这里简述一下盒子推动的思路:
首先,推箱子最少需要两个箱子,一个箱子a用键盘来控制移动,另外一个箱子则是被推动的箱子b,用键盘来控制箱子a移动很简单,使用键盘事件onkeydown,用keyCode来选择你要移动箱子所使用的键,一般常用 wasd这四个键,被推的箱子b如果要实现被推着走,需要两步来完成,第一步是判断两个箱子相碰撞,因为只有当两个箱子配到一起的时候才可能发生推着走的事情,当确定两个箱子碰到以后,我们再次按下前后左右键,我们需要把箱子a移动的距离与之对应的加到箱子b的身上,但这里要判断箱子a移动的方向,如果箱子a是从左边往右边移动20px,那么箱子b也应该向右边移动20px,如果是从箱子a是从上边往下边移动20px,那么箱子b也应该往下方移动20px,以此类推,从右边往左边推和从下边往上边推也是一样的,这样就有推箱子的效果。(大神有其他方法也请告诉小弟,多谢)
第二个问题就是游戏地图的创建,其实我使用的是很常见的一种创建方式,我自己给他取了个名字,叫做数组地图,主要是因为他很形象模拟地图,数组写成什么样子,最后生成的地图就是什么样子,比如
[
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,3,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,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,1,1,1,1,6,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,1,1,1,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,4,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,4,4,4,4,4,4,4,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,1,1,1,1,1,2,4,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,1,1,4,1,1,4,4,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,4,4,4,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,4,4,4,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,4,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,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,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,
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,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
]
这个数组看起来像是一个矩形,实际上就是一个数组,只是我们人为的把他排列成一个矩形的样子,之所以这样做就是为了依照我们排列的样子来创建地图,首先我们看到这个数组里有很多1,还有3,4,2,他们分别表示不同的类型,比如说1表示可以行走的地方,4表示不能行走的地方,3表示箱子a初始化时候的位置,2表示箱子最终要被推到的地方,6表示被推的箱子b初始化的位置,我们在css表里面写好对应的样式,比如 .pos1{20px;height:20px;background:#666;float:left}, .pos2{20px;height:20px;background:red;float:left}, .pos3{20px;height:20px;background:yellow;float:left},.pos4{20px;height:20px;background:black;float:left},.pos6{20px;height:20px;background:pink;float:left},写好这些样式以后,开始用js循环整个数组,再循环的过程中不断创建div,而每一个div都要给他们一个class,至于这个class是什么则根据数组样式来赋值,比如数组里的第一个值是1,那么对应的样式就是.pos1,如果数组里的样式是2,那么对应的class就是.pos2,以此类推,每创建一个div就把它插入到父盒子里面,因为每一个小div都有浮动属性,他们自然会一个一个排开,最终就会平凑成数组展现出来的样式。这种地图创建方好处就是非常容易修改地图样式,并且可以很直观的修改地图,只要修改数组就等于修改了地图,简单的游戏中常用这种方式。
式。下面是代码:
this.gameBox = document.getElementById('game_box'); //父盒子
for(var i=0;i<this.map.length;i++){
this.litBox = document.createElement('div');
this.litBox.className = "pos"+this.map[i];
this.gameBox.appendChild(this.litBox);
}
第三个问题就是推箱子时候,当箱子遇到class为.pos4的div时候,也就是遇到墙的时候,不能把箱子推过去,这个问题肯定有更好的解决方法,因为我在这个例子中所有的障碍都是用div完成,所以我们看到的那一列或者一黑色竖障碍都是div,我原本希望使用上面说的碰撞检测的方法来处理,但是发现这个方法检测多个连续的元素时候不是很好用,不过也有可能是我用不对,我最终的使用的方法比较愚蠢,将所有的class为.pos4的div全部选中,用循环得到每个div的上下左右值,并把他们放到一个数组里,然后在我们每次按下按键的时候都检测一次当前箱子的位置。并把这个位置拿到数组里面的去循环比较,看看是否有一样的值,如果有一个一样的,那么表示箱子碰到了障碍,只是这种做法比较耗损性能,毕竟在不停的循环检测,所以不是很推荐,不过我暂时也没想出其他方法(求大神赐教!!!)
第四个问题就是,这个例子只是演示了推一个子箱子,实际的游戏中为了增加难度会有很多被推的箱子,至于如何实现,相信大家也有自己的想法了。
/*******以下是整个案例代码(因本人技术有限,大神就当成反面教材吧) 请大家多多指教***************/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Document</title>
<style>
*{margin:0px;padding:0px;}
#game_box{500px;height:500px;margin:150px auto;position:relative;}
.pos1{20px;height:20px;background: #666;float: left;}
.pos2{20px;height:20px;background: red;float: left;}
.pos3{20px;height:20px;background:#666;float: left;}
.pos4{20px;height:20px;background:black;float: left;}
.pos6{20px;height:20px;background: #666;float: left;}
.begin{20px;height:20px;background:yellow;position: absolute;top:0;left:0px;z-index:1000;}
.target{20px;height:20px;background:pink;position: absolute;top:0;left:0px;z-index:1000;}
</style>
</head>
<body>
<div id="game_box"></div>
</body>
<script>
var game = {
gameBox:0, //外层的大盒子
litBox:0, //里面的每一个小格子
beginLeft:0, //运动的盒子最开始距离左边的距离
beginRight:0, //运动的盒子最开始距离上边的距离
targetLeft:0, //将要推动的盒子最开始距离左边的距离
targetTop:0, //将要推动的盒子最开始距离上边的距离
mObj:0, //运动的盒子元素
mBox:0, //被推动的盒子
prevleft:0, //记录运动的盒子上一步距离左边的位置
prevtop:0, //记录运动的盒子上一步距离上边的位置
pushprevleft:0,
pushprevtop:0,
iftouch:false, //记录是否碰撞到目标盒子
prev:0, //保存之前按下的方向值
direct:0,
last:0,
//地图数组
map:[
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,3,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,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,4,4,4,4,4,4,4,4,4,4,4,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,1,1,1,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,4,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,4,4,4,4,4,4,4,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,1,1,1,1,1,2,4,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,1,1,4,1,1,4,4,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,4,4,4,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,6,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,4,4,4,4,4,4,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,4,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,4,1,1,1,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,4,4,4,4,1,
1,1,1,1,4,1,1,1,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,4,4,4,4,4,1,4,1,4,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,4,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,1,1,1,1,1,1,1,1,
1,1,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,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,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,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
],
//创建地图
createMap:function(){
this.gameBox = document.getElementById('game_box');
for(var i=0;i<this.map.length;i++){
this.litBox = document.createElement('div');
this.litBox.className = "pos"+this.map[i];
this.gameBox.appendChild(this.litBox);
if(this.map[i]==3){
this.beginLeft = this.litBox.offsetLeft;
this.beginRight = this.litBox.offsetTop;
}
if(this.map[i]==6){
this.targetLeft = this.litBox.offsetLeft;
this.targetTop = this.litBox.offsetTop;
}
}
},
//获取class方法
getClass:function(classname,parent){
var par = parent||document;
var pele = par.getElementsByTagName('*');
var arr=[];
for(var i=0;i<pele.length;i++){
var pclass = pele[i].className.split(' ');
for(var j=0;j<pclass.length;j++){
if(pclass[j] == classname){
arr.push(pele[i]);
break;
}
}
}
if(arr.length == 1){
return arr[0];
}else{
return arr;
}
},
//创建将要移动的盒子
createObj:function(){
this.mObj = document.createElement('div');
this.mObj.className = "begin";
this.gameBox.appendChild(this.mObj);
this.mObj.style.left = this.beginLeft+'px';
this.mObj.style.top = this.beginRight+'px';
},
//创建将要被推的盒子
createMbox:function(){
this.mBox = document.createElement('div');
this.mBox.className = "target";
this.gameBox.appendChild(this.mBox);
this.mBox.style.left = this.targetLeft+'px';
this.mBox.style.top = this.targetTop+'px';
},
//控制盒子移动
move:function(obj){
_this = this;
document.onkeydown=function(ev){
_this.prevleft = obj.offsetLeft;
_this.prevtop = obj.offsetTop;
_this.pushprevleft = _this.mBox.offsetLeft;
_this.pushprevtop = _this.mBox.offsetTop;
var oev = ev || event;
var key = oev.keyCode;
if(key == 87){
obj.style.top=obj.offsetTop-20+'px';
}
else if(key == 68){
obj.style.left=obj.offsetLeft+20+'px';
}
else if(key == 83){
obj.style.top=obj.offsetTop+20+'px';
}
else if(key == 65){
obj.style.left=obj.offsetLeft-20+'px';
}
_this.rang(_this.mObj,1);
_this.current(_this.mObj,1);
_this.pushbox(_this.iftouch?key:0);
}
},
rang:function(obj,no2){
if(no2==1){
if(obj.offsetLeft<=0){
obj.style.left=0+'px';
}
if(obj.offsetLeft>=this.gameBox.offsetWidth-this.mObj.offsetWidth){
obj.style.left=this.gameBox.offsetWidth-this.mObj.offsetWidth+'px';
}
if(obj.offsetTop<=0){
obj.style.top=0+'px';
}
if(obj.offsetTop>=this.gameBox.offsetHeight-this.mObj.offsetHeight){
obj.style.top = this.gameBox.offsetHeight-this.mObj.offsetHeight+'px';
}
}
if(no2==2){
if(obj.offsetLeft<0){
obj.style.left=0+'px';
this.mObj.style.top = this.prevtop+'px';
this.mObj.style.left = this.prevleft+'px';
}
if(obj.offsetLeft>this.gameBox.offsetWidth-this.mObj.offsetWidth){
obj.style.left=this.gameBox.offsetWidth-this.mObj.offsetWidth+'px';
this.mObj.style.top = this.prevtop+'px';
this.mObj.style.left = this.prevleft+'px';
}
if(obj.offsetTop<0){
obj.style.top=0+'px';
this.mObj.style.top = this.prevtop+'px';
this.mObj.style.left = this.prevleft+'px';
}
if(obj.offsetTop>this.gameBox.offsetHeight-this.mObj.offsetHeight){
obj.style.top = this.gameBox.offsetHeight-this.mObj.offsetHeight+'px';
this.mObj.style.top = this.prevtop+'px';
this.mObj.style.left = this.prevleft+'px';
}
}
},
//不能触碰的区域
forbidden:function(){
var totalBox = this.getClass('pos4');
var arr=[];
for(var i=0;i<totalBox.length;i++){
var json = {};
json.left = totalBox[i].offsetLeft;
json.top = totalBox[i].offsetTop;
json.right = totalBox[i].offsetLeft+20;
json.bottom = totalBox[i].offsetTop+20;
arr.push(json);
}
return arr;
},
//盒子当前的位置判断
current:function(obj,no){
var notouch = this.forbidden();
var curleft = obj.offsetLeft;
var curright = obj.offsetLeft+20;
var curtop = obj.offsetTop;
var curbottom= obj.offsetTop+20;
for(var i =0;i<notouch.length;i++){
if(curleft == notouch[i].left && curtop == notouch[i].top){ //如果碰到黑色的墙就退回到前一步的位置
if(no==1){
obj.style.top = this.prevtop+'px';
obj.style.left = this.prevleft+'px';
}
if(no==2){
obj.style.top = _this.pushprevtop+'px';
obj.style.left = _this.pushprevleft+'px';
this.mObj.style.top = this.prevtop+'px';
this.mObj.style.left = this.prevleft+'px';
}
}
}
},
//检测碰撞
crash:function(){
if(this.mObj.offsetLeft==this.mBox.offsetLeft-20&&this.mObj.offsetTop==this.mBox.offsetTop){
//left
this.iftouch = true;
this.direct = 'left';
this.last=2;
}
else if(this.mObj.offsetLeft==this.mBox.offsetLeft+20&&this.mObj.offsetTop==this.mBox.offsetTop){
//right
this.iftouch = true;
this.direct = 'right';
this.last=2;
}
else if(this.mObj.offsetTop==this.mBox.offsetTop-20&&this.mObj.offsetLeft==this.mBox.offsetLeft){
//top
this.iftouch = true;
this.direct = 'top';
this.last=2;
}
else if(this.mObj.offsetTop==this.mBox.offsetTop+20&&this.mObj.offsetLeft==this.mBox.offsetLeft){
//bottom
this.iftouch = true;
this.direct = 'bottom';
this.last=2;
}
},
//推盒子方法
pushbox:function(key){
this.crash();
//判断回传的keycode是多少来检测用户往哪个方向推箱子
if(key){
if(this.direct == 'left'){ //检测到从左边往右边推箱子
if(key!=68){
this.iftouch = false;
this.crash();
}else{
this.mBox.style.left=this.mBox.offsetLeft+20+'px';
this.current(this.mBox,2);
this.rang(this.mBox,2);
}
}
if(this.direct == 'top'){ //检测到从上往下推箱子
if(key!=83){
this.iftouch = false;
this.crash();
}else{
this.mBox.style.top=this.mBox.offsetTop+20+'px';
this.current(this.mBox,2);
this.rang(this.mBox,2);
}
}
if(this.direct == 'bottom'){ //检测到从下往上推箱子
if(key!=87){
this.iftouch = false;
this.crash();
}else{
this.mBox.style.top=this.mBox.offsetTop-20+'px';
this.current(this.mBox,2);
this.rang(this.mBox,2);
}
}
if(this.direct == 'right'){ //检测到从右往左推箱子
if(key!=65){
this.iftouch = false;
this.crash();
}else{
this.mBox.style.left=this.mBox.offsetLeft-20+'px';
this.current(this.mBox,2);
this.rang(this.mBox,2);
}
}
}
},
//初始化对象
init:function(){
this.createMap();
this.createObj();
this.createMbox();
this.move(this.mObj);
this.forbidden();
}
}
game.init();
</script>
</html>