I have been working on SDL2 2D Game Engine for several years now. Just ditched inheritance approach to define game entities with composition approach where I have Entity class and it has vector of Component classes and recently I got into lua, because I want to define entities using Lua table with optional callback functions.
Working parts
I am using Lua5.4 and C API to bind some engine methods and Entity class to Lua. I use XML file to load list of scripts for each Entity defined by Lua:
<script name="player" filename="scripts/player.lua" type="entity"/>
Then Entity gets created in C++ with ScriptComponent which holds a pointer to Lua state. Lua file gets loaded at this point and state is not closed unless Entity is destroyed. player.lua script might look something like this:
-- Entity
player = {
-- Entity components
transform = {
X = 100,
Y = 250
},
physics = {
mass = 1.0,
friction = 0.2
},
sprite = {
id = "player",
animation = {},
width = 48,
height = 48
},
collider = {
type = "player",
onCollide = function(this, second)
print("Lua: onCollide() listener called!")
end
},
HP = 100
}
Using this I managed to create each Component class using Lua C API with no issues. Also while loading this I detect and set "onCollide" function in Lua.
Also I have managed to register some Engine functions so I can call them to lua:
playSound("jump")
in C++:
static int lua_playSound(lua_State *L) {
std::string soundID = (std::string)lua_tostring(L, 1);
TheSoundManager::Instance()->playSound(soundID, 0);
return 0;
}
Also have created meta table for Entity class with __index
and __gc
metamethods and it works if I call these methods with Entity created in Lua outside of player table, such as:
-- This goes in player.lua script after the main table
testEntity = Entity.create() -- works fine, but entity is created in Lua
testEntity:move(400, 400)
testEntity:scale(2, 2)
testEntity:addSprite("slime", "assets/sprite/slime.png", 32, 32)
Problem
Now whenever collision happens and Entity has ScriptComponent, it correctly calls onCollide
method in Lua. Even playSound
method inside triggers correctly. Problem is when I try to manipulate Entities which are passed as this
and seconds
arguments to onCollide
onCollide = function(this, second)
print(type(this)) -- userdata
print(type(second)) --userdata
--Entity.scale(this, 10, 10) --segfault
--this:scale(10, 10) --segfault
playSound("jump") -- works fine, does not need any metatables
end
This is how I am calling onCollide
method and passing existing C++ object to Lua:
// This is found in a method which belongs to ScriptComponent class, it holds lua state
// owner is Entity*, all Components have this
// second is also Entity*
if (lua_isfunction(state, -1)) {
void* self = (Entity*)lua_newuserdata(state, sizeof(Entity));
self = owner;
luaL_getmetatable(state, "EntityMetaTable");
assert(lua_isuserdata(state, -2));
assert(lua_istable(state, -1));
lua_setmetatable(state, -2);
assert(lua_isuserdata(state, -1));
void* second = (Entity*)lua_newuserdata(state, sizeof(Entity));
second = entity;
luaL_getmetatable(state, "EntityMetaTable");
lua_setmetatable(state, -2);
// Code always reaches cout statement below unless I try to manipulate Entity
// objects passed to Lua in Lua
if (luaOk(state, lua_pcall(state, 2, 0, 0))) {
std::cout << "onCollide() Called sucessfully!!!" << std::endl;
}
script->clean(); // Cleans lua stack
return;
}
So basically I have managed to load data from table, bind and use some methods from C++ engine and mapped Entity class using metatable and __index and __gc meta methods which work fine for objects created in Lua but not when I try to pass existing C++ object and set existing meta table.
I still think I will be alright without using any Lua binders, because all I wanted here is to load data for all Components which works fine and script some behaviour based on events which also almost works except for not being able to correctly pass existing C++ object to onCollide method. Thank you for your help!