Box2D v2.1.0用户手册翻译 - 第10章 世界(World Class)

内容很多摘自
Aman JIANG(江超宇)翻译的Box2D v2.0.1 用户手册

第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,可能并不包含在当前列表中。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值