The last time we looked at how to enabled Lua scripting in your C++ code. This time, we take a step forward and actually write something with Lua!

Hello? Lua?

So, in the game-specific layer, I have three methods that get called from the engine: create(), update() and draw(). In the create() method, all the systems are created and the start-up script is loaded and called. In update() the systems are updated, and in the draw() function, all the “passive” rendering systems are updated. All in all it looks like this:

// Initializes the systems and loads the starting script
void GameStage::create()
{
    // Add an "action system"
    m_world.addSystem<ActionSystem>();
    // Add a physics system
    m_world.addSystem<Physics>();
    // Add a sprite drawing system
    m_world.addSystem<SpriteSystem>();
 
    // Load the game scene from script
    loadFromScript();
}
 
void GameStage::loadFromScript()
{
    // Check if there's an existing state and close it
    if(L != nullptr)
    {
        lua_close(L);
        L = nullptr;
    }
 
    // Create a new lua state
    L = luaL_newstate();
    luaL_openlibs(L);
 
    // Expose all the stuff neede
    expose();
 
    // Push the "World" and "MainCamera" as global variables
    push(L, &m_world);
    lua_setglobal(L, "World");
    push(L, &m_camera);
    lua_setglobal(L, "MainCamera");
 
    // Load the script
    luaL_dofile(L, "scripts/stage.lua");
 
    // Call the "create" method in the script
    luabridge::getGlobal(L, "create")();
}
 
void GameStage::update(float delta)
{
    // Update the systems
    m_world.process(delta);
}
 
void GameStage::draw()
{
    // Draw all the sprites
    m_world.getSystem<SpriteSystem>().process();
}

I’ve taken some of the code out for clarity’s sake, but the basic idea is still there. The Lua portion can be updated by calling the loadFromScript() method whenever we want, for example when the user presses F3 or when any of the scripts has been modified. Nifty!

So, that’s pretty much what’s going on in C++ to get things rolling. Let’s take a look at Lua now.

Finally, some scripting!

First off, let’s take a look at the stage.lua script we loaded in the previous snippet:

-- stage.lua
 
-- Some basic stuff about this game stage
stage = {
    levelFilename = "testlevel", -- name of the level file
    stageCreated = false         -- flag to indicate if stage is created
}
 
-- Main entry point, called from C++
function create()
    -- Check if the "World" is exposed
    if(not World) then
        print("No world found!")
        return
    end
 
    -- Load the level
    loadLevelFromFile(stage.levelFilename)
 
    -- Set the camera position
    MainCamera:setPosition(120.0, 400.0, 800.0)
 
    -- Stage created, yay!
    stage.stageCreated = true
end

This script should be pretty self-explanatory, but let’s go through it anyway. First, we define the stage table and set a couple of properties there. This could also be dynamically loaded from different files that denote different stage set-ups. Then, we have the create() method that gets called from C++. This method loads the level from a file, sets the camera position and marks the stage as successfully created. Great! Although… Nothing happens yet. Let’s look at the loadLevelFromFile() function a bit closer:

-- Loads a level from a Tiled map file
function loadLevelFromFile(filename)
    -- Check that the Tiled map file exists
    local lvlFile = "assets/" .. filename .. ".tmx"
    local f = io.open(lvlFile, "r")
    if(f == nil) then
        print("Level file " .. filename .. " not found!")
        return
    end
 
    -- Call C++ helper function to load the level
    TiledMap = Game.Helper.loadLevel(World, lvlFile, "TestLevel")
 
    -- Process the tiled map (place objects etc.)
    processTiledMap(TiledMap)
end

Here we load a Tiled map from a file. The actual loading code is in C++, but it could just as well be done in Lua - that’s up to you. If the file exists, the map is loaded and passed on to a new function called processTiledMap(). This function is responsible for looking through the objects in the map and creating them. This is where the magic starts to really happen!

Dynamic stuff

The main thing I wanted to incorporate was dynamic object creation. I want to create Tiled maps, place objects here and there and not really care whether or not I have already implemented them in code. When I design the levels, I want to focus on that and not care about the code - or worse, jump back and forth between coding and level creation. So, let’s take a look at the processTiledMap() function:

-- Processes a Tiled map
function processTiledMap(Map)
    -- Get the number of layers in the map
    NumLayers = Map:getLayerCount()

    -- Loop through the layers
    for I = 1, NumLayers do
        -- Get the layer (I - 1) (C++ indexes from 0 onwards)
        MapLayer = Map:getLayer(I - 1)
        
        -- If the map layer is called "Spawns",
        -- call the processSpawns() function on it
        if(MapLayer:getName() == "Spawns") then
            processSpawns(MapLayer)
        end
    end
end

Looks rather simple, doesn’t it? We go through the map layers, and if the map layer is called “Spawns”, we process it further. At the moment, this is the only layer we’re interested of. Moving onward, let’s look at the processSpawns() function:

-- A table of game objects
EntityMap = {}

-- Processes the "Spawns" layer
function processSpawns(SpawnLayer)
    -- Get the number of objects in the layer
    ObjectCount = SpawnLayer:getObjectCount()

    -- Loop through the objects
    for I = 1, ObjectCount do
        -- Get the next map object
        MapObj = SpawnLayer:getMapObject(I - 1)
  
        -- Get the object name and turn it to lowercase
        ObjName = MapObj:getName():lower()

        -- Check if we have a script for the object
        local f = io.open("scripts/" .. ObjName .. ".lua", "r")
        if(f == nil) then
            print("Skipping a spawn: did not find module for " .. ObjName)
            return
        end

        -- Load the script and create a new game object
        ObjModule = require('scripts/' .. ObjName)
        ObjInstance = ObjModule:new(MapObj)

        -- Store the entities by name
        EntityMap[ObjName] = ObjInstance
    end
end

What happens here? We go through the map objects and check if we have a script with the same name as the object. If we have such a script, we load it with require() and call new() method on it (which acts as a constructor). For example, if we have an objects named “Torch” in our map, we check if there’s a file called “torch.lua” in the scripts folder. If there is, we construct a new Torch game objects by calling the new() function on the script. By using require() we ensure that each item module is loaded only once and none of the scripts are loaded that we do not need.

Next, let’s look at the Torch.lua file to see how we construct the item in the end:

-- A table of game objects
EntityMap = {}

-- Processes the "Spawns" layer
function processSpawns(SpawnLayer)
    -- Get the number of objects in the layer
    ObjectCount = SpawnLayer:getObjectCount()

    -- Loop through the objects
    for I = 1, ObjectCount do
        -- Get the next map object
        MapObj = SpawnLayer:getMapObject(I - 1)
  
        -- Get the object name and turn it to lowercase
        ObjName = MapObj:getName():lower()

        -- Check if we have a script for the object
        local f = io.open("scripts/" .. ObjName .. ".lua", "r")
        if(f == nil) then
            print("Skipping a spawn: did not find module for " .. ObjName)
            return
        end

        -- Load the script and create a new game object
        ObjModule = require('scripts/' .. ObjName)
        ObjInstance = ObjModule:new(MapObj)

        -- Store the entities by name
        EntityMap[ObjName] = ObjInstance
    end
end

With the help of underlying C++ and its helper methods, constructing a new Torch is rather simple. We create a new entity, add a physics body sensor, create a sprite and set the position of the torch to where the map objects says it should be.

Conclusion

Using Lua along with C++ is rather simple and provides a nice set of dynamic functionality that’s missing from C++. Creating the underlying engine and bindings can be rather tedious, but it will definitely pay off in the end.

In upcoming parts, I’ll show some more advanced things I’ve used throughout my set-up. Until then, why don’t you buy me a cup of coffee? :)