bitcicle


loxel

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.

Click here to see the GitHub repo

July 1st, 2018

Exploring Lua's C library

Creating the Lua script

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:

Creating the C code

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.

Parsing Lua Table

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

Calling Lua functions from C

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);

Final thoughts

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.