C++ Lua ゲーム開発

【luabind】luabind を使って C++ と Lua のやり取りをする ~実践編~

まとめ

  • luabind を使って,期待していたことが実現できることを確認した
  • 使い方のメモをまとめた

 

はじめに

luabind の公式

www.rasterbar.com

luabind の環境構築

当然だが,
困ったら公式ドキュメントを読むと良さげ.

www.rasterbar.com

※ 記事内で使用しているコードは,
実際にゲームを作っているときに書いているコードなので,
サンプル的な感じでわかりやすいものになっていない点は許して.

実現したいこと

luabind に期待していること,
実現したいことは以下.

  • クラス情報の受け渡し
  • 継承したクラス情報の受け渡し
  • 変数,定数,関数の受け渡し
  • C++ から Lua の関数を呼べること.
  • Lua から,C++ の関数を呼べること.
  • Lua から,C++ の変数にアクセスできること.
  • Lua から,C++ のクラスやインスタンス,メンバにアクセスできること.
  • 事前に Lua ファイルを読んでおき,オンメモリのバイト情報を渡して実行できること
  • 異なる Lua ファイルの同名関数を呼び分けられること.
  • Lua 内でエラーがあった場合にハンドリングできること
  • 上記が比較的低コストで実現できること

    概ね達成できたのでそのメモを残しておく.

    初期化終了

    luabind の lib をリンクし,
    lua のヘッダと luabind のヘッダをインクルードする.

    #include <lua.hpp>
    #include <luabind\luabind.hpp>
    

    初期化終了コードは以下.

     // 初期化
    lua_State* L;
    L = luaL_newstate();
    luaL_openlibs(L);
    luabind::open(L);
    // 終了
    lua_close(L);
    

    簡単.

    変数,定数,関数の受け渡し

    C++ ⇔ Lua の受け渡しには,
    事前に C++ 側で登録が必要.

    変数 ( enum や define を含む ) や,
    グローバル関数の登録は以下のようにする.

     // グローバル関数
    luabind::module( LuaManager::GetLuaState() )[
    luabind::def( "Input_GetKeyState", &Input::GetKeyState ),
    ];
    // 変数
    luabind::object g = luabind::globals( LuaManager::GetLuaState() );
    g[ "KEY_LSHIFT" ] = KEY_INPUT_LSHIFT;
    g[ "KEY_RIGHT" ] = KEY_INPUT_RIGHT;
    g[ "KEY_LEFT" ] = KEY_INPUT_LEFT;
    g[ "KEY_UP"] = KEY_INPUT_UP;
    g[ "KEY_DOWN"] = KEY_INPUT_DOWN;
    

    Lua からは,以下のように呼び出せる.

    if Input_GetKeyState( KEY_DOWN ) >= 1 then
    end
    

    クラスの受け渡し

    クラスの受け渡しは
    以下のようにする.

     // Entity クラス
    luabind::module( L )[
    luabind::class_<Entity>( "Entity" )
    .def( "GetTransform", &Entity::GetComponent<Transform> )
    .def( "GetRigidBody2D", &Entity::GetComponent<RigidBody2D> )
    .def( "Destroy", &Entity::Destroy )
    .def( "GetTag", &Entity::GetTag )
    ];
    

    Entity というクラスを登録している.

    メンバ関数として,
    GetComponent
    Destroy(),GetTag()
    を持つ.

    テンプレートの呼び分けは,
    別の関数として登録してやれば良い.

    メンバ変数については↓.

    継承したクラスの受け渡し

    継承したクラスを登録する場合,
    継承していることを明示的に登録する必要がある.

    具体的には以下のようにする.

     // Enemy クラス
    luabind::module( LuaManager::GetLuaState() )[
    luabind::class_<Enemy, Entity>( "Enemy" )
    .def( luabind::constructor<>() )
    .def( "SetPosition", &Enemy::SetPosition )
    .def_readwrite( "m_speed", &Enemy::m_speed )
    .def_readwrite( "m_life", &Enemy::m_life )
    ];
    

    Entity クラスを継承した,
    Enemy クラスを登録している.

    メンバ関数として
    SetPosition(),
    メンバ変数として
    m_speed , m_life を持つ.

    上記のように登録したい場合,
    メンバ変数を public に持つ必要があるので注意.
    嫌な場合は setter , getter を用意しよう.

    クラスには lua からはこんな風に書ける.

    // ap は c++ から渡ってきた Enemy のインスタンス
    ap.m_speed = 2.0;
    ap:Destroy();
    

    メンバ変数の場合は「.」
    メンバ関数の場合は「:」
    な点に注意. *1

    Lua 関数の呼び出し

    以下のようにする.

    luabind::call_function<void>(L, "Init", this);
    

    戻り値 void の Init 関数に, this を渡して呼び出している.

    lua 側は以下のような感じ.

    function Init(ap)
    ap.m_speed = -1.8;
    ap.m_life = 4;
    end
    

    事前に Lua ファイルを読み,オンメモリのバイトを渡して呼び出す

    スクリプトを実行するたびに
    ファイルを読みに行くわけには行かないので,

    事前に lua ファイルを読んでおき,
    オンメモリの状態で使用したい.

    当然 lua にはその機能が用意されていて,
    以下のようにする.

    luaL_loadbuffer(L, buf, size, name);
    lua_pcall(L, 0, 0, 0);
    

    事前に読んでおいた バッファと,
    そのサイズと,デバッグ情報用*2の名前
    をわたして luaL_loadbuffer .

    一度 lua_pcallしておくことで,
    オンメモリの lua を呼び出せる.

    異なる Lua ファイルの同名関数を呼び分ける

    満たしたいこととして,
    異なる lua スクリプトの同名関数を呼び分けたい.

    というのがある.

    Unity の C# スクリプトのように使いたいので,

    • Init()
    • Update()

    というシンプルな名前の関数のみを使いたい.

    実現には,
    ファイルを分けてやり,
    上記の オンメモリの呼び出しをもう一度行えば良い.*3

    つまり,

    // A の Init が呼ばれる
    int ret = luaL_loadbuffer(L, a_buf, a_size, "A");
    lua_pcall(L, 0, 0, 0);
    luabind::call_function<void>(L, "Init", this);
    ...
    // B の Init が呼ばれる
    int ret = luaL_loadbuffer(L, b_buf, b_size, "B");
    lua_pcall(L, 0, 0, 0);
    luabind::call_function<void>(L, "Init", this);
    

    こんな感じ.

    エラーハンドリング

    別記事参照

    実現できなかったこと

    RTTI の無効化

    RTTI をしたところ, ハングした.
    どうやら RTTI は無効化できない.

    昔はできたようだが,
    0.9.1 ではその機能が削除されている.
    *4

    luabind を使用するには RTTI が必要.

    おわりに

    ↓ 実際にゲームに実際に組み込んでみた.

    必要な全ての変数,関数,クラスの登録を行って Lua でスクリプト書くのと,
    全て C++ でオブジェクトの挙動を書くの,
    どちらが効率が良いだろうか.

    所感としては
    Lua を組み込んだほうが良さそうではあるが,
    デバッガが欲しくなったりしてきたらどうなるか.

    ていうかデバッガって使えるんだろうか.
    調べて使えたらまとめてみる.

    *1:※たぶん

    *2:(?)

    *3:正しい方法かは知らないが,一応できている.

    *4:0.8 頃まではあったらしい

    • この記事を書いた人

    GOTH

    鹿児島県出身,吉祥寺在住の27歳.職業はゲーム会社でプログラマー.趣味はバイク,車,キャンプ,ガジェット,読書,そしてゲーム開発. サイトのテーマはプログラミングとガジェットでライフハック.たまに趣味に関する雑感や記録を残していく備忘録.ツイッターもやってます.
    自己紹介
    お問い合わせ

    -C++, Lua, ゲーム開発