第10章 世界(World Class)
关于
b2World类包含物体和关节。它管理着模拟的方方面面,并允许异步查询(就像AABB查询和光线投射)。 你与Box2D的大部分交互都将通过 b2World 对象来完成。
创建和摧毁world
创建一个world十分的简单。你只需提供一个重力矢量,和一个布尔量去指定物体是否可以休眠。 通常你会使用new和delete去创建和摧毁一个world。
b2World* myWorld = newb2World(gravity, doSleep);
... do stuff ...
delete myWorld;
使用World
world类含有用于创建和摧毁物体与关节的工厂函数, 已在物体和关节的章节中讨论过。 在这里我们讨论b2World的其它交互。
模拟(Simulation)
世界类用于驱动模拟。你需要指定一个时间步和一个速度及位置的迭代次数。例如:
float32 timeStep = 1.0f / 60.f;
int32 velocityIterations = 10;
int32 positionIterations = 8;
myWorld->Step(timeStep,velocityIterations, positionIterations);
在时间步完成之后,你可以调查物体和关节的信息。最经常的情况是你会获取物体的位置,这样你才能更新你的角色并渲染它们。 你可以在游戏循环的任何地方执行时间步,但你应该注意事情发生的先后顺 序。例如,如果你想要在一帧(frame)中得到新物体的碰撞结果,你必须在时间步之前创建物体。
正如之前我在 HelloWorld 教程中说明的,你需要使用一个固定的时间步。使用大一些的时间步你可 以在低帧率的情况下提升性能。 但通常情况下你应该使用一个不大于 1/30 秒 的时间步。1/60 的时间步通常会呈现一个高质量的模拟。
迭代次数控制了约束求解器会遍历多少次世界中的接触以及关节。更多的迭代总能产生更好的模拟, 但不要使用小频率大迭代数。60Hz和10次迭代远好于30Hz和20次迭代。
时间步之后,你应该清除任何施加到物体之上的力。使用b2World::ClearForces可以完成。
myWorld->ClearForces();
探测世界(Exploring the World)
世界是物体和关节的容器。你可以获取世界中所有物体和关节并遍历它们。例如, 这段代码会唤醒世界中的所有物体:
for (b2Body* b = myWorld->GetBodyList();b; b = b->GetNext())
{
b->WakeUp();
}
不幸的是真实的程序可能很复杂。例如,下面的代码是有错误的:
for (b2Body* b =myWorld->GetBodyList(); b; b = b->GetNext())
{
GameActor* myActor = (GameActor*)b->GetUserData();
if (myActor->IsDead())
{
myWorld->DestroyBody(b);// 错误: 现在GetNext会返回无用信息(garbage)
}
}
在物体摧毁之前一切都很顺利。一旦物体摧毁了, 它的next指针就变得非法。所以 b2Body::GetNext() 就会返回无用信息。 解决方法是在物体摧毁之前拷贝next指针。
b2Body* node =myWorld->GetBodyList();
while (node)
{
b2Body* b = node;
node = node->GetNext();
GameActor* myActor = (GameActor*)b->GetUserData();
if (myActor->IsDead())
{
myWorld->DestroyBody(b);
}
}
这能安全地摧毁当前物体。然而,你可能想要调用一个游戏的函数来摧毁多个物体,这时你需要十分小心。 解决方案取决于具体应用, 但为求方便,在此我给出一种解决这问题的方法:
b2Body* node =myWorld->GetBodyList();
while (node)
{
b2Body* b = node;
node = node->GetNext();
GameActor* myActor =(GameActor*)b->GetUserData();
if (myActor->IsDead())
{
bool otherBodiesDestroyed =GameCrazyBodyDestroyer(b);
if (otherBodiesDestroyed)
{
node =myWorld->GetBodyList();
}
}
}
很明显要保证这个能正确工作, GameCrazyBodyDestroyer对它都摧毁了什么必须要诚实。
AABB查询(AABB Queries)
有时你需要得出一个区域内的所有fixture。b2World类为此使用了broad-phase数据结构,提供了一个 log(N) 的快速方法。你提供一个世界坐标的AABB和b2QueryCallback的一个实现。只要fixture的AABB和需查询的AABB有重合,world类就会调用你的b2QueryCallback类。返回true表示要继续查询,否则就返回false。例如,下面的代码找到所有大致与指定AABB相交的fixtures并唤醒所有关联的物体。
class MyQueryCallback : publicb2QueryCallback
{
public:
bool ReportFixture(b2Fixture*fixture)
{
b2Body* body =fixture->GetBody();
body->WakeUp();
// 返回true,继续查询
return true;
}
};
...
MyQueryCallback callback;
b2AABB aabb;
aabb.lowerBound.Set(-1.0f, -1.0f);
aabb.upperBound.Set(1.0f, 1.0f);
myWorld->Query(&callback,aabb);
你不能假定回调函数会以固定的顺序执行。
光线投射(Ray Casts)
你可以使用光线投射去做现场(line-of-site)检查, 开枪扫射等等。通过实现一个回调类,并提供一个开始点和结束点,你就可以执行光线投射。只要fixture被光线穿过,world就会调用你提供的类。回调时会传递fixture,交点,单位法向量,和光线通过的分数距离(fractional distance along the ray)。你不能假定回调会以固定的顺序执行。
通过返回fraction, 你可以控制光线投射是否继续执行。返回的fraction为0,表示应该结束光线投射。fraction为1,表示投射应该继续执行,并且没有和其它形状相交。如果你返回参数列表中传近来的fraction, 表示光线会被裁剪到当前的和形状的相交点。这样通过返回适当的fraction值,你可以投射任何形状,投射所有形状,或者只投射最接近的形状。
另外你可以返回fraction为-1,去过虑fixture。这样光线投射会继续执行,并表现得似乎fixture根本就存在。
(译注:关于fraction的含义,可以看第04章的注释)
这里是个例子:
// This class captures the closesthit shape.
class MyRayCastCallback : publicb2RayCastCallback
{
public:
MyRayCastCallback()
{
m_fixture = NULL;
}
float32 ReportFixture(b2Fixture*fixture, const b2Vec2& point,
constb2Vec2& normal, float32 fraction)
{
m_fixture = fixture;
m_point = point;
m_normal = normal;
m_fraction = fraction;
return fraction;
}
b2Fixture* m_fixture;
b2Vec2 m_point;
b2Vec2 m_normal;
float32 m_fraction;
};
MyRayCastCallback callback;
b2Vec2 point1(-1.0f, 0.0f);
b2Vec2 point2(3.0f, 1.0f);
myWorld->RayCast(&callback,point1, point2);
注意
由于舍入误差,光线投射可能会通过在静态环境中的多边形之间的细小裂缝。如果这不是您的应用程序中的可接受的,请稍微扩大您的多边形。
力和冲量(Forces and Impulses)
你可以将力,扭矩,及冲量应用到物体上。当应用一个力或者冲量时,你需要提供一个在世界坐标下的受力点。这经常导致相对于质心,会有个扭矩。
void ApplyForce(const b2Vec2&force, const b2Vec2& point);
void ApplyTorque(float32 torque);
void ApplyLinearImpulse(constb2Vec2& impulse, const b2Vec2& point);
void ApplyAngularImpulse(float32impulse);
应用力,扭矩或冲量会唤醒物体。有时这是不合需求的。例如,你可能想要应用一个固定的力,并允许物体休眠来提升性能。这时,你可以使用这样的代码:
if (myBody->IsAwake() == true)
{
myBody->ApplyForce(myForce,myPoint);
}
坐标转换(Coordinate Transformations)
body类包含一些工具函数,它们可以帮助你在局部和世界坐标系之间转换点和向量。如果你不了解这些概念,请看 Jim Van Verth 和 Lars Bishop 的“游戏和交互应用的数学基础(Essential Mathematics for Games and Interactive Applications)”。这些函数都很高效(当inline时)。
b2Vec2 GetWorldPoint(constb2Vec2& localPoint);
b2Vec2 GetWorldVector(constb2Vec2& localVector);
b2Vec2 GetLocalPoint(constb2Vec2& worldPoint);
b2Vec2 GetLocalVector(constb2Vec2& worldVector);
列表(Lists)
你可以遍历一个物体的fixture, 主要用途是帮助你访问fixture中的用户数据。
for (b2Fixture* f =body->GetFixtureList(); f; f = f->GetNext())
{
MyFixtureData* data =(MyFixtureData*)f->GetUserData();
... do something with data ...
}
你也可以用类似的方法遍历物体的关节列表。
body也提供了访问相关contact的列表。你可以用来得到当前contact的信息。但使用时请小心,因为前一个时间步存在的contact,可能并不包含在当前列表中。