#はじめに
@tkyajiと申します。Advent Calendar初参加です。
Cocos2d-xに、Script bindingを追加する際の実装方法を書いていきたいと思います。
Cocos2d-xのバージョンは3.2を使っています。
なお、bindings-generatorを使用する際に必要となる準備は、昨年のAdvent Calendarで
giginetさんがとてもわかりやすい記事を書いてくれているので、
その辺りの説明は省きます(助かった)
【cocos2d-x 3.0】binding-generatorでScript Bindingを自動化する方法
以下の説明の中で、追加するスクリプト言語を"Lang"と記述しています。
例えばLuaなら"Lang"は"Lua"に読み替えてください。
#おおまなか手順
- ScriptEngineProtocolを継承したLangEngineクラスを作る
- C++の型とLang言語の型を変換するための関数を書く
- bindings-generatorにターゲットとなる言語を追加
- bindings-generatorで、グルーコード生成ロジックをひたすら書いていく
- 一通りできたら、cocosコマンドに対応する
だいたいこんな感じです。bindings-generatorとcocosコマンドの実装はPythonで書きます。
cocosコマンドに対応するのはおそらく最後になるので、まずはLua Bindingのプロジェクトを作り、
その中で実装していくのが良いと思います。
#LangEngineクラスを作る
Luaの場合はLuaEngine
、JSの場合はScriptingCore
にあたるEngineクラスを作成します。
cocos2d::ScriptEngineProtocol
を継承し、以下のvirtual関数をオーバーライドします。
- getScriptType
スクリプト言語ごとの定数を返却します。
ScriptEngineProtocol
にccScriptType
というenumがあるので、ここにkScriptTypeLang
定数を 追加し、
それを返却します。
- removeScriptObjectByObject
Refクラスのデストラクタから呼ばれます。
autorelease等でC++上のインスタンスが死ぬ際に、Lang言語上のインスタンスも消す、
というような処理を実装します。
なお、Refクラスには_ID
,_scriptObject
というフィールドがあり、
ここにLang言語上のインスタンス情報を設定することで、C++のインスタンスとLang言語のインスタンスとを
紐づけることができます。
- executeString
指定したLang言語の文字列を読み込んで実行します。
- executeScriptFile
指定したLang言語のスクリプトファイルを読み込んで実行します。
基本的にはこのメソッドでsrcディレクトリ配下のスクリプトファイルを実行していくので、
最初に実装が必要なメソッドになります。
####executeGlobalFunction
指定した名前のLang言語上のグローバル関数を実行します。
####sendEvent
各種イベント(メニュボタン押下、タッチ、スケジュール等々)発火時に呼び出されます。
引数のScriptEvent
でイベントの種類を判定します。
C++とは違う方法でイベント登録をしている場合は、このメソッドの中で登録した処理を呼び出します。
例えばLuaではregisterScriptTapHandler
というメソッドでコールバックを登録していますので、
この場合、sendEvent
の中でregisterScriptTapHandler
で登録したLuaの関数を実行します。
逆に言うと、C++と同様にEventDispatcher
にイベント登録している場合は、
sendEvent
では実装不要となります。
####handleAssert
CCASERTから呼び出されます。ここではエラーログの出力と、Lang言語のエラー処理を実装します。
例えばLang言語上で例外を発生させる等です。
####parseConfig
CocoStudioとの連携用?
#C++ ⇔ Lang言語間の型変換関数を実装
Luaの場合はLuaBasicConversions
, JSの場合はjs_manual_conversions
にあたるソースです。
C++の型と、Lang言語の型を変換する関数を作っていきます。
例えばLuaの場合、Vec2を変換する関数を以下のように定義しています。
// c++ -> lua
void vec2_to_luaval(lua_State* L,const cocos2d::Vec2& vec2)
{
if (NULL == L)
return;
lua_newtable(L); /* L: table */
lua_pushstring(L, "x"); /* L: table key */
lua_pushnumber(L, (lua_Number) vec2.x); /* L: table key value*/
lua_rawset(L, -3); /* table[key] = value, L: table */
lua_pushstring(L, "y"); /* L: table key */
lua_pushnumber(L, (lua_Number) vec2.y); /* L: table key value*/
lua_rawset(L, -3);
}
// lua -> c++
bool luaval_to_vec2(lua_State* L,int lo,cocos2d::Vec2* outValue)
{
if (nullptr == L || nullptr == outValue)
return false;
bool ok = true;
tolua_Error tolua_err;
if (!tolua_istable(L, lo, 0, &tolua_err) )
{
#if COCOS2D_DEBUG >=1
luaval_to_native_err(L,"#ferror:",&tolua_err);
#endif
ok = false;
}
if (ok)
{
lua_pushstring(L, "x");
lua_gettable(L, lo);
outValue->x = lua_isnil(L, -1) ? 0 : lua_tonumber(L, -1);
lua_pop(L, 1);
lua_pushstring(L, "y");
lua_gettable(L, lo);
outValue->y = lua_isnil(L, -1) ? 0 : lua_tonumber(L, -1);
lua_pop(L, 1);
}
return ok;
}
これらの変換関数は、bindings-generatorの実装時に使います。
bindings-generatorを実装しながら、必要になった型を随時追加すれば良いので、最初に全て作る必要はありません。
また、上記のような変換関数は作らないで実装することも可能ですが、大抵の場合は作った方が楽だと思います。
とりあえず、本日はここまでです。
明日はbindings-generatorの実装周りを説明したいと思います。