5.1さらうどん

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

cocos2d-xのバグを直したらmergeされた話

cocos2d-xで割と大規模なゲーム開発をしております。


チームでRPGなどを開発していると、

Bルート3面最終面に出てくるナイトが10%の確率で2ターン溜め攻撃するんだけど、
溜め攻撃の最中の1ターン分だけナイトを点滅させるようにしてくれない?

などとピンポイントなオーダーがディレクターから飛んできます。


プログラマはこういう事態を予期しておき、敵キャラの技のスクリプトは全てLuaで記述できるようにしておきます。

このようなスペシフィックな注文が来たときに、待ってました!と言わんばかりにわしわしと技のスクリプトに処理を書き込むわけです

local actions = CCArray:create() -- 空の配列作って
actions:addObject(CCFadeTo:create(0.05, 128)) -- 0.05秒で透明度128に
actions:addObject(CCFadeTo:create(0.05, 255)) -- 0.05秒で透明度255に
local sequence = CCSequence:create(actions) -- それらをアクションにして
user:runAction(CCRepeat:create(sequence, 10)) -- それを10回繰り返す動きを適応


点滅をはじめとして、ちょっとした表現のクォリティアップをいとも簡単に行えてしまうのがcocos2dの良いところですよね!


満を持して実行してみると、エラーを吐く。なんだこれは!何も間違ってないぞ!
バージョンは2.0.3

...9C-411D-A8EC-E8454ECC9071/YourFantasticGame.app/charge_attack.lua:5: error in function 'create'.
argument #2 is 'CCFiniteTimeAction'; 'CCActionInterval' expected.

エラーの原因

悩むこと数分、cocos2d-xのLua-Binding自体が怪しい、という結論にいたり、内部のコードを読み始めるわけです。


cocos2d-xのドキュメントと見比べてみると、Lua-BindingでCCRepeatのcreateに渡している型がドキュメントと異なっている!

ドキュメントによると、繰り返すアクションをCCFiniteTimeActionとして受け取らないといけないのに、そのサブクラスであるCCActionIntervalとして受け取っている!

CCSequence#createはCCFiniteTimeActionを返す仕様になっているため、引数エラーが出てしまったというわけです。なるほど納得!

つまりこんな感じで直した

http://cdn-ak.f.st-hatena.com/images/fotolife/g/gigi-net/20130304/20130304195232_original.png

Pull Requestから始まるコミュニケーション

というわけで修正して、Pull Requestを送ってみたら

f:id:gigi-net:20130304193416p:plain
Fix CCRepeat#create is recieved bad argument on Lua binding. by giginet · Pull Request #2030 · cocos2d/cocos2d-x


マージされた!!!!

f:id:gigi-net:20130304193430p:plain
Fix CCRepeat#create is recieved bad argument on Lua binding. · 8f4340f · cocos2d/cocos2d-x

実写アイコンに颯爽と並ぶぎぎねっとアイコン


これで晴れてcocos2d-xコミッターです!やったね!

まとめ

というわけで、なかなかハマる人も少ない現象だとは思いますが、まとめてみました。

Forumとか見てるとScript-Binding周りはまだまだバギーみたいなので、報告すると喜ばれると思います。

初めて知り合い以外にまともなPull Request送ってみましたが、感謝されるとなかなか嬉しいですな。

iPhoneゲーム開発に役立つツール13選まとめ

みなさん、iPhoneでゲーム開発してますか!

iPhoneでのゲーム開発は、cocos2d for iPhoneを初めとして、cocos2d-x, Kobold2D, Unity, Corona SDK, GameSaladなど、様々な開発環境で割りと手軽に行えるようになってきました、


それに伴って、非プログラマ向けのゲーム開発支援ツールも非常に充実してきていますが、日本語で読める詳しい記事が少なかったことが気になったため、自分が使っているモノをまとめてみました。

デザイナー向け

CocosBuilder

f:id:gigi-net:20130223123219p:plain
CocosBuilder – Graphical Interface Builder for Cocos2D iPhone and iPad

GUI設計ツール。

コードでちまちまとスプライトを配置していくのは発狂するので、GUIツールを使った方が楽。読み込み用のライブラリも付属していて、吐きだしたファイルを簡単にゲームに反映できます。

cocos2d/cocos2d-x両方から利用可能なのも嬉しい。


GlobalGameJamの時に実戦投入してみたんですが、画面のOrientationが勝手に戻ってしまったりと、少々バギーなところがあってなかなか使いづらいかも。

Particle Designer

f:id:gigi-net:20130223122402p:plain
71 Squared: Particle Designer - Particle Simulation Editor for OSX

OpenGL向けの美しいParticleを簡単にデザインできるツール。UnityやBlenderなどに付属しているパーティクルツールに近い使い勝手で簡単に利用できます。

非常に手軽にエフェクトをリッチにできるので、導入しない手はないかと。ライセンスも1400円と非常に手頃なので購入をオススメします。

zwoptex

f:id:gigi-net:20130223122418j:plain
Zwoptex

複数画像をまとめたTextureAtlasを簡単に生成してくれるツール。

上手く利用できれば、パフォーマンス改善が見込めるため有用な感じです。

簡単なゲームの場合、ムリにテクスチャを1枚にまとめずとも遊べるモノは作れるので、必要になったら導入する程度で良いかと。

Sprite Helper

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

SpriteHelper | Game dev Helper

スプライト生成用のツール。前述のzwoptexと同等の機能の他、物理エンジンの属性の設定やアニメーションなどがGUIで手軽に行えるようです。

また、後述のLevel Helperとも連携ができるらしい。

ガッチリ使ったわけじゃないので、以下の記事を読んでみると良いかと。

サクッと Cocos2D ゲームを作ろう!(1)SpriteHelperでのテクスチャー処理 | Zero4Racer PRO Developer's Blog

スクリプター・プランナ向け

Level Helper

f:id:gigi-net:20130223122502p:plain
LevelHelper | Game dev Helper

cocos2d/cocos2d-x向けレベルデザイン支援ツール。

マップの設計やアニメーション、演出などがかなり自由に設計できるようです。

これも買って使ってみたわけではないので、便利そうだったら誰か詳しく教えてください

Code Helper

f:id:gigi-net:20130223122545p:plain
CodeHelper | Game dev Helper

Corona SDK, cocos2d-html5特化のLua, JavaScript IDE

トライアル版のみしか使っていませんが、設定項目がほとんどなかったりと、機能面ではまだ不十分だなぁと感じました。

これらの開発環境で補完を利用したい場合には便利そうですが、単にLuaIDEが欲しいぐらいなら後述のLua Development Toolsを使うのが良いのかも。

Lua Development Tools

f:id:gigi-net:20130223122603p:plain
Koneki - Lua Development Tools, an IDE for the Lua programming language

Eclipse派生のLua向けIDEXcodeLuaをサポートしていないので、スクリプターにはこれで書いてもらうのがオススメ。vimは敷居が高いですしね。

基本的な機能は揃ってるし、クロスプラットフォームだし、取っつきやすいのでちょっとしたコードを書くぐらいならこれで十分なのかも。

ちなみに、Xcode4でLuaシンタックスハイライトを有効にするプラグインなんかもあるので、一応導入しておくと良いかも。

bastos/lua-xcode-coloring · GitHub

Tiled Map Editor

f:id:gigi-net:20130223122808p:plain
Tiled Map Editor

よくあるマップエディター。cocos2dでは最初からこの形式のパーサーが用意されているので、はき出したファイルを簡単に利用できます。

Qtで実装されているので、クロスプラットフォームで利用できるのもポイント高い。

マップを実装する類いのゲームであれば、このツールを利用できる設計にしておいた方が良いかと。

プログラマ向け

Dash

f:id:gigi-net:20130223110521p:plain:w128
Mac App Store - Dash (Docs & Snippets)

高速シンプルなドキュメントビュワーツール

ゲームやiPhone開発に限らず、Macでの開発者必携のツールな気がしています。

利用可能なドキュメントの量が膨大で、iOS SDKはもちろん、ゲーム開発にだけに注目してもcocos2d, cocos2d-x, Kobold2D, Unity辺りが完全に網羅されています。C++STLLua、Box2Dなどのドキュメントも用意されているので、どのような開発環境であっても役に立ちます。

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

XcodeやAlfred/QuickSilverとも連携できて強力。とりあえず導入しておきましょう。

デバッガー向け

TestFlight

f:id:gigi-net:20130223105906p:plain
TestFlight » Beta Testing On The Fly

もはや語る必要もないチーム向けデバッグ版リリースサービス。

これもゲーム開発に限らず、iPhone開発者には欠かせないツールなのではないでしょうか。

iPhoneアプリの複数人開発にはもはや必須になってきてますね。開発者はともかく、デザイナーや音屋さんが実機の動作を確認するのに重宝します。

詳しい使い方は下記ページなどを見ると良いでしょう。

TestFlight とりあえずどんなものか | MUSHIKAGO APPS MEMO

Reflector

f:id:gigi-net:20130223112958p:plain:w128

Reflector.app - AirPlay mirroring to your Mac or PC, wirelessly.

チームメンバーがiOS実機を持っていなかったり、効果音を付けるために動画で動作を確認したいという需要があるでしょう。

そんなときにオススメなのが、iOS端末の動画を簡単に撮影できるReflector。

簡単に撮影できて、ゲームでも処理落ちはあまりしないです。

PV撮影や、ドロワーを起動したiPadの画面をミラーリングして、Skypeで画面共有し、お絵かきチャット代わりに使う、なんて使い方も。

その他いろいろな状況で便利なので、シェアウェアですがライセンスを持っておくと便利。

WunderList

f:id:gigi-net:20130223115441j:plain:w128
Wunderlist 2 - Your beautiful and simple to-do list

クラウド型のタスク管理ツール。

タスク管理については各々使いやすいモノがあると思いますが、導入してみて上手くいったモノをご紹介。

githubのissueは非プログラマには敷居が高かったり、ブラウザを開いていちいち登録するのが地味に煩わしかったりするので、思いついたらホイホイと投げれるWunderListは結構便利でした。

チームメンバーで同期して利用できるし、クロスプラットフォームなのもポイント高い。TestFlightとあわせたバグ報告などに便利です。

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

カテゴライズが貧弱なので、大規模な管理には向かないかもですが、一応ご紹介。

その他

GameSalad

f:id:gigi-net:20130223115759p:plain:w128

Game Design Engine, Make Games for iPhone & Android - GameSalad

全然使ってないけど、ちょっと注目しているのでご紹介。

コードを書かずに簡単にiOS/Android向けのゲームが作成できるツール。

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

使い勝手は「MultimediaFusion」や「Scratch」などに近いかも。

初学者にはまだ英語版しかないのが若干敷居が高いかも、という印象。

フリーで利用できるし、書籍も出ているようなので、今後流行ると良いですね。

まとめ

如何でしたでしょうか!

こんな感じで、非プログラマでもゲーム開発に参加しやすい下地が整ってきたように感じます。

上手く使いこなせれば、分業して大作を開発するのに役に立つかと思います。


この記事がなるべく多くのゲーム開発者の一助になれば幸いです。


この記事を読んでゲーム開発にご興味を持った方は札幌ゲーム製作者コミュニティKawazにどうぞ。ゲーム作りましょう!

cocos2d-xのLuaバインディングについて解説してみる

はじめに

最近、cocos2d-xというゲームフレームワークを利用してゲーム開発を行っています。


このフレームワークWindows/Linux/iOS/Android向けのゲームをC++で記述できる優れもの。

プラットフォーム間の差異をほとんど吸収してくれるので、何も考えなくとも移植性に優れたゲーム開発が行えます。

国内ではあんまり流行っていない感じですが、そのうちデファクトスタンダードになりそうな予感。


命名規則などはcocos2dを参考にしていたり、CocoaのNSObjectやNSArrayと似たような挙動をするクラスが移植されていたりと、Objective-Cの文化を知らないと書きにくいのが難点ですが、クロスプラットフォームのゲーム開発をしたいのであれば現状ではこれ一択な気がします。

特にObjective-C未経験者はCCObjectのメモリ管理周りでハマりそう。


cocos2d-xには、レベルデザインLuaで記述できるようなLuaエンジンが搭載されているのですが、公式ドキュメントや公式フォーラムはおろか、国内外でほとんど情報がない感じだったので弄ってみた知見をまとめてみた。おそらく国内初で国外でもほとんど情報ないです。


凄く凄くニッチな記事ですが、誰かの役に立てば幸いです。

Lua-C++連携の基本

LuaスクリプトC++で値をやりとりするためにはlua_Stateと呼ばれるモノを利用して、値のやりとりを行います。

lua_Stateは1つのスタックを持っていて、そのスタックに値をpush, popすることのみで値のやりとりが行えます。

以下の記事を読んで、なんとなく内容を把握しておくと良いでしょう。

Luaスタックの操作 - karetta.jp
Luaスタック上のテーブルの格納と参照 - karetta.jp
その2 Luaスクリプト事始め

cocos2d-xに実装されているLua Wrapper

cocos2d-xには以下のクラスが定義されています。(バージョン2.0.3時点)

CCLuaEngine

基本的なLua連携をラップしたエンジンです。

単にスクリプトを実行したり、Stateにpushしたり基本的なことがそれなりに行えます。詳しくはドキュメントを読んでください。

cocos2d-x: CCLuaEngine Class Reference

CCLuaValue

Luaの変数をラップしたオブジェクトです。

CCLuaValue::intValue()などを実行することで、型に応じて中身を取り出すことができます。

また、CCObjectを持たせることもできて、Luaと簡単にCCObjectのやりとりもできて良い。


詳しくはドキュメントを読んでください。

cocos2d-x: CCLuaValue Class Reference

CCLuaValueArray

std::listへのエイリアス。Arrayって名前なのにlistなのが使いにくい

CCLuaValueDict

std::mapへのエイリアス

Luaのテーブルを参照する

例えばLua側に以下のようなテーブルを定義したとします。

someTable = {
  hoge = 10,
  piyo = "string"
}

C++側からは以下のように取り出してあげると良いでしょう

CCLuaEngine* engine = CCLuaEngine::defaultEngine(); // デフォルトのengineを取り出す
string path = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("filename.lua"); // Luaスクリプトの絶対パスを取り出す
engine->executeScriptFile(path.c_str()); // Luaファイルを実行する
lua_State* L = engine->getLuaState(); // 現在のStateを取り出す
lua_getglobal(L, "someTable"); // someTableを取り出す
lua_getfield(L, -1, "hoge"); // someTableからfield:hogeを取り出す
int hoge = lua_tointeger(L, -1); // hogeの中身をintとして取り出す
lua_getglobal(L, "someTable"); 
lua_getfield(L, -1, "piyo"); // someTableからfield:piyoを取り出す
string piyo = lua_tostring(L, -1); // piyoの中身をstringとして取り出す

まず、lua_getglobalで"someTable"がStateの一番上にpushされます。


lua_getfield(L, -1, "hoge");で、State Lの一番上(-1)に積んであるtableからフィールド"hoge"を取り出し、Stateの一番上にpushします。


最後にlua_tointeger(L, -1)で、State Lの一番上(-1)に積んである値をintとして取り出しています。


テーブルの中にテーブルが含まれていたとき、再帰的にテーブルを読んでくれないみたいなので自分で実装してください。参考までに、僕はObjective-Cで書かれたKobold2DのKKLua::loadLuaTableFromFileをcocos2d-x向けに移植して使っています。

Luaの関数をC++から呼ぶ

基本的にやることは2つで

  • C++からLuaに渡す引数はLuaStateにpushしてあげる
  • 関数実行後に戻り値はStateにpushされるのでC++からpopする

例えば、Lua側でこのような関数があったとすると

function add(a, b)
   return a + b
end

cocos2d-x側からはこのように呼んでやればOK

CCLuaEngine* engine = CCLuaEngine::defaultEngine(); // デフォルトのengineを取り出す
string path = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("filename.lua"); // Luaスクリプトの絶対パスを取り出す
engine->executeScriptFile(path.c_str()); // Luaファイルを実行する
lua_State* L = engine->getLuaState(); // 現在のStateを取り出す
lua_getglobal(L, "add"); // globalからaddを取り出して、Stateにpushする
lua_pushinteger(L, 10); // 第1引数(a)に10をpush
lua_pushinteger(L, 20); // 第2引数(b)に20をpush
if (lua_pcall(L, 2, 1, 0)) { // 関数を実行
  // もし、実行時にエラーがあれば、エラーを出力
  cout << lua_tostring(L, lua_gettop(L)) << endl;
}
int returnValue = lua_tointeger(L, lua_gettop(L)); // 戻り値を取り出す
cout << returnValue << endl; // 30

ここでのミソは関数の実行を司るlua_pcallで、第1引数にState, 第2引数に引数の数、第3引数に戻り値の数を指定します。第4引数はエラー時に実行する関数を指定するようですが、0で大丈夫です。

lua_pcallは、Luaの実行時にエラーが出たときに、エラー文をpushして、戻り値1を返します。

そのため、上記のように記述しておくとエラーを出力することができます。


また、Luaは関数から複数個の戻り値を返すことができます。
例えば、3つの引数を全てintで返す場合、pcallの第3引数に3を指定した後、

int returnValue0 = lua_tointeger(L, -1);
int returnValue1 = lua_tointeger(L, -2);
int returnValue2 = lua_tointeger(L, -3);

のように受け取ってやれば良いです。


例えば、敵やアイテムの出現率、経験値テーブルなど、レベルデザインに直結するちょっとした計算を行いたいときに、関数をLuaで記述して、C++から呼んでやると良いでしょう。

C++のクラスやメソッドを公開し、Luaから使用する

cocos2d-xのLuaエンジンには、予めtolua++と呼ばれるC++Luaバインディングを良い感じでやってくれるライブラリが搭載されています。これを利用することで簡単にC++のクラスやメソッドをAPIとして公開して利用することができます。


cocos2d-xではLuaCocos2d.cppというファイルで、cocos2d-xの全APILuaに公開されており、Lua側から自由に利用することができるようになっているようです。ソースコードはバージョン2.0.3の時点で58000行にも及んでおり、力業でゴリゴリ書いてる感じ。すごい。

cocos2d-x/scripting/lua/cocos2dx_support/LuaCocos2d.cpp at gles20 · cocos2d/cocos2d-x


公開の方法はLuaCocos2d.cppとかを読めば良いですが、簡単にまとめておきます。細かい挙動はよくわかってないので察してください

class UserClass :public CCObject {
 public: 
  int someMethod(int arg) {
    return arg + 1;
  }
};

static void tolua_reg_types (lua_State* tolua_S) {
  tolua_usertype(tolua_S, "UserClass"); // UserClassクラスをregisterする
}

static int tolua_UserClass_someMethod(lua_State* tolua_S) {
  UserClass* self = (UserClass*)tolua_tousertype(tolua_S, 1, 0); // オブジェクトを取り出す
 int arg0 = (int)tolua_tonumber(tolua_S, 2, 0); // Luaから渡された1つめの引数を取り出す
  int return = self->someMethod(arg0); // UserClassオブジェクトのsomeMethodを実行する
  tolua_pushnumber(tolua_S, (int)return); // someMethodの実行結果をpushする
  return 1;
}

TOLUA_API int tolua_userdefine_open(lua_State* tolua_S) {
  tolua_open(tolua_S);
  tolua_reg_types(tolua_S);
  tolua_module(tolua_S, NULL, 0);
  tolua_beginmodule(tolua_S, NULL);
   tolua_constant(tolua_S, "SOME_CONSTANT", 10); // enumや定数を定義する
   tolua_cclass(tolua_S, "UserClass", "UserClass", "CCSprite", NULL); // UserClassをクラスとして割り当てる。第4引数にはスーパークラスのクラス名を記述する
   tolua_beginmodule(tolua_S, "UserClass"); // UserClassのメソッド定義開始
    tolua_function(tolua_S, "someMethod", tolua_UserClass_someMethod); // someMethodを登録する
   tolua_endmodule(tolua_S); // メソッド定義終了
  tolua_endmodule(tolua_S);
  return 1;
}

CCLuaEngine* engine = CCLuaEngine::defaultEngine(); // デフォルトのengineを取り出す
tolua_userdefine_open(engine->getLuaState()); // クラスを登録する

Lua側ではこういう関数を定義しておきます。

function executeSomeMethod(obj)
  -- objにはUserClass型のオブジェクトが渡される
  print(obj:someMethod(10)) -- 11
end

最後に、上記同様にC++側からexecuteSomeMethodを実行してやります

CCLuaEngine::pushCCObject(CCObject* obj);でCCObjectを簡単にLuaに渡せるため、関数の引数としてオブジェクトを簡単に渡すことができます。


これを上手く利用することで、アイテムの使用時の効果を全てLua側で記述するなど、レベルデザインとシステムの分離が行えます。良い。

まとめ

この記事に書いたことを大体理解しておけば、Lua側から基本的に何でもできるので良いです。あんまり頻繁にLuaを実行するとオーバーヘッドが気になる感じなのですが、それなりに高速に動作してくれるのであんまり気になりません。毎フレーム呼び出したりとかしなければ大丈夫かと。


今開発中のゲームでは、非プログラマLuaを覚えて貰ってレベルデザインをして貰っているのですが、比較的簡単に習得して貰えたようで良かった。試行錯誤もかなりやりやすくなるのでオススメです。


こういう技術を使って、レベルデザインとシステムを上手く分離して設計できると、プログラマの仕事が飛躍的に少なくなるのでプランナとプログラマ、お互いにとって幸せだと思います。