5
\$\begingroup\$

I'm struggling to get Lua implemented into my game engine. I've read dozens of blogs and articles revolving around how to wrap C++ classes and I seem to have hit a wall. No matter how I structure the code I can't figure out a logic path that doesn't involve creating global variables in my project so the Lua helper functions can access the core of my engine.

Let me start off by explaining the end goal. I want to have an entities folder which holds scripts that describe unique spawnable entities within the game. A typical file would look like this:

function ENTITY:Initialize()
    --Do stuff when the entity is initialized
    self:SetModel("models/2x2x2-plate.obj")
    self:SetPosition(Vector3D(100,100,100))
end

function ENTITY:Think()
    --Do stuff every time the entity thinks
    self:SetVelocity(Vector3D(0,0,100))
end

The file name of this .Lua file would be a unique identifier to create an entity of this type from within other scripts.

local MyEnt = Entities.Create("MyCustomEntity")
MyEnt:Spawn()

What would seem like an easier way to accomplish this task would be to set it up so scripts look like this:

Entities.Add( "MyCustomEntity", {
    PrintName = "My Custom Entity",

    Initialize = function( self )
        --Do stuff when the entity is initialized
        self:SetModel("models/2x2x2-plate.obj")
        self:SetPosition(Vector3D(100,100,100))
    end,

    Think = function ( self )
        --Do stuff every time the entity thinks
        self:SetVelocity(0,0,100)
    end
} )

However I feel like I would loose some functionality if it were setup like that.

On the C++ side of things a class called BaseEntity would be where these entity scripts would inherit all their functions from.

I've been able to figure out the following on my own so far

#include <lua.hpp>
#include "LuaActor.h"
#include "BaseActor.hpp"

BaseActor* Check_BaseActor(lua_State* L)
{
    luaL_checktype(L, 1, LUA_TUSERDATA);
    return (BaseActor*)reinterpret_cast<BaseActor*>(lua_touserdata(L, 1));
}

int LuaActor_Create(lua_State* L)
{
    BaseActor** Actor = (BaseActor**)lua_newuserdata(L, sizeof(BaseActor*));
    *Actor = new BaseActor();
    luaL_getmetatable(L, "Actors");
    lua_setmetatable(L, -2);

    return 1;
}

int LuaActor_Destroy(lua_State* L)
{
    // Do something when Actor is removed from the lua environment
    BaseActor* Actor = Check_BaseActor(L);
    return 0;
}

int LuaActor_SetIndex(lua_State* L)
{
    BaseActor* Actor = Check_BaseActor(L);

    luaL_checktype(L, 2, LUA_TNUMBER);
    const int Index = lua_tointeger(L, 2);
    Actor->SetIndex(Index);

    return 0;
}

int LuaActor_GetIndex(lua_State* L)
{
    BaseActor* Actor = Check_BaseActor(L);

    lua_pushnumber(L, Actor->GetIndex());

    return 1;
}





static const luaL_reg LuaActor_Funcs[] =
{
    { "Create", LuaActor_Create },
    { NULL, NULL }
};

static const luaL_reg LuaActor_Methods[] =
{
    { "__gc", LuaActor_Destroy },
    { "SetIndex", LuaActor_SetIndex },
    { "GetIndex", LuaActor_GetIndex },
    { NULL, NULL }
};

void Register_LuaActor(lua_State* L)
{
    luaL_newmetatable(L, "Actors");
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);

    luaL_openlib(L, 0, LuaActor_Methods, 0);
    luaL_openlib(L, "Actors", LuaActor_Funcs, 0);
}

This gives me the ability to do things such as local MyActor = Actors.Create()

I can't find any information about doing what I described in the 2nd paragraph. I also understand that I should let the garbage collector manage deletion of any C++ objects created through Lua which confuses me on how I should track my entities on the C++ side. I have an ActorHandler class which holds a std::vector of all my entities. This class loops through each entity every game tick calling their Update() functions, the entities are also accessed from other classes such as physics and networking via their unique entity id's to apply positional and collision updates. I am very confused on how I should keep track of entities on the C++ side which were created on the Lua side and still be able to call their Lua callback functions from within C++.

I've found out that you can create references to your Lua objects within C++ to quickly access the Lua object again but I can't find any articles describing how to advantageously use this feature.

Any help would be greatly appreciated and I can give more information if needed.

\$\endgroup\$
  • \$\begingroup\$ Not a real answer, but have you tried looking at existing implementations? For example LuaBridge? It might provide useful hits to how things can/should be done. \$\endgroup\$ – Martin Green Jul 20 '14 at 10:36
  • \$\begingroup\$ I was looking through LuaWrapper which didn't help for going from C++ back into lua. I'm looking at LuaBridge now, hopefully it can shed some light while waiting for more information here. Thank you. \$\endgroup\$ – KKlouzal Jul 20 '14 at 10:43
  • \$\begingroup\$ Not really an answer since it doesn't use Lua but since I had some time on my hands and I was curious about how it would look exactly in code, I tried to implement a part of your system using my scripting engine - SGScript (sgscript.org). You can download the code (precompiled Windows x86 binaries are included) here: sgscript.org/files/sgs-entities.zip P.S. Please let me know if there's anything important missing from the example or just anything else that you'd like to see there. I'm looking for as many use cases for testing as I can get my hands on. :) \$\endgroup\$ – snake5 Jul 20 '14 at 12:47
3
\$\begingroup\$

This is not exactly an answer - rather a suggestion. A lot of game developers are shifting from LUA (it is slow and it forces you to push data back and forth). For example Star Citizen is using Runtime compiled C++ (actually they use Kythera which runs RCC++). RCC++ lets you script in C++ and reload your modified code on the fly, which - I guess - would solve a lot of your issues. I however have no first hand experience with RCC++.

In my workplace, we are trying to keep LUA usage at minimum and we use our custom behavior tree implementation for most of the work (and we are getting some very nice performance results)

EDIT: To answer the comments, RCC++ is probably not a good solution if you want to support game mods. I reiterate that I have no first hand experience with RCC++. The main point is still that there are alternatives to LUA, which may be better for many use cases. Another example is Unity's support for C# as scripting language - it is often faster to execute than LUA and strong typing and existence of ready made tools let's you bring developer support to a next level (code completion, refactoring, ...).

\$\endgroup\$
  • \$\begingroup\$ Cool stuff - had never heard of that! \$\endgroup\$ – Babis Dec 12 '14 at 22:17
  • \$\begingroup\$ Sounds really interesting, but at the same time running native/compiled code written by the user sounds a bit like a security nightmare. Also sounds like players would need a compiler and header files on their gaming rig? Sounds a bit excessive and very confusing for someone to get started into modding. Mind expanding a bit on how (if) that's handled? \$\endgroup\$ – Mario Dec 13 '14 at 9:07
  • 1
    \$\begingroup\$ @Mario Not all script code is intended to be modified/accessible by end-users or modders, nor should all games be modded. It's perfectly fine to script things to ease development or accelerate iteration times, if your project needs such things. \$\endgroup\$ – Lars Viklund Dec 13 '14 at 12:09
  • \$\begingroup\$ @LarsViklund Okay, point taken. Could work for something like that or maybe even just for overrides/testing (later on getting "hardcoded"). \$\endgroup\$ – Mario Dec 13 '14 at 13:56
0
\$\begingroup\$

No matter how I structure the code I can't figure out a logic path that doesn't involve > creating global variables in my project so the Lua helper functions can access the core > of my engine.

That's basically inevitable, given the required signature of functions registered to the Lua API. Data can only get in to the scope of a free function body via globals or function arguments. What I do to get around this, or at least make myself feel better, is to use Lua upvalues, or the Lua registry, to store C-side data that I want to pass to my bound Lua functions.

That said, the rest of the question is too broad to answer, as I address in my article about binding Lua to C++. Bi-directional binding is very complex to do in any useful way. So, I wouldn't recommend reinventing the wheel. Let someone else play in the dirt.

From a functionality perspective, Luabind is pretty much the best, in my experience, but it comes at a hefty price of performance on all fronts. Slow compiles and slow bound code.

OOLua is also very good. Faster compiles than LuaBind, better performance, but it doesn't swing both ways, only C++ -> Lua.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.