Last winter, I spent all my time developing a game engine in C++. One of the features I spent the most time implementing was Lua scripting support. In this post, I will walk you through the setup and obstacles of setting the scripting stuff up.

This post was originally posted to my old blog, but is now migrated here with some clean-up here and there. Without further ado, let’s get cracking!

Why Lua? Why not just C++?

At first you may ask: “Why should we use Lua (or any other scripting language in that metter)? Why not just, you know, make the whole game in C++?” The answer is, you absolutely can make the whole game in C++ (or any other language of your choice), but the thing is, when you’re making a large-ish game, the time you spend compiling and re-compiling the code after each adjustment starts to pile and and takes a lot out of your actual development time. Sure, there are some ways to mitigate this (e.g. compiling modules to DLLs and loading them dynamically during runtime), but it’s sub-optimal, at least for a hobby programmer.

With Lua and other scripting languages (or rather, interpreted languages), you don’t have to compile the code. You just write the scripts and launch your game. You can also reload scripts during runtime (automatically, if you wish) and see changes happen in real-time. It’s also somewhat faster to try things out in a scripting language and revert back if you didn’t like the results. Adjusting things like GUI element positions becomes a lot faster as you can see the results immediately. Also, since Lua and most other similar languages are dynamically typed, your code is quite often simpler than C++. In Lua, there’s just a single data structure called “table”, which is sufficient enough for just about any scenario - it can be used as a key-value dictionary, as an array… You can even implement object-oriented programming with tables when you use them as prototypes (which is also a common way of doing OOP in other languages such as Javascript).

Foundations in C++

When creating a game using C++ and Lua, you should create a really solid foundation in C++ first. Just about everything that is not game-specific should be done in C++. These include window management, rendering, physics simulation, audio handling, asset management and so on. Why? Mainly for performance reasons, but there’s also the thing of Lua not being able to do all that stuff for you. Lua itself is just the language and a couple of utility libraries - nothing more. It’s also an interpreted language and (in common cases) it runs a tad slower than native code.

In my case, I wrote the game engine using a set of open source libraries such as GLFW, GLM and Tecs. For those who are not familiar with these, let’s do a quick recap: GLFW is an OpenGL utility library for managing OpenGL contexts, creating windows and handling user input. GLM is a header-only math library that works wonderfully with OpenGL. Last, Tecs in an entity-component-system framework that lets you create light-weight entities and attach components to them while systems manipulate the components.

In addition to the engine, I wrote a game-specific layer in C++ as well. This layer includes the main entry point for the game, creates all the required systems and providers some game-specific helper functions. However, this is pretty much up to you if you wish to do so; the systems creationg could easily be lifted from the C++ code and done in Lua.

Exposure

In order to use all the underlying C++ stuff, it has to be “exposed” to Lua first. This can be done using the interface provided by Lua, but there’s also a number of libraries available that make it a lot more simpler. For my purposes, I chose LuaBridge for its simple usage and sufficient feature set. Bear in mind that while the features were more than enough for me, you should do a comparison between the libraries before getting your hands dirty with LuaBridge or any alternatives. One thing that’s almost an issue for me is that C++ classes cannot be inherited in Lua using LuaBridge. It could make some situations easier, but I personally think that it keeps the Lua scripts a bit cleaner.

Anyhoo, no matter which utility library you choose, you have to expose all the stuff you want to use. This means that Lua has to be aware of the things that are available in C++ and how to use that stuff. In my case, I wrote a helper class called LuaExposer that exposes all the stuff that is needed. This exposure code looks like this:

// This exposes the class 'Tecs::World' to Lua (L is the lua_State*)
// and two of its functions called "createEntity" and "deleteEntity"
getGlobalNamespace(L)
    .beginClass<Tecs::World>("World")
    .addFunction("createEntity", &Tecs::World::createEntity)
    .addFunction("deleteEntity", &Tecs::World::deleteEntity)
    .endClass();

// This exposes the class 'Tecs::Entity' to Lua
getGlobalNamespace(L)
    .beginClass<Tecs::Entity>("Entity")
    .addFunction("getID", &Tecs::Entity::getId)
    .endClass();

This all is rather simple: you can create classes, give them names, and map functions and data to class members. You can also expose constructors so you can create new C++ objects in Lua scripts. The great thing about this is that you can have “Lua lifetime” for your C++ objects - they get automatically garbage-collected when they get out of scope.

Now, the engine exposes a nautical ton of stuff like in the code above. There’s Tecs, there’s lots of components like PhysicsBody, Sprite and SpriteAnimation. GLM is also exposed so that the math structs can be used in Lua. I wanted to keep some parts of the code “C++ only” so I did not expose the constructor or Tecs::World. Instead, the one and only World objects is created in the game-specific C++ layer. In the game-specific layer, I added a number of helper functions to add components to entities like this:

4
5
6
7
8
9
10
11
12
13
14
// Adds a sprite component to the given entity.
Sprite& LuaHelper::addSprite(Entity& entity, const std::string& spritename)
{
    return entity.addComponent<Sprite>(AssetManager::loadTexture(spritename));
}
 
// Exposes the helper functions
void LuaHelper::expose(lua_State* L)
{
    getGlobalNamespace(L)
        .beginClass<LuaHelper>("Helper")
        .addStaticFunction("addSprite", &LuaHelper::addSprite)
        .endClass();
}

Great! Now we have stuff exposed to Lua and some helper stuff to make life a bit easier.

Conclusion

Implementing a Lua scripting support to your C++ game code is a rather simple task. When you want to use any stuff written in C++ in your Lua scripts, you first have to “expose” them to Lua. After that, scripting is enabled and can speed up your development tremendously.

In the second part, I’ll show you how to get cracking with the actual scripts and make your game a bit more lively.

If you found this article helpful and want to see more stuff like this, buy me a cup of coffee - it fuels my writing and makes this all possible. And you know, I’m broke.