C++でゲームとか作ってて、敵とか弾とかの制御にスクリプト使いたい!コルーチン使いたい!ってなることありますよね。
それを実現するメジャーな組み込みスクリプトがLuaなんですが、C++のオブジェクトをLuaで制御する手軽な方法が分からず、導入に二の足を踏んでました。
(複数のオブジェクトがあった場合、どのオブジェクトを処理するのかLuaに教える手軽な方法が分からなかった。
UserDataやLightUserDataを使うのかなとは思いつつ、実際書いてみると引数増えるしスタック操作増えるしで実用的でないように感じてしまう)
ですがある日、これでいいんでない?って方法見つけたのでシェアします。
おそらく使ってる人には常識なんだと思いますが、似たようなことで悩んでる人もいるかもしれないので。
- Luaを呼び出す前に、Lua環境が制御対象とするC++オブジェクトをC++の変数にセットする。
- Luaから呼び出された関数は、C++の変数から制御対象のC++オブジェクトを取り出し、そのオブジェクトに対して処理を行う。
まあなんの変哲もない方法なんですが、これに気付くのに何ヶ月もかかったというアホみたいな話です。
シングルスレッド限定にはなりますが、コードも単純だしスタックも基本不要だしでまあまあ実用的かと思います。
以下、サンプルです。
main.cpp
#include <iostream>
#include "lua/lua.hpp"
#include "lua/lauxlib.h"
#include "lua/lualib.h"
#include "Chara.h"
#include "luaHelper.h"
int main(int argc, const char * argv[]) {
lua_State *L = l_setup();
setupLuaChara(L);
Chara chara(L, "Tom");
// ステップ実行
while(chara.update()) {
}
lua_close(L);
return 0;
}
Chara.h
#include <iostream>
#include "lua/lua.hpp"
#include "lua/lauxlib.h"
#include "lua/lualib.h"
#include "luaHelper.h"
//static const char Key = 'k';
class Chara;
// CharaのLua環境をセットアップ
void setupLuaChara(lua_State *L);
// ■■■Lua環境のターゲットを指定■■■
void setCurrentChara(Chara *chara);
class Chara {
public:
Chara(lua_State *L, std::string name):L(L), name(name) {
// コルーチンの作成
// ※ ガベージコレクションを防ぐためレジストリに保管
lua_pushlightuserdata(L, (void*)&Key);
thread = lua_newthread(L);
lua_settable(L, LUA_REGISTRYINDEX);
lua_getglobal(thread, "step");
x = y = 0;
}
~Chara() {
// コルーチンの削除
lua_pushnil(L);
lua_pushlightuserdata(L, (void*)&Key);
lua_settable(L, LUA_REGISTRYINDEX);
}
virtual bool update() {
// ■■■Lua環境のターゲットを設定■■■
setCurrentChara(this);
// コルーチンの再開
if(lua_resume(thread, NULL, 0)) {
std::cout << name << ":" << x << "," << y << std::endl;
return true;
}
return false;
}
void north() {
y --;
}
void south() {
y ++;
}
void west() {
x --;
}
void east() {
x ++;
}
int getX() {
return x;
}
int getY() {
return y;
}
std::string getName() {
return name;
}
private:
lua_State *L;
lua_State *thread;
std::string name;
int x, y;
const char Key = 'k';
};
Chara.cpp
#include "Chara.h"
// ■■■Lua環境のターゲットを指定■■■
Chara *currentChara;
void setCurrentChara(Chara *chara) {
currentChara = chara;
}
// ■■■Luaから呼ばれる関数。ターゲットに対して処理を行う■■■
static int l_north(lua_State *L) {
currentChara->north();
return 0;
}
static int l_south(lua_State *L) {
currentChara->south();
return 0;
}
static int l_west(lua_State *L) {
currentChara->west();
return 0;
}
static int l_east(lua_State *L) {
currentChara->east();
return 0;
}
// CharaのLua環境をセットアップ
void setupLuaChara(lua_State *L) {
if (luaL_dofile(L, "Chara.lua")) {
printf("%s\n", lua_tostring(L, lua_gettop(L)));
lua_close(L);
return false;
}
// C関数追加
lua_pushcfunction(L, l_north);
lua_setglobal( L, "c_north");
lua_pushcfunction(L, l_south);
lua_setglobal( L, "c_south");
lua_pushcfunction(L, l_west);
lua_setglobal( L, "c_west");
lua_pushcfunction(L, l_east);
lua_setglobal( L, "c_east");
}
Luaスクリプト(Chara.lua)
function north()
c_north()
coroutine.yield()
end
function south()
c_south()
coroutine.yield()
end
function west()
c_west()
coroutine.yield()
end
function east()
c_east()
coroutine.yield()
end
-- C++から呼ばれる関数
function step()
while true do
north()
west()
south()
east()
end
end