HTML5吃豆豆游戏开发实战(四)2d碰撞检测、重构-第二篇
今天下午在家没事,写代码。先总结一下学习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; }; }
游戏运行截图:
一步步来完善吧!加油。。。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。