5.1さらうどん

@giginetの技術ブログ。ゲーム開発、iOS開発、その他いろいろ

CocosStudioが使いにくかったからcocos2d-xにPull Request送った話

cocos2d-xにPR送った

f:id:gigi-net:20140829182816j:plain

Add the feature that CCSSceneReader can load name properties as node names. by giginet · Pull Request #7883 · cocos2d/cocos2d-x

無事に通った、めでたい

何が良くなったか

PRを送ったのは、『CocosStudio』で作成したシーンをcocostudio::SceneReaderで読み込んだときの挙動。

今までは、CocosStudioで作成したシーンプロジェクトを読み込んだとき、その子ノードにアクセスするためには、タグを使ってアクセスする以外の方法がなかった。

auto scene = cocos2d::cocostudio::SceneReader::getInstance()->createNodeWithSceneFile("Scene.json");
// 子ノードを取るにはタグからアクセスする必要があった
auto object = dynamic_cast<cocos2d::Sprite *>(scene->getChildByTag(42));

これは、いちいちタグを定数として定義しておいたりしないといけなくて見通しが悪く、使いづらい。

しかし、CocosStudioには各ノードに対してnameを付ける機能が存在する。

f:id:gigi-net:20140829184534j:plain

現状の実装では、nameが出力されるシーンファイルには書き出されているが、ノードとして読み込む際に、特にどこにも設定されずに捨てられていた。

一方、cocos2d-x 3.2からは、Nodeに名前を付けることができるようになっていて、名前から子ノードが取り出せるAPIが提供された。

cocos2d-x: Node Class Reference

というわけで、このname属性をそのままNode::nameとして利用できるようにしたという話。

auto scene = cocos2d::cocostudio::SceneReader::getInstance()->createNodeWithSceneFile("Scene.json");
// CocosStudio上で設定したname属性からアクセスできるようになった!(ついでにテンプレートも使えるようになった!)
auto object = scene->getChildByName<cocos2d::Sprite *>("objectName");

一方でGUIReaderの場合は

Node::nameプロパティはcocos2d-x 3.2から追加された新しいプロパティなので、対応が追いついていなかったのではないか、という好意的解釈もできるが、以下の点について説明が付かない。

SceneReaderと似た機能に、cocostudio::GUIReaderというクラスもある。

これは、CocosStudioのシーンプロジェクトではなく、UIプロジェクトをノードとして読み込むクラス。

しかし、似たような機能を持つGUIReaderでは、Widgetを読み込むときにちゃんとname属性を読んで名前からアクセスできるようになっていた。なぜなのか。(バージョン3.2で確認)

cocos2d-x/CCSGUIReader.cpp at 2210d1fe87c3dd7d2ce79d01945b85a0de24862d · cocos2d/cocos2d-x

// Widgetを読み込む
auto widget = cocostudio::GUIReader::getInstance()->widgetFromJsonFile("Widget.json");
// ラベルのノードを取り出す
auto atlas = widget->getChildByName<ui::TextAtlas *>("AtlasLabel");

cocos2d-xヤバい

前回と同じ結論だけど、使えば使うほど闇っぽい。

今回は似たような機能なのに、両者間で微妙に仕様が違うと言ったことが起きていた。

name属性を引き継ぐかどうか、という話もそうだし、getChildByNameはテンプレートが利用できるのに、getChildByTagはなぜかテンプレートが利用できない点とか。

何か、GitHub覗いてる感じ、来たお便利機能のPRを片っ端からマージしてるみたいな感じで、ロードマップとかあるのかなあと疑問に思ってしまう。

とはいえ、日々送りつけられ続ける大量のPull Requestをしっかりレビューして、保守管理しているデベロッパーさんには頭が上がらない。

次回予告

あと何個かネタがあるので気が向いたらPRを送りたい。バグ修正だけじゃなくて、「ぼくのかんがえた最強のAPI」みたいな感じで欲しいモノ作って送ってもちゃんと見てくれてる感じがあるので、どんどん貢献していきたいところですね。

cocos2d-xでPhysicsWorldを使ったときのScene Graphの挙動にハマった話

f:id:gigi-net:20140710105133p:plain

ちょっと物理エンジンを使う必要があって、cocos2d-x 3.0から本体に取り込まれた物理エンジンを使ってみることにしました。

ところが、以前一度使ったことがあったのですが、バグっぽい挙動で数時間ハマって困った。

あれこれ調べてみて無事に解決したのでブログに記しておきます。

以下、3.1環境を想定して読んでください。

問題

今回起こった問題は、「剛体を付けた子ノードが親ノードに追従しない」という問題。

3.x系になってから一度物理エンジンは使ったことがあるけど、スクロール無しで完結するゲームだったので特に困らなかった。

Scene Graphが維持されないという問題は、画面スクロールがあるようなゲームを作るときに大変困る問題だと思う。

問題を検証するために、3.1環境で新規プロジェクトを作成し、HelloWorldSceneを書き換えてみた。

物理エンジンの初期化

Scene* HelloWorld::createScene()
{
    // 物理エンジンを有効にしたシーンを作成する
    auto scene = Scene::createWithPhysics();
    
    // 物理空間を取り出す
    auto world = scene->getPhysicsWorld();
    
    // 重力を設定する
    world->setGravity(Vec2(0, -1));
    
    // デバッグモードを有効化する
    world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
    
    auto layer = HelloWorld::create();
    
    scene->addChild(layer);
    
    return scene;
}

まず、createScene()メソッドを書き換えて、物理エンジンを有効化します。

2.xと違い、3.xからはPhysicsWorldがSceneに取り込まれたので、Sceneにくっつけてあげます。

ちなみに、公式のドキュメントがあるんですが、3.0α版の時点で書かれたモノのようで、公式ドキュメントなのに、最新版の環境でドキュメントを写すとビルドすら通らないという割と酷い代物なのでご注意を!おそらく最新環境では上記コードが正しいです。

Physics | Cocos2d-x

問題:子ノードが親ノードに追従しない

   
    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("HelloWorld.png");
   
    // Spriteを剛体にする
    auto body = PhysicsBody::createBox(sprite->getContentSize());

    // ここをコメントアウトすると物理エンジン無しの挙動を簡単にシミュレートできる
    sprite->setPhysicsBody(body);
    
    // 重力を無効化する
    body->setGravityEnable(false);
    
    // 適当なラッパー用のレイヤーを用意する(わかりやすいように色を付けている)
    auto node = LayerColor::create(Color4B::BLUE);
    this->addChild(node);
    
    // 検証のためにラッパーがゆっくり上に上がるようにする
    node->runAction(RepeatForever::create(MoveBy::create(30, Vec2(0, 5000))));
    
    // position the sprite on the center of the screen
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    
    // Spriteをシーンに直接追加せず、ラッパーに追加する
    node->addChild(sprite, 0);
    

こういうコードを書くと、nodeが上に上がる際に、それの子ノードであるspriteが追従して一緒に上がる挙動になるかと思います。

物理エンジン無し(3.1)

f:id:gigi-net:20140710103828p:plain

青いレイヤーがActionで上に移動していき、その子ノードのSpriteが一緒に追従して上っていく。想定された挙動。

sprite->setPhysicsBodyの行をコメントアウトするだけ。

物理エンジンあり(3.1)

f:id:gigi-net:20140710103708p:plain

同じ挙動をして欲しいのに、なぜか親ノードだけが上がっていき、Spriteは動かない。バグっぽい。

ちなみに、ラッパーを作らずに、thisを直接上に動かしても再現するけど、見やすさのためこのような実装にした。

明らかにバグっぽい

どうやら、PhysicsBodyをセットしたノードは親ノードの動きに追従してくれないみたい。

酷い挙動だ。

親ノードに剛体を付けたりしてみたけれど、それでもダメ。

マニアックな問題ではなく、物理エンジンとスクロールを併用しようとすると結構ハマる問題では。

例えば、『AngryBird』や、『LIMBO』のような物理エンジンを載せたプラットフォームアクション的なよくあるゲームを作るだけでも困る。

解決するには

当初は、自分で中のコードを弄って解決していたのですが、3日前(2014/7/7)に出たばかりの3.2rc0のCHANGELOGを見ていたらこんな記述が

   [FIX]           Physics integration: child node can move with its father

cocos2d-x/CHANGELOG at e1b29a8ef61248ec047b17be921e031d0d637904 · cocos2d/cocos2d-x

どうやら3日前に出たバージョンでは直っていたようだ。

3.2rc0

f:id:gigi-net:20140710104753p:plain

3.2rc0に上げたところ無事に動く。よかった!

がんばってdiffを追ってみたところ、2週間前のこのPull Requestで修正されていたようだ。

closed #5614: add transform support for physics by boyu0 · Pull Request #7234 · cocos2d/cocos2d-x

このPR出している人、物理エンジンガチ勢みたいな人で、履歴を見てみると物理エンジン周りに大量のPRを送っている。ありがたい限りである。

テストアプリ

3.2rc0から、付属しているcpp-testアプリに「Physics Transform Test」というテストが追加されていて、そこで挙動を確認できる。

このように、移動だけでなく、親ノードに対して回転や拡縮を行っても子ノードが追従して動くようになった。嬉しい。

f:id:gigi-net:20140710105133p:plain

まとめ

cocos2d-xヤバい。数週間に1回バージョンアップする上に、1回のバージョンアップで大量の変更が取り込まれ、数日前に動かなかった物が今日は動く、みたいなことがよく起きる。

今回発見できた問題はたまたま氷山の一角で、この手のバグは大量にありそうなのでご注意を!

cocos2d-x 3.0でクロスプラットフォームなインディーゲームを開発した話

『Wave Weaver』リリースしました!

シンプル操作で脅威の中毒性。波をよけ続けるゲーム『Wave Weaver』をリリースしました!

詳しくは以下の記事とトレーラーをチェック!

波をかわして曲を紡ぐ、カジュアル中毒ゲー『Wave Weaver』をリリースしました - 5.1さらうどん

Google PlayApp Storeで大好評配布中です!非常に多くの方に遊んで頂けているようで驚いています。

for iPhone / iPad

https://itunes.apple.com/jp/app/waveweaver/id841280819?at=10l8JW&ct=hatenablog

iTunes の App Store で配信中の iPhone、iPod touch、iPad 用 WaveWeaver

for Android

Android app on Google Play

Wave Weaver - Android Apps on Google Play

この記事は

この記事はWave Weaverの開発話を交えて、cocos2d-xを使ったクロスプラットフォームなゲーム開発の知見をお伝えするつもりです。

Wave Weaverは、cocos2d-x 3.0のβ版を使用しており、国内では最速レベルのプロダクトだと思われます。そのため、今後使う人に使用感など含め、お伝えしていきたいと思います。

Global Game Jam Sapporo

f:id:gigi-net:20140323102644j:plain

このゲームのプロトタイプは、48時間でゲームを開発するハッカソン、GlobalGameJamの中で作られています。48時間で基本的なプロトタイプはできあがっていた!

詳しくは、gihyo.jpに僕が寄稿したイベントレポートをご参照ください。

ソーシャルゲーム,ボードゲーム,ファミコンまで!? Global Game Jam 2014参加レポート:レポート|gihyo.jp … 技術評論社

cocos2d-x 3.0を使ってみた

最終的に、本作ではcocos2d-x 3.0beta2を使っています。

現在ではRC版が出ているのですが、GGJ当時(1月末)の最新版はβ1でした。

lambda超便利

3.0最大の恩恵は、C++11で実装されたlambdaに完全対応しているところ!

特に、今作ではエンディングなど、逐次的な処理があったので、大体はAction + lambdaで、cocos2dのBlocksライクにかなりお手軽に記述できました。今まではわざわざコールバックを定義して、なんてことをやっていたので、非常に利便性が上がっています。

反面、lambdaを使いすぎると、コードの可読性が落ちて大変でした。特にGGJでは尚更。

さらにC++のlambda-captureと、cocos2d-xのautoreleaseが相性最悪な感じで、captureしたと思ったら、autoreleaseされててぬるぽ、みたいなことがよく起きたのもハマりどころでした

クロスプラットフォーム化が楽

3.0からプロジェクトを生成した時点で、全てのプラットフォーム用のテンプレートが生成されるので、クロスプラットフォーム化がかなり楽でした。ありがたい変更。

alpha, beta, rcで仕様が変わりまくった

まだリリース前なので、仕様がコロコロ変わって大変でした。

以前、alpha版を基準にスライドを作ったのですが、それからGGJで利用したbeta1は、ArrayやDictionaryが廃止されていたりと、結構大きな変更が。

cocos2d-x 3.0 + C++11で始めるゲーム開発超入門

最近リリースされた3.0のrc版ではプロジェクトの生成方法が変わっていたりして、あんまり原形を留めていない感じです。

開発スピードが速すぎて仕様が安定しなさすぎるので、正式リリース前のフレームワークを使うのは危険な感じ。

クロスプラットフォーム + Universal対応

今回はゲーム自体がシンプルだったのもあって、プラットフォーム別に4種類画面を作りました。

f:id:gigi-net:20140323104935p:plain

左からiPhone(4.0インチ)、 iPhone(3.5インチ)、iPadAndroid

iPhone / iPad

iPhone開発はノウハウがあったので、いつも通りこちらを基準に作りました。

iPhoneRetinaとNon Retinaの画像を用意する必要があるのですが、Retina版を基準に作り、Pythonで簡単なスクリプトを書いて、Non Retina用に自動変換をしていました。PIL便利。

4インチ対応は背景画像だけ別に用意して、あとは同じリソースを画面サイズから相対位置で配置するように切り替え。

iPad対応もほぼ同様。iPad版は実は実装上はiPhone版より画面が狭いです。反面、非常にボタンが押しやすくなっていて割と別ゲーに。

Retina iPad用にはSplash画像を別途用意しただけで特に何もしていません。意外と見た目は気にならない感じ。

Android

Android対応は初だったので、動けばいいや的な感じで、あまり機種ごとの最適化は行わずに、一律iPhone3.5インチのNon Retina版と同じ表示になるように作りました。Androidはこの辺が大変ですね。

ビルドにはGradleを使用。NDKの対応にはこちらの記事が大変参考になりました。

gradleでCocos2d-x3.0alpha1のアプリをbuildしてAndroidのapkを作成する - きょこみのーと

古い機種だと重たいという話も聞こえており、今後の課題ですね。

ちなみに、開発中は幻のOUYA版が存在してましたが、コントローラーへの対応が面倒だったので、リリースは見送りました。

Jenkins + TestFlight/DeployGateで高速テストプレイ

ゲームの質は、開発者が如何に遊びまくったかが全てなので、テストプレイ超重要です。

今作では、Jenkinsを用いて、コミットと同時に、各プラットフォーム向けに自動ビルドをしておりました。

f:id:gigi-net:20140323105404p:plain

コミッタはiPhone 4.0インチ環境で開発するだけで、あとはチームメンバーが勝手に確認したりテストしたりしてくれるので、非常にスピード感がありました。

今回のようなクロスプラットフォームの開発では、いちいち全機種ビルドして確認してられないので、CIは必須な感じがしますね。

gitは共通言語

f:id:gigi-net:20140323110020p:plain

ゲーム開発はプログラマの仕事が増えると大変なので、gitを共通言語にして、皆がリポジトリにコミットできる環境を作るべきだと感じました。

例えばデザイナーに「画像が差し替わったのでDropboxに上げましたー」とか言われると、わざわざ持ってきて、ファイル名を変えて、差し替えたのか差し替えてないのかわからずに面倒なことになります。

今回はgitを使えるメンバーが多かったので、その辺が非常に楽でした。プログラマ以外のデザイナーやコンポーザーもgitを使うべき。

辛かったところ

逆に今回の開発環境で上手く行かなかったところについて。

SpriteStudio for cocos2d-xが辛い

f:id:gigi-net:20140323111825p:plain

今回は演出を少しでもリッチにするために、アニメーション作成にSpriteStudioというソフトウェアを使用していました。

SpriteStudioはGGJのスポンサーも努めており、インディー開発者向けに、プロユースの物と同等の機能を持ったモノが無償で提供されています。ありがたい!

「超汎用」データ制作 あらゆるゲーム開発シーンに対応。2Dスプライトアニメーションデータ作成ツール「OPTPiX SpriteStudio」 | ウェブテクノロジ

しかも公式でもcocos2d-xをサポートしていたため、今回導入してみたのですが、標準で用意されてるものを再実装していたり、インターフェイスが独特だったりと、ライブラリのできがあまりよろしくなくて大変でした。

補助ツール・ライブラリ群・サンプルのダウンロード | OPTPiX Help Center

リリース前だったので、当然3.0には対応しておらず、動くように書き直したのですが、アルファブレンディングなどがちゃんと動かずに、そのまま使えない自体が多発。

結局、SpriteStudioで作ってもらったアニメーションを目コピーして、コードで実装したりしていました。

さらに、現在Mavericksで動かないのも辛いところ。わざわざVM立ち上げて作業してました。

ライブラリも1000行ぐらいあって、読み解いたり修正が大変な感じだったので泣く泣くそのまま使用。今後とも使っていくのであれば、しっかりと書き直したい感じですね。

とはいえ、今回のように趣味で開発する場合は、SpriteStudioは無償で使えるという点でかなり魅力的。

お金があるのであれば、標準でサポートしているSpineなどを買うのが良いと思いますが・・・・・・。

CocosDenshion::SimpleAudioEngineが辛い

cocos2d-xの音周りは、SimpleAudioEngineってのがついているんですが、必要最低限の機能しかついていない上に、なぜか実装がいろいろと酷い

などなど。

そのため、ゲーム途中でアプリをバックグラウンドに送ったときの音周りの挙動などに苦労しました。

前作の『VOXCHRONICLE』はサウンドを動的に操作する必要があったので、自前でOpenALガリガリ書いていたため、音周りの混乱はありませんでした。iOS版のみのリリースであったことも幸いして、クロスプラットフォームを意識せずに設計できたのも楽な点。

もう少し時間があればガッチリと手を入れていきたいところ。結構基本的な機能も揃っていないので、割とみんな困ってると思うんですけど、みなさまどうしてるんでしょうね。何かご存じでしたら教えてください。

ビルド時間が長すぎて辛い

cocos2d-x 3.0は2.xに比べて、かなりビルド時間が長くなっているように感じました。

特にiOS版はObjective-C++を使っている関係で、ビルドがタダでさえ遅いのに加え、本プロジェクトではboostを使っていたため、さらにビルド時間が長くなりました。

特にMacBook Airでは、クリーンビルドだと5分以上待たされることもザラ。48時間しかないGGJでは致命的な感じでした。

レベルデザイン

『Super Hexagon』を意識しまくった

f:id:gigi-net:20140323110707p:plain

このゲームは、『Super Hexagon』というインディーゲームに強くインスパイアされています。

『Super Hexagon』の中毒性を踏襲すべく、以下のような仕組みでレベルデザインを行いました。

  • 波の排出や、数秒間待機するなどの命令をOrderと呼ぶ
  • 各Orderには排出開始位置や、波の色、Easingなどが設定されている
  • 一連のOrderのまとまりをPatternと呼び、レベル毎にPatternを何種類か定義し、ランダムに出現順を変えている

このように、毎回適当に波を出すのではなく、ある程度のパターンをデザインし、ランダムで出すような仕様になっています。

これによって、一見突破不可能な波であっても、何度も遊ぶことで記録が伸びていき、中毒性の高いゲームに仕上がっています。

レベルデザインについては様々な声を頂いています。ありがとうございます。

レベルデザインや、波のエミッター周りの実装は、『Super Hexagon』を最高レベルまでやり尽くした[twitter:@tcptr]さんが作り込んでくれたので本当に良いモノができました。

ちなみに、だいたい1時間ぐらいでエンディングを見れるように調整してみたのですが、概ねそんな感じになっているようで何よりです。

タダでもここまでできる!

最近はゲーム開発用の環境もだいたいはOSSで揃うようになってきたので、お金をかけずにここまでの開発が個人でできます。良い時代ですね!

他の人の開発裏話

デザインや音楽などにも、細かなところに工夫が生きています。ご興味のある方は是非。

ゲーム開発したい方は

f:id:gigi-net:20140323111018p:plain

札幌ゲーム製作者コミュニティKawazでは、プロアマ交えて楽しくストイックにインディーゲーム開発をしています。是非ご参加ください!

おまけ

ありがたい

*1:最初はTestFlightを使っていたが、サービス終了に伴い移行した