5

服务端Box2D进阶

 3 years ago
source link: https://www.lanindex.com/%e6%9c%8d%e5%8a%a1%e7%ab%afbox2d%e8%bf%9b%e9%98%b6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

服务端Box2D进阶

2017/03/05 · Leave a comment

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

Your email address will not be published.

在此浏览器中保存我的名字、电邮和网站。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK