HTML5物理游戏开发 - 越野山地自行车(三)粉碎自行车
自上一章发布到现在已时隔四月,实在对不住大家,让大家久等了~话说不是我不关注我的博客,而是事情一多起来写博客的时间就少了。待到今日有空了,回头看了看自己以前写的文章,猛得发现已经四个月不曾写文章了,便只得叫声:“苦也~”,我害怕本系列文章会拖得更久,于是立刻提笔,也好为本系列文章留个凤尾。
首先,大家来温习一下前面两篇里的内容吧:
HTML5物理游戏开发 - 越野山地自行车(二)创建一辆可操控的自行车
http://blog.csdn.net/yorhomwang/article/details/21300253
HTML5物理游戏开发 - 越野山地自行车(一)建立各式各样的地形
http://blog.csdn.net/yorhomwang/article/details/19710537
今天我们要实现的内容就是——当我们自行车的关键部分碰壁后,立刻使其粉碎,通俗点讲就是“自行车碎一地”。Ok,闲话何苦多说呢,我们就开始咯~
先放上两张截图吧:
※再次声明,本次开发用到了lufylegend.js开源html5游戏引擎和box2dweb物理引擎,请到官方网站下载使用。官方网站地址已在第一章中说过了。
一,粉碎原理
用过锤子的人都知道(如果你没用过,而且也不知道怎么用,建议你去问问雷神索尔),要砸碎一个自行车该怎么砸呢?如果你不会,我教你三招吧:
法一:使劲砸;这种方法适用于你想换把锤子
法二:到阿斯嘉找雷神大哥去,这个速率最快,估计不到抽完一根烟的工夫,你的自行车就只剩原子了
法三:去某个地方把锤子换成螺丝刀等工具,然后把你那自行车零件一块一块地给卸下来
显然,这三种方法各有所长,不过既然我们的自行车是一块一块地拼起来的,那么还是一块一块地给拆了好,于是,我选择了3(实际上是因为Box2dWeb没有锤子这玩意,也认不得雷神)。我们在上一章中提到过如何把零件拼起来,原理是运用了Box2dWeb里的关节,这些关节把零件们连在了一起,那么如果这些关节一销毁,那么这些零件就会散落。但是如何销毁关节呢?Box2dWeb的b2World里有一个DestroyJoint函数,参数就是你要销毁的b2Joint对象。我们来看看在lufylegend里如何销毁关节吧。
首先,在lufylegend里通过LBox2d关键关节的函数都会返回创建出的b2Joint对象,也就是说:
var j = LStage.box2d.setRevoluteJoint(a.box2dBody, b.box2dBody); LStage.box2d.world.DestroyJoint(j);我们可以把调用setRevoluteJoint创建出的旋转关节保存在变量j里,然后销毁时就直接调用LStage.box2d.world.DestroyJoint函数,参数则是j。
如果我们有多个关节怎么办呢?放数组里呗,想必聪明的你在我说这话之前就已经想到了这点吧。
原理搞定,那么我们开始看代码吧。
二,更新自行车类和Main类
上一章中我们着重讲了如何实现自行车类,这次因为要粉碎它,所以要先在它身上动手术,安放几个炸弹再说。装炸弹前要做好准备,以免把自己给炸over了,所以得先搞个盒子把要炸毁的地方装起来。恰巧Main类路过(关于Main请看第一章),我把它一手提了过来,唱声喏,道:“阁下暂且替我装两个东西,如何?”,Main类寻思道:“这厮可以掌管所有程序,万一这厮火了,一把delete把我等删个干净却不是个好事。”,于是他无可奈何地替我担负了这个重任。却说看官欲知是哪两个东西呢?原来是一个名叫jointList的数组和一个唤作gameOverController的bool型变量;这两厮一个管装所有生成的关节,一个管游戏是否结束。于是Main的构造器改造后如下:
function Main(){ var s = this; base(s,LSprite,[]); /**设置场景大小*/ s.sceneWidth = 8500; s.sceneHeight = LStage.height+1000; /**关节列表*/ s.jointList = new Array(); /**游戏结束控制器*/ s.gameOverController = false; }
却说上面那段明显有些水浒的风格,不好意思,近期《水浒传》看多了,望各位包含包含。
我们知道,炸弹都是要有点火线的,或者更高级一点的开关啊,反正就是一个引爆装置,这个工作还是交给Main吧。修改Main的init方法:
Main.prototype.init = function(){ var s = this; /**加入边框*/ s.addBorder(); /**加入路面*/ s.addRoad(); /**加入自行车*/ s.addBicycle(); /**加入刚体碰撞事件*/ LStage.box2d.setEvent(LEvent.POST_SOLVE,s.postSolve); /**加入循环事件*/ s.addEventListener(LEvent.ENTER_FRAME,s.loop); };主要是加了个调用LStage.box2d.setEvent函数。这个函数是LBox2d类的一个方法(LStage.box2d是LBox2d的实例化对象),具体的使用方法是这样子滴:
■setEvent(type, func)
参数介绍:
type box2d世界里的碰撞事件类型
func 触发事件时调度的函数
碰撞事件的类型可以为:
LEvent.BEGIN_CONTACT:刚刚碰撞开始的时候会触发这个函数
LEvent.END_CONTACT:碰撞结束的时候会触发这个函数
LEvent.POST_SOLVE:碰撞后会处理这个函数
LEvent.PRE_SOLVE:碰撞前即将碰撞的时候
这里我们选的是POST_SOLVE,也就是碰撞后会处理这个函数,其实选择其他的事件类型,效果也应该是一样的。事件触发时调度的函数是postSolve,这个函数也交给Main吧~ [Main类:说好的装两个呢(T_T)]
Main.prototype.postSolve = function(contact){ if(world.gameOverController)return; var l = world.jointList; if(l.length == 0)return; //获取碰撞的LSprite对象 var cA = contact.GetFixtureA().GetBody().GetUserData(); var cB = contact.GetFixtureB().GetBody().GetUserData(); //判断是否摧毁自行车 if( //-------------------------------------------- //条件一:当自行车和墙碰撞时 //-------------------------------------------- ( (cA.name=="wall" && cB.name=="bicycle") || (cA.name=="bicycle" && cB.name=="wall") ) || //-------------------------------------------- //条件二:当自行车的车把、车把到轮子的支架或者车座碰到其他物体时 //-------------------------------------------- ( (cA.trigger=="destroy_bicycle" && cB.name!="bicycle") || (cA.name!="bicycle" && cB.trigger=="destroy_bicycle") ) ){ //去掉自行车上的所有关节以达到催毁自行车 for(var i in l){ var jo = l[i]; //去掉关节 LStage.box2d.world.DestroyJoint(jo); //将游戏结束控制器设置为游戏结束 world.gameOverController = true; } //从自行车关节列表中移除所有关节 l.length = 0; //添加游戏结束提示 var gameOverText = new LTextField(); gameOverText.text = "Game Over"; gameOverText.size = 50; gameOverText.alpha = 0; gameOverText.x = (LStage.width-gameOverText.getWidth())*0.5; gameOverText.y = (LStage.height-gameOverText.getHeight())*0.5; addChild(gameOverText); LTweenLite.to(gameOverText,5,{ delay:1.5, alpha:1 }); } };
该函数会接受一个参数,参数是个啥对象呢?其实我也不清楚,反正里面有GetFixtureA和GetFixtureB这两个函数。这两个函数能取到正在碰撞的刚体的刚形,通过刚形的GetBody取到b2Body对象,然后用b2Body的GetUserData取到最终的LSprite对象。
参数介绍完了,我还是来介绍一下这个函数的执行逻辑吧。首先,如果游戏已经结束或者jointList为空的数组,则不再运行后面的代码;如果继续运行代码,则首先把碰撞的两个刚体所在的LSprite取出来,然后进行游戏结束判断,如果通过则执行游戏结束的代码。有人也许纳闷那个world是个啥?如果你从第一章看起,就应该会明白了。主要是postSolve是个回调函数,里面的this不是指向Main的。
在那个长达19行的判断条件里,我设定了两个条件,满足这两个条件之一,便进行销毁关节:条件一:当自行车和墙碰撞时;条件二:当自行车的车把、车把到轮子的支架或者车座碰到其他物体时。
销毁部分的代码主要是注意,在box2d里,销毁关节用DestroyJoint这个函数,这一点在“粉碎原理”中就已经提到过了,这个函数是在b2World类中的,LBox2d的world属性就是b2World的实例化对象。
能不能结束游戏关键要看碰撞的b2Body所在的LSprite对象的name和trigger(英文翻译过来是“触发器”的意思),这些属性在哪里设置的呢?当然是在构造自行车的类Bicycle类里呢。接下来就来看看Bicycle类里加入&改进的代码。
首先来看Bicycle的init函数的变化:
Bicycle.prototype.init = function(){ var s = this; var sx = s.sx; var sy = s.sy; /**轮子半径*/ var wheelR = 20; /**轮子之间的距离*/ var gapBetweenWheelAndWheel = 100; /**车手柄到轮子的距离*/ var gapBetweenWheelAndHandlebar = 50; /**车把尺寸*/ var handlebarWidth=20,handlebarHeight=5; /**座椅到轮子支架的距离*/ var gapBetweenWheelFrameAndSeat = 30; /**座椅尺寸*/ var seatWidth=30,seatHeight=5; /**支架尺寸*/ var frameSize = 10; /**加入支架*/ //轮子上的支架 var frameAObj = new LSprite(); frameAObj.x = sx+gapBetweenWheelAndWheel/2; frameAObj.y = sy+frameSize/2; frameAObj.addBodyPolygon(gapBetweenWheelAndWheel,frameSize,1,5); world.addChild(frameAObj); s.bodyList.push(frameAObj); //车把到轮子的支架 var frameBObj = new LSprite(); frameBObj.trigger = "destroy_bicycle"; frameBObj.x = sx+gapBetweenWheelAndWheel-frameSize/2; frameBObj.y = sy-gapBetweenWheelAndHandlebar/2; frameBObj.addBodyPolygon(frameSize,gapBetweenWheelAndHandlebar,1,2); world.addChild(frameBObj); s.bodyList.push(frameBObj); /**加入车把*/ var handlebarObj = new LSprite(); handlebarObj.trigger = "destroy_bicycle"; handlebarObj.x = sx+gapBetweenWheelAndWheel-handlebarWidth/2-frameSize; handlebarObj.y = sy-gapBetweenWheelAndHandlebar+handlebarHeight/2; handlebarObj.addBodyPolygon(handlebarWidth,handlebarHeight,1,.5); world.addChild(handlebarObj); s.bodyList.push(handlebarObj); /**加入座椅*/ //座椅到轮子支架的支架 var seatFrameObj = new LSprite(); seatFrameObj.x = sx+30; seatFrameObj.y = sy-gapBetweenWheelFrameAndSeat/2; seatFrameObj.addBodyPolygon(frameSize,gapBetweenWheelFrameAndSeat,1,1); world.addChild(seatFrameObj); s.bodyList.push(seatFrameObj); //座椅 var seatObj = new LSprite(); seatObj.trigger = "destroy_bicycle"; seatObj.x = sx+30; seatObj.y = sy-gapBetweenWheelFrameAndSeat-seatHeight/2; seatObj.addBodyPolygon(seatWidth,seatHeight,1,.5); world.addChild(seatObj); s.bodyList.push(seatObj); /**加入轮子*/ //左边轮子A var wheelAObj = new LSprite(); wheelAObj.x = sx-wheelR; wheelAObj.y = sy; wheelAObj.addBodyCircle(wheelR,wheelR,wheelR,1,2.5,.2,.4); world.addChild(wheelAObj); s.bodyList.push(wheelAObj); //右边轮子B var wheelBObj = new LSprite(); wheelBObj.x = sx+gapBetweenWheelAndWheel-wheelR; wheelBObj.y = sy; wheelBObj.addBodyCircle(wheelR,wheelR,wheelR,1,2.5,.2,.4); world.addChild(wheelBObj); s.bodyList.push(wheelBObj); /**添加关节*/ //轮子A和轮子支架的旋转关节 world.jointList.push(LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelAObj.box2dBody)); //轮子B和轮子支架的旋转关节 world.jointList.push(LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelBObj.box2dBody)); //车把到轮子的支架和轮子支架的焊接关节 world.jointList.push(LStage.box2d.setWeldJoint(frameAObj.box2dBody, frameBObj.box2dBody)); //车把到轮子的支架和车把的焊接关节 world.jointList.push(LStage.box2d.setWeldJoint(handlebarObj.box2dBody, frameBObj.box2dBody)); //轮子的支架和座椅的焊接关节 world.jointList.push(LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, frameAObj.box2dBody)); //座椅的支架和座椅的焊接关节 world.jointList.push(LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, seatObj.box2dBody)); /**遍历所有自行车零件刚体*/ for(var key in s.bodyList){ var obj = s.bodyList[key]; //加入鼠标拖动 if(obj.box2dBody)obj.setBodyMouseJoint(true); //设置对象名称 obj.name = "bicycle"; } /**设置主刚体*/ s.mainBody = frameAObj.box2dBody; /**设置拉压操作刚体*/ s.tcBody = wheelBObj.box2dBody; };主要改的是这里:
/**添加关节*/ //轮子A和轮子支架的旋转关节 world.jointList.push(LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelAObj.box2dBody)); //轮子B和轮子支架的旋转关节 world.jointList.push(LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelBObj.box2dBody)); //车把到轮子的支架和轮子支架的焊接关节 world.jointList.push(LStage.box2d.setWeldJoint(frameAObj.box2dBody, frameBObj.box2dBody)); //车把到轮子的支架和车把的焊接关节 world.jointList.push(LStage.box2d.setWeldJoint(handlebarObj.box2dBody, frameBObj.box2dBody)); //轮子的支架和座椅的焊接关节 world.jointList.push(LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, frameAObj.box2dBody)); //座椅的支架和座椅的焊接关节 world.jointList.push(LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, seatObj.box2dBody));我把所有的关节都加入到wolrd的jointList里了,这样一来我们就可以通过遍历取出关节来,然后进行销毁,这一点在Main的postSolve里就已经实现了。
还有修改的就是:1,给所有的属于自行车的刚体都加了一个name属性,设定为:“bicycle”;2,给关键部位的刚体(车把、车把到轮子的支架和车座)加了trigger属性,设置为“destroy_bicycle”,表示如果这些部位碰到了其他不属于自行车的刚体,就结束游戏。
至于name为"wall"的刚体其实就只有一个(bottomBorder底部边框,促使自行车跌到底部时结束游戏):
Main.prototype.addBorder = function(){ var s = this; /**创建边框*/ //设置边框尺寸 var borderSize = 10; //顶部边框 var topBorder = new LSprite(); topBorder.x = s.sceneWidth/2; topBorder.y = 5; topBorder.addBodyPolygon(s.sceneWidth,borderSize,0); s.addChild(topBorder); //右部边框 var rightBorder = new LSprite(); rightBorder.x = s.sceneWidth-5; rightBorder.y = s.sceneHeight/2; rightBorder.addBodyPolygon(borderSize,s.sceneHeight,0); s.addChild(rightBorder); //底部边框 var bottomBorder = new LSprite(); bottomBorder.name = "wall"; bottomBorder.x = s.sceneWidth/2; bottomBorder.y = s.sceneHeight-5; bottomBorder.addBodyPolygon(s.sceneWidth,borderSize,0); s.addChild(bottomBorder); //左部边框 var leftBorder = new LSprite(); leftBorder.x = 5; leftBorder.y = s.sceneHeight/2; leftBorder.addBodyPolygon(borderSize,s.sceneHeight,0); s.addChild(leftBorder); };Ok,运行代码,得到的就是本文最上方图片所示的效果了。
奉上源代码下载地址:http://files.cnblogs.com/yorhom/box2dBicycle%283%29.rar
测试地址:http://game.hdc.h5stars.com/201428053f36b0853f15~
本系列教程就到此为址了,其实如果要做一个真正的“越野山地自行车”这种游戏,还需要对刚体进行贴图,胜利判断等。这些都很简单,大家可以自己动手做一做吧~(目前我做的demo应该可以当作一种发泄工具吧,压力大了,就来把这辆虚拟的自行车拿来狠狠地摔吧~ 哈哈)
本章就先到这里了。如果文章有任何疏漏之处,欢迎指正。当然,有不懂之处也欢迎各位在本文下方留言,我会尽力回复大家的。
----------------------------------------------------------------
欢迎大家转载我的文章。
转载请注明:转自Yorhom‘s Game Box
http://blog.csdn.net/yorhomwang
欢迎继续关注我的博客
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。