loxel is a voxel engine that is written in C++ but can be scripted in LUA. This allows an entire game to be completely made and developed without ever having to see or recompile the engine code.
July 1st, 2018
In order to start developing my Lua scripting based game engine, I had to learn how Lua's C API worked. I started by writing a simple Lua script that would have all the basic functions I would like to be able to interface with in C.
function place()
print("Placing cobble")
end
function updateGrass()
print ("Grass is green")
end
create_block({name = "Cobblestone",
id = 12,
durability = 43,
model = "scaled_cobble.svg",
onclick = place})
create_block({name = "Grass",
id = 16,
durability = 35,
model = "grass.png",
onclick = updateGrass})
This is a basic example of creating two blocks for the engine to use, allowing for Lua functions to be called when an action happens in the engine. This would test a variety of features the Lua integration engine would have to test:
The first thing I wanted to implement was the C function create_block()
which
would take in a Lua table as an argument.
In order to take a Lua object in as a parameter, the function definition should look like so:
static int create_block(lua_State *L){}
In order to navigate through the table, we have to use a basic for loop that will pop each element off the top of the list and get each pairing of name and value of each table element.
for (lua_pushnil(L); lua_next(L, -2) != 0; lua_pop(L, 1)) {
std::string name = lua_tostring(L, -2);
int type = lua_type(L, -1);
}
This loop works in the following:
After loading the table into L
the stack will look like the following:
-1: table level
In order to iterate through the table, we must move the stack upwards. To do this we must call:
lua_pushnil(L)
This will set the stack to:
-1: key (nil)
-2: table "level"
Now the table is at level -2. In order to put the keys and values on the stack
calling lua_next
with the index -2 is what grabs the stack from the -2 position
i.e: the table.
-1: value
-2: key
-3: table "level"
This is why the key is pulled from stack position -2 and the value is pulled from stack position -1.
The only data known about each table entry is its type and its key. In order to parse the value of each entry, its type must be known. This will be done using a switch statement.
std::string tmp_str;
double tmp_num;
bool tmp_bol;
int tmp_fnc;
switch(type) {
case LUA_TSTRING:
tmp_str = lua_tostring(L, -1);
break;
case LUA_TBOOLEAN:
tmp_bol = lua_toboolean(L, -1);
break;
case LUA_TNUMBER:
tmp_num = lua_tonumber(L, -1);
break;
case LUA_TFUNCTION:
tmp_fnc = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pushvalue(L, 1);
break;
default:
std::cout << lua_typename(L, t);
}
There can certainly be better ways to store these Lua variables, e.g: Lua variable class. This class could have the ability to store the variable key and the variable value, no matter the type.
In this case, the variables won't be stored for later but instead will be placed
in the Block
class so it's not as crucial to have them stored in a special class.
In order to parse each variable field and set the corresponding class data, a basic statement set can be used.
if (name == "name") {
// set name of new block
} else if (name == "id") {
// set id of the block
} // go until all entries are done
As seen in the Lua script, each block as an onclick
function.
That function corresponds to a Lua function that will get run when the
block is clicked on.
The block click will get triggered in the C++ part of the engine, so the engine
will have to call the Lua function. The onclick
function is stored as an
integer in C++. In order to call this function the following must be used:
lua_rawgeti(L, LUA_REGISTRYINDEX, onclick);
lua_pcall(L, 0, 0, 0);
The Lua C API doesn't seem too difficult, but implementing C++ classes in Lua might be rather difficult, so I may have to use a library such as LuaBridge.
I'm also not entirely sure the best architecture for my Lua functions. Each "mod" for the engine should be used to create a "section" of the game. e.g: Blocks, Entities, Items, etc...
I'm still in the stage of determining which files should be run first and what Lua helper functions should be exposed to the "mods" being run.