服务端Box2D进阶

此文紧接我的上一篇博文 – 服务端Box2D入门,进阶篇会以更加贴近实际工程的角度来对入门篇的内容进行优化。box2d

首先我们看看入门篇遗留的一些问题:

密度

在Box2D中,刚体的密度(density),但是千克/平方米(因为是2D,所以不是立方米),默认值是0千克/平方米。

所以我们若把密度设置为1,形状设置成SetAsBox(1.0f, 1.0f),那么这个刚体的重量是4千克。

冲量、力、速度的关系

首先我们看看力、速度、时间、刚体质量之间的关系:

力 = 质量 * 速度 / 时间

那么冲量是什么呢?它就是力在一定时间的内的积累:

冲量 = 力 * 时间

所以我们可以得到:

冲量 = 质量 * 速度

那么对于入门篇中,帧时间为1/60秒刚体质量为4千克

若调用bodyA->ApplyLinearImpulse(b2Vec2(10, 0), bodyA->GetWorldCenter(), true),会对bodyA瞬间施加一个(2.5f, 0)的速度。

如果调用bodyA->ApplyForce(b2Vec2(10, 0), bodyA->GetWorldCenter(), true),会对bodyA施加一个(0.04f, 0)的速度。

ps:Box2D刚体最大速度限制为2米/帧,如果1秒对应60帧,那么换算出来出的最大速度就是120米/秒(这是一个很快的速度,声音在空气中的传播速度才340米/秒),该最大的值的定义在b2Settings.h中。

 

用户自定义数据

说了这么多的概念都是围绕Box2D内置的数据结构体展开,那么实际工程中我们要将自己的数据结构绑定到Box2D中进行物理运算应该如何处理呢?

在Box2D内置的数据结构b2Fixtureb2Bodyb2Joint(关节,包含了一些复杂高端的用法)中,均有void* m_userData这个成员变量;

b2Fixtureb2Joint都有指向b2Body的指针,意思就是说我们最终都可以从b2Fixture、b2Joint找到b2Body从而找到其m_userData指向的内容。

所以我个人把用户自定义数据交给b2Body->m_userData进行管理,对应的接口是b2Body->SetUserData(void *UserData)

ps1:这种做法只是个人理解,不一定是最合适最优雅的,同时缺少对于关节这块的理解;

ps2:注意b2Body->CreateFixture()这个接口中会把b2Body->m_userData赋值给b2Fixture->m_userData,接口的调用顺序可能会有坑,比如先调用了CreateFixture(),再调用SetUserData(),那么b2Fixture->m_userData其实指向了一个“奇怪的值”。

 

碰撞的优化

在入门篇中我们粗暴了直接对world.GetContactList()进行遍历来获得有物理接触的刚体,官方其实推荐了一种更优雅,更高效的做法:

//继承类b2ContactListener,监听碰撞事件的回调
class MyContactListener : public b2ContactListener
{
public:
    void BeginContact(b2Contact* contact)
    {
    }

    void EndContact(b2Contact* contact)
    {
    }

    void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
    {
    }

    void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse)
    {
    }
};

4个接口的按时间顺序调用关系如下:

BeginContact->PreSolve ->PostSovle(->PreSolve ->PostSovle……)->EndContact

BeginContact方法中进行查看的常用信息;

PreSolve方法为我们提供了一个在碰撞响应计算之前改变接触特性的机会,或者甚至是同时取消响应;

PostSolve方法我们可以找到碰撞响应的具体信息,比如造成的冲量大小,用户拿到冲量值可以做一些碰撞自定义行为;

以上的部分措辞参考了这篇文章,文章对于碰撞这块讲的很细。

 

围墙的优化

写完入门篇之后,私下觉得那长方形来强行做围墙有点太low了,于是乎寻找有没有更加高端大气的方法。

利用链接形状(Chain Shapes)

示意图:Box2D-2

最后上实例代码:

#include <stdint.h>
#include <stdio.h>
#include <string>
#include <Box2D/Box2D.h>

class UserData {
public:
    UserData(const std::string name) : name(name) {}
public:
    std::string name;
};

class MyContactListener : public b2ContactListener
{
public:
    void BeginContact(b2Contact* contact)
    {
        DumpInfo(contact, "BeginContact");
    }

    void EndContact(b2Contact* contact)
    {
        DumpInfo(contact, "EndContact");
    }

    void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
    {
        DumpInfo(contact, "PreSolve");
    }

    void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse)
    {
        DumpInfo(contact, "PostSolve");
    }
private:
    void DumpInfo(b2Contact* contact, const std::string preLog)
    {
        b2Body *A = contact->GetFixtureA()->GetBody();
        b2Body *B = contact->GetFixtureB()->GetBody();
        b2Vec2 positionA = A->GetPosition();
        float32 angleA = A->GetAngle();
        b2Vec2 positionB = B->GetPosition();
        float32 angleB = B->GetAngle();
        printf("%s A:%4.2f %4.2f %4.2f %s|B:%4.2f %4.2f %4.2f %s\n",
                preLog.c_str(), positionA.x, positionA.y, angleA,
                ((UserData *)A->GetUserData())->name.c_str(), positionB.x,
                positionB.y, angleB, ((UserData *)B->GetUserData())->name.c_str());
    }
};

int32_t main()
{
    //创建世界
    b2Vec2 gravity(0.0f, -0.0f);  //无重力
    b2World world(gravity);
    world.SetAllowSleeping(false);

    //链接形状围墙
    b2BodyDef wallDef;
    wallDef.type = b2_staticBody;
    b2Body *wall = world.CreateBody(&wallDef);

    b2Vec2 vs[4];
    vs[0].Set(20.0f, 20.0f);  //第一象限
    vs[1].Set(-20.0f, 20.f);  //第二象限
    vs[2].Set(-20.0f, -20.0f);  //第三象限
    vs[3].Set(20.0f, -20.0f);   //第四象限

    b2ChainShape chain;
    chain.CreateLoop(vs, 4);
    wall->CreateFixture(&chain, 0.0f);


    //创建二个动态的body
    b2BodyDef bodyDefA;
    bodyDefA.type = b2_dynamicBody;
    bodyDefA.position.Set(-10.0f, 10.0f);
    b2Body *bodyA = world.CreateBody(&bodyDefA);

    b2BodyDef bodyDefB;
    bodyDefB.type = b2_dynamicBody;
    bodyDefB.position.Set(10.0f, 5.0f);
    b2Body *bodyB = world.CreateBody(&bodyDefB);

    //为这个动态bodyA设置形状
    b2PolygonShape dynamicBox;
    dynamicBox.SetAsBox(1.0f, 1.0f);

    //为这个动态body设置材质
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &dynamicBox;
    fixtureDef.density = 1.0f; //密度
    fixtureDef.friction = 0.3f; //摩擦系数
    fixtureDef.restitution = 1.0f; //恢复系数
    bodyA->CreateFixture(&fixtureDef);
    bodyB->CreateFixture(&fixtureDef);

    //装载UserData
    UserData BoxA = UserData("BoxA");
    UserData BoxB = UserData("BoxB");
    UserData Wall = UserData("Wall");
    bodyA->SetUserData((void *)&BoxA);
    bodyB->SetUserData((void *)&BoxB);
    wall->SetUserData((void *)&Wall);

    //施加一点力
    //冲量与速度的换算  速度 = 冲量 / 质量
    //力与速度的换算  力 = 质量 * 速度 / 时间,其中时间为timeStep迭代间隔时间
    bodyA->ApplyLinearImpulse(b2Vec2(0, 150), bodyA->GetWorldCenter(), true);
    bodyB->ApplyLinearImpulse(b2Vec2(-80, 0), bodyB->GetWorldCenter(), true);
//  bodyA->ApplyForce(b2Vec2(30, 0), bodyA->GetWorldCenter(), true);
//  bodyB->ApplyForce(b2Vec2(-30, 0), bodyB->GetWorldCenter(), true);

    //监听碰撞的回调
    MyContactListener myListener;;
    world.SetContactListener(&myListener);

    //迭代的时间间隔,这里是1秒60次
    float32 timeStep = 1.0f / 60.0f;

    int32 velocityIterations = 6; //碰撞时速度迭代
    int32 positionIterations = 2; //碰撞时位置迭代

    for (int32 i = 0; i < 240; ++i) { 
        world.Step(timeStep, velocityIterations, positionIterations); 
        b2Vec2 positionA = bodyA->GetPosition();
        float32 angleA = bodyA->GetAngle();
        printf("A %4.2f %4.2f %4.2f\n", positionA.x, positionA.y, angleA);

        b2Vec2 positionB = bodyB->GetPosition();
        float32 angleB = bodyB->GetAngle();
        printf("B %4.2f %4.2f %4.2f\n", positionB.x, positionB.y, angleB);
    }
    return 0;
}

可以自行修改上面代码细节进行测试。

(全文结束)


转载文章请注明出处:漫漫路 - lanindex.com

Leave a Comment

Your email address will not be published.