自定义Box2D刚体碰撞处理函数
在谁动了我的刚体——Box2D刚体碰撞中,我们学会了用b2Contact和b2ContactListener来处理发生碰撞的刚体,但是正如我之前所说的,在b2Contact或b2ContactListener中,我们获取的bodyA和bodyB无法知道哪个是游戏主角,哪个是敌人,分不出个青红皂白,所以只能一棒子打死啦。所以今天我们学习在b2Body类中自定义一个碰撞处理函数。
用b2Contact和b2ContactListener来处理发生的碰撞,就免不了下面这样的代码:
if (contactList != null) { //如果发生了碰撞,记录碰撞的双方bodyA和bodyB var bodyA:b2Body = contactList.GetFixtureA().GetBody(); var bodyB:b2Body = contactList.GetFixtureB().GetBody(); /** * 碰撞的双方有3种可能: * 1.动态刚体与静态地面碰撞 * 2.动态刚体与动态刚体碰撞 * * 我们需要处理的动态刚体与静态地面碰撞,并移动动态刚体,但是我们不知道哪个是静态地面 * 所以只能分别对bodyA和bodyB进行判断 */ //判断bodyA是不是静态地面 if (bodyA.GetUserData().name == "ground") { //如果是静态地面,然后根据bodyB的类型,进行不同的处理 if (bodyB.GetUserData().name == "rect") { bodyB.ApplyImpulse(new b2Vec2(1*bodyB.GetMass()), bodyB.GetWorldCenter()); }else { bodyB.ApplyImpulse(new b2Vec2(-1*bodyB.GetMass()), bodyB.GetWorldCenter()); } //判断bodyA是不是静态地面 //试着删除elseif里面的内容,看看不同的结果(矩形刚体将不会向右移动) }else if (bodyB.GetUserData().name == "ground") { //判断bodyA是不是静态地面 if (bodyA.GetUserData().name == "rect") { bodyA.ApplyImpulse(new b2Vec2(1*bodyA.GetMass()), bodyA.GetWorldCenter()); }else { bodyA.ApplyImpulse(new b2Vec2(-1*bodyA.GetMass()), bodyA.GetWorldCenter()); } }
具体我在谁动了我的刚体——Box2D刚体碰撞中已经讲过了,最大的缺点就是要反复进行多次判断。
原因是因为我们分不出bodyA和bodyB具体是哪个对象,不过刚体知道他们自己是谁,我们可以在b2Body里定义一个碰撞处理函数,如contactWith(),而这个函数中可以用this应用刚体自己,另个可以用参数传入,然后让对碰撞刚体分别调用bodyA.contactWith(bodyB),bodyB.contactWith(bodyA)就可以轻松分出青红皂白啦。
好吧,我再讲细一点。首先在b2Body中声明一个beginContactHanlder公共变量,我们可以在外部自定义这个函数。
public var beginContactHanlder:Function;
然后再定义一个beginContactWith公共函数,在碰撞发生时,我们可以在b2Contact或b2ContactListener处理函数中直接用bodyA.contactWith(bodyB),bodyB.contactWith(bodyA)来实现碰撞处理。
public function beginContactWith(bodyB:b2Body):void { //通过this来引用刚体自己 beginContactHanlder(this, bodyB); }
最后在Main类中自定义了beginContactHanlder函数。注意,这个函数一定要包含两个参数,分别表示刚体自己,和与它发生碰撞的刚体。
groundBody.beginContactHanlder = contactHanlder; private function contactHanlder(selfBody:b2Body, contactedBody:b2Body):void { //如果被碰撞的刚体是矩形刚体rect if (contactedBody.GetUserData().name == "rect") { //则添加一个向右的速度 contactedBody.ApplyImpulse(new b2Vec2(1*contactedBody.GetMass()), contactedBody.GetWorldCenter()); }else { //则添加一个向左的速度 contactedBody.ApplyImpulse(new b2Vec2(-1*contactedBody.GetMass()), contactedBody.GetWorldCenter()); } }
这样我们可以只对需要侦听碰撞事件的刚体设置碰撞函数,并进行碰撞处理。如谁动了我的刚体——Box2D刚体碰撞中的例子,我们只需要在刚体与地面刚体发生碰撞时让他们分别往右或左移动,所以只要对地面刚体设置beginContactHanlder函数就可以啦。
同样的效果:
完整的代码和注释如下:
package { import Box2D.Dynamics.Contacts.b2Contact; import flash.display.MovieClip; import flash.events.MouseEvent; import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.b2Body; import Box2D.Dynamics.b2World; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.ui.Keyboard; /** * http://www.ladeng6666.com * @author ladeng6666 */ public class CustomContactFunction extends Sprite { //创建世界的基本元素 private var world:b2World; private var debugSprite:Sprite; private var contactList:b2Contact; private var age:Number = 30; private var interval:Number = 0; public function CustomContactFunction() { LDEasyBox2D.stage = this; world=LDEasyBox2D.createWorld(); debugSprite=LDEasyBox2D.createDebug(world); addChild(debugSprite); //创建刚体数据,通过个对象的.name属性,可以判断对象的类型,是矩形?是圆形?还是静态地面 var bd:BodyData = new BodyData(); bd.name = "ground"; //创建矩形地面刚体 var groundBody:b2Body=LDEasyBox2D.createBox(world, stage.stageWidth / 2, 300, 400,20, true,bd); //设置地面刚体的碰撞处理函数, groundBody.beginContactHanlder = contactHanlder; //侦听事件 addEventListener(Event.ENTER_FRAME, loop); } //自定义的地面刚体处理函数,记住这个函数必须有两个参数,第一个是碰撞刚体本身slefBody,另外一个是被碰撞的刚体contactedBody private function contactHanlder(selfBody:b2Body, contactedBody:b2Body):void { //如果被碰撞的刚体是矩形刚体rect if (contactedBody.GetUserData().name == "rect") { //则添加一个向右的速度 contactedBody.ApplyImpulse(new b2Vec2(1*contactedBody.GetMass()), contactedBody.GetWorldCenter()); }else { //则添加一个向左的速度 contactedBody.ApplyImpulse(new b2Vec2(-1*contactedBody.GetMass()), contactedBody.GetWorldCenter()); } } private function createBodies():void { //没30帧随机创建一个刚体,可能是圆形刚体,也可能是矩形刚体 if (++interval > age) { var bd:BodyData = new BodyData(); if (Math.random() > 0.5) { //通过BodyData对象定义刚体的类型,在碰撞检测时,我们会通过这个属性判断刚体的类型 bd.name = "rect"; LDEasyBox2D.createBox(world, Math.random() * 300 + 100, 0, 40, 30, false, bd); }else { //通过BodyData对象定义刚体的类型,在碰撞检测时,我们会通过这个属性判断刚体的类型 bd.name = "circle"; LDEasyBox2D.createCircle(world, Math.random() * 300 + 100, 0, 30, false, bd); } interval = 0; } } private function loop(e:Event):void { //更新世界 LDEasyBox2D.updateWorld(world); //创建刚体 createBodies(); //获取world的b2Contact对象 contactList = world.GetContactList(); if (contactList != null) { //如果发生了碰撞,记录碰撞的双方bodyA和bodyB var bodyA:b2Body = contactList.GetFixtureA().GetBody(); var bodyB:b2Body = contactList.GetFixtureB().GetBody(); //因为我们还是不知道,到底a是地面,还是b是地面,所以还是要对两个刚体都进行判断 //但是跟以前不同的是,因为我们只为地面刚体设置了碰撞处理函数beginContactWith,所以只有它会运行碰撞处理 //也就是说,下面的两个语句只会运行一个,或则一个都不运行,因为要么碰撞刚体没有地面,要么就只有一个 if (bodyA.beginContactHanlder != null) bodyA.beginContactWith(bodyB); if (bodyB.beginContactHanlder != null) bodyB.beginContactWith(bodyA); } //清除超出屏幕的刚体 var body:b2Body = world.GetBodyList(); for (; body; body = body.GetNext()) { if (body.GetPosition().y > 400/30) { world.DestroyBody(body); } } } } } import flash.display.Sprite; class BodyData extends flash.display.Sprite{ }
联系作者
把拉登兄目前为止的所有box2d教程都看了一遍,收获真是不小!
期待你的新教程~另外我也该思考一下,利用目前所学到的这些知识,能否创建一个有意思的物理游戏,谢谢拉登!
谢谢你的支持,做一个简单的小游戏应该是没有问题的,后续我会讲一些游戏实例的。期待吧…
嗯“拉登兄。。我想知道有没可能做一个人物动态的刚体???就是刚体是随着那个图的形状来变动。?还是说是用别的方法来实现那效果的.?
HI,感谢你的教程.
我自己写的Demo,发现无法在碰撞检测自定义类里destory b2Body,这是框架限定功能了么.
看到了,原来是刚体在碰撞时,world是lock状态,要等刚体成休眠时才可destory
对的,world有时候会出现lock状态,这时候不进行模拟。
感谢!
那个。。。虽然是个很小的问题。。。但是觉得很容易因为不注意造成后续的错误。。。就是句柄的单词拉登大师你拼错了=。=
在这篇文章中全部用的hanlder,应该是handler 。就是beginContactHandler。因为我自己就是贴一部分敲一部分然后出现问题了。。。。。。orz
sorry啦,以后会注意啦!谢谢你的关注!
请问一下,想要清除所有刚体的时候需不需要先清除贴图?,清除贴图是用SetUserData(null)吗?
for (var body:b2Body = world.GetBodyList(); body; body = body.GetNext())
{
if (body.GetUserData() != null)
{
if (body.GetUserData().name.indexOf("brick") != -1)
{
body.SetUserData(null);
world.DestroyBody(body);
}
}
}
这样好像没有办法删除刚体?贴图也没有反应?
DestroyBody()就可以删除刚体,你的方法是正确的。SetUserData(null);是解除贴图与刚体的绑定关系,如果你所谓的彻底删除,是把贴图也删除掉的话,应该相应的使用Flash的removeChild()函数。更多问题,可以微信或qq联系我(qq:328800655)