HTML5物理游戏开发 - 越野山地自行车(二)创建一辆可操控的自行车

第二章又拖到现在才发布,话说本次更新离上次已经很久了。不知道大家还记得上一章讲的内容否?

在上一章中,我们创建了各式各样的地形,今天我们就在这个地形之上,创建一辆自行车,并让它受到我们的控制。首先放上一张截图:


没看过上一章的同学可以先移步到上一章。在了解上一章的内容之后,读起本章方可容易一些。

HTML5物理游戏开发 - 越野山地自行车(一)建立各式各样的地形

http://blog.csdn.net/yorhomwang/article/details/19710537


※再次声明,本次开发用到了lufylegend.js开源html5游戏引擎和box2dweb物理引擎,请到官方网站下载使用。官方网站地址已在第一章中说过了。


一,基础知识

首先我们来科普几个知识,只有了解这些知识之后,读起下面的内容才不会感觉头疼。

1,如何使刚体移动

在box2dweb中,要想使刚体移动,不能单纯地改变x、y坐标。其一,这样会使你的游戏失去了物理运动的效果;其二,在box2dweb中,直接调整刚体的位置是一个十分不好的方法,会违背物理运动原理,只有在刚体创建前的时候才用这个方法。所以在我们的自行车创建出来以后,想要移动刚体,最好的方法就是给刚体施加一个力。

在box2dweb中,施加力的方法有:ApplyForce、ApplyImpulse、SetLinearVelocity。本次使用的只有ApplyForce。其余的几种可以暂且不管。如果你确实想了解的话,可以看看ladeng6666的这篇文章:让刚体听我的——ApplyForce、ApplyImpulse、SetLinearVelocity

ApplyForce的用法如下:

ApplyForce(vec, pos)

这个函数的第个一个参数是给物体施加力的向量(b2Vec2);第二个pos是施加的位置,一般不确定位置的时候就取物体的重心(可以用b2Body的GetWorldCenter获取)。


2,如何构造像自行车这样形状复杂的物体

如何构造像自行车这样形状复杂的物体确实是一个值得思考的话题。对我们而言,第一个出现在脑子里的想法就是创建一个多边形刚体。当然这样实现起来极其麻烦,而且不仅仅是麻烦,做到最后更扫兴的是使用这种方法弄出来的刚体碰撞检测有问题,换言之,一个原本是凹凸多边形的刚体,突然就成了柔体,碰在其他物体上就会渗入其他物体里,so qipa~

那到底应该怎么办呢?我们不妨假设自己就真正地在制作一辆自行车。首先,我们要把材料和工具准备好。材料就是几个木块,工具就是一个锤子,一把铁钉。接下来,我们要做的就是用锤子铁钉把几个木块组装起来就ok了。这样看起来虽然so easy,但是有朋友会问,在box2dweb里到哪里去找锤子铁钉呢?锤子铁钉在box2dweb里倒是真没有,不过有一个更先进的东西——关节(joint)。

在这里主要就是需要两种关节:旋转关节(用于把轮子和支架绑起来),焊接关节(用于把各个支架固定起来)。

这两个关节在box2dweb里的使用方法依然不是那么简单,因此同样用到了lufylegend.js的封装。接下来就对这几个关节的用法进行说明。

■setRevoluteJoint(b2BodyA, b2BodyB, limits, motors)

b2BodyA:表示物体A (b2Body对象,可以用LSprite的box2dBody属性获取)
b2BodyB:表示物体B(b2Body对象,可以用LSprite的box2dBody属性获取)
limits:表示旋转角度限制数组,这个数组的内容是:[最小角度,最大角度],它在这里可以限制旋转关节旋转的角度(可以不传)
motors:表示马达数组,这个数组的内容是:[力度,速度],马达可以有很多用途,在这里,它可以是关节自动进行旋转(可以不传)

示例:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. var backLayer,cLayer;  
  2.     function main(){      
  3.     LGlobal.setDebug(true);   
  4.     backLayer = new LSprite();    
  5.     addChild(backLayer);      
  6.   
  7.     LGlobal.box2d = new LBox2d();  
  8.     cLayer = new LSprite();  
  9.     cLayer.x = 300;  
  10.     cLayer.y = 390;  
  11.     backLayer.addChild(cLayer);  
  12.     cLayer.addBodyPolygon(600,10,0,5,0.4,0.2);  
  13.     //加入一个动态的圆形物体1  
  14.     box01 = new LSprite();  
  15.     box01.x = 250;  
  16.     box01.y = 200;  
  17.     backLayer.addChild(box01);  
  18.     box01.addBodyCircle(100,0,0,1,1,0.4,0.2);  
  19.     box01.setBodyMouseJoint(true);  
  20.     //加入一个静态的圆形物体2  
  21.     box02 = new LSprite();  
  22.     box02.x = 250;  
  23.     box02.y = 150;  
  24.     backLayer.addChild(box02);  
  25.     box02.addBodyCircle(10,0,0,0,1,0.4,0.2);  
  26.     //加入一个旋转关节  
  27.     LGlobal.box2d.setRevoluteJoint(box01.box2dBody, box02.box2dBody ,[-360,720*5],[450,2]);  
  28. }  

■setWeldJoint (b2BodyA, b2BodyB)

b2BodyA:表示捆绑对象物体A(b2Body对象,可以用LSprite的box2dBody属性获取)
b2BodyB:表示捆绑对象物体B(b2Body对象,可以用LSprite的box2dBody属性获取)

示例:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. var backLayer,cLayer;  
  2. function main(){      
  3.     LGlobal.setDebug(true);   
  4.     backLayer = new LSprite();    
  5.     addChild(backLayer);      
  6.    
  7.     LGlobal.box2d = new LBox2d();  
  8.     cLayer = new LSprite();  
  9.     cLayer.x = 300;  
  10.     cLayer.y = 390;  
  11.     backLayer.addChild(cLayer);  
  12.     cLayer.addBodyPolygon(600,10,0,5,0.4,0.2);  
  13.     //加入一个动态的圆形物体1  
  14.     box01 = new LSprite();  
  15.     box01.x = 200;  
  16.     box01.y = 100;  
  17.     backLayer.addChild(box01);  
  18.     box01.addBodyCircle(50,0,0,1,1,0.4,0.2);  
  19.     box01.setBodyMouseJoint(true);  
  20.     //加入一个动态的圆形物体2  
  21.     box02 = new LSprite();  
  22.     box02.x = 250;  
  23.     box02.y = 100;  
  24.     backLayer.addChild(box02);  
  25.     box02.addBodyCircle(50,0,0,1,1,0.4,0.2);  
  26.     box02.setBodyMouseJoint(true);  
  27.     //加入一个焊接关节  
  28.     LGlobal.box2d.setWeldJoint(box01.box2dBody, box02.box2dBody);  
  29. }  

ok,基础知识差不多讲完了。进入正题吧。


二,修改Main类

上一章中的Main类大家还记得否?上次我没有加入真正的自行车,而是拿一个圆形小球在那里充当着,主要是给大家看看镜头跟随效果和各式各样的地形。这次既然要实现一个自行车,那么就要先把小球换掉,于是更改addBicycle函数,更新后如下:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Main.prototype.addBicycle = function(){  
  2.     var s = this;  
  3.   
  4.     //创建自行车对象  
  5.     s.bicycleObj = new Bicycle(50,385);  
  6.     s.addChild(s.bicycleObj);  
  7. };  
这里明显用到了Bicycle这个类。那么,我就立刻把笔尖指向Bicycle类吧,这个类是本章的重点内容,前面的基础讲解就是为这个类作铺垫的。


三,自行车类(Bicycle)

先看看构造器:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. function Bicycle(sx,sy){  
  2.     var s = this;  
  3.     base(s,LSprite,[]);  
  4.   
  5.     /**初始坐标*/  
  6.     s.sx = sx;  
  7.     s.sy = sy;  
  8.   
  9.     /**刚体所属LSprite对象列表*/  
  10.     s.bodyList = new Array();  
  11.   
  12.     /**添加左右移动力度向量*/  
  13.     s.moveVec = new LStage.box2d.b2Vec2();  
  14.     /**添加拉压操作力度向量*/  
  15.     s.tcVec = new LStage.box2d.b2Vec2();  
  16.   
  17.     /**添加事件*/  
  18.     //键盘按下事件  
  19.     LEvent.addEventListener(window,LKeyboardEvent.KEY_DOWN,function(e){  
  20.         s.onKeydown(e,s);  
  21.     });  
  22.     //键盘松开事件  
  23.     LEvent.addEventListener(window,LKeyboardEvent.KEY_UP,function(e){  
  24.         s.onKeyup(e,s);  
  25.     });  
  26.   
  27.     //初始化  
  28.     s.init();  
  29. }  

构造器代码加入了详细的注释,所以我们直接进入讲解Bicycle的init函数。这是代码:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Bicycle.prototype.init = function(){  
  2.     var s = this;  
  3.   
  4.     var sx = s.sx;  
  5.     var sy = s.sy;   
  6.   
  7.     /**轮子半径*/  
  8.     var wheelR = 20;  
  9.     /**轮子之间的距离*/  
  10.     var gapBetweenWheelAndWheel = 100;  
  11.     /**车手柄到轮子的距离*/  
  12.     var gapBetweenWheelAndHandlebar = 50;  
  13.     /**车把尺寸*/  
  14.     var handlebarWidth=20,handlebarHeight=5;  
  15.     /**座椅到轮子支架的距离*/  
  16.     var gapBetweenWheelFrameAndSeat = 30;  
  17.     /**座椅尺寸*/  
  18.     var seatWidth=30,seatHeight=5;  
  19.     /**支架尺寸*/  
  20.     var frameSize = 10;  
  21.   
  22.     /**加入支架*/  
  23.     //轮子上的支架  
  24.     var frameAObj = new LSprite();  
  25.     frameAObj.x = sx+gapBetweenWheelAndWheel/2;  
  26.     frameAObj.y = sy+frameSize/2;  
  27.     frameAObj.addBodyPolygon(gapBetweenWheelAndWheel,frameSize,1,5);  
  28.     world.addChild(frameAObj);  
  29.     s.bodyList.push(frameAObj);  
  30.     //车把到轮子的支架  
  31.     var frameBObj = new LSprite();  
  32.     frameBObj.x = sx+gapBetweenWheelAndWheel-frameSize/2;  
  33.     frameBObj.y = sy-gapBetweenWheelAndHandlebar/2;  
  34.     frameBObj.addBodyPolygon(frameSize,gapBetweenWheelAndHandlebar,1,2);  
  35.     world.addChild(frameBObj);  
  36.     s.bodyList.push(frameBObj);  
  37.   
  38.     /**加入车把*/  
  39.     var handlebarObj = new LSprite();  
  40.     handlebarObj.x = sx+gapBetweenWheelAndWheel-handlebarWidth/2-frameSize;  
  41.     handlebarObj.y = sy-gapBetweenWheelAndHandlebar+handlebarHeight/2;  
  42.     handlebarObj.addBodyPolygon(handlebarWidth,handlebarHeight,1,.5);  
  43.     world.addChild(handlebarObj);  
  44.     s.bodyList.push(handlebarObj);  
  45.   
  46.     /**加入座椅*/  
  47.     //座椅到轮子支架的支架  
  48.     var seatFrameObj = new LSprite();  
  49.     seatFrameObj.x = sx+30;  
  50.     seatFrameObj.y = sy-gapBetweenWheelFrameAndSeat/2;  
  51.     seatFrameObj.addBodyPolygon(frameSize,gapBetweenWheelFrameAndSeat,1,1);  
  52.     world.addChild(seatFrameObj);  
  53.     s.bodyList.push(seatFrameObj);  
  54.     //座椅  
  55.     var seatObj = new LSprite();  
  56.     seatObj.x = sx+30;  
  57.     seatObj.y = sy-gapBetweenWheelFrameAndSeat-seatHeight/2;  
  58.     seatObj.addBodyPolygon(seatWidth,seatHeight,1,.5);  
  59.     world.addChild(seatObj);  
  60.     s.bodyList.push(seatObj);  
  61.   
  62.     /**加入轮子*/  
  63.     //左边轮子A  
  64.     var wheelAObj = new LSprite();  
  65.     wheelAObj.x = sx-wheelR;  
  66.     wheelAObj.y = sy;  
  67.     wheelAObj.addBodyCircle(wheelR,wheelR,wheelR,1,2.5,.2,.4);  
  68.     world.addChild(wheelAObj);  
  69.     s.bodyList.push(wheelAObj);  
  70.     //右边轮子B  
  71.     var wheelBObj = new LSprite();  
  72.     wheelBObj.x = sx+gapBetweenWheelAndWheel-wheelR;  
  73.     wheelBObj.y = sy;  
  74.     wheelBObj.addBodyCircle(wheelR,wheelR,wheelR,1,2.5,.2,.4);  
  75.     world.addChild(wheelBObj);  
  76.     s.bodyList.push(wheelBObj);  
  77.       
  78.     /**添加关节*/  
  79.     //轮子A和轮子支架的旋转关节  
  80.     LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelAObj.box2dBody);  
  81.     //轮子B和轮子支架的旋转关节  
  82.     LStage.box2d.setRevoluteJoint(frameAObj.box2dBody, wheelBObj.box2dBody);  
  83.     //车把到轮子的支架和轮子支架的焊接关节  
  84.     LStage.box2d.setWeldJoint(frameAObj.box2dBody, frameBObj.box2dBody);  
  85.     //车把到轮子的支架和车把的焊接关节  
  86.     LStage.box2d.setWeldJoint(handlebarObj.box2dBody, frameBObj.box2dBody);  
  87.     //轮子的支架和座椅的焊接关节  
  88.     LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, frameAObj.box2dBody);  
  89.     //座椅的支架和座椅的焊接关节  
  90.     LStage.box2d.setWeldJoint(seatFrameObj.box2dBody, seatObj.box2dBody);  
  91.       
  92.     /**遍历所有自行车零件刚体*/  
  93.     for(var key in s.bodyList){  
  94.         var obj = s.bodyList[key];  
  95.         //加入鼠标拖动  
  96.         if(obj.box2dBody)obj.setBodyMouseJoint(true);  
  97.         //设置对象名称  
  98.         obj.name = "bicycle";  
  99.     }  
  100.   
  101.     /**设置主刚体*/  
  102.     s.mainBody = frameAObj.box2dBody;  
  103.     /**设置拉压操作刚体*/  
  104.     s.tcBody = wheelBObj.box2dBody;  
  105. };  
这个函数看上去超长,但是逻辑却异常简单,大部分是重复的代码,再通过上面的基础讲解外加注释,看上去应该是无压力了吧,当然如果有不懂的,欢迎在本文下方留言。其中,我为了大家测试时的简化操作,给每一块刚体都设置了鼠标拖动,但是又由于刚体有很多,所以我把它们统统装在bodyList这个数组了,然后最后用遍历的方式给每一个刚体都设置了鼠标拖动。
最后来看看如何实现键盘操作。不难发现,在构造器里有这么一段代码:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /**添加事件*/  
  2. //键盘按下事件  
  3. LEvent.addEventListener(window,LKeyboardEvent.KEY_DOWN,function(e){  
  4.     s.onKeydown(e,s);  
  5. });  
  6. //键盘松开事件  
  7. LEvent.addEventListener(window,LKeyboardEvent.KEY_UP,function(e){  
  8.     s.onKeyup(e,s);  
  9. });  
在段代码里,我们还用到了onKeydown和onKeyup这两个函数。代码如下:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Bicycle.prototype.onKeydown = function(e,s){  
  2.     var force = 50;  
  3.     switch(e.keyCode){  
  4.         //向右  
  5.         case 39:  
  6.             s.moveVec.x = force;  
  7.             break;  
  8.         //向左  
  9.         case 37:  
  10.             s.moveVec.x = -force;  
  11.             break;  
  12.         //向上  
  13.         case 38:  
  14.             s.tcVec.y = -force;  
  15.             break;  
  16.         //向下  
  17.         case 40:  
  18.             s.tcVec.y = force;  
  19.             break;  
  20.         default:  
  21.             return;  
  22.     }  
  23.     /**施加移动的力*/  
  24.     s.mainBody.ApplyForce(s.moveVec,s.mainBody.GetWorldCenter());  
  25.     /**施加拉压的力*/  
  26.     s.tcBody.ApplyForce(s.tcVec,s.tcBody.GetWorldCenter());  
  27. };  
  28. Bicycle.prototype.onKeyup = function(e,s){  
  29.     /**清空力度相量*/  
  30.     s.moveVec.SetZero();  
  31.     s.tcVec.SetZero();  
  32. };  

在这里我们用到了之前讲过的ApplyForce。之前讲的时候,没有具体讲第一个参数是因为没有实际用到讲了等于徒劳。现在可以看到,这个b2Vec2有x,y属性,这两个属性可以控制力的方向。x<0向左,x>0向右,y>0向下,y<0向上。

当然,tcVec和moveVec是两个不同的向量,也就是说控制的不是同一个效果。moveVec是控制移动自行车的,这个不用多说。而tcVec是用于拉、压自行车的,我们知道,当在游戏中要开翻车的时候,往往可以用拉、压的方式来操控自行车,使其重新平衡,这个tcVec就是干这活儿的。

BTW,如果你不知道tcBody和mainBody是什么,可以看看Bicycle里init函数的代码。

添加完键盘事件后,我们就可以通过方向键来控制自行车了。操作方式为:上-拉,下-压,左-后退,右-前进。大家可以打开本文最下方的测试链接进行测试。

ok,至此,运行一下代码,得到的就是本文最上方图片所示的效果了。(什么?太丑了?你指的是我还是这个游戏?这个游戏啊,没贴图当然丑呢。)


奉上源代码下载地址:http://files.cnblogs.com/yorhom/box2dBicycle%282%29.rar

测试地址:http://www.cnblogs.com/yorhom/articles/box2dweb_bike2.html


下一章预告:目前,咋们的自行车是金刚不坏之身,你怎么摔它,它就是摔不碎(除非你把显示器抱起来,然后使劲往地上摔)。但是这不科学对吧……所以在下一章,我们就来看看,如何把这个自行车摔得支离破碎,体无完肤。哈哈,尽情期待~


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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值