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.
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.
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 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
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:
Great! Now we have stuff exposed to Lua and some helper stuff to make life a bit easier.
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.