cocos2d-xに実装されているLua Wrapper
cocos2d-xには以下のクラスが定義されています。(バージョン2.0.3時点)
CCLuaValueArray
std::listへのエイリアス。Arrayって名前なのにlistなのが使いにくい
CCLuaValueDict
std::mapへのエイリアス
Luaのテーブルを参照する
例えばLua側に以下のようなテーブルを定義したとします。
someTable = {
hoge = 10,
piyo = "string"
}
C++側からは以下のように取り出してあげると良いでしょう
CCLuaEngine* engine = CCLuaEngine::defaultEngine();
string path = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("filename.lua");
engine->executeScriptFile(path.c_str());
lua_State* L = engine->getLuaState();
lua_getglobal(L, "someTable");
lua_getfield(L, -1, "hoge");
int hoge = lua_tointeger(L, -1);
lua_getglobal(L, "someTable");
lua_getfield(L, -1, "piyo");
string piyo = lua_tostring(L, -1);
まず、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向けに移植して使っています。
基本的にやることは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();
string path = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("filename.lua");
engine->executeScriptFile(path.c_str());
lua_State* L = engine->getLuaState();
lua_getglobal(L, "add");
lua_pushinteger(L, 10);
lua_pushinteger(L, 20);
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;
ここでのミソは関数の実行を司る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の全APIがLuaに公開されており、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");
}
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);
int return = self->someMethod(arg0);
tolua_pushnumber(tolua_S, (int)return);
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);
tolua_cclass(tolua_S, "UserClass", "UserClass", "CCSprite", NULL);
tolua_beginmodule(tolua_S, "UserClass");
tolua_function(tolua_S, "someMethod", tolua_UserClass_someMethod);
tolua_endmodule(tolua_S);
tolua_endmodule(tolua_S);
return 1;
}
CCLuaEngine* engine = CCLuaEngine::defaultEngine();
tolua_userdefine_open(engine->getLuaState());
Lua側ではこういう関数を定義しておきます。
function executeSomeMethod(obj)
print(obj:someMethod(10))
end
最後に、上記同様にC++側からexecuteSomeMethodを実行してやります
CCLuaEngine::pushCCObject(CCObject* obj);でCCObjectを簡単にLuaに渡せるため、関数の引数としてオブジェクトを簡単に渡すことができます。
これを上手く利用することで、アイテムの使用時の効果を全てLua側で記述するなど、レベルデザインとシステムの分離が行えます。良い。
まとめ
この記事に書いたことを大体理解しておけば、Lua側から基本的に何でもできるので良いです。あんまり頻繁にLuaを実行するとオーバーヘッドが気になる感じなのですが、それなりに高速に動作してくれるのであんまり気になりません。毎フレーム呼び出したりとかしなければ大丈夫かと。
今開発中のゲームでは、非プログラマにLuaを覚えて貰ってレベルデザインをして貰っているのですが、比較的簡単に習得して貰えたようで良かった。試行錯誤もかなりやりやすくなるのでオススメです。
こういう技術を使って、レベルデザインとシステムを上手く分離して設計できると、プログラマの仕事が飛躍的に少なくなるのでプランナとプログラマ、お互いにとって幸せだと思います。