私はBitfighterの主任開発者であり、Luna (Luna のバリアントで、こちらから入手可能) を使用して Lua と C++ を組み合わせて作業しています。
この環境ではオブジェクト指向と継承が適切にサポートされていないことはわかっていますが、これらの制限を少なくとも部分的に回避する方法を見つけたいと考えています。
ここに私が持っているものがあります:
C++ クラス構造
ゲームアイテム |---- ロック |---- ストーン |---- ロッキーストーン ロボット
Robot はgetFiringSolution(GameItem item)というメソッドを実装します。このメソッドはitemの位置と速度を調べ、ロボットがitemを攻撃するために発砲する必要がある角度を返します。
-- This is in Lua
angle = robot:getFiringSolution(rock)
if(angle != nil) then
robot:fire(angle)
end
したがって、私の問題は、rocks、stone、またはrockyStonesを getFiringSolution メソッドに渡したいということですが、その方法がわかりません。
これはロックのみで機能します:
// C++ code
S32 Robot::getFiringSolution(lua_State *L)
{
Rock *target = Lunar<Rock>::check(L, 1);
return returnFloat(L, getFireAngle(target)); // returnFloat() is my func
}
理想的には、私がやりたいことは次のようなものです:
// This is C++, doesn't work
S32 Robot::getFiringSolution(lua_State *L)
{
GameItem *target = Lunar<GameItem>::check(L, 1);
return returnFloat(L, getFireAngle(target));
}
Lunar のチェック関数は、スタック上のオブジェクトが GameItem に定義されたものと一致する className を持つことを望んでいるため、この潜在的な解決策は機能しません。(Lunar に登録するオブジェクト タイプごとに、オブジェクトが正しいタイプであることを確認するために Lunar が使用する文字列の形式で名前を指定します。)
考えられるすべてのサブクラスをチェックする必要がある場合、次のようなものに落ち着きます。
// Also C++, also doesn't work
S32 Robot::getFiringSolution(lua_State *L)
{
GameItem *target = Lunar<Rock>::check(L, 1);
if(!target)
target = Lunar<Stone>::check(L, 1);
if(!target)
target = Lunar<RockyStone>::check(L, 1);
return returnFloat(L, getFireAngle(target));
}
このソリューションの問題点は、スタック上の項目が正しいタイプでない場合、check 関数がエラーを生成し、対象のオブジェクトをスタックから削除してしまうことです。
スタックから Rock/Stone/RockyStone オブジェクトへのポインタを取得し、それがどのタイプであるかを把握し、それを操作する前に正しいものにキャストする必要があると考えています。
型チェックを行う Lunar の重要な部分は次のとおりです。
// from Lunar.h
// get userdata from Lua stack and return pointer to T object
static T *check(lua_State *L, int narg) {
userdataType *ud =
static_cast<userdataType*>(luaL_checkudata(L, narg, T::className));
if(!ud) luaL_typerror(L, narg, T::className);
return ud->pT; // pointer to T object
}
このように呼ぶと:
GameItem *target = Lunar<Rock>::check(L, 1);
次に、luaL_checkudata() がスタック上のアイテムが Rock かどうかを確認します。もしそうなら、すべてが桃色であり、getFiringSolution() メソッドに渡される私の Rock オブジェクトへのポインターを返します。スタックに Rock 以外のアイテムがある場合、キャストは null を返し、luaL_typerror() が呼び出され、アプリが lala land に送られます (エラー処理によって診断が出力され、極度の偏見でロボットが終了します)。
これを進める方法についてのアイデアはありますか?
どうもありがとう!!
私が思いついた最善の解決策...醜いが機能する
以下の提案に基づいて、私はこれを思いつきました:
template <class T>
T *checkItem(lua_State *L)
{
luaL_getmetatable(L, T::className);
if(lua_rawequal(L, -1, -2)) // Lua object on stack is of class <T>
{
lua_pop(L, 2); // Remove both metatables
return Lunar<T>::check(L, 1); // Return our object
}
else // Object on stack is something else
{
lua_pop(L, 1); // Remove <T>'s metatable, leave the other in place
// for further comparison
return NULL;
}
}
じゃあ後で...
S32 Robot::getFiringSolution(lua_State *L)
{
GameItem *target;
lua_getmetatable(L, 1); // Get metatable for first item on the stack
target = checkItem<Rock>(L);
if(!target)
target = checkItem<Stone>(L);
if(!target)
target = checkItem<RockyStone>(L);
if(!target) // Ultimately failed to figure out what this object is.
{
lua_pop(L, 1); // Clean up
luaL_typerror(L, 1, "GameItem"); // Raise an error
return returnNil(L); // Return nil, but I don't think this
// statement will ever get run
}
return returnFloat(L, getFireAngle(target));
}
おそらくこれでさらに最適化を行うことができます...これをループに折りたたむ方法を本当に知りたいです。実際には、処理するクラスが 3 つよりもはるかに多いため、このプロセスは次のとおりです。少し面倒です。
上記のソリューションのわずかな改善
C++:
GameItem *LuaObject::getItem(lua_State *L, S32 index, U32 type)
{
switch(type)
{
case RockType:
return Lunar<Rock>::check(L, index);
case StoneType:
return Lunar<Stone>::check(L, index);
case RockyStoneType:
return Lunar<RockyStone>::check(L, index);
default:
displayError();
}
}
じゃあ後で...
S32 Robot::getFiringSolution(lua_State *L)
{
S32 type = getInteger(L, 1); // My fn to pop int from stack
GameItem *target = getItem(L, 2, type);
return returnFloat(L, getFireAngle(target)); // My fn to push float to stack
}
Lua ヘルパー関数。ユーザーが手動でコードに追加する必要がないように、別のファイルとして含まれています。
function getFiringSolution( item )
type = item:getClassID() -- Returns an integer id unique to each class
if( type == nil ) then
return nil
end
return bot:getFiringSolution( type, item )
end
ユーザーは Lua から次のように呼び出します。
angle = getFiringSolution( item )