結果だけでなく過程も見てください

たい焼きさんの日々の奮闘を綴る日記です。

Lua/Luabind/コルーチンの使い方まとめ その1

最近、自作ゲームのスクリプトLuaにシコシコ移植しています。

そんなわけで今回は移植作業中に得た知識を備忘録も兼ねてまとめておきます。

バインドにLuabindを使ってます。
LuaやLuabindのセットアップについては前の記事を参考にしてください。

C++で組み込みスクリプト(Lua)を使用する
http://d.hatena.ne.jp/taiyakisun/20110327#1301252614

●luabindをビルドする方法
http://d.hatena.ne.jp/taiyakisun/20110525#1306333280

コルーチンも使っています。
使い方が正しいのか非常にアヤシイのですが、
とりあえず自分はこうやってるよ、程度に紹介します。

もっと良い方法があったらぜひ教えてください!
正直Luaの扱いは真っ暗闇の中を手探りで進んでる感じで不安です(汗)

とにかく処理をLuaに逃がすのだ!

サンプルを交えて使い方を説明していきます。

C++側で以下のような処理を行っています。

ゲーム内で特定の条件が成立しているかをチェックし、
条件が成立している場合は指定された複数のコマンドを順番に実行する。

この「条件成立チェック」と「コマンド実行」をLua側に逃がして
C++からLuaを呼び出すようにするのが今回のミッションです。

実際には上の処理は1つのクラスで実現しています。

check()で「条件成立チェック」を、
exec()で「コマンド実行」を行っています。

class CGameEvent
{
public:
  CGameEvent(lua_State*    pLuaState,
             const string& rstrLuaFilename):
    m_pLuaState(pLuaState),
    m_pLuaStateForCoroutine(0),
    m_nCoroutineRef(0),
    m_strLuaFilename(rstrLuaFilename),
    m_bFormingCondition(false){ ... }
  ~CGameEvent(){ ... }

  void check();   // 条件成立チェックを行う
  void exec();    // コマンド実行を行う

  void onInit();  // 初期化処理を行う

private:

  /// セットアップ済のlua_State(外部から渡してもらえばよい)
  lua_State*  m_pLuaState;

  /// コルーチンのためのステート
  lua_State*  m_pLuaStateForCoroutine;
  int         m_nCoroutineRef;

  /// Luaファイル名
  string      m_strLuaFilename;

  /// 条件成立すると真になる
  bool        m_bFormingCondition;

};

Luaファイルの読み込みとコルーチンの生成

まずはLuaファイルを読み込みます。
そのあとコルーチンの生成を行います。
コルーチンは「コマンド実行」を行うときに使用します。

これらは初期化処理関数onInit()で行います。

Luaファイルの読み込みはこんな感じです。特に説明はいらないと思います。
エラー時は自作のメッセージ出力関数DEBUGPRINTを使用していますが、
説明がめんどうなので、このへんは自分なりのログ出力関数等に置き換えてください。

int rc  = luaL_dofile( m_pLuaState, m_strLuaFilename.c_str() );
if ( rc != 0 )
{
  string error_message = lua_tostring(m_pLuaState, -1);
  DEBUGPRINT( "Luaファイルの読み込み失敗(%s)(%s)", m_strLuaFilename.c_str(), error_message.c_str() );
}

続いてコルーチンを生成します。
onInit()が複数回呼ばれても問題ないように前回分がある場合は解放するようにします。

LUA_REGISTRYINDEXとは、C++Luaのデータを所持しなければならない場合に利用する領域です。
Luaからは参照や変更などの操作させたくない場合に使用します。詳しくはLuaの資料を見てください。

int top = lua_gettop( m_pLuaState );

// 前回の分があったら削除しておく
if ( m_pLuaStateForCoroutine && 
     m_nCoroutineRef != 0 )
{
  luaL_unref( m_pLuaState,
              LUA_REGISTRYINDEX, 
              m_nCoroutineRef );

  m_nCoroutineRef = 0;
  m_pLuaStateForCoroutine = NULL;
}

// コルーチンを生成する
m_pLuaStateForCoroutine = lua_newthread( m_pLuaState );
m_nCoroutineRef = luaL_ref( m_pLuaState, LUA_REGISTRYINDEX );

// スタックを元に戻す
lua_settop( m_pLuaState, top );

「条件成立チェック」処理

Luaファイルには必ずcheck()という名前の関数を作成することに決めます。
この関数内で条件成立チェックをし、成立したら1を返し、成立しなかったら0を返します。

Luaのコードはこんな感じです。
GetPlayerHP()はC++の関数でプレイヤーのHPを返すものです。

function check()
  if GetPlayerHP() <= 20 then
    return 1
  end
  return 0
end

上のコードは、プレイヤーのHPが20以下だったら条件成立(1を返す)
そうでなければ条件不成立(0を返す)ということです。

GetPlayerHP関数はC++の関数なので、当然Luaから使うにはバインドが必要です。

GetPlayerHP関数がグローバル関数だとすると、バインドはこんな感じです。
第一引数で指定した文字列がLua内でのGetPlayerHP関数の名前となります。

lua_State* L;    // セットアップ済みとする

  :

luabind::module( L )
[
  luabind::def( "GetPlayerHP", GetPlayerHP )
];

C++からLua内のcheck関数をコールするコードはこんな感じです。

void CGameEvent::check()
{
  // 条件チェック関数を呼ぶ
  if ( luabind::call_function<int>( m_pLuaState, "check" ) == 1 )
  {
    // 条件成立
    m_bFormingCondition = true;

    // 条件成立した最初の1ループを示すフラグ
    m_bFirstExec = true;
  }
}

C++からLua内の関数をコールするにはcall_functionを使用します。
戻り値はintなのでテンプレートの型はintを指定しています。

C++では1を受け取ったら条件成立ということなので、
条件成立フラグをtrueにしておきます。

また、「コマンド実行」で条件成立をした最初の1ループかどうかの
判定を行う必要があるので、そのフラグもtrueにしておきます。
詳細はコマンド実行のところで説明します。

後続処理では、条件成立フラグがtrueの場合に「コマンド実行」を行います。


長くなったので「コマンド実行」は次回行います。