此文紧接我的上一篇博文 – 服务端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内置的数据结构b2Fixture、b2Body、b2Joint(关节,包含了一些复杂高端的用法)中,均有void* m_userData这个成员变量;
b2Fixture与b2Joint都有指向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)
最后上实例代码:
#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