AKever

导航

Cocos2d-x(1) Box2D引擎简介

Demo:http://www.cnblogs.com/TS-qrt/articles/Box2d_demo.html

Box2D引擎简介

  Box2D 是与Cocos2d-x 一起发布的一套开源物理引擎,也是 Cocos2d-x 游戏需要使用物理引擎时的首选。二者同样提供 C++开发接口,所使用的坐标系也一致,因此Box2D 与Cocos2d-x 几乎可以做到无缝对接。 

  Box2D 是一套基于刚体模拟的物理引擎,它的核心概念为世界物体形状约束关节,这些概念具体实现为Box2D 的各个组件,它们的描述见表12- 1。

   为了实现一个物理场景,我们需要做的是创建一个世界,然后创建我们需要的物体,设置好它的形状和其他属性,将其添加到世界当中,然后周期性地更新这个世界,那么Box2D 就会为我们高效且出色地完成模拟物理运动的任务。  

  值得注意的是,Box2D 仅仅更新它所管理的物理模型的位置和速度等信息。在游戏中,我们想要做的通常是赋予精灵物理性质,我们会为精灵创建Box2D 物理模型,然而物理引擎运作起来后,为了把物理模型的运动体现在屏幕上,我们必须手动把物理模型的数据同步到精灵中。 

----------------------Box2D引擎简介---------小小的分割线--------------------------------

接入Box2D (1)

  下面我们就来着手接入Box2D 到游戏中。 

  Box2D 相关的头文件为"Box2D/Box2D.h" 。和"cocos2d.h" 一样,该头文件囊括了 Box2D 需要用到的所有头文件。Box2D 没有自己的命名空间,所以不需要相关的命名空间引用。 

  接入Box2D 的每个精灵在物理世界中都有相关联的物理模型" 物体" ,即b2Body 。首先,我们扩展引擎的精灵类来保存这一关联: 

class B2Sprite  : public CCSprite      
{    
public:    
    static B2Sprite* spriteWithSpriteFrameName(const char* file);   
    bool initWithSpriteFrameName(const char *pszSpriteFrameName);   
  
    CC_SYNTHESIZE(b2Body*, m_b2Body, B2Body);    //物理世界的“物体”    
    CC_SYNTHESIZE(bool, m_isDead, IsDead);       //是否死去    
    CC_SYNTHESIZE(bool, m_isAlive, IsAlive);    //是否存活    
};    

  此处我们添加了3 个属性B2BodyIsDeadIsAliveB2Body表示的是物理引擎中的物体对象。对于其余两个属性,若IsAlive 属性为true ,则物体"存活" ,代表仍然存在碰撞体积,需要考虑碰撞,也就是仍然存在于Box2D 的物理世界中;若IsDead属性为 true ,则物体" 死去" ,表示彻底从屏幕上消失。由于鱼在确认被击中之后存在一个挣扎画面,此时尽管不再存在被碰撞的可能,但仍会在屏幕上存在一段时间,我们特意设置以上两个属性来作为区别。 

  考虑到Box2D 并不是游戏的核心组成,所占的代码量并不大,我们将其相关操作都集中封装到一起,避免零星地散落在不同的类中Box2dHandler类的代码如下所示: 

class Box2dHandler : public CCNode, public b2ContactListener    
{    
    GLESDebugDraw* m_d ebugDraw;    
    b2World*  m_world;    
    typedef pair<b2Fixture*, b2Fixture*> MyContact;    
    set <MyContact> m_contacts;    
  
    bool initBox2D();   
    void addBodyForSprite(B2Sprite* sprite, double density = 10.0);    
    void addFixtureForSprite(B2Sprite* sprite, double density=10.0);    
    void dealCollisions();    
public:    
    virtual void BeginContact(b2Contact*  contact);    
    virtual void EndContact(b2Contact* contact);    
    virtual void PreSolve(b2Contact* contact,  const b2Manifold* oldManifold);         
    virtual void PostSolve(b2Contact* contact,  const b2ContactImpulse* impulse);    
  
    static Box2dHandler*  handler();    
    bool init();    
    void draw();    
    void update(ccTime dt);    
    void addFish(B2Sprite* fish);    
    void addBall(B2Sprite* ball, CCPoint direction);    
    void removeSprite(B2Sprite* node);    
    CC_SYNTHESIZE(Box2dHandlerDelegate*, m_delegate,Delegate);    
};    

  Box2dHandler类负责和Box2D 相关的所有操作,下面我们将一一讲解其中的方法与属性,也请读者和我们一起借此探索如何将Box2D的世界接入Cocos2d-x 的世界中。 

  首先,我们在Box2dHandler的初始化方法中初始化了 Box2D 的世界,相关代码如下: 

bool Box2dHandler::initBox2D()    
{    
    CCSize s = CCDirector::sharedDirector() ->getWinSize();     
    b2Vec2 gravity;     
    gravity.Set(0.0f, 0.0f);    //重力为0
  
    m_world = new  b2World(gravity);    
    m_world->SetAllowSleeping(true);     //允许睡眠
    m_world->SetContinuousPhysics(true);    //开启连续物理测试
    m_world->SetContactListener(this);     //设置碰撞事件的监听器
    return true;    
}    

  这个初始化操作较为简单,我们用无重力状态创建了一个物理世界,并设置了两个状态允许睡眠开启连续物理测试允许睡眠,可以提高物理世界中物体的处理效率,只有在发生碰撞时才唤醒该对象开启连续物理测试,这是因为计算机只能把一段连续的时间分成许多离散的时间点再对每个时间点之间的行为进行演算,如果时间点的分割不够细致,速度较快的两个物体碰撞时就可能会产生" 穿透" 现象,开启连续物理将启用特殊的算法来避免该现象。最后设置了碰撞事件的监听器,碰撞相关的内容将在12.6节专门讲述(《Cocos2d-x捕鱼达人》)。 

  接下要为我们的精灵创建属于它们的Box2D 物体。然而在此之前,我们必须介绍 Box2D 世界里的最佳物体大小。根据 Box2D参考手册,Box2D 针对大小为0.1~10 个单位长度的物体作了优化,处理效率相对高一些。因此,我们在 Box2D 世界中创建的物体也应该尽量遵循这一大小的限制。考虑到屏幕上的可见对象都是以像素定义的,一条鱼的大小就是80×10像素,若直接使用像素数值来作为Box2D 物体的大小,显然已经严重超出了Box2D 建议的最佳大小。我们定义一个转换率PTM_RATIO,单位为" 像素/ 米" ,以便把物体尺度在 Cocos2d-x 与Box2D 中相互转换。在许多游戏中,PTM_RATIO 取32。 

 

接入Box2D (2)

  现在我们可以为精灵创建Box2D 中对应的物体了。我们将这一过程拆分成了两个步骤第一步使用 addFixtureForSprite方法为已经创建了body的精灵添加夹具(fixture),供接下来的步骤使用;第二步使用 addBodyForSprite 方法为一个精灵创建物体(body),供其他代码调用。这样拆分的好处在于,对于普通子弹碰撞后的撒网的动作,我们只需要重新设置它的夹具,而并不需要重新创建Box2D 物体,此时,就可以减少重复创建物体的代码了。正如前面介绍的,在 Box2D 模拟的物理世界中,一个物体可以附加多个夹具,每个夹具代表一个特定密度、形状的部件,而物体的主要特征则集中表现为整个物体在空间中的位置和速度信息。addFixtureForSprite 方法和addBodyForSprite 方法的代码如下所示: 

void Box2dHandler::addFixtureForSprite(B2Sprite*  sprite, double density)     
{    
    b2PolygonShape spriteShape;     //隐形的形状
    CCSize size = sprite->getContentSize();   
  
    spriteShape.SetAsBox(size.width / PTM_RATIO / 2, size.height / PTM_RATIO / 2);  
  
    b2FixtureDef spriteShapeDef;    //body定义,
    spriteShapeDef.shape = &spriteShape;     //通过b2PolygonShape(b2CircleShape)定形
    spriteShapeDef.density =density;    //定义密度
  
    b2Body *spritespriteBody = sprite->getB2Body();    
    spriteBody->CreateFixture(&spriteShapeDef);    
}    
void Box2dHandler::addBodyForSprite(B2Sprite* sprite, double density)     
{    
    b2BodyDef spriteBodyDef;    
    spriteBodyDef.type  = b2_dynamicBody;    
    spriteBodyDef.position.Set(sprite ->getPosition().x / PTM_RATIO,    
        sprite->getPosition().y / PTM_RATIO);    
    spritespriteBodyDef.userData = sprite;    
  
    b2Body *spriteBody = m_world->CreateBody(&spriteBodyDef);    
    sprite->setB2Body(spriteBody);    
  
    this->addFixtureForSprite(sprite,density);    
}    

  b2Body中的userData 属性允许存放一个任意类型的指针指向我们自定义的数据。在我们的例子中,此处可以存放物体对应精灵的指针,方便后续的更新,这与b2Sprite中的 m_b2Body 属性是对应的。 

  和Cocos2d-x 类似,在Box2D 的世界中,世界-物体-部件也呈现树状的从属关系。而不同的是,我们不能随意地创建物体或组件,必须通过上一级对象提供的以"Create"为前缀的接口来创建对象。一个对象一旦被创建,就从属于它的创建者:从m_world 创建的spriteBody已经被添加到m_world 中,而由 spriteBody创建的夹具也已经是该spriteBody的一部分。 

  在创建对象时,由于创建对象需要的参数可能会比较多,Box2D 提供了诸如b2BodyDef 和b2FixtureDef等定义参数的结构体,用于传递创建所需参数。因此,读者遇到这种情况不必惊慌,只需要逐个参数赋值即可。在这里,我们暂且定义所有的物体都由多边形组成,并定义为矩形,矩形的形状和大小直接通过精灵的可视大小转换得来。后面我们将会看到如何进一步精确地模拟一个对象的碰撞。除了最常用的多边形外,Box2D 还提供了圆形、条形和链状 3 种不同的形状。 

  此前已经提到过PTM_RATIO 参数。在 Cocos2d-x 对象和Box2D 对象中,尺度转换都是通过PTM_RATIO 来完成的。 

----------------------接入Box2D---------------小小分割线-----------------------

 

更新状态 

  当我们创建好精灵及其对应的Box2D 对象后,不会看到任何效果,因为它们仅仅是被创建。在 12.2节中我们介绍Box2D 引擎时,曾提到Box2D 是一个独立的引擎,并不是 Cocos2d-x 的一部分,因此需要通过" 更新状态" 的操作来联系游戏引擎与物理引擎,以实现二者同步,这样我们才能在游戏中看到物理现象" 更新状态" 包含两步第一步是周期性地更新物理引擎,第二步是利用物理演算的结果修改游戏中精灵的属性。 

  为了实现更新的这两个步骤,首先需要在游戏中创建一个定时器调用物理引擎的更新方法,以便驱动引擎的演算。其次,需要定时获取物理引擎演算的结果,用物体当前的状态更新精灵的属性。为此,我们添加一个每帧调用的update定时器,用于刷新Cocos2d-x 和Box2D 中对象的位置: 

void Box2dHandler::update(ccTime dt)     
{    
    for(b2Body* b = m_world->GetBodyList(); b; bb = b->GetNext()) {    
        if (b->GetUserData()  != NULL) {    
            B2Sprite* sprite = static_cast<B2Sprite *>(b->GetUserData());    
  
            if(sprite->getTag()  == ball_tag) {    
                b2Vec2 pos  = b->GetPosition();    
                float rotation  = - b->GetAngle() / 0.01745329252f ;    
  
                sprite->setPosition(ccp(pos.x * PTM_RATIO, pos.y * PTM_RATIO));    
                sprite->setRotation(rotation);    
            }    
            else {    
                b2Vec2 b2Position = b2Vec2(sprite->getPosition().x / PTM_RATIO,    
                    sprite->getPosition().y / PTM_RATIO);    
                float32 b2Angle = -CC_DEGREES_TO_RADIANS(sprite ->getRotation());  
  
                b->SetTransform(b2Position, b2Angle);    
            }    
        }    
    }    
    m_world->Step(dt,  8, 8);     
    this->dealCollisions();    
}    

  在Box2D 的对象结构中,一系列对象都是以顺序列表的形式存放在父对象中,可以通过GetList 系列方法(例如GetBodyList方法)获得该列表。这里我们就用GetBodyList 获得了m_world 中所有物体的列表,并在for 循环中迭代物理世界中的每一个物体对象,根据对象的类型作不同的更新。 

  对于鱼来说,它们游动的路线是由我们定义的,不受物理引擎演算的影响。因此,我们首先根据精灵的状态(例如位置和旋转角度等)设置Box2D 中对应物体的位置和旋转角度。 

  而对于超级武器中出现的弹性球,我们把它的运动交给Box2D 来计算,模拟其碰撞和弹射的特性。因此,在此处应该从Box2D对象中获取位置和旋转信息,设置到精灵上。 

  更新位置信息后,调用Box2D 世界的刷新接口Step: 

void b2World::Step(float32 timeStep,int32 velocityIterations, int32 positionIterations)  

  这个接口的3 个参数都十分重要。第一个参数 timeStep是更新引擎的间隔时间,也就是距离上一次更新物理引擎后经过的时间,一般只需要直接传递update 定时器提供的时间间隔即可;而后两个参数velocityIterationspositionIterations分别代表计算速度和位置时迭代的次数,下面介绍这两个参数的含义。 

  前面曾提到,Box2D 是用离散的时间点来演算连续的物理事件的,换句话说,它把物理世界的连续变化切分为许多片段逐个计算,如同数学中的微分操作一样。这两个迭代次数控制的就是微分的粒度,迭代次数越多,则模拟越精细,效果越细腻,但相应地,耗费的计算量也越大。一般情况下,我们将这两个参数设置为8~10。 

  在update方法的最后,我们调用 dealCollisions方法来处理碰撞事件,这个方法将在后面详细介绍。 

-------------------更新状态--------------------------小小分分割线-------------------------

 

调试绘图

  到目前为止,我们完成了游戏引擎与物理引擎的同步,并且已经可以借助Cocos2d-x 的定时器来驱动物理引擎演算了。然而我们还无法观察到任何与之前不同的现象--我们还没有在场景中添加能量球,而鱼群又不随物理引擎的规则更新状态,因此目前还无法确认物理引擎的工作状态。其实为了便于开发者调试,Box2D 提供了调试模式,在调试模式下,我们可以把Box2D 中的对象绘制到屏幕上,由此观察 Box2D 世界中各个物体的位置信息。 

  幸运的是,Box2D 的调试绘图也是使用 OpenGL 作为绘图引擎的,于是Box2D 和Cocos2d-x 恰好可以无缝结合到一起。与Box2D调试绘图相关的封装已经在Cocos2d-x 测试工程中提供给我们了,它们是位于 Cocos2d-x 目录中 "tests/tests/Box2DTestBed"目录下的"GLES-Render.h" 和"GLES-Render.cpp" ,我们需要将这两个文件添加到工程文件中,并在Box2dHandler的初始化部分添加调试绘图的初始化。 

  首先,在文件中引用"GLES-Render.h" 头文件。然后,我们在Box2dHandler的初始化方法中添加如下代码: 

m_debugDraw = new  GLESDebugDraw( PTM_RATIO );    
m_world->SetDebugDraw(m_debugDraw);    
uint32 flags = 0;    
flags += b2Draw::e_shapeBit;    
//flags += b2Draw::e_jointBit;    
//flags += b2Draw::e_aabbBit;     
//flags += b2Draw::e_pairBit;     
//flags += b2Draw::e_centerOfMassBit;    
m_debugDraw->SetFlags(flags);    

  在上面的代码中,我们首先创建了一个 GLESDebugDraw 对象,这是 Cocos2d-x 测试项目提供给我们的调试绘图工具。然后,我们为创建的Box2D 世界对象设置刚才创建的调试绘图工具。最后,我们需要设置调试绘图模式中显示的内容。现在我们只让引擎显示出形状对象,因此为绘图工具设置b2Draw::e_shapeBit状态位。如果需要绘制更多的对象,只需要设置其他的状态位即可。 

Box2dHandler继承自CCNode,因此可以当做一个游戏元素添加到绘图树中。为了在屏幕上显示调试绘图模式的内容,我们重写draw方法,让调试绘图能显示到屏幕上:

void Box2dHandler::draw()    
{    
    CCNode::draw();    
  
    ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position);    
  
    kmGLPushMatrix();   
    m_world->DrawDebugData();    
    kmGLPopMatrix();    
}    

  在draw方法中,我们需要在绘图前保存 OpenGL绘图状态,以免接下来的操作影响之后的绘图。因此,我们首先调用kmGLPushMatrix函数来保存状态,再调用 m_worldDrawDebugData 方法来绘制调试图形,最后调用 kmGLPopMatrix 函数来恢复刚才保存的状态。 

  成功添加后,将可以看到一个尽管不太漂亮但足以看出效果的Box2D 世界所有的鱼都被橙色的半透明矩形包围住了,矩形会跟随鱼的动作变化位置和旋转方向,如图12- 1 所示。 

------------------调试绘图------------小小的分割线--------------

 

碰撞检测 

  完成前面的工作后,实际上已经把游戏中的对象和物理引擎中的对象关联了起来。超级武器" 能量球" 发射之后会碰到鱼群,被碰到的鱼群就会被直接捕捉到,因此我们需要得知什么时候、在哪里产生了碰撞。 

  在每次更新引擎时,物体可能会产生碰撞。Box2D 提供了多个碰撞事件,用于提供给开发者处理物体的碰撞。碰撞事件通过代理类b2ContractListener通知,为了监听碰撞事件,我们需要继承并实现以下 4 个事件回调函数: 

virtual void BeginContact(b2Contact*  contact);                         //碰撞开始    
virtual void EndContact(b2Contact* contact);                           //碰撞结束    
virtual void PreSolve(b2Contact* contact,  const b2Manifold* oldManifold);   //碰撞处理前    
virtual voi d PostSolve(b2Contact* contact,  const b2ContactImpulse* impulse); //碰撞处理后  

  Box2D 对碰撞的处理分为两个步骤,具体如下所示。 

  1. 碰撞检测:发现物体间重叠。BeginContact和EndContact 事件会在两个物体开始和结束重叠时通知。
  2. 碰撞处理:根据物理定律处理速度改变和位置变化等效果,PreSolve和PostSolve 正是对应碰撞处理的前后时间点。由于我们只需要处理碰撞事件,所以只需要实现BeginContact和EndContact两个回调接口即可。

  这里有一个需要注意的地方:我们不能直接在BeginContactEndContact中改变物理引擎的状态,这是因为这两个事件的调用都是在Box2D 的b2World::Step 方法中,此时正在进行物理世界的演算,是不允许从外部对物理世界中的物体状态进行改变的。例如,我们不能在碰撞事件中直接删除被捕捉鱼的Box2D 对象,只能用一个容器把需要处理的事件保存起来,待Step方法执行完毕后再异步处理这些碰撞。 

  在代码中添加对BeginContact和EndContact两个事件的响应,具体如下所示: 

inline bool notFish(const  int& tag)    
{    
    return tag  == net_tag || tag  == bullet_tag || tag  == ball_tag;    
}    
void Box2dHandler::BeginContact(b2Contact* contact)     
{    
    CCSprite* spa  = static_cast<CCSprite *>(contact ->GetFixtureA()    
        ->GetBody()->GetUserData());    
    CCSprite* spb  = static_cast<CCSprite *>(contact ->GetFixtureB()    
        ->GetBody()->GetUserData());    
  
    int  ta = spa ->getTag();    
    int  tb = spb ->getTag();    
  
    if( ta == tb || ((notFish(ta)) + (notFish(tb))) == 2)    
        return;    
  
    MyContact myContact(contact->GetFixtureA(), contact->GetFixtureB());    
    m_contacts.insert(myContact);    
}    
void Box2dHandler::EndContact(b2Contact* contact)     
{    
    MyContact myContact(contact->GetFixtureA(), contact->GetFixtureB());    
    m_contacts.erase(myContact);     
}    

  此处我们创建了m_contacts容器,用于保存一次更新中收集到的碰撞事件。在 Begin-Contact 的回调中,我们对两个碰撞物体所对应的精灵类型进行判断,屏蔽了鱼和鱼的碰撞以及子弹和子弹的碰撞后,将碰撞事件加入容器中。容器在Box2dHandler中定义,以集合的形式存放,以避免物理引擎有时出现的重复碰撞。声明集合m_contacts的代码如下: 

typedef pair<b2Fixture*, b2Fixture*> MyContact;    
set <MyContact>  m_contacts;  

  最后则是具体的碰撞处理函数,这里我们将 m_contacts集合中的碰撞事件都分发到事件处理的代理类中去,完成碰撞后游戏状态的处理,相关代码如下: 

void Box2dHandler::dealCollisions()  
{    
    if(m_delegate  != NULL) {    
        set <MyContact>::iterator it;     
        for(it = m_contacts.begin(); it != m_contacts.end(); ++it) {    
            B2Sprite  *bullet = static_cast<B2Sprite *>(    
                it->first->GetBody()->GetUserData());    
            B2Sprite  *fish =  static_cast<B2Sprite *>(    
                it->second->GetBody()->GetUserData());    
  
            if(notFish(fish->getTag()))    
                swap(bullet, fish);    
  
            m_delegate->collisionEvent(bullet, fish);    
        }    
    }    
    m_contacts.clear();    
}    

  这个代理将由精灵层实现,其中要对事件作相应的处理,包括鱼的捕捉确认、捕捉后将鱼从Box2D 世界中收回等,在此就略去不讲了。 

------------------------碰撞检测 ----------------小小的分割线------------------

 

弹射

  完成了碰撞检测,可以继续让我们的游戏更有趣一点,使发射的能量球能够在鱼群中弹射:击中一条鱼后可以根据原来的射击方向弹射,改变能量球下一步的方向,能量球的速度也会适当地慢下来--就像真实世界中的弹射一样。 

  其实需要做的并不多,只要在创建能量球在物理引擎中的对象时,合理设置初始状态即可,相关代码如下: 

void Box2dHandler::addBall(B2Sprite* ball, CCPoint direction)    
{    
    this->addBodyForSprite(ball, density_ball);    
  
    b2Body* body = ball->getB2Body();    
    b2Vec2 b2Position = b2Vec2(ball->getPosition().x / PTM_RATIO,   
        ball->getPosition().y / PTM_RATIO);   
    float32 b2Angle = -CC_DEGREES_TO_RADIANS(ball->getRotation());    
  
    body->SetTransform(b2Position, b2Angle);    
    body->SetLinearVelocity(b2Vec2(direction.x * 100, direction.y * 100));    
}    

  我们为能量球设置了速度,不过单单这样还不够。因为鱼的位置是根据精灵状态更新的,所以鱼在物理世界中并没有速度,与高速运行中的能量球碰撞后并不足以产生足够的冲量改变能量球的方向。回顾addFixtureForSprite 函数,我们设置了一个参数density,用来指定创建的夹具的密度,在这里可以派上用场了。我们将鱼的密度设置得比能量球大一些,以实现弹射的效果: 

const double density_ball = 1;    
const double density_fish = 20;  

  接下来的演算就可以完全交给Box2D 实现了,物理引擎会忠实地为我们模拟碰撞的发生和碰撞后的弹射等物理变化,这些变化会在update定时器中同步至 Cocos2d-x 精灵,从而反映到屏幕上。 

-----------------------弹射-----------------小小的分割线----------------

 

精确碰撞 

  从调试绘图中可以注意到,用矩形做碰撞检测实际上是非常粗糙的,无法完全和鱼的外形吻合,能量球与鱼碰撞的效果会如同和一个砖头碰撞一样。尤其是灯笼鱼这种形状十分不规则的鱼,会导致与真实的碰撞效果差距很大。为了解决这个问题,我们需要更精确地定义鱼的碰撞边界。在Box2D 中,可以使用一组顶点来创建多边形形状,用于描述这个边界,但是人工计算这些顶点比较麻烦。 

  我们可以使用工具VertexHelper完成多边形顶点的计算工作。和 TexturePacker 等工具一样,VertexHelper是一个可视化顶点编辑器,用它创建的顶点信息可以直接导出为Box2D 可使用的代码。唯一可惜的是,目前该软件仅支持苹果OS X 系统,好在其使用的算法都不甚复杂,在GitHub上也能找到它的源代码,Windows 平台用户不妨考虑为其移植一个 Qt版本。 

  工具的界面如图12- 2 所示,使用方法非常简单,在此不再赘述了。当我们创建好顶点之后,就可以在 Box2D 程序中使用了。然而Box2D 对于多边形还有两个限制,具体如下所示。 

  Box2D 规定了多边形的最大顶点数量为 8 ,但我们可以在"b2Settings.h"中修改最大顶点数目,把 b2_maxPolyVertices改为所需的数目即可。 

  编辑所创建的多边形必须是凸多边形,否则物理引擎无法正确地演算。 

  当我们使用多边形替换原来的矩形来创建夹具后,编译并运行工程后,我们会看到物体的边界基本上和精灵的图形边界吻合了,这样就实现了更加真实的碰撞检测。 

-------------------精确碰撞 --------------小小的分割线------------------------

 

小结 

  在这一章中,我们为了引入新的" 能量球" 超级武器,探索了物理引擎 Box2D 的基本用法,并且实现了一套精确的碰撞检测系统。下面我们总结一下这一章比较重要的知识点。 

  Box2D 对象结构:Box2D 中包含形状(b2Shape )、夹具(b2Fixture )、物体(b2Body)与世界(b2World )等几个最重要的概念。形状规定了一个几何形状,可使用几何形状来创建夹具。夹具用来设置物体的几何形状与摩擦系数、密度等参数。物体表示一个物理实际中的宏观物体,可以相互作用发生运动、碰撞等现象,物体包含位置、旋转角度等信息,是物理引擎演算的单位。世界是全部物体的容器,物体需要添加到世界中才会产生物理现象。 

  创建物体:物体是一个拥有形状和密度等属性,并会参与物理现象的Box2D 对象,因此,创建了一个物体后,需要利用形状创建夹具,把夹具绑定到物体上。此外,一个物体也可以绑定多个夹具,而绑定了多个夹具的物体具有多个形状合并后的属性。 

  尺度(PTM_RATIO):在 Box2D 中,长度通常在0.1~1 个单位长度之间,而游戏中的物体通常以像素为单位,为了把精灵与物体对应起来,我们定义了PTM_RATIO 作为精灵长度单位到物理引擎长度单位的转换比例,它的单位是" 像素/ 米" 。 

  引入物理引擎:物理引擎与游戏引擎相互独立,因此我们需要为每一个精灵在物理引擎中创建物体对象,然后在游戏中不断更新物理引擎,并反过来把物理引擎的演算结果反映到游戏中。通常,这个过程可以在 Cocos2d-x 提供的update 定时器事件中实现。 

  调试绘图:Cocos2d-x 的测试样例中包含一套用于在屏幕上显示物理引擎状态的工具,它们位于"tests/tests/Box2dTestBed"目录下的"GLES- Render.h (.cpp )" 文件中。使用Box2D 世界对象的 SetDebugDraw方法,可以启用调试绘图。 

  Cocos2d-x 和Box2D 是两个配合十分完美的引擎,从绘图机制到接口,都能很好地融合。这里我们还只是使用了Box2D 的碰撞检测功能,关节等众多特性尚未深入展开,但感兴趣的读者可以从文档中找到答案。 

posted on 2014-01-27 10:18  AKever  阅读(1981)  评论(0)    收藏  举报