3

我正在构建一个简单的 2D 游戏引擎,并且它越来越大,在 Lua 中公开所有功能将是不可能的:所以我试图自动化一点这个过程,无论如何要获得所有 n 个参数(具有不同类型)一次从堆栈中取出并将它们直接注入 C++ 函数。我已经自动化了函数参数检查。仍然是有点棘手的函数绑定

例如:我有更改精灵位置的正常代码:

int LuaSprite::SetSpritePosition(lua_State* L)
{
    Drawable* sprt = (Drawable*)lua_touserdata(L, 1);
    int x = (int)lua_tonumber(L, 2);
    int y = (int)lua_tonumber(L, 3);
    sprt->setPosition(Vec2i(x, y));
    return 0;
}

我想要实现的概念更像是这样的:

int LuaSprite::SetSpritePosition(lua_State* L)
{   
    LOAD_ARGS(Sprite*, int, int)
    GET_ARG(1)->setPosition(Vec2i(GET_ARGS_FROM(1)));
    LOAD_RETURN(true, x, y, 3);
    return 3;
}

所以

  • LOAD_ARGS 将从堆栈中分别获取给定的类型。
  • GET_ARG(i) 将在索引 i 处获取 arg。
  • GET_ARGS_FROM(i) 会做这样的事情 x,y,z,type

我没有要求相同的行为,因为它可能是不可能的,但至少类似的事情
而且我确信这仅使用“普通”C++ 是无法实现的,而且我还需要一些“魔术”宏。
我没有使用现成的自动 Lua 绑定“库”,因为有自定义结构和一些函数正在使用复杂的结构和类,我已经做了很多搜索,我真的很迷茫。

4

4 回答 4

4

试试 Luaaa(https://github.com/gengyong/luaaa),它更轻量级,只在一个头文件中实现。

它完美地满足了您的要求。

示例代码:

    // include luaaa file
    #include "luaaa.hpp"
    using namespace luaaa;  

    // Your exists class
    class Cat
    {
    public:
        Cat();
        virtual ~Cat();
    public:
        void setName(const std::string&);
        const std::string& getName() const;
        void eat(const std::list<std::string>& foods);
        //...
    private:
        //...
    };

    lua_State * state; // create and init lua

    // To export it:
    LuaClass<Cat>(state, "AwesomeCat")
    .ctor<std::string>();
    .fun("setName", &Cat::setName);
    .fun("getName", &Cat::getName);
    .fun("eat", &Cat::eat);
    .def("tag", "Cat");

您可以在此处找到更多详细信息(https://github.com/gengyong/luaaa)。

我是这个库的作者,我强烈建议您尝试一下,如果您有任何问题,请随时提问。

于 2019-01-03T07:09:18.310 回答
2

我发现Luabridge在执行这些任务时非常方便。它是一个仅适用于较旧 C++ 代码(c++11 之前及之后)的标头库。

您不直接针对 Lua 堆栈进行编程,而是将普通类/结构包装到绑定定义中。

绑定代码如下所示:

 getGlobalNamespace(L).beginNamespace("GameFrameworkName")

.beginClass<RectF>("Rect")
.addStaticFunction("__call", &RectF_Ctor) // Constructor from Table!
.addData<float>("x", &RectF::x)
.addData<float>("y", &RectF::y)
.addData<float>("width", &RectF::width)
.addData<float>("height", &RectF::height)
.addFunction("Union", &RectF::Union)
.addFunction("Intersects", (bool (RectF::*)(const RectF&)) &RectF::Intersects)
.addFunction("Intersection", &RectF::Intersection)
.addFunction("Contains", (bool (RectF::*)(const PointF&)) &RectF::Contains)
.addFunction("Offset", (void (RectF::*)(const PointF&)) &RectF::Offset)
.addFunction("Inflate", (void (RectF::*)(float, float)) &RectF::Inflate)
.addFunction("__tostring", &RectF_ToString)
.addFunction("__eq",  (bool   (RectF::*)(const RectF&)) &RectF::operator==)
.addFunction("Copy", &RectF_Copy)
.endClass()


.beginClass<PointF>("Point")
.addStaticFunction("__call", &PointF_Ctor) // Constructor from Table!
.addData<float>("x", &PointF::x)
.addData<float>("y", &PointF::y)
.addFunction("__tostring", &PointF_ToString)
.addFunction("__add", (PointF (PointF::*)(const PointF&)) &PointF::operator+ )
.addFunction("__sub", &PointF_SubtractOperator )
.addFunction("__mul", &PointF_MultiplyOperator )
.addFunction("__div", &PointF_DivideOperator )
.addFunction("__eq",  (bool   (PointF::*)(const PointF&)) &PointF::operator==)
.addFunction("__unm", &PointF_Unm)
.addFunction("Copy",  &PointF_Copy)
.endClass()

.endNamespace();

对于游戏对象,我通常会在 C++ 中管理生命周期,并将指针传递给 Lua。Luabind 允许您覆盖 Lua CTor (__call) 和 ~Tor (__gc)。这是我的副本:

template<typename T>
std::string DoNotCreate()
{
    DBG_ASSERT(false); // Do not try to create a new instance of this type
    return StrFormat("ERROR: Lua cannot create an instance of %s.", typeid(T).name());
}


///////////////////////////////////////
// \brief       Do Not Allow Lua or Luabridge to delete us
void DoNotGarbageCollect(void*) {}

以及它们的用途:

.deriveClass<Sprite, DisplayObject>("Sprite")
  .addStaticFunction("__call", &DoNotCreate<Sprite>)
  .addFunction("__gc", (void (*) (Sprite*)) &DoNotGarbageCollect)

要从 C++ 调用 Lua 代码,您可以使用 LuaRefs,它 (IIRC) 基本上是可以是任何 Lua 类型的变体。


如果你有兴趣,我发现Zerobrane是一个很好的调试器,你只需要添加套接字 lua 库

于 2018-09-29T03:15:24.760 回答
1

根据您对参数与 Lua 之间的获取方式的控制程度,您可能需要完全自动绑定,或者我更喜欢半自动绑定。我写了一些东西,允许您定义“胶水”函数,这些函数可以使用 Lua 堆栈操作函数为您提供的所有功能,但可以为您处理对象生命周期。

不适合所有人 - 但对某些人有好处。

https://github.com/merlinblack/manualbind

于 2020-06-10T04:47:24.110 回答
1

在我的实践中,我发现遵循 Lua 关于参数类型或参数的不同布局的灵活性非常方便。您不限于严格的 C/C++ 规则,您可以使用相同的方法执行不同的工作,具体取决于确切的参数。即调用的语义将表达一些更高层次的概念。

在做这种灵活的方法时,全自动绑定大多是没用的。比如说,您可以接受各种对象作为参数。例如,表示亮度的单个数字,或表示精确颜色的 rgb 三元组,甚至是提供纹理图像路径的字符串。再加上一个可选的以下参数,指定非默认混合模式。你不能在上面抛出固定的通用参数阅读器。

因此,与其尝试使用一些模板伏都教预先解析一些参数,不如拥有一个实用函数更容易,为您读取 Vec2/Vec2i/Vec3/...,只需一个指向 Lua 状态的指针,堆栈上的索引开始,并返回结果标志或实际读取值的数量,以帮助跟踪参数解析。

GET_ARG(1)构造是如此通用,以至于在我的例子中,它只是一个基类中的一个方法,用于所有必须从 Lua 访问的对象。该基础是一个模板,因此它知道对象的类型,自动将索引 1(它始终为 1)处的 Lua 用户数据转换为精确的本机类型,而无需编写任何内容。

返回值要么太简单而无法抱怨(一个布尔值或几个整数),要么太复杂而无法通过自动绑定来处理。比如说,你返回了一些大对象,在构造它之后需要一些配置/更新/注册,然后是更多的值,其中一些是可选的,或者取决于之前返回的数据的类型。

因此,您在我的代码中的示例将转换为:

int LuaSprite::SetSpritePosition(lua_State* L)
{   
    get_object(L)->setPosition(read_lua_vec2i(L, 2));
    return 0;
}
于 2018-09-29T09:07:48.297 回答