我是Bitfighter的首席开发人员,我们正在使用 Lua 和 C++ 的混合体,使用 Lunar(Luna 的一种变体,可在此处获得)将它们绑定在一起。
我知道这个环境对面向对象和继承没有很好的支持,但我想找到一些方法来至少部分解决这些限制。
这是我所拥有的:
C++ 类结构
游戏物品 |---- 摇滚 |---- 石头 |---- 洛基石 机器人
Robot 实现了一个名为getFiringSolution(GameItem item)的方法,该方法查看item的位置和速度,并返回机器人需要开火才能击中item的角度。
-- This is in Lua
angle = robot:getFiringSolution(rock)
if(angle != nil) then
robot:fire(angle)
end
所以我的问题是我想将rock 、 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 定义的类名匹配的类名。(对于您在 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));
}
这个解决方案的问题是,如果堆栈上的项目类型不正确,检查函数会生成错误,并且我相信会从堆栈中删除感兴趣的对象,所以我只有一次尝试抓取它。
我想我需要从堆栈中获取指向 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。如果是这样,一切都很好,它返回一个指向我的 Rock 对象的指针,该指针被传递回 getFiringSolution() 方法。如果堆栈上有一个非 Rock 项目,则强制转换返回 null,并调用 luaL_typerror(),这会将应用程序发送到 lala 土地(错误处理会打印诊断信息并以极端偏见终止机器人)。
关于如何推进这一点的任何想法?
非常感谢!!
我想出的最佳解决方案......丑陋,但有效
根据以下建议,我想出了这个:
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));
}
我可能可以对此进行进一步的优化...我真的很想弄清楚如何将其折叠成一个循环,因为实际上,我将处理三个以上的类,而这个过程是有点麻烦。
对上述解决方案略有改进
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 )