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的位置來更新精靈的位置了。
記得清理記憶體