筋肉とエンジニアリングで すべてを解決するブログ

筋トレ、JavaScript、Ruby で世界を変えてやります。

Cocos2d-x の基本的なクラスの作り方について

f:id:ma3tk:20150114000942j:plain

前提

インストールは済んだ、ビルドもできるようになった!

「けど、何をどんな感じでコード書いていけばいいの?」って僕はなったので、そこの導入部分を書いてみようと思います。

API Reference 見ればそれぞれの使い方はわかると思うんですが、具体的なイメージが湧きにくいなと思ったのでコードを交えて解説してみようと思います。

おおまかな概要

  • そもそも Scene がひとつの大きな塊
  • Scene の中に Layer や Node が存在する
  • Scene とは1画面全体、くらいのイメージ
  • Layer とは Scene の中にある透明な層(Photoshop のレイヤーと大体一緒。アニメのフィルムみたいな。)
  • Node は Layer の中に場所を指定して何らかを配置するためのものの総称。画像とか載せたり。フィルムに指すためのピン的なイメージでもある
  • 表示には関係しないそれ以外のクラスは Ref を継承して作る
  • 既に Scene や Layer や Node や Sprite 等 は Ref クラスを継承している
  • 継承しなくても自前で色々できるけど、メモリ管理するのに Ref クラスを継承してそれのお作法に沿ったほうがベターかと思います
  • 経路探索の計算とか AudioManager のような管理用のクラスだったり

結論から言えば…

Scene や Layer や Node や Sprite を継承した自前クラス作って肉付けしていくのが基本です。

Node もしくは Node クラスを継承したクラス(Scene, Layer, Node, Sprite等) を Node クラスで実装されている addChild() で親子関係を作り描画します。

以下に具体的な話を書いてみます。

自前クラス作って色々書く

ただ単に Layer とか Node 使うんじゃなくて、Layer や Node を継承したクラスを作ってコードを書く

こんな感じに

http://tenclaps.hatenablog.com/entry/2014/06/06/180000

// 基本的なやつ
Scene* BattleScene::createScene()
{
    auto scene = Scene::create();
    auto battleScene = BattleScene::create();
    scene->addChild(battleScene);
 
    return scene;
}
 
bool BattleScene::init() {
    if (!Scene::init()) {
        return false;
    }
    return true;
}

もちろん layer とか Node も同じようなお作法で作れます

// .h ファイル内で以下を記述
class UILayer : public Layer
{
public:
    virtual bool init();
    CREATE_FUNC(UILayer);
};
 
// .cpp 内
bool UILayer::init() {
    if (!Layer::init()) {
        return false;
    }
    return true;
}
// .h ファイル内で以下を記述
class HogeNode : public Node
{
public:
    virtual bool init();
    CREATE_FUNC(HogeNode);
};
 
// .cpp 内
bool HogeNode::init() {
    if (!Node::init()) {
        return false;
    }
    return true;
}

上記で UILayer は Layer クラスを継承してるので、Layer クラスのお作法に適したようにまずテンプレを書くところから始まります。

基本的すぎるのでアレだけど、基本全部こんな感じになってますね。

古いけど

http://www.cocos2d-x.org/wiki/Cocos2d-x_Coding_Style_C++

上記を読んでみてください。

もしくはシングルトンにしてあげて管理する

コードみてもらうのが速いかもしれません。

Util* Util::utilSingleton = NULL;
Util* Util::getInstance()
{
    // utilSingleton は↑のグローバル変数のこと
    if (!utilSingleton) {
        utilSingleton = new Util();
    }
     
    return utilSingleton;
}

シングルトンというデザインパターンにすれば 共通の処理とかの時に毎回クラス作らなくていいのがいいところですね。

その他設計について参考になる記事

http://labs.gree.jp/blog/2014/12/12698/

基本的な各 Scene と Layer と Node のやりとりの仕方

addChild()

基本的に Scene が一番上の親となって存在し、 Layer や Node (またはそれを継承したクラス)と親子関係になるイメージです。

Cocos2d-x では addChild() を頻繁に書くことになると思います。各 Layer や Node を Scene に持たせる時も addChild() します。

cocos2d-x の addChild() によるメモリ管理

Cocos2d-x のメモリ管理は

http://brbranch.jp/blog/201311/cocos2d-x/cocos2dx_memory_leak/

こちらを見ると大体わかるかと思います。

addChild() することで retain() されるので、わざわざ自分でメモリ管理のためになんらかする必要が無いところが Cocos2d-x を使ったプログラミングの特徴ですね。もちろん自分で作ったお作法に沿ってないクラスはメモリ管理を自分でする必要がありますが。

それと注意しなくてはならない部分としては、addChild() しても親が どこかに addChild() されてなかったりするとメモリが解放されてて /(^o^)\ な状態になります。

ちゃんと addChild() されたかどうかはデバッガで確認できます。Node クラスで親子を確認できるので

こういう状態じゃないかとかチェックしてみるといいと思います。

逆に親はある場合

親の children の中に children として存在するかどうかをチェックするのが重要です。また、これにかぎらず、localzorder が違うとかも zorder とか 座標も _position はじめ、くさんのメンバが Node クラスだとか Layer クラスにたくさん入っているので表示されないとか位置がズレてるとかチェックしてあげてください。

また、メンバとして持たせる場合に 普通にメンバ変数としてもたせられるけど、メンバ変数に代入するだけではなく、 addChild() しないといけないことは忘れずに。

ちなみに、

  • メンバに Building クラスで std::Vector<Unit*> unitList というメンバを保持している状態
  • Unit クラスにも Building* target というメンバをもたせてる状態

にしてしまうと、コンパイル時にどっちのコンストラクタから作ればええねんという Link - Error みたいなのが出るのでそこら辺の設計は注意してください。オブジェクト指向を意識。

それか素直にマネージャークラス作ってそこでやりとりしてあげるのがいいと思います。

具体的には以下の部分が参考になるかと重います。

http://www.cocos2d-x.org/wiki/Layer

ベースとなるような作り

大体概要がわかったかと思いますが、実際にコードを書くとどうなんねん!という肝心な部分が抜けているので簡単にコードを書いてみます。

// BattleScene のクラスで以下のコードを実行する
Layer* uiLayer = Layer::create();
 
 
// this->addChild(uiLayer); と同義なので、BattleScene に addChild() される
addChild(uiLayer);
 
 
Sprite* spriteBase = Sprite::create();
 
// uiLayer に addChild() されるので、spriteBase の親は uiLayer、親の親は BattleScene になる
uiLayer->addChild(spriteBase);
 
 
Sprite* spriteMain = Sprite::create();
 
// スプライトにスプライトを addChild() もできる
spriteBase->addChild(spriteMain);
 
 
Node* nodeBase1 = Node::create();
Node* nodeBase2 = Node::create();
Node* nodeBase3 = Node::create();
 
// Node も どこにでも addChild() できる
uiLayer->addChild(nodeBase1);
spriteMain->addChild(nodeBase2);
addChild(nodeBase3);

自前で作ったやつらももちろんいけます、というか、以下のように作っていくのが基本です

// BattleScene のクラスで以下のコードを実行する
// もちろん、各継承したクラスは 別ファイルにて クラスが定義されているものとします
 
// SpecialLayer は Layer クラスを継承している前提
SpecialLayer* specialLayer = SpecialLayer::create();
addChild(specialLayer);
 
 
// Unit は Node クラスを継承している前提
Unit* unit1 = Unit::create();
 
// Node を継承したクラス(親クラスでも親の親でも)ならいけます
specialLayer->addChild(unit1);
 
 
// Effect は Sprite を継承しているとして…
Effect* effect1 = Effect::create();
 
// もちろんこれもいける
unit1->addChild(effect1);
 
// EffectChild は Effect クラスを継承している
EffectChild* effect2 = EffectChild::create();
 
// 無論
unit1->addChild(effect2);
 
// 結果、
// battleScene->specialLayer->unit1->(effect1, effect2) という親子関係になっていることが確認できるでしょうか?

大体上記のような書き方をした Scene が複数あって、シーンの切り替えを行うことで大枠が成り立ちます。

上記に加えて、

  • Scene の init() 内で何かを実行させる
  • Sprite を runAction() でアニメーションさせることによって動きが出る
  • update() や schedule() をすることで毎フレームごとに処理チェックをすることが出来る
  • タップ等のイベントを追加することでユーザ入力を受け付けることができる

ので、上記のような init() を作成するといいと思います。

その他気になる点/知っておいた方がいい点

Scene は Layer ?

自分で作る Scene は 実は Layer クラスを継承している点には疑問を抱かずに。これはなんか深い話があるらしいですw

create() やら init() やらわからん

CREATE_FUNC(****); はマクロなんで見ると動きわかります

参照カウンタとか

メモリ管理は addChild() だけではなく、根本で retain() だとか autorelease() などの動きを知ってると大前提となる知識なので推奨です。というか必須ですね。

http://brbranch.jp/blog/201311/cocos2d-x/cocos2dx_memory_leak/

参照カウンタとか知らないと自前でメモリ管理する必要があって面倒なので、cocos のお作法に沿って作るといいと思います。

コンストラクタは?

書かないとデフォルトの挙動するので書かないようです。

デストラクタは?

C++ だと ~ が付いている関数がデストラクタなんですが、書かなければデフォルトの挙動をするので特に処理が必要なければ書きません。