読者です 読者をやめる 読者になる 読者になる

nirasan's tech blog

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

cocos2d-x で Box2D を使う 〜 重力のある空間と地面と物体を作る

はじめに

Box2D上に地面と物体をひとつ作成するサンプルです。
下記の参考書籍からBox2Dを使用したゲームのコードを抜粋し、ゲームロジックなどをのぞいて記載してみたいと思います。

参考書籍

cocos2d-x入門

cocos2d-x入門

新規プロジェクトの作成

今回は「b2test」という名前で作成します。

cd PATH/TO/COCOS2DX/
cd tools/project-creator
./create_project.py -project b2test -package com.example.b2test -language cpp

設定ファイルの作成

定数を宣言します。
ヘッダーファイル「Config.h」を作成して以下のように編集します。

Config.h
#ifndef b2test_Config_h
#define b2test_Config_h

// Pixel To Meter : nピクセルが1メートルという意味
#define PTM_RATIO 32

#endif

Box2D用のCCSprite拡張クラスの作成

Box2D上の物質とCCSpriteのひも付けをし、Box2D上で物質が移動したときにCCSpriteの表示位置も追従させるため、CCSprite拡張クラスを作成します。
C++クラスファイル「PhysicsSprite」を作成し、以下のようにヘッダーと本体を作成します。

PhysicsSprite.h
#ifndef __b2test__PhysicsSprite__
#define __b2test__PhysicsSprite__

#include <iostream>
#include "cocos2d.h"
#include "Box2D.h"

USING_NS_CC;

class PhysicsSprite : public CCSprite
{
protected:
    b2Body* m_pBody;
    
public:
    PhysicsSprite();
    void setPhysicsBody(b2Body* body);
    
    virtual bool isDirty(void);
    virtual CCAffineTransform nodeToParentTransform(void);
};

#endif /* defined(__b2test__PhysicsSprite__) */
PhysicsSprite.cpp
#include "PhysicsSprite.h"
#include "Config.h"

PhysicsSprite::PhysicsSprite()
:m_pBody(NULL)
{
}

void PhysicsSprite::setPhysicsBody(b2Body* body)
{
    m_pBody = body;
}

// CCSpriteクラスの関数をオーバーロード

bool PhysicsSprite::isDirty(void)
{
    return true;
}

// CCNodeクラスの関数をオーバーロード
// 表示されている画像を m_pBody に追従させるコード
// 参考書籍には「図形の変形などを行ったりするアフィン変換を利用した処理」とあり、それ以上詳しい解説は無く画像処理の専門書などを参照するように書いてある。
CCAffineTransform PhysicsSprite::nodeToParentTransform(void)
{
    b2Vec2 pos = m_pBody->GetPosition();
    
    float x = pos.x * PTM_RATIO;
    float y = pos.y * PTM_RATIO;
    
    if (isIgnoreAnchorPointForPosition()) {
        x += m_obAnchorPointInPoints.x;
        y += m_obAnchorPointInPoints.y;
    }
    
    float radians = m_pBody->GetAngle();
    float c = cosf(radians);
    float s = sinf(radians);
    
    if (!m_obAnchorPointInPoints.equals(CCPointZero)) {
        x += c * -m_obAnchorPointInPoints.x + -s * -m_obAnchorPointInPoints.y;
        y += s * -m_obAnchorPointInPoints.x + c * -m_obAnchorPointInPoints.y;
    }
    
    m_sTransform = CCAffineTransformMake(c, s, -s, c, x, y);
    return m_sTransform;
}

ゲームシーンの作成

Box2Dを使ったゲームシーンの作成をします。
C++クラスファイル「GameScene」を作成し、ヘッダーと本体を以下の通り編集します。
作成後、AppDelegate.cpp で HelloWorldScene と差し替えます。

GameScene.h
#ifndef __b2test__GameScene__
#define __b2test__GameScene__

#include <iostream>

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

USING_NS_CC;

class GameScene : public cocos2d::CCLayer
{
public:
    virtual bool init();
    static cocos2d::CCScene* scene();
    CREATE_FUNC(GameScene);
    
    b2World* world;
    void initPhysics();
    virtual void update(float delta);
    
    void createGround();
    void createSprite();
    
    enum kTag {
        kTag_Sprite,
        kTag_Ground,
    };
};

#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();
    // 物質の初期化
    createSprite();
    
    // 物理エンジン空間の更新
    scheduleUpdate();
    
    return true;
}

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

void GameScene::update(float delta)
{
    // 物理シミュレーションの正確さを決定するパラメータ
    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->initWithFile("CloseNormal.png");
    pSprite->setPosition(ccp(size.width * 0.5, size.height * 0.5));
    pSprite->setTag(kTag_Sprite);
    this->addChild(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);
}

おわりに

PhysicsSpriteは難しくて、ほぼおまじないとして写経しました。
Box2D上でのものの作り方はなんとなくわかった気がします。
次回は物質を動かしてみたいと思います。