step 1:
Create "cocos2d Box2d Application" Project:
step 2:
add variable in HelloWorldScene.h
b2World* world_; |
step 3:
add the code in -(id) init() function
CGSize winSize = [CCDirector sharedDirector].winSize; //create a ccprite CCSprite *ball = [CCSprite spriteWithFile:@"ball.png"]; ball.position = ccp(100,300); ball.tag = 1; [self addChild:ball]; //create world b2Vec2 gravity = b2Vec2(0.0f, -10.0f); bool doSleep = true; world_ = new b2World(gravity,doSleep); //create a rectangle of a world b2Body *groundBody; b2BodyDef groundBodyDef; groundBodyDef.position.Set(0,0); groundBody = world_->CreateBody(&groundBodyDef); //set edges b2PolygonShape groundBox; b2FixtureDef boxShapeDef; boxShapeDef.shape = &groundBox; groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO,0)); groundBody->CreateFixture(&groundBox,0); groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(0,winSize.height/PTM_RATIO)); groundBody->CreateFixture(&groundBox,0); groundBox.SetAsEdge(b2Vec2(winSize.width/PTM_RATIO,0), b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO)); groundBody->CreateFixture(&groundBox,0); groundBox.SetAsEdge(b2Vec2(0,winSize.height/PTM_RATIO), b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO)); groundBody->CreateFixture(&groundBox,0); b2BodyDef ballBodyDef; ballBodyDef.type = b2_dynamicBody; ballBodyDef.position.Set(100/PTM_RATIO, 300/PTM_RATIO); ballBodyDef.userData = ball; b2Body *body = world_->CreateBody(&ballBodyDef); b2CircleShape circle; circle.m_radius = 26.0/PTM_RATIO; b2FixtureDef ballShapeDef; ballShapeDef.shape = &circle; ballShapeDef.density = 0.5; ballShapeDef.friction = 0.2f; ballShapeDef.restitution = 0.7; body->CreateFixture(&ballShapeDef); [self schedule: @selector(tick:)]; |
step 4:
add a tick function
-(void) tick: (ccTime) dt { int32 velocityIterations = 8; int32 positionIterations = 1; world_->Step(dt, velocityIterations, positionIterations); for (b2Body* b = world_->GetBodyList(); b; b = b->GetNext()) { if (b->GetUserData() != NULL) { CCSprite *myActor = (CCSprite*)b->GetUserData(); myActor.position = CGPointMake(b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO); myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()); } } } |
上述程式部份完成, 我們再來一步一步看看究竟寫了些什麼?
b2World* world_; b2Vec2 gravity = b2Vec2(0.0f, -10.0f); bool doSleep = true; world_ = new b2World(gravity,doSleep); |
b2World class 是一個比較特殊的物件, 一個遊戲中只會有一個 b2World, 有寫過 Java JSR184 的人相信對於這類型的 class 就比較不陌生, b2World 很相似於 JSR184 裡的 World class. 此 class 控制了這整個遊戲世界裡的狀態, 包含畫面的更新, 遊戲物件的控制, 以及遊戲世界中的重力系統..等.
所以再開始利用 box2d 實作之前, 一定都要先 new b2World, 而 b2World 被創建的時候會需要兩個參數
- const b2Vec2& gravity - 遊戲世界的重力向量
- bool doSleep - 當物體靜止(sleep)時, 忽略此物體的計算
b2Body *groundBody; b2BodyDef groundBodyDef; groundBodyDef.position.Set(0,0); groundBody = world_->CreateBody(&groundBodyDef); |
設定遊戲世界的原點, 由於 cocos2d 和 box2d 的坐標系統不同, 所以我們必須要將兩者的坐標系統做一個 mapping, 設定坐標系統會需要兩個步驟, 第一個是如上面程式去設定原點, cocos2d 的原點在左下角, 所以便將 box2d 的原點設定在 (0,0) 的位置.
b2PolygonShape groundBox; b2FixtureDef boxShapeDef; boxShapeDef.shape = &groundBox; //bottom groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO,0)); groundBody->CreateFixture(&groundBox,0); //left groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(0,winSize.height/PTM_RATIO)); groundBody->CreateFixture(&groundBox,0); //right groundBox.SetAsEdge(b2Vec2(winSize.width/PTM_RATIO,0), b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO)); groundBody->CreateFixture(&groundBox,0); //top groundBox.SetAsEdge(b2Vec2(0,winSize.height/PTM_RATIO), b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO)); groundBody->CreateFixture(&groundBox,0); |
#define PTM_RATIO 32 |
Box2d 的坐標系統範圍為 0.1 ~ 10, 因此定義一個 PTM_RATION 為 32 來作為 480x320 的映射比例. 在上面這段 code 裡分別設定四個邊的位置, 設定完的坐標對應會如下圖所示
CCSprite *ball = [CCSprite spriteWithFile:@"ball.png"]; ball.position = ccp(100,300); ball.tag = 1; [self addChild:ball]; b2BodyDef ballBodyDef; ballBodyDef.type = b2_dynamicBody; ballBodyDef.position.Set(100/PTM_RATIO, 300/PTM_RATIO); ballBodyDef.userData = ball; b2Body *body = world_->CreateBody(&ballBodyDef); |
利用 cocos2d 去產生一個 CCSprite, 透過 b2BodyDef 將此 CCSprite 和 b2World 做關聯
- type : 設定此物體是可以自由移動的
- position : 設定此物體在 b2World 裡的位置
- userData : 將 CCSprite pointer 紀錄在 userData 中, 之後可以利用這 pointer 來對 CCSprite 做變化
b2CircleShape circle; circle.m_radius = 26.0/PTM_RATIO; b2FixtureDef ballShapeDef; ballShapeDef.shape = &circle; ballShapeDef.density = 0.5; ballShapeDef.friction = 0.2f; ballShapeDef.restitution = 0.7; body->CreateFixture(&ballShapeDef); |
因為此範例所載入的圖行為一圓球, 所以必須相對應的使用 b2CircleShape 來告知 b2World, 針對此 body 要怎樣去做計算, 並且指定此圓球的密度(density), 摩擦係數(friction),彈力係數(restitution)
[self schedule: @selector(tick:)]; -(void) tick: (ccTime) dt { int32 velocityIterations = 8; int32 positionIterations = 1; world_->Step(dt, velocityIterations, positionIterations); for (b2Body* b = world_->GetBodyList(); b; b = b->GetNext()) { if (b->GetUserData() != NULL) { CCSprite *myActor = (CCSprite*)b->GetUserData(); myActor.position = CGPointMake(b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO); myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()); } } } |
最後透過 cocos2d 的 schedule 來更新所有畫面的動作, (ccTime) dt 就是 cocos2d 的更新頻率, 預設的值會是 1/60 second, 當每次 tick 被調用的時候, 上述的 code 會透過 world_->GetBodyList() 去將裡面所有的 body 取出, 且當 b->GetUserData() 有值的時候(也就是剛剛設定的 CCSprite), 就根據經過 box2d 所計算出來的位置和角度分別設定 position & rotation
範例程式 : Tutorial - FallingBall
沒有留言:
張貼留言