Box2D是一个用于游戏的2D刚体仿真库。它的核心目的是为了模拟真实的2D环境,让物体运动更加真实,交互性更好。本文主要介绍一些使用Box2D的基础知识,并且记录如何在没有图形化界面的Linux服务器上使用它。(例子就是我使用的这台:D)。
开始准备安装Box2D
$git clone https://github.com/erincatto/Box2D
完成之后因为没有图形化界面,去CMakeCache.txt中把BOX2D_BUILD_EXAMPLES设置从ON改成OFF
$cd Box2D/Box2D/Build/ && vim CMakeCache.txt
然后用cmake创建makefile,这里如果有库的缺失补全一下就可以了
$cmake ..
然后直接make会有一处编译错误,找到地方把”nullptr”改成”NULL”或者0
$make
$sudo make install
$locate Box2D
最后使用locate Box2D查看Box2D的安装情况,默认是在/usr/local/相关目录,至此安装完毕。
一些基础知识
世界(world)
虚拟化的物理时间,里面包含了各类刚体的演算。
刚体(rigid body)
最基础的单位,代表一个不会发生形变的实体,我们通常用body来指代刚体。
夹具(fixture)
个人喜欢叫它为材质,依附在刚体上,约束刚体的形状(shape),密度(density),摩擦(friction),恢复(restitution)等特性。
单位
使用米-千克-秒(MKS)单位制。
场景测试
一个无重力环境的2D场景,正方形40mx40m,正方形边是厚2m的静态围墙,正方形的中心是世界中心;
有bodyA(正方形2mx2m,坐标-10.0f, 10.0f)与bodyB(正方形2mx2m,坐标10.0f, 10.0f),对A施加一个向右的力,对B施加一个向左的力;
示意图:
详细的释义直接附加在代码里:
//file:myBox2D.cpp
#include ;
#include ;
#include ;
int32_t main()
{
//创建世界
b2Vec2 gravity(0.0f, -0.0f); //无重力,若要设置自然重力为(0.0f, -9.8f)
b2World world(gravity);
world.SetAllowSleeping(false); //静止的物理也会参与碰撞检测
//构建一个边长40m正方形
//创建下面围墙
b2BodyDef wall;
b2PolygonShape wallBox;
wall.type = b2_staticBody;
wall.position.Set(0.0f, -1.0f);
b2Body *wallDown = world.CreateBody(&wall);
//下面围墙的形状
wallBox.SetAsBox(20.0f, 1.0f);
//设置材质
wallDown->CreateFixture(&wallBox, 0.0f);
//创建上面围墙
wall.position.Set(0.0f, 21.0f);
b2Body *wallUp = world.CreateBody(&wall);
//上面围墙的形状
wallBox.SetAsBox(20.0f, 1.0f);
//设置材质
wallUp->CreateFixture(&wallBox, 0.0f);
//创建左面的围墙
wall.position.Set(-21.0f, 20.0f);
b2Body *wallLeft = world.CreateBody(&wall);
//左面围墙的形状
wallBox.SetAsBox(1.0f, 20.0f);
wallLeft->CreateFixture(&wallBox, 0.0f);
//创建右面的围墙
wall.position.Set(21.0f, 20.0f);
b2Body *wallRight = world.CreateBody(&wall);
//右面围墙的形状
wallBox.SetAsBox(1.0f, 20.0f);
wallRight->CreateFixture(&wallBox, 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, 10.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);
//施加一点力
bodyA->ApplyLinearImpulse(b2Vec2(10, 0), bodyA->GetWorldCenter(), true);
bodyB->ApplyLinearImpulse(b2Vec2(-10, 0), bodyB->GetWorldCenter(), true);
//迭代的时间间隔,这里是1秒60次
float32 timeStep = 1.0f / 60.0f;
int32 velocityIterations = 6; //碰撞时速度迭代
int32 positionIterations = 2; //碰撞时位置迭代
for (int32 i = 0; i < 600; ++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);
for (b2Contact* c = world.GetContactList(); c; c = c->GetNext()) {
b2Body *A = c->GetFixtureA()->GetBody();
b2Body *B = c->GetFixtureB()->GetBody();
b2Vec2 positionA = A->GetPosition();
float32 angleA = A->GetAngle();
b2Vec2 positionB = B->GetPosition();
float32 angleB = B->GetAngle();
printf("b2Contact A:%4.2f %4.2f %4.2f|B:%4.2f %4.2f %4.2f\n",
positionA.x, positionA.y, angleA, positionB.x, positionB.y, angleB);
}
}
return 0;
}
编译后可查看效果
$g++ -o test myBox2D.cpp -lBox2D
$./test
关于代码的一些解释
b2BadyDef的type类型:
b2_staticBody:静态物理,质量无穷大,不会因为碰撞移动;不会和其它static或kinematic物体相互碰撞,例如上面样例的围墙;
b2_kinematicBody:可以行动的、质量无穷大的;
b2_dynamicBody:可以受力运动,也可以根据用户指令固定移动;拥有完整的fixtures各种特性;
关于fixtures:
形状(shape),密度(density),摩擦(friction)从字面意思就比较好理解了,说一下恢复(restitution):
官方文档给的释义:恢复可以使对象弹起。恢复的值通常设置在0到1之间。想象一个小球掉落到桌子上,值为0表示着小球不会弹起, 这称为非弹性碰撞。值为1表示小球的速度跟原来一样,只是方向相反,这称为完全弹性碰撞。
若2个均有restitution的刚体碰撞,那么取最大的restitution作为最终计算值。
关于施加力:
ApplyForce:增加力;
ApplyLinearImpulse:增加冲量;具体怎么换算为速度未知,同等单位下,相同时间比ApplyForce运动的更远;
ApplyTorque:角力矩;
ApplyAngularImpulse:角动量;
SetTransform:瞬移物体;
关于迭代:
时间迭代:可以抽象成帧的概念,比如我们需要一秒60帧那么就是float32 timeStep = 1.0f / 60.0f;
碰撞时速度、碰撞时位置迭代:核心是用来检测碰撞物体的相对位置,以及碰撞后的位移;如果值太小,会出现一个物理运动另外一个物理“内部”再碰撞;值越高就越接近真实物理,当然运算负载也越大;
个人觉得Box2D在接口设计上面非常合理,迭代逻辑与业务的主逻辑契合比较紧密(基于帧的概念),看后续有没有机会继续深入使用,若有使用还会更新进阶篇。
(全文结束)