2011年5月19日 星期四

cocos2d & box2d tutorial - 1

目前越來越多遊戲應用到基本的物理特性, 包含下墜, 撞擊等, 經典的例子就是目前最為人知道的 "Angry Bird", 這邊簡單介紹如何透過 cocos2d & box2d 來達成這些物理現象.

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_RATIO300/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 

沒有留言:

張貼留言