LevelHelperとCocos2dXの勉強 その1


さて、今新作の作成に取り掛かっているのですが、勉強しながら進めています。ちょっと勉強しながらということで、最初のソフトはより簡単なゲームにしていく方針です。

いろいろ問題が発生しながらだとは思いますが、それも含めて生暖かく見てくださったら有難いです。

現状目指しているソフトはBox2機能は使わない方針でいます。それでも、LevelHelperとSpriteHelper(今後LHとSHと表記します)の以下の機能は損なわれなさそうです。

  • 各部品をGUIベースで配置できる機能
  • 画像をまとめて管理してメモリの節約に役立つ機能
  • 各アニメーションを管理できる機能
  • 各部品を簡単にコード上から扱える機能

Box2dを使ったものであれば、なおさら威力は発揮できるものだと思います。

英語が苦手でcocos2d-xもBox2dもわかってないので、テンプレートでついてくるソース特にHelloWorldScene.cppをいじったり、じっくり眺めてみました。勘違いや、ツッコミどころ満載の可能性ありますが、突っ込んだり無視したりしてくださいw

 

//
//  HelloWorldScene.cpp
//  2dx_1
//
//  Created by hira on 2013/02/13.
//  Copyright __MyCompanyName__ 2013年. All rights reserved.
//
#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"

using namespace cocos2d;
using namespace CocosDenshion;

#define PTM_RATIO 32

enum {
    kTagParentNode = 1,
};

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

}

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

// this method will only get called if the sprite is batched.
// return YES if the physics values (angles, position ) changed
// If you return NO, then nodeToParentTransform won't be called.
bool PhysicsSprite::isDirty(void)
{
    return true;
}

//部品(b2Body)の座標行列を返すみたいです。
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を作成
    m_sTransform = CCAffineTransformMake( c,  s,
        -s,    c,
        x,    y );

    return m_sTransform;
}

HelloWorld::HelloWorld()
{
    //タッチを有効にする
    setTouchEnabled( true );
    //加速度を有効にする
    setAccelerometerEnabled( true );

    //CCDirectorクラスはシングルトンクラスになっていて常に1つ。
    //以下は画面サイズを取得するという意味になる。
    CCSize s = CCDirector::sharedDirector()->getWinSize();
    // 物理法則初期化
    this->initPhysics();

    //A~Dのパーツが定義されている画像を読み込む
    CCSpriteBatchNode *parent = CCSpriteBatchNode::create("blocks.png", 100);
    m_pSpriteTexture = parent->getTexture();

    addChild(parent, 0, kTagParentNode);

    //スプライトを追加している。最初のブロックを出しているのはここ。
    addNewSpriteAtPosition(ccp(s.width/2, s.height/2));

    //ラベルを定義
    CCLabelTTF *label = CCLabelTTF::create("Tap screen", "Marker Felt", 32);
    addChild(label, 0);
    label->setColor(ccc3(0,0,255));
    label->setPosition(ccp( s.width/2, s.height-50));

    //このNode(CCLaryはCCNodeを継承している)の描画時(1FPS毎)にUpdateを呼ぶようにする。
    scheduleUpdate();
}

HelloWorld::~HelloWorld()
{
    delete world;
    world = NULL;

    //delete m_debugDraw;
}

void HelloWorld::initPhysics()
{
    //ウインドウのサイズを取得
    CCSize s = CCDirector::sharedDirector()->getWinSize();

    //b2Vec2は2次元ベクトルのクラスらしい。別に2次元への趣味の方向性とかそういう意味ではないだからね!
    //確認するために月面の重力-1.66当たりに変更するといいかも。やってみよう。
    b2Vec2 gravity;
    //gravity.Set(0.0f, -10.0f);
    gravity.Set(0.0f, -1.622);

    //物理空間を上記の重力で作成
    world = new b2World(gravity);

    //動きが止まった物 体について、計算を省略するようになる。
    world->SetAllowSleeping(true);

    //オブジェクトの衝突判定をする時にすり抜けないように調整して計算する。
    world->SetContinuousPhysics(true);

//     m_debugDraw = new GLESDebugDraw( PTM_RATIO );
//     world->SetDebugDraw(m_debugDraw);

    uint32 flags = 0;
    flags += b2Draw::e_shapeBit;
    //        flags += b2Draw::e_jointBit;
    //        flags += b2Draw::e_aabbBit;
    //        flags += b2Draw::e_pairBit;
    //        flags += b2Draw::e_centerOfMassBit;
    //m_debugDraw->SetFlags(flags);

    // 地面の始まりの点を設定しているらしい。とりあえずパラメータを弄っておく
    b2BodyDef groundBodyDef;
    //groundBodyDef.position.Set(0, 0); // bottom-left corner
    groundBodyDef.position.Set(1.0F, 2.0F);

    // 地面を定義
    b2Body* groundBody = world->CreateBody(&groundBodyDef);
    // 地面の形の定義(直線の当たり判定定義)
    //参考:http://hamken100.blogspot.jp/2012/03/cocos2dbox2d.html
    b2EdgeShape groundBox;

    //参考:http://obc-fight.blogspot.jp/2013/02/cocos2d-Box2D-Basic.html
    //以下の部分は上記を参照、どうやらメートルの単位で箱の線を定義しているらしい。
    // bottom①
    groundBox.Set(b2Vec2(0,0), b2Vec2(s.width/PTM_RATIO,0));
    groundBody->CreateFixture(&groundBox,0);

    // top②
    groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO));
    groundBody->CreateFixture(&groundBox,0);

    // left③
    groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(0,0));
    groundBody->CreateFixture(&groundBox,0);

    // right④
    groundBox.Set(b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,0));
    groundBody->CreateFixture(&groundBox,0);

    //        ②
    // ---------------
    // |             |
    //③|             |④
    // |             |
    // ---------------
    //        ①

}

void HelloWorld::draw()
{
    //
    // IMPORTANT:
    // This is only for debug purposes
    // It is recommend to disable it
    //
    CCLayer::draw();

    ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position );

    kmGLPushMatrix();

    world->DrawDebugData();

    kmGLPopMatrix();
}

void HelloWorld::addNewSpriteAtPosition(CCPoint p)
{
    CCLOG("Add sprite %0.2f x %02.f",p.x,p.y);

    //タグから画像を取得
    CCNode* parent = getChildByTag(kTagParentNode);

    //先ほど読み込んだ画像からパーツをランダムに選んで表示する
    //CCRANDOM_0_1は0から1までのランダムな数値(float)を返すマクロなので、0と1を二分の一で出している。
    int idx = (CCRANDOM_0_1() > .5 ? 0:1);
    int idy = (CCRANDOM_0_1() > .5 ? 0:1);
    PhysicsSprite *sprite = new PhysicsSprite();
    //座標指定
    sprite->initWithTexture(m_pSpriteTexture, CCRectMake(32 * idx,32 * idy,32,32));
    sprite->autorelease();

    parent->addChild(sprite);
    //指定した場所に設置
    sprite->setPosition( CCPointMake( p.x, p.y) );

    //Box2Dで動的(静的にすると中に浮かぶのかな?)なオブジェクトとして設定。メートル単位で座標を指定
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);

    b2Body *body = world->CreateBody(&bodyDef);

    // 縦横1メートルのボックス(スプライトは縦横32ピクセルなので)として設定される。
    // SetAsBoxのパラメーターはボックスの中心から辺までの距離を指定するので、
    // 0.5メートルは一辺が1メートルという意味になる。らしい
    //参考:http://jp.evergizmo.com/2011/12/23/
    b2PolygonShape dynamicBox;
    dynamicBox.SetAsBox(.5f, .5f);

    // 属性の定義。密度、摩擦、反発などを設定する。
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &dynamicBox;
    //密度
    fixtureDef.density = 1.0f;
    //摩擦
    fixtureDef.friction = 0.3f;
    //反発は定義されてなかったので、ちょっといじってみる。
    fixtureDef.restitution = 0.8f;
    body->CreateFixture(&fixtureDef);
    //スプライトとBox2Dの動的オブジェクトを結びつける
    sprite->setPhysicsBody(body);
}

//一定時間ごとに呼ばれる。
void HelloWorld::update(float dt)
{
    //フレームを計算して描画するにはいろんな方法があるとか何とか。以下のURLで勉強してくれとのことです。
    //http://gafferongames.com/game-physics/fix-your-timestep/

    int velocityIterations = 8;
    int positionIterations = 1;

    // Instruct the world to perform a single step of simulation. It is
    // generally best to keep the time step and iterations fixed.
    world->Step(dt, velocityIterations, positionIterations);

    //世界に配置されている部品を全部見て回ってる。
    for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
    {
        //部品に関連づいているスプライトに対しても物理的な動きを与える。
        //今のところそんな部品はないので、動いていない。
        //キャラクターに張り付いているラベルとか、ゲージとか使うときに使用するようだ
        //参考:http://www.haphands.com/sw_flash/tech/box2d/box2d_09.html
        if (b->GetUserData() != NULL) {
            //Synchronize the AtlasSprites position and rotation with the corresponding body
            CCSprite* myActor = (CCSprite*)b->GetUserData();
            //部品の座標をスプライトの座標として設定
            myActor->setPosition( CCPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO) );
            //部品の回転角度をスプライトの回転角度として設定
            myActor->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()) );
        }
    }
}
//タッチした時の動作
void HelloWorld::ccTouchesEnded(CCSet* touches, CCEvent* event)
{
    //Add a new body/atlas sprite at the touched location
    CCSetIterator it;
    CCTouch* touch;
    //複数タッチ(ホントはタッチを離す動作)されても大丈夫なようになっている。タッチされた点をイテレータに格納して
    //インクリメントしているようだ
    for( it = touches->begin(); it != touches->end(); it++) 
    {
        touch = (CCTouch*)(*it);

        if(!touch)
            break;

        CCPoint location = touch->getLocationInView();

        location = CCDirector::sharedDirector()->convertToGL(location);
        //タッチされた点にスプライトを追加
        addNewSpriteAtPosition( location );
    }
}
//シーンを作ってレイヤーを追加。ちなみにこのHelloWorld自体はCCLayerを継承しているので、厳密にはsceneではない
//と思ったりする。staticだから、どこからでも呼べるよー。
CCScene* HelloWorld::scene()
{
    // sceneはオートリリースされるらしい。
    CCScene *scene = CCScene::create();

    // sceneに結びつくレイヤーを作成。このレイヤーこそがHellowWorld。
    CCLayer* layer = new HelloWorld();
    scene->addChild(layer);
    //え?なんでリリースしてんの?って思って調べたのですが、
    //どうやらCCLayerはCCObjectを継承しているっぽいです。
    //で、このCCObjectはobjective-cのratinやreleaseのようなメモリ管理のためのメソッドを実装しているっぽいです。
    //あとは参照数を管理して勝手にメモリから消えてくれる。
    layer->release();

    return scene;
}

結果、おお月面でスーパーボールを落としたような動きだ!

スクリーンショット 2013-02-20 0.16.07

静止画なのでわかりづらいですが、

  • 部品は反発するからスーパーボール的な動き
  • 月面的なホワンホワンとした重力
  • 見えない壁の外(画面左部、下部)に部品を作ると地面がないのでどこかへ消えていく
  • 見えない壁が画面外に有るため、右側の見えない領域にも部品が行く

な感じになってます。ふー。多少は理解できた気がします。今日はこの辺で。次回はLevelHelperとの連携をやってみようかなと。

またがおまるさんにお世話になるブログが始まるよ!