Я ведущий разработчик для Bitfighter , и мы работаем со смесью Lua и C ++, используя Lunar (вариант Luna, доступен здесь ), чтобы связать их все вместе.
Я знаю, что в этой среде нет хорошей поддержки ориентации и наследования объектов, но я бы хотел найти способ хотя бы частично обойти эти ограничения.
Вот что у меня есть:
Структура класса C ++
GameItem
|---- Rock
|---- Stone
|---- RockyStone
Robot
Робот реализует метод под названием getFiringSolution (элемент GameItem) , который смотрит на положение и скорость item и возвращает угол, под которым роботу нужно будет стрелять, чтобы ударить пункт .
-- This is in Lua
angle = robot:getFiringSolution(rock)
if(angle != nil) then
robot:fire(angle)
end
Так что моя проблема в том, что я хочу передать камни , камни или 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));
}
Это потенциальное решение не работает, потому что функция проверки Лунара хочет, чтобы объект в стеке имел className, совпадающее с определенным для 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));
}
Проблема с этим решением состоит в том, что функция 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, приведение возвращает 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 )