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

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

C++ と Cocos2d-x でよく使うメソッド等

f:id:ma3tk:20140412223423j:plain

C++ 関係でよく使うもの

詳しくは wiki とか見ると勉強になります

http://ja.wikipedia.org/wiki/C%2B%2B11

PHPer にとっては注意すべき if 文 (主に C とか C++ 忘れてたり、書いたこと無い僕用の注釈w)

if 文って 100% 使いますよね。

ただし PHPer にとって癖があるなって思ったのが、 if の { } 内で auto var1 = 100; とかってやっても、 if の外に出るとこの var1 は使えなかったりします。

つまり、

int var1;
if (hoge) { 
    var1 = 100; 
}

ってやらなきゃいけない所があります。

PHP しかやってない人だとそう感じるけど、C言語は当たり前のことですのでそこをお忘れなく。

面倒な分、スコープを小さくすることができるというメリットがあります!

C++11 から便利になった for 文

int my_array[5] = {1, 2, 3, 4, 5};
for (int& x : my_array) {
    x *= 2;
}

いちいちあの煩わしい文を書かなくていい!

理解するまでに時間がかかるけど即時関数として便利なラムダ式

// C++ での標準的な例
[](int x, int y) -> int { int z = x + y; return z + x; }

上記のように書けます。

で、例えば Cocos2d-x でのコールバックを使った書き方は

auto bulletEffect = CallFunc::create(CC_CALLBACK_0(Base::playBulletEffect, this));

ですが、上記の式の代わりにラムダ式で書くと…

auto bulletEffect = CallFunc::create([=](){
    // do something
});

のように書けます。

応用編

ラムダ式は 内部にある関数みたいなものなので、実行順序等に注意!

auto childSprite = Sprite::create();
int number = 0;
auto hoge = CallFunc::create([=, &number](){
    CCLOG("test2, %d", number);
    number++;
    auto fuga = CallFunc::create([=, &number](){
        CCLOG("test3, %d", number);
        number++;
    });
    childSprite->runAction(fuga);
});
CCLOG("test1, %d", number);
number++;
runAction(hoge);
CCLOG("test4, %d", number);
number++;

もちろん上記の hoge や fuga は宣言されているだけで実際に callback として呼ばれた時にそのコードが動きます。

上記のような場合、式の実行順序、variable{} の variable がきちんと意図したタイミングで渡せるかどうか注意すべきポイントです。

普通の変数で渡さずに pointer で渡す事ももちろんできるので把握しておいたほうが良いですね。それと、[=] や [&] や[val1, &val2] 等どう違うのか知っておくといいと思います。 基本 [=] で事足りますが。

実行時に高速化するための const

書き換えが必要ない場合は const 使いましょう!他の言語でも同様だと思いますが。

dynamic_cast<型>(変数); による型変換

ある型の変数を その子クラスの型にキャストする時に使います。

getChildrenByName("child1"); で addChild() 済みの Node型の子が取れるが、Layer に変換したい時とかも使いますね! (getChildrenByName<Layer*>("child1"); でいけます)

また、自分で定義したクラスとそれを継承したクラスでの変換に便利ですね。

ただし、型変換できなかった場合は、NULL になるはずなので注意して使ってください。

また、 static_cast もきちんと見ておいて、違いは何なのか知っておいてください。

C++ の逆に使わないやつ

this の省略

.h 内で変数を定義しておけば グローバルに変数名が使えるので this 地獄にならなくて済みます。

逆に変数定義しすぎてグローバル汚染にならないように気をつけてください。

僕はあるアプリを作ってる時に途中まで知らなくて this 地獄になったのでリファクタリングしたいです。

Cocos2d-x でよく使う奴ら

CC_CALLBACK_0(Func::function, this) とかのマクロ

コールバック関数。

// new callbacks based on C++11
#define CC_CALLBACK_0(__selector__,__target__, ...) std::bind(&__selector__,__target__, ##__VA_ARGS__)
#define CC_CALLBACK_1(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__)
#define CC_CALLBACK_2(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__)
#define CC_CALLBACK_3(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)

コールバックについてそもそも知ってる?ってところな方もいなくはないかと思いますが。

コールバック関数は便利なんですが、詰まりポイント、やらかしポイントとして、時間に差異があることで object が破棄されていて 中身が NULL だったりすることも大いに有るので通常のデバッグ以上に注意しましょう。

CCLOG("%d %f", int_value, float_value, ........);

update() とかの常時呼ばれるものだったり、内容が多いループとかでいちいちデバッガ使いたくない時にはこれを使ってコンソールに出してみるとGood。

一回くらいのプリントしたい場合は デバッガで breakpoint 張ってチェックした方がいいですね。

もちろん #include して cout << var1 してもいいけど、CCLOG も cout もファイルの入出力なので必要時以外は消しておくといいと思います。

Release build の時には削られるので心配ないですが、デバッグ時にすげー落ちるけどリリースビルドを触ると全然動く、みたいなこともあったりします。

ClassName::functionName() と functionName() の違い

インスタンス化されたそれかそうじゃないかかな?

const の場合は きちんと明示して .cpp に書いておきたいですね。

localZOrder と globalZOrder について

基本 local を使って使って管理すると良いです。

local は同じレベル間での重なりの順序。

global はそれに関係なく全てでグローバルな重なりの順序。

描画に関するあれこれあるので、なるべく local でやると多数の要素を描画しなくてはならない時にミスが少なくなるかと思います。

Z-Order の設計は気をつけてください。

setVisible(bool)

ただ単に setVisible して見えなくしたりするだけだと動作が重くなる原因になるので注意した方がいいです。

removeFromParent() を自身で呼ぶか、 removeChildByName() で親クラスから メモリから解放したりしてあげる必要がありますね。

たぶんそうしないと 描画のために走査はされるが 表示されないだけなので、毎フレームごとに 中身は呼ばれてるんちゃうかと思いますが、未検証です。

setPosition(Vec2)

表示する位置を決めます。

親のノード からどれくらいの位置に動かすのかという話であって、画面全体の Vec2(0, 0) からのどの位置なのかという話ではないです。

もちろん、親のノードの Position が setPosition(Vec2(0, 0)) の場合であればそういう話になりますが。

親ノードに影響されて位置が決まるので、Scene, Layer, Node の設計はある程度早い段階から考えて影響が内容にすることが重要です。

ついでに anchorPoint についても理解しておくといいと思います。

真ん中揃えが基本で、どの位置を基準にするかって話です。

Position については cocos studio を使って Node の位置と Position あたりに触れておくと理解がすごい早いです。

上位レイヤーに Node 変更すると位置もぜんぜん変わるのがわかるかと思います。

なんでセットされたポジションに表示されないのかとかはそもそも

  • 自身を addChild() した上の親クラスが addChild() されていななかった
  • 画面外にセットされてしまっている
  • setVisible(false) になってしまっている
  • setOpacity(0) にしてしまっている
  • setPosition(Vec2) の Vec2 が 思っていた値じゃなくて Vec2(0, 0) になっていて左下に描画されている

など。が考えられますのでバグったら確認してみてください。

アニメーション周りのネタ

Spawn, FadeIn, FadeOut, FadeTo, Sequence, RepeatForever, MoveTo(By), ScaleTo(By), DelayTime, CallFunc あたりはよく使いますね。

runAction() 時に、 Vector<FiniteTimeAction*> を作って array で渡しても OK なように作られています。ループで同じようなアニメーションを作りたい時に便利そうです。

runAction() の Sequence とか Spawn の罠

上述しましたが、

runAction(Sequence::create(Spawn::create(hoge, 
                                         fuga,
                                         NULL), 
                           Spawn::create(aaa, 
                                         bbb, 
                                         DelayTime::create(0.5f), 
                                         CallFunc::create([=](){ 
                                             hogehoge(); 
                                         }), 
                                         NULL), 
                           CallFunc::create([=](){
                               fugafuga(); 
                           }), 
                           NULL)
         );

こういった複雑な挙動の場合どう動くかちゃんと理解しておくといいと思います。

C++ とか Cocos2d-x のコーディング

f:id:ma3tk:20150114002138j:plain

Cocos2d-x でよく使われる型/クラス 概要

C++ は Cocos2d-x v3.x から C++11 というバージョンが採用されました。

簡単に言えば、前のバージョンに比べて C++11 は便利ではやい!(略

C++ の基本的な型

auto

型を value から判断して自動でつけてくれます。便利。

体感でコードの7割くらい auto って書いて型宣言してそのまま "= 値;" って書く感じですね。頻度大。

auto param = 10;

ただ、明示したい時や宣言だけする場合は そのまま型を明示的に書きます。

int param;
param = 10;

void

bool

true / false が使えます。以上。

int

これも略。

float / double

意外にそこまで利用コストが大きくないので float / double にするか int にするかは後回しでいいと思います。

ただし、細かい数値計算が必要な場合はもちろん float / double 。

int への型変換とか注意。

細かいところは僕も調べきれてませんので略。

std::string

string を使うのはコストが比較的大きいのでなるべく使うのを避けられるように工夫した方がいいです。

僕の場合、php が長いので基本連想配列に文字列使って管理してたたけど、pair とか tuple とか使えばもっと綺麗にかっこよく早くかけます。

連想配列っぽくしたい時は、 enum で宣言した値使えば代用できたりするので、 enum を使う選択肢も考えてみるといいと思います。

その他 char 型との比較だとか c_str() の型変換もよく使います。

enum

列挙。

GenderType とかの指定を enum で持っておくとコード的にも綺麗になりますね。

std::map

連想配列ですね。std::map<int, Vec2> とかやるのに便利です。

key-value で管理できるので、value を識別して使いたい時に。

std::vector

1次元配列。 std::map とかやれば、Vec2 で辿りたい位置 の配列ができますね。

std::pair

2つの組。std::make_pair とかも合わせて知っておくといいと思います。

python とか触ったことあるとわかると思いますが、 PHP しかやってないと PHP には存在しない概念なので、理解が若干戸惑いますねw

std::pair と std::vector と std::map あたり組わせると…

std::map<std::pair<int, Vec2>, std::vector<Vec2>> moveData;

みたいにやると php で言う、

<?php
$array[$unit_type]["start_point"] = array("x" => 100, "y" => 180);

っぽい感じのデータ構造が実現できます。

std::tuple

複数の組。

3つ以上に使えるけどそんなに使うシーンがなかったかも。

pair と同じような形で使えるので、使いドコロがあれば是非使うといいと思います。

例えば、 (性別、年代、住んでる都市) みたいなデータで value を引っ張ってくる時に使えたりしそうですね。

cocos2dx のよく使う型

この記事が勉強になります

http://qiita.com/edo_m18/items/4a53eee51bbc95974770

Vector と Map

std::vector と cocos2d::Vector、 std::map と cocos2d::Map の違いは「 value に指定できる型が Ref を継承しているクラスのみ(つまり cocos2dx の型である Node とか Layer とか Sprite とか…)」になります。

Ref* 継承していない標準の pair とか int とか int や string 等であれば、 std::vector もしくは std::map を使ってください。

Vector

  • .pushBack()
  • .at()
  • .size()
  • .popBack()
  • .clear()

Map

  • .insert(K, V)
  • .erase(K)
  • .at(K)
  • .empty()
  • .size()
  • .begin()
  • .end()

Vec2

ドキュメント見るとわかるけど、Vec2 にはたくさんの便利関数が標準でついてます。

  • .distance(Vec2)
    • .getDistanceSq(Vec2) を使うと sqrt() で処理しない近似値的なもので測定できるのでよほど精度求めなければこちらを推奨します。
  • .add(Vec2)
  • .getAngle()

operator も使えるので Vec2(1, 10) + Vec2(10, 20) みたいなことも簡単にできますね!

Value

cocos のフレームワークでラッピングしてくれてる、汎用的な型ですね。

asString(), asInt(), asFloat()... 便利なメソッドたくさんあります。

Node

超基本の型ですね。

これはある程度舐めるようにメソッドの種類を見ておくといいかもです。

これに合わせて、 Ref クラスも見ておくといいと思います。

Sprite

Node との差分を確認しておくことが重要ですね。

setName, setTag, ... などから、画像の読み込み、自由変形などができるようになります。

Layer

これも Node や Sprite との差分を確認しておくといいと思います。

基本的に View として存在させ、インスタンスへの値のセットくらいに抑えておくとスッキリした設計になります。

CallFunc か FiniteTimeAction

アニメーション用。自分で動きを定義する関数ですね。コールバック関数自体を切り出すパターンにも使えます。

TMXTiledMap

Tiled Map Editor で作ったファイルを読み込む場合に使いました。

cocos では position と coodinate を相互に変換してくれるメソッド等が存在するので使いやすいです。

ui::Text

文字列を変える場合などに使いました。

Clonable

abstract クラスで基本的に継承して使います。

使い方がすこし他と違うので初めての場合難しいのですが、オブジェクトをコピーできるようにします。

例えば、何回もインスタンス生成するんじゃなくて、処理の回数減らしたいときとか使い回したい物があるときとかに役立ちます。

Animation とかは これを既に継承しているので、どちらかというと自前で作ったクラスに適用したいときだとかに非常に有益です。

v2.x にあった CCObject クラスではこのコピーするメソッドが入っていたのですが、 v3.x からの Node は継承してないので、注意。

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