1. 程式人生 > >cocos2d-x 使用box2d實現物理彈跳效果

cocos2d-x 使用box2d實現物理彈跳效果

一直對物理引擎灰常感興趣。下面的是一個簡單的物理效果演示。

彈跳的小球球,(呵呵,用了它自己的icon,沒裝PS,小球球沒切好)


使用cocos2d-x新建一個box2d的模板工程。

把HelloWorldScene.h換成下面的程式碼,(如果你用的是cocos2d,記得檔案後最是.mm,oc和c++混編時檔案要是mm)

//
//  HelloWorldScene.h
//  testBox2d
//
//  Created by Yanghui Liu on 11-11-4.
//  Copyright ard8 company 2011年. All rights reserved.
//
#ifndef __HELLO_WORLD_H__
#define __HELLO_WORLD_H__

#include "cocos2d.h"
#include "Box2D.h"
USING_NS_CC;

class HelloWorld : public cocos2d::CCLayer {
public:
    ~HelloWorld();
    HelloWorld();
    static cocos2d::CCScene* scene();
    void mInit();
    b2World *_world;
    b2Body *_body;
    CCSprite *_ball;
    void tick(cocos2d::ccTime dt);
    
};

#endif // __HELLO_WORLD_H__
這個很簡單,就不用說了。
下面是cpp檔案
//
//  HelloWorldScene.cpp
//  testBox2d
//
//  Created by Yanghui Liu on 11-11-4.
//  Copyright ard8 company 2011年. All rights reserved.
//
#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"

using namespace cocos2d;
using namespace CocosDenshion;

#define PTM_RATIO 32

HelloWorld::HelloWorld()
{

}

HelloWorld::~HelloWorld()
{
    delete _world;
    _body = NULL;
    _world = NULL;
}

void HelloWorld::mInit()
{
    setIsAccelerometerEnabled(true);
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    _ball = CCSprite::spriteWithFile("Icon.png");
    _ball->setPosition(ccp(100, 100));
    addChild(_ball);
    
    //建立世界的重力方向,為Y軸-30。即向下
    b2Vec2 gravity = b2Vec2(0.0f, -30.0f);
    
    //設定世界裡面的物件是否可以休眠
    bool doSleep = true;
    _world = new b2World(gravity, doSleep);
    
    //建立世界
    b2BodyDef groundBodyDef;
    groundBodyDef.position.Set(0,0);
    b2Body *groundBody = _world->CreateBody(&groundBodyDef);
    b2PolygonShape groundBox;
    b2FixtureDef boxShapeDef;
    boxShapeDef.shape = &groundBox;
    groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO, 0));
    groundBody->CreateFixture(&boxShapeDef);
    groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(0, winSize.height/PTM_RATIO));
    groundBody->CreateFixture(&boxShapeDef);
    groundBox.SetAsEdge(b2Vec2(0, winSize.height/PTM_RATIO), 
                        b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO));
    groundBody->CreateFixture(&boxShapeDef);
    groundBox.SetAsEdge(b2Vec2(winSize.width/PTM_RATIO, 
                               winSize.height/PTM_RATIO), b2Vec2(winSize.width/PTM_RATIO, 0));
    groundBody->CreateFixture(&boxShapeDef);
    
    //建立周圍世界邊框,在螢幕的周圍一圈
    b2BodyDef ballBodyDef;
    ballBodyDef.type = b2_dynamicBody;
    ballBodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);
    ballBodyDef.userData = _ball;
    _body = _world->CreateBody(&ballBodyDef);
    
    b2CircleShape circle;
    circle.m_radius = 26.0/PTM_RATIO;
    
    b2FixtureDef ballShapeDef;
    ballShapeDef.shape = &circle;
    ballShapeDef.density = 1.0f;
    ballShapeDef.friction = 0.2f;
    ballShapeDef.restitution = 0.8f;
    _body->CreateFixture(&ballShapeDef);
    
    schedule(schedule_selector(HelloWorld::tick));
}

void HelloWorld::tick(cocos2d::ccTime dt)
{
    _world->Step(dt, 10, 10);
    for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) { 
        if (b->GetUserData() != NULL) {
            CCSprite *ballData = (CCSprite *)b->GetUserData();
            ballData->setPosition(ccp(b->GetPosition().x * PTM_RATIO,
                                    b->GetPosition().y * PTM_RATIO));
            ballData->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()));
        } 
    }
}


CCScene* HelloWorld::scene()
{
    CCScene *scene = CCScene::node();
    HelloWorld* layer = new HelloWorld();
    layer->mInit();
    scene->addChild(layer);
    layer->release();
    return scene;
}
下面是相關資料的介紹,我就直接搬過來了。

Box2D世界相關理論

  在我們開始之前,讓我們先交待一下Box2D具體是如何運作的。

  你需要做的第一件事情就是,當使用cocos2d來為box2d建立一個world物件的時候。這個world物件管理物理模擬中的所有物件。

  一旦我們已經建立了這個world物件,接下來需要往裡面加入一些body物件。body物件可以隨意移動,可以是怪物或者飛鏢什麼的,只要是參與碰撞的遊戲物件都要為之建立一個相應的body物件。當然,也可以建立一些靜態的body物件,用來表示遊戲中的臺階或者牆壁等不可以移動的物體。

  為了建立一個body物件,你需要做很多事情--首先,建立一個body定義結構,然後是body物件,再指定一個shap,再是fixture定義,然後再建立一個fixture物件。下面會一個一個解釋剛剛這些東西。

  • 你首先建立一個body定義結構體,用以指定body的初始屬性,比如位置或者速度。
  • 一旦建立好body結構體後,你就可以呼叫world物件來建立一個body物件了。
  • 然後,你為body物件定義一個shape,用以指定你想要模擬的物體的幾何形狀。
  • 接著建立一個fixture定義,同時設定之前建立好的shape為fixture的一個屬性,並且設定其它的屬性,比如質量或者摩擦力。
  • 最後,你可以使用body物件來建立fixture物件,通過傳入一個fixture的定義結構就可以了。
  • 請注意,你可以往單個body物件裡面新增很多個fixture物件。這個功能在你建立特別複雜的物件的時候非常有用。比如自行車,你可能要建立2個輪子,車身等等,這些fixture可以用關節連線起來。

  只要你把所有需要建立的body物件都建立好之後,box2d接下來就會接管工作,並且高效地進行物理模擬---只要你週期性地呼叫world物件的step函式就可以了。

  但是,請注意,box2d僅僅是更新它內部模型物件的位置--如果你想讓cocos2d裡面的sprite的位置也更新,並且和物理模擬中的位置相同的話,那麼你也需要週期性地更新精靈的位置。

OK,繼續我的程式碼:

建立了world物件的時候需要指定一個初始的重力向量,上面我設定y軸方向為-30,因此,所有的body都會往螢幕下面下落。

第二個引數設定世界裡面的物件是否可以休眠,一個休眠的物件將不會花費處理時間,直到它與其實物件發生碰撞的時候才會“醒”過來。


接下來,建立一圈不可見的邊:

首先建立一個body定義結構體,並且指定它應該放在左下角。
然後,使用world物件來建立body物件。(注意,這裡一定要使用world物件來建立,不能直接new,因為world物件會做一些記憶體管理操作。)

接著,為螢幕的每一個邊界建立一個多邊形shape。這些“shape”僅僅是一些線段。注意,我們把畫素轉換成了“meter”。通過除以之前定義的比率來實現的。再建立一個fixture定義,指定shape為polygon shape。再使用body物件來為每一個shape建立一個fixture物件。注意:一個body物件可以包含許許多多的fixture物件。

  接下來,我們建立籃球的body。這個步驟和之前建立地面的body差不多,但是有下面一些差別需要注意一下:
我們指定body的型別為dynamic body。預設值是static body,那意味著那個body不能被移動也不會參與模擬。很明顯,我們想讓籃球參與模擬。

設定body的user data屬性為籃球精靈。你可以設定任何東西,但是,你設定成精靈會很方便,特別是當兩個body碰撞的時候,你可以通過這個引數把精靈物件取出來,然後做一些邏輯處理。這裡使用了一個不同的shape型別--circle shape。在這裡,我們需要為這個fixture指定一些引數,因此,我們沒有使用便捷方法來建立fixture。後面我們會講到這些引數的具體意義。
 
  第一件事情就是呼叫world物件的step方法,這樣它就可以進行物理模擬了。這裡的兩個引數分別是“速度迭代次數”和“位置迭代次數”--你應該設定他們的範圍在8-10之間。(譯者:這裡的數字越小,精度越小,但是效率更高。數字越大,模擬越精確,但同時耗時更多。8一般是個折中,如果學過數值分析,應該知道迭代步數的具體作用)。

  接下來,我們要使我們的精靈匹配物理模擬。因此,我們遍歷world物件裡面的所有body,然後看body的user data屬性是否為空,如果不為空,就可以強制轉換成精靈物件。接下來,就可以根據body的位置來更新精靈的位置了。

記得清理記憶體