此文紧接我的上一篇博文 – 服务端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
