您的位置:首页 > Web前端 > HTML5

HTML5吃豆豆游戏开发实战(四)2d碰撞检测、重构-第二篇

2015-04-04 17:56 906 查看
           今天下午在家没事,写代码。先总结一下学习HTML5和JS的一些经验,再来说游戏的事吧!

        这完全是一个HTML5和JS的入门新手的见解,如果您和我一样是新手的话,欢迎交流,当然,高手如果不介意的话,帮小弟指点一二那就更好啦,谢谢,嘿嘿!入正题吧!

        1.语法方面

            1.1  JS关于数组的定义方法要注意:

                  比如:var walls = [new Wall(262,200,100,30),new Wall(662,60,30,400),new Wall(762,300,200,30)];

                  这是定义一个墙壁对象数组,基本元素是Wall对象,然后是构造函数(坐标,长宽)

                  更多的关于JS数组定义的可以看这里:JS定义数组以及初始化

            1.2  JS定义了一个类,在类的成员变量初始化的时候,似乎不可以调用成员函数来初始化它??

                   比如:

                  function Wall(x_,y_,width_,height_)
{
this.x = x_;
this.y = y_;
this.width = width_;
this.height = height_;
this.test = this.M();
/**
* @return {number}
*/
this.M = function()
{
alert("....");
return 1;
};.....(只是部分代码)

这样就不会看到alert效果,怎么回事呢?恳请各位指点下我初学者。

               1.3 注意我写的代码中的注释:

               

this.hero = hero_;        //玩家
this.walls = walls_;      //阻碍物数组
this.enemys = enemy_;     //敌人数组
this.isFind = false;      //临时变量,是否找到
this.tempWall = null;     //临时变量
// alert("cc");
this.walls_y = this.walls.slice();  //这里不要写walls_,因为写这个的话,就和this.walls指向一个地方,排序后,walls也变了,终于找到原因,所以我用了复制

             函数传参的时候,若传的是对象,则是引用传递,如果是传数值类型,则是值传递。

           2. 一点点感想,JS是弱类型语言,var其实用着开始还觉得不习惯,因为以前一直写C,C++,JAVA,C#之类的强类型语言的原因吧,但是后来还是感觉很方便的,因为定义一个变量就是var,数值类型不用自己去考虑,但是这样也会有它的缺点,因为可能存在数据类型的使用是否正确,有没有重复定义、相互覆盖之类的,程序运行前的正确性判断基本靠人了,出错的可能性比较大,这里要注意,尤其是变量名的重复要注意!

          3.关于重构的一点点初步认识

        好的结构是后面更大的发展的基础,如果架子都不稳,怎么建高楼,所以我觉得重构确实还是很有必要的,尤其是稍微大一点点的,层次结构又模糊的项目,如果需要继续写下去,就最好重构一下。

       我个人认为重构的目的就是理清该项目的脉络,使各块代码到正确的位置上,而不是“你中有我,我中有你”

这种混乱不堪的局面,嘿嘿,比喻比喻,把代码模块儿理清楚,哪一部分该做什么,这样自己看得清楚,别人也看得清楚,同时为以后的扩展也打下基础。一个好的结构是很有必要的。

         好了,总结完毕,回到游戏中来。

         这是目前的工程文件目录截图:

        

       


             

        然后说一下完成的碰撞检测吧,其实核心就是坐标的比较,图形都很规范,所以不涉及复杂计算。

        当然,具体说一下,当玩家在同一个方向前进时,找到离自己最近的障碍物wallTemp,这就是继续在该方向行走下去一定会碰到的,只寻找一次,找到了把isFind=true。如果改变方向,则重新寻找wallTemp。玩家在某方向移动时,则比较自己和wallTemp之间的距离即可。上下左右都是这样。

        全部代码:

        1.<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>物体移动简单演示</title>
</head>
<!--注意在body这里添加按键响应事件,引号内为函数名-->
<body onkeydown="onGetKey()">
<h1>小球上下移动</h1>
<canvas id="test" width="1200px" height="600px" style="background-color: white"></canvas>
<!--把引用的资源引入本页面-->
<script type="text/javascript" src="Ball.js"></script>
<script type="text/javascript" src="Wall.js"></script>
<script type="text/javascript" src="Collide.js"></script>
<script type="text/javascript" src="GameSys.js"></script>
<script type="text/javascript" src="Control.js"></script>
<script type="text/javascript">
//1.定义游戏世界原点坐标 窗口宽高
var Ox = 162;
var Oy = 0;
var WinWidth = 900;
var WinHeight = 500;
//2.实例化游戏管理类
var mana = new GameSys(Ox,Oy,WinWidth,WinHeight);
//3.获取画布
var can = document.getElementById("test");
var cxt = can.getContext("2d");
//4.实例化小球对象
var ball = new Ball(30 + mana.ox,30,20,3);
//walls先按x从小到大排序
//5.
var walls = [new Wall(262,200,100,30),new Wall(662,60,30,400),new Wall(762,300,200,30)];
//6.
FreshWindow(162,0,900,500,walls);
//7.玩家控制类
var ctrl = new Control(ball,walls,null);
//8.设置小球的动画循环
setInterval("ball.drawBall()",100);

function FreshWindow(ox,oy,winWidth,winHeight,walls_) //参数代表地图上的静态景物
{
cxt.clearRect(ox,oy,winWidth,winHeight); //清理画布
cxt.strokeRect(ox,oy,winWidth,winHeight);
//document.write("xx");
for(var i=0;i<walls_.length;i++)
{
cxt.strokeRect(walls_[i].x,walls_[i].y,walls_[i].width,walls_[i].height);
}
// alert("alter");
}
function onGetKey()
{
ctrl.moveCtrl();

}
</script>
</body>
</html>

        2.

//玩家操控类,封装了W,A,S,D等等操作
function Control(hero_,walls_,enemies_)
{
this.keyTemp = 0;//按键寄存器,存放上一次的按键值
this.ball = hero_;
this.wall = walls_;
this.enemy = enemies_;
this.collide = new Collide(hero_,walls_,enemies_);
this.moveCtrl = function()
{
var code = event.keyCode;//对应字母的ascii
//如果上一次的按键和这一次的不相等
if(this.keyTemp != code)
{
this.keyTemp = code;         //更新按键值
this.collide.ColideReferenceClean();  //修改了方向,清空上一次的计算值(待碰撞物体),重新的方向计算新的待碰撞物体
}
switch(code)
{
//数字是ASCII码,记不住的话可以对照ASCII码表
//上
case 87:
this.ball.dir=0;
// document.write("c");
//if(this.ball.getMinY() >= mana.topY)
if( !this.collide.IsCollide() && this.ball.getMinY() >= mana.topY)
this.ball.moveUp();
break;
//下
case 83:
this.ball.dir=1;
//if(this.ball.getMaxY() <= mana.buttomY)
if( !this.collide.IsCollide() && this.ball.getMaxY() <= mana.buttomY)
this.ball.moveDown();
break;
//左
case 65:
this.ball.dir=2;
if( !this.collide.IsCollide() && this.ball.getMinX() >= mana.leftEgdeX)
this.ball.moveLeft();
break;
//右
case 68:
this.ball.dir=3;
if( !this.collide.IsCollide() && this.ball.getMaxX() <= mana.rightEdgeX)
this.ball.moveRight();
break;
default :break;
}
};
}

3.

//小球类
//注意js里面类的定义方法,直接类加函数参数的形式,就相当于定义了,然后里面直接this.xx=xx_,很高效
//参数:x,y坐标,球的方向,球的半径,运动速度
function Ball(x_,y_,r_,sp_)
{

this.x = x_;
this.y = y_;
this.dir = 0;
this.r = r_;    //灰色表示还没用
this.sp = sp_;
this.state = 0;
//定义上下左右:0,1,2,3
this.moveUp = function()
{
this.y -= this.sp;//上
this.dir = 0;
};
this.moveDown = function()
{
this.y += this.sp;//下
this.dir = 1;
};
this.moveLeft = function()
{
this.x -= this.sp;//左
this.dir = 2;
};
this.moveRight = function()
{
this.x += this.sp;//右
this.dir = 3;
};
//获得小球的坐标
this.getX = function()
{
return this.x;
};
this.getY = function()
{
return this.y;
};
//获得球的各个方向的边界值
this.getMaxX = function() {
return this.x + this.r;
};
this.getMaxY = function()
{
return this.y + this.r;
};
/**
* @return {number}
*/
this.getMinX = function()
{
return this.x - this.r;
};
/**
* @return {number}
*/
this.getMinY = function()
{
return this.y - this.r;
};
this.getDir = function()
{
return this.dir;
};
this.drawBall = function()
{
FreshWindow(Ox,Oy,WinWidth,WinHeight,walls);
switch (this.dir)
{
case 0:
this.drawBall_UpOrDown(true);
break;
case 1:
this.drawBall_UpOrDown(false);
break;
case 2:
this.drawBall_RightOrLeft(false);
break;
case  3:
this.drawBall_RightOrLeft(true);
break;
default :
break;
}
};
this.drawBall_RightOrLeft = function(isRight)
{
//document.write(state);
//画眼睛,眼睛是公共的
//画眼睛-外圈
var eyeX;
if(isRight == true)     //右
eyeX = this.x - 5;
else eyeX = this.x + 5;//左
var eyeY = this.y-8;
var eyeR = 6;//目前限定死这个
cxt.beginPath();
cxt.fillStyle="#000000";
cxt.arc(eyeX,eyeY,eyeR,0,Math.PI * 2,false);
cxt.fill();
cxt.closePath();
//画眼睛-眼球
var qiuR = eyeR / 2;
cxt.beginPath();
cxt.fillStyle="#FF0000";
cxt.arc(eyeX,eyeY,qiuR,0,Math.PI * 2,false);
cxt.fill();
cxt.closePath();
switch(this.state)
{
//张嘴
case 1:
//画红球
cxt.beginPath();
cxt.fillStyle="#FF0000";
//嘴巴大小为90°
//画圆弧--脸
if(isRight)
cxt.arc(this.x,this.y,this.r,1/4 * Math.PI,3/2 * Math.PI + 1/4 * Math.PI,false);
else
cxt.arc(this.x,this.y,this.r,3/4 * Math.PI, Math.PI + 1/4 * Math.PI,true);
cxt.stroke();
cxt.closePath();
cxt.beginPath();
//画嘴巴
var ax = 0,ay = 0;
var bx = 0,by = 0;
var temp = this.r * Math.sqrt(2)/2;
if(isRight)
ax = this.x + temp;
else
ax = this.x - temp;
ay = this.y - temp;
bx = ax;
by = this.y + temp;
cxt.moveTo(this.x,this.y);
cxt.lineTo(ax,ay);
cxt.moveTo(this.x,this.y);
cxt.lineTo(bx,by);
cxt.closePath();
cxt.stroke();
this.state = 0;
break;
//闭嘴
case 0:
//画圆弧--脸
cxt.beginPath();
cxt.arc(this.x,this.y,this.r,0,Math.PI * 2,false);
cxt.stroke();
cxt.closePath();
//从圆心到嘴巴末点的连线
cxt.beginPath();
cxt.moveTo(this.x,this.y);
if(isRight)
cxt.lineTo(this.x + this.r,this.y);
else
cxt.lineTo(this.x - this.r,this.y);
cxt.stroke();
cxt.closePath();
this.state = 1;
break;
default :
break;
}
};
this.drawBall_UpOrDown = function(isUp)
{
//document.write(state);
//画眼睛,眼睛是公共的
//画眼睛-外圈
var eyeX = this.x - 5;
var eyeY = this.y + 8;
if(!isUp)
{
eyeX = this.x + 5;
eyeY = this.y - 8;
}

var eyeR = 6;//目前限定死这个
cxt.beginPath();
cxt.fillStyle="#000000";
cxt.arc(eyeX,eyeY,eyeR,0,Math.PI * 2,false);
cxt.fill();
cxt.closePath();
//画眼睛-眼球
var qiuR = eyeR / 2;
cxt.beginPath();
cxt.fillStyle="#FF0000";
cxt.arc(eyeX,eyeY,qiuR,0,Math.PI * 2,false);
cxt.fill();
cxt.closePath();
switch(this.state)
{
//张嘴
case 1:
//画红球
cxt.beginPath();
cxt.fillStyle="#FF0000";
//嘴巴大小为90°
//画圆弧--脸
if(!isUp)
cxt.arc(this.x,this.y,this.r,1/4 * Math.PI ,3/4 * Math.PI,true);
else
cxt.arc(this.x,this.y,this.r,Math.PI +  1/4 * Math.PI,3/2 * Math.PI+  1/4 * Math.PI,true);
cxt.stroke();
cxt.closePath();
cxt.beginPath();
//画嘴巴
var ax = 0,ay = 0;
var bx = 0,by = 0;
var temp = this.r * Math.sqrt(2)/2;
ax = this.x - temp;
ay = this.y - temp;
by = ay;
bx = this.x + temp;
if(!isUp)
{
ax = this.x + temp;
ay = this.y + temp;
by = ay;
bx = this.x - temp;
}
cxt.moveTo(this.x,this.y);
cxt.lineTo(ax,ay);
cxt.moveTo(this.x,this.y);
cxt.lineTo(bx,by);
cxt.closePath();
cxt.stroke();
this.state = 0;
break;
//闭嘴
case 0:
//画圆弧--脸
cxt.beginPath();
cxt.arc(this.x,this.y,this.r,0,Math.PI * 2,false);
cxt.stroke();
cxt.closePath();
//从圆心到嘴巴末点的连线
cxt.beginPath();
cxt.moveTo(this.x,this.y);
if(!isUp)
cxt.lineTo(this.x ,this.y + this.r);
else
cxt.lineTo(this.x ,this.y- this.r);
cxt.stroke();
cxt.closePath();
this.state = 1;
break;
default :
break;
}

};
}

4.

//2d碰撞检测引擎
//参数:玩家,阻碍物数组,敌人数组
function Collide(hero_,walls_,enemy_)
{
this.hero = hero_;        //玩家
this.walls = walls_;      //阻碍物数组
this.enemys = enemy_;     //敌人数组
this.isFind = false;      //临时变量,是否找到
this.tempWall = null;     //临时变量
// alert("cc");
this.walls_y = this.walls.slice();  //这里不要写walls_,因为写这个的话,就和this.walls指向一个地方,排序后,walls也变了,终于找到原因,所以我用了复制
this.isSort = false;      //数组是否排序。好像不能调用类中的函数,调用了没反应。。。。

alert(this.walls);
/**
* @return {boolean}
*/
//检测是否碰到,返回true,碰到,else没碰到
this.IsCollide = function()
{
//是否找到离自己最近的墙壁

//如果没有找到,则寻找

switch (this.hero.getDir())
{

//上:
case 0:
if(this.tempWall == null)
{
//     alert("wall_已被清空");
this.tempWall = this.getUpNestWall();
}
else
{
if(this.hero.getMinY() <= this.tempWall.getWY()+this.tempWall.getHeight())
{

//  alert(this.tempWall.getWX());
return true;
}

}
break;
//下
case 1:
if(this.tempWall == null)
{
//     alert("wall_已被清空");
this.tempWall = this.getButtomNestWall();
}
else
{
if(this.hero.getMaxY() >= this.tempWall.getWY())
{

//  alert(this.tempWall.getWX());
return true;
}

}
break;
//左
case 2:
// alert("按左");//因为按下左的瞬间,球是先判断,并不是先转向,所以此时的方向还是上一个方向,这样便造成了错误,逻辑bug,应该按下后先转向,再判断该方向的障碍

if(this.tempWall == null)
{
//     alert("wall_已被清空");
this.tempWall = this.getLeftNestWall();
}
else
{
if(this.hero.getMinX() <= this.tempWall.getWX()+this.tempWall.getWidth())
{

alert(this.tempWall.getWX());
return true;
}

}
break;
//右
case 3:
//alert(this.hero.getMaxX());
if(this.tempWall == null)
{
//alert("NULL");
this.tempWall = this.getRightNestWall();
}
else
{

//alert("墙壁的是:");
return this.hero.getMaxX() >= this.tempWall.getWX();
}
break;
default : return false;
break;

}
};
//获得向右方向离自己最近的墙壁
this.getRightNestWall = function ()
{
var y;
y = 0;
var temp = 0;
//往右
y = this.hero.getY();
var i;
var wallTemp = null;
if(!this.isFind)
{
for(i = 0;i<this.walls.length;i++)
{
temp = this.walls[i].getWY();
if(y >= temp && y<=temp + this.walls[i].height)
{
if(this.hero.getMaxX() <= this.walls[i].getWX())
{
wallTemp = this.walls[i];
// alert("......find");
alert("i="+i+"x="+wallTemp.getWX()+"y="+wallTemp.getWY()+"width="+wallTemp.getWidth()+"height="+wallTemp.getHeight());
this.isFind = true;      //找到
// alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
return wallTemp;//已经找到自己前进方向上离自己最近的墙壁,不再寻找,跳出循环
}
}

}

}
};
//获得左边离自己最近的墙
this.getLeftNestWall = function()
{
var y;
y = 0;
var temp = 0;
//往左
y = this.hero.getY();
var i;
var wallTemp = null;
var end = this.walls.length - 1;
if(!this.isFind)
{
for(i = end;i>=0;i--)
{
temp = this.walls[i].getWY();
if(y >= temp && y<=temp + this.walls[i].height)
{
if(this.hero.getMinX() >= this.walls[i].getWX() + this.walls[i].getWidth() )
{

wallTemp = this.walls[i];
// alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);

this.isFind = true;      //找到
//alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
return wallTemp;//已经找到自己前进方向上离自己最近的墙壁,不再寻找,跳出循环
}
}

}

}
};
//获得上边离自己最近的墙
this.getUpNestWall = function()
{
//如果没有排序,先排序
if(this.isSort == false)
{
this.sortBy_y(this.walls_y);
this.isSort = true;
//alert(this.walls_y);
}
else
{
var x = 0;
var temp = 0;
//往右
x = this.hero.getX();
var i;
var wallTemp = null;
var end = this.walls_y.length - 1;
if(!this.isFind)
{
for(i = end;i >= 0;i--)
{
temp = this.walls_y[i].getWX();
if(x >= temp && x<=temp + this.walls_y[i].width)
{
if(this.hero.getMinY() >= this.walls_y[i].getWY() + this.walls_y[i].height)
{
wallTemp = this.walls_y[i];
// alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
this.isFind = true;      //找到
//alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
return wallTemp;//已经找到自己前进方向上离自己最近的墙壁,不再寻找,跳出循环
}
}

}

}
}
};
this.getButtomNestWall = function()
{
//如果没有排序,先排序
if(this.isSort == false)
{
this.sortBy_y(this.walls_y);
this.isSort = true;
//alert(this.walls_y);
}
else
{
var x = 0;
var temp = 0;
//往右
x = this.hero.getX();
var i;
var wallTemp = null;
if(!this.isFind)
{
for(i = 0;i<this.walls_y.length;i++)
{
temp = this.walls_y[i].getWX();
if(x >= temp && x<=temp + this.walls_y[i].width)
{
if(this.hero.getMaxY() <= this.walls_y[i].getWY())
{
wallTemp = this.walls_y[i];
// alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
this.isFind = true;      //找到
//alert("i="+i+"y="+wallTemp.getWY()+"height="+wallTemp.getHeight()+"temp="+temp+"dir="+ball.getDir()+"isRight="+isRight);
return wallTemp;//已经找到自己前进方向上离自己最近的墙壁,不再寻找,跳出循环
}
}

}

}
}
};
//排序,根据y来排,由小到大
//冒泡排序算法
this.sortBy_y = function(arr)
{
// alert("endxxxxx");
var temp = null;
var l =  arr.length;
//alert("length="+l);
for(var i = 0;i < l-1;i++)
{
for(var j = 0;j<l -1- i;j++)
{

if(arr[j].getWY() >= arr[j + 1].getWY())
{
//交换
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}

}
}

};
this.ColideReferenceClean = function()
{
this.isFind=false;
//存放离自己最近的、如果继续直线前进不转向将会碰撞的wall
this.tempWall=null;
// alert("clean");
};
}


5.

//游戏系统管理类

function GameSys(ox_,oy_,winWidth_,winHeight_)
{
// this.can = document.getElementById(id_);//画布id
// this.cxt = can.getContext("2d");
this.ox = ox_;                      //游戏窗口的坐标原点
this.oy = oy_;
this.winWidth = winWidth_;          //窗口宽高
this.winHeight = winHeight_;
this.rightEdgeX = ox_ + winWidth_;  //四周边界值
this.leftEgdeX =  ox_;
this.topY =   oy_;
this.buttomY = oy_ + winHeight_;
this.walls = null;                  //地图上的静态景物,不会多,也不会少的物体,是一个数组

document.write("xx");

}

6.

function Wall(x_,y_,width_,height_)
{
this.x = x_;
this.y = y_;
this.width = width_;
this.height = height_;
this.getWX = function()
{
return this.x;
};
this.getWY = function()
{
return this.y;
};
this.getWidth = function()
{
return this.width;
};
this.getHeight = function()
{
return this.height;
};

}


游戏运行截图:



                 

          

        

介于近期有其他优先级更高的事要做,此篇暂停更。抱歉。


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息