• 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://yuehaowang.github.io/demo/ridebike_box2d/

    本系列教程就到此为止了,事实上假设要做一个真正的“越野山地自行车”这样的游戏,还须要对刚体进行贴图,胜利推断等。这些都非常easy,大家能够自己动手做一做吧~(眼下我做的demo应该能够当作一种发泄工具吧,压力大了,就来把这辆虚拟的自行车拿来狠狠地摔吧~ 哈哈)

    还有同学(这会儿但是真正意义上的同学了...)问我怎样操作,好吧。算我失误。没有在本章告诉大家(事实上你看了第两章就会知道的),在这里补充一下。

    操作说明:上下左右键操作,至于这些按键相应的效果就自己摸索吧,实在总结不出来就去第二章慢慢找吧,我也就偷个懒吧~


    本章就先到这里了。假设文章有不论什么疏漏之处,欢迎指正。当然,有不懂之处也欢迎各位在本文下方留言。我会尽力回复大家的。

    ----------------------------------------------------------------

    欢迎大家转载我的文章。

    转载请注明:转自Yorhom's Game Box

    http://blog.csdn.net/yorhomwang

    欢迎继续关注我的博客

  • 相关阅读:
    C#单例模式的三种写法转载
    silverlight 添加配置项
    oracle 如何实现上一条、下一条、查找不连续的值
    一个IT民工眼中的保障房不能保证公平,赞成取消保障房
    c# where 转载
    进度条 silverlight
    中国软件公司我深表认同:软硬结合
    计算经纬度两点之间的距离(c#)
    如何高效使用SQLITE .NET (C#)
    如何判断系统是否安装了flash插件
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/7353301.html
Copyright © 2020-2023  润新知