【#TechBuzz】第5回Shinjuku.cocos2d-x 〜少人数ハンズオン勉強会〜 かんたんなゲームをつくってみよう! (2/21)に行って来ました!


●cocos2dxとcocosBilderの連携を勉強。勉強内容は以下のURL。
講師は清水 友晶さんです。cocos2dxを調べると清水さんのスライドが出てきます。
めちゃ勉強になるので他のスライドも必見!
・今回のスライド
 http://www.slideshare.net/doraemonsss/cocos2dxcocosbuilder
・絵素材
 http://tks2.net/nyanrun/images.zip
・cocos2dxプロジェクトとcocosBilderのプロジェクト
 http://tks2.net/nyanrun/nyanrun.zip 

●個人的にな感想
・途中までは行きましたが、まだまだCocosBilderの使い方が把握できてない。
 あとでまた勉強し直そう。
・スライドがあるので復習できるのが有難い。
・ちょっとcocosBilderとlevelhelperの違いを直接TKS2の清水さんに聞きました。
 ーcocosBilder
 ・タダ
ーLevelHelper
 ・有料
 ・Bos2dとの連携、SpriteHelperとの連携が強力
 ・バグが多少有る。
・古いMACはちょっと恥ずかしいw
・古いMACのビルドは遅いw
・今回のようなハンズオンは周りの人と協力しあって進めるのが吉。
 お互いにわからないところを保管できる。
・お隣さんとは名刺交換なり、握手なりして仲良く始めましょう!
・次回のハンズオンは早いうちに席に座っておとなりさんと名刺交換しておこう。
・清水さんに、「cocosBilderのプロジェクトも欲しいです」
 という話をしたら上記にURLに置いてくださいました!
 ソースがブルドックに見えるほど眺めさせて頂きます!

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との連携をやってみようかなと。

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

LevelHelperとSpriteHelperを動かして試してみた。(失敗例です。)の続報 その2


さて、LevelHelperでエクセプションはLevelHelper-PROを使用することで回避することが可能というところまででした。

LevelHelper-PROは試用期間があります。(LevelHelperをお試ししたい人はおすすめ。)使用期間を超えて使用するには、ライセンスが必要です。

ライセンスは、HPのContactにLevelHelperPROを使用したい旨を伝えて、返信して来るであろうメールにLevelHelperとSpriteHelperの明細書(アップルからメールで来るアプリの購入内容)のスクリーンショットを取って送信してください。

HPのContact:http://www.gamedevhelper.com/contact/

明細書は日本語のままだったのですが、問題なかったようです。

LevelHelperPROとSpriteHelperPROのライセンスファイルが添付されてくるので、ファイルをクリックするだけで自動認証してくれます。

さてLevelHelperPROとSpriteHelperPROですが、PRO付きじゃないものに比べて機能が増えてます。ベータ版と考えたほうがいいと思いますので、現状で問題が起こったとか、ベータ版の機能を使いたいと言う人以外はアップグレードしないほうが良いと思います。

この場を借りて拙い英語よりも日本語でいいます。Bogdanさんありがとうございました!

LevelHelperとSpriteHelperを動かして試してみた。(失敗例です。)の続報


LevelHelperの件をメールしたら、速攻返信が!早くてびっくり。

内容はヴァーチャルマシンでやってないか?という話でした。

違うよーと先ほどメールしたら、今度は速攻アップデートでバグ修正完了したから確認してくれと返信がwはやいw早すぎる。

メールによるとLevelHelperProにて修正済みとか。公式のDLページから落とせます。

が、ライセンスキーが必要らしいです。これはBogdanさんに購入したAppID(Eメールアドレス)をメールで送るともらえるらしい?です。※勘違いして無ければw

ともかく、AppIDを送信してライセンスーもらったら、またLevelHelperを使用して行きますね。

Bogdanさんありがとうございます!

 

LevelHelperとSpriteHelperを動かして試してみた。(失敗例です。)


LevelHelperとSpriteHelperを動かして試してみた。(結果思うように動いてません!)

今回は失敗例です。同じ症状の方がいたら情報を共有し合いたいです。
cocos2d-xの記事はまだ少ないらしいので、失敗例も書いていくのが努めかなと。
※今回の記事は直接2dxの話ではないちゃーないですが・・・。

さて、LevelHelperとSpriteHelperを使うとどうやら、

SpriteHelper
・複数の素材を1つにまとめた画像を作成。
・それぞれの素材の座標を関連付けてくれる。
LevelHelper
・SpriteHelperで作成した素材をGUIベースで設置
・素材は名前をつけてコード上でも扱える。
・それらの情報をcocos2d-xのソースに変換できる。

ということらしい。それ以上に機能がありそうですが・・・自分は試せませんでした!

自分がやったことは以下。また、がおまるさんの記事をトレースしてやってます。

まずはLevelHerperとSpriteHelperをダウンロードしてインストール。
2200+1500円します。iTunesカード買って購入。しっかりがおまるさんのリン(ry

第1回:プロジェクトの作成
http://gaomar.blog.fc2.com/blog-entry-12.html
シュミレータのところは一度ダウンロードしてくる必要があるようです。
ダウンロードしてインストール。もう一度LevelHelperに戻ってシュミレーション実行、
どこにインストールしたシュミレーションソフトを指定してやると、シュミレーションが動きます!
なるほどなるほど。シュミレーションで配置できた!

第2回:cocos2d-xに組み込む
http://gaomar.blog.fc2.com/blog-entry-13.html
ここからが重要だなー。
「Supporting Code」タブをクリックして・・・・んん?エクセプションエラーだと・・・?
スクリーンショット 2013-02-12 21.14.27
なんぞこれー・・・・ぐぐっても同じ症状の方がいない。あれ?他のタブ開いても同じ症状でるぞ?!
いろいろやったけど、改善せず。うーん・・・。とりあえず、エクセプションエラーでたよと作者のHPへ投稿して置きました。
ちなみに、
・ネットの回線を切った状態でも同じエクセプションが発生します。
・MACを英語設定にしても発生します。
・もちろんHDDが足らないってことはないです。

自分の環境のせいで発生している可能性が高い気がします。iOS10.8にムリやりアップデートしたこのPCが・・・。
うーん。それ以上LevelHelper進めない・・・。とりあえず、2dxの方を進めていきます!
なにか情報があったらまた書きます。

cocos2d-xをインストールしていじってみた。その2


さて次のステップ。昨日動かしたcocos2d-xのテンプレートを動かするとiPhone3の解像度で動きます。

御存知の通り、iPhone3、iPhone4、iPhone5では解像度が違います。もちろんiPadも。

ちょっとiPhoneとiPadの解像度についておさらいを。

iOS対応機種の画素数とアスペクト比
機種名 画素数(縦x横) アスペクト比
iPhone3 480×320 3:2
iPhone4 960×640 3:2
iPhone5 1136×640 16:9
iPad1&2 1024×768 4:3
iPad3 2048×1536 4:3
iPad mini 1024×768 4:3

iOS対応機種全てに対応しようとすると、すべての解像度の画像を用意すると、5種類。
アスペクト比でまとめる事で3種類ということになりますかね。
なんか次に出るiPhoneの話がちらほら出てますが、あんまり種類増やしてほしくないですよねw。

で、どうやってこれらの解像度の違いに対応するかという点ですが、以下のURLで説明されています。

http://www.cocos2d-x.org/projects/cocos2d-x/wiki/Mechanism_of_loading_resources

何々?解像度の違うそれぞれの種類にフォルダを分けて管理しろということか。
記事がちょっと古いから5のことは乗ってないけど、ここに5も追加すればOKですね。

1.1
CCFileUtils::setSearchResolutionsOrder()で対応する種別を追加できるぜって言ってますね。
その後は画像を指定するだけで勝手に探してくれるっぽい。
1.2
アンドロイドではこうは行かないぜと書いてますね。まだアンドロイドに手を出せないのでとりあえず、置きます。

んー、では、アスペクト比が違う種類の画像座標の計算はどうするんだろう?これはまだ書いてないですね。
思えば、この環境ってXIBやストーリーボードがないので、直感的なボタンの配置などがあんまり出来ないですよね。

などと思っていたら、何やらCocosBilderなる面白そうなものを発見。
http://d.hatena.ne.jp/shinriyo/20121014

っと思ったが、できない!!!と同じ症状!!調べてもなかなか出てこない!という事でとりあえず、コレも置く方向でw

ハマった時は周りを見渡すようにしてます。ということで、がおまるさんとかはどうしてるのかなーとがおまるさんブログをキョロキョロ。よく見てみたらLevelHelperっていう有料のツールを使っているっぽいです。

CocosBilderはゲーム前のタイトルとかには使えるぞ的な発言が見えたですが、LevelHelperの方はゲーム自体の部品配置も可能っぽいですね。がおまるさんのアプリがそれを証明している。さすがの有料。2200円かー。買うならまだ円安の影響の出ない今がチャンスか。(大した差じゃないかw)今日のところは以上で。