nirasan's tech blog

趣味や仕事の覚え書きです。Linux, Perl, PHP, Ruby, Javascript, Android, Cocos2d-x, Unity などに興味があります。

cocos2d-x で Box2D を使う 〜 複数の物体を扱う

はじめに

前々回、前回から引き続いて、Box2Dで複数の物体を扱ってみます。
タップ時に物体をタップしたら上にはねるのはそのままですが、物体以外の箇所をタップしたら新しい物体が作成されるようにしました。
また、物体が画面外に出た場合、削除する処理も追加しています。

コード

主な追加変更点は、initSprite(), createSprite(), deleteSprite() とその関連箇所です。

GameScene.h
#ifndef __b2test__GameScene__
#define __b2test__GameScene__

#include <iostream>
#include <list>

#include "cocos2d.h"
#include "Box2D.h"
#include "Config.h"
#include "PhysicsSprite.h"

USING_NS_CC;

class GameScene : public cocos2d::CCLayer
{
private:
    CCSpriteBatchNode* spriteBatchNode;
    list<PhysicsSprite*> pSpriteList;
public:
    virtual bool init();
    static cocos2d::CCScene* scene();
    CREATE_FUNC(GameScene);
    
    b2World* world;
    void initPhysics();
    virtual void update(float delta);
    
    void createGround();
    
    void initSprite();
    void createSprite();
    void deleteSprite();
    
    enum kTag {
        kTag_Sprite,
        kTag_Ground,
    };
    
    virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent * pEvent);
    virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
};

#endif /* defined(__b2test__GameScene__) */
GameScene.cpp
#include "GameScene.h"

using namespace cocos2d;
using namespace std;

CCScene* GameScene::scene()
{
    CCScene* scene = CCScene::create();
    GameScene* layer = GameScene::create();
    scene->addChild(layer);
    
    return scene;
}

bool GameScene::init()
{
    if (!CCLayer::init()) {
        return false;
    }
    
    // 物理エンジン空間の初期化
    initPhysics();
    // 地面の初期化
    createGround();
    // 物質の初期化
    initSprite();
    createSprite();
    
    // 物理エンジン空間の更新
    scheduleUpdate();
    
    // タッチ有効化
    setTouchEnabled(true);
    setTouchMode(kCCTouchesOneByOne);
    
    return true;
}

void GameScene::initSprite()
{
    spriteBatchNode = CCSpriteBatchNode::create("CloseNormal.png");
    this->addChild(spriteBatchNode);
}

void GameScene::initPhysics()
{
    // 重力の設定
    b2Vec2 gravity;
    gravity.Set(0.0, -10.0);
    
    // worldを作成
    world = new b2World(gravity);
}

void GameScene::update(float delta)
{
    // 画面外のspriteの削除
    deleteSprite();
    
    // 物理シミュレーションの正確さを決定するパラメータ
    int velocityIterations = 8;
    int positionIterations = 1;
    // worldを更新する
    world->Step(delta, velocityIterations, positionIterations);
}

void GameScene::createGround()
{
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    
    /*
     物理エンジン空間上に地面を設定する
     */
    
    // 地面ノードの作成
    CCNode* pGround = CCNode::create();
    pGround->setTag(kTag_Ground);
    this->addChild(pGround);
    
    // 地面の定義
    b2BodyDef groundBodyDef;
    groundBodyDef.position.Set(0, 0);
    groundBodyDef.userData = pGround;
    
    // 地面作成
    b2Body* groundBody = world->CreateBody(&groundBodyDef);
    
    // 地面の形と大きさの定義
    float groundHeight = size.height * 0.2;
    b2EdgeShape groundBox;
    groundBox.Set(b2Vec2(0, groundHeight / PTM_RATIO),
                  b2Vec2(size.width / PTM_RATIO, groundHeight / PTM_RATIO));
    groundBody->CreateFixture(&groundBox, 0);
}

void GameScene::createSprite()
{
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    
    /* 画像の表示 */
    PhysicsSprite* pSprite = new PhysicsSprite();
    pSprite->autorelease();
    pSprite->initWithTexture(spriteBatchNode->getTexture());
    pSprite->setPosition(ccp(size.width * 0.5, size.height * 0.5));
    pSprite->setTag(kTag_Sprite);
    this->addChild(pSprite);
    
    pSpriteList.push_back(pSprite);
    
    /*
     物理エンジン上の物理構造設定
     */
    
    /* 物理エンジン上の物質の定義 */
    b2BodyDef spriteBodyDef;
    /* 
       物体の力に対する属性
         b2_dynamicBody: 速度があり力に反応する
         b2_staticBody: 速度が無く力に反応しない
         b2_kinematicBody: 速度があり力に反応しない
     */
    spriteBodyDef.type = b2_dynamicBody;
    /* 物理エンジンの空間上の座標 */
    spriteBodyDef.position.Set(pSprite->getPositionX() / PTM_RATIO,
                               pSprite->getPositionY() / PTM_RATIO);
    spriteBodyDef.userData = pSprite;
    /* 物理エンジン上の物質作成 */
    b2Body* spriteBody = world->CreateBody(&spriteBodyDef);
    
    /* 物理エンジン上の物質の形と大きさ */
    b2CircleShape spriteShape;
    spriteShape.m_radius = pSprite->getContentSize().width * 0.3 / PTM_RATIO;
    
    /* 物質の性質定義(形、密度、摩擦) */
    b2FixtureDef spriteFixturedef;
    spriteFixturedef.shape = &spriteShape;
    spriteFixturedef.density = 1;
    spriteFixturedef.friction = 0.9;
    
    /* 物質の性質定義適用 */
    spriteBody->CreateFixture(&spriteFixturedef);
    
    /* 画像と物理エンジン上の物質の関連付け */
    pSprite->setPhysicsBody(spriteBody);
}

// 画面外に出たspriteの削除
void GameScene::deleteSprite()
{
    // 画面サイズの取得
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    
    // イテレータの取得
    list<PhysicsSprite*>::iterator it = pSpriteList.begin();
    while (it != pSpriteList.end()) {
        // spriteの取得
        PhysicsSprite* pSprite = *it;
        // spriteの位置の取得
        b2Vec2 position = pSprite->getPhysicsBody()->GetPosition();
        float x = position.x * PTM_RATIO;
        float y = position.y * PTM_RATIO;
        // 画面外に出たかどうか
        if (x < 0 || size.width < x || y < 0 || size.height < y) {
            // Box2d上の要素の削除
            world->DestroyBody(pSprite->getPhysicsBody());
            // spriteの削除
            pSprite->removeFromParentAndCleanup(true);
            // spriteのリストから削除&削除した要素以降のイテレータの取得
            it = pSpriteList.erase(it);
        }
        else {
            // イテレータを進める
            it++;
        }
    }
}


bool GameScene::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
{
    return true;
}

void GameScene::ccTouchEnded(CCTouch* pTouch, CCEvent* pEvent)
{
    // タップポイント取得
    CCDirector* pDirector = CCDirector::sharedDirector();
    CCPoint touchPoint = pDirector->convertToGL(pTouch->getLocationInView());
    
    CCLOG("[ccTouchEnded](%f,%f)", touchPoint.x, touchPoint.y);
    
    bool touched = false;
    
    // spriteをタッチしたらランダムで上向きの力を与える
    list<PhysicsSprite*>::iterator it = pSpriteList.begin();
    while(it != pSpriteList.end()) {
        PhysicsSprite* pSprite = *it;
        CCRect rect = pSprite->boundingBox();
        if (rect.containsPoint(touchPoint)) {
            b2Body* b = pSprite->getPhysicsBody();
            // 飛び先の座標をランダムに
            int x = random() % 200 - 100;
            int y = random() % 200;
            b->ApplyForce( b2Vec2(x, y), b->GetWorldCenter() );
            touched = true;
        }
        it++;
    }
    
    // spriteをタッチしなかったら、新しいspriteを追加する
    if (!touched) {
        createSprite();
    }
}