Why use Lua?

Using scripts with C++ is great. They provide lots of flexibility and free you from using those .txt files for your configuration files and also let you write complex functions. You can modify your scripts without recompiling. This is really important because you get distracted less often. Even a short compilation can break your concentration.

And you can even design your system that even those people who don’t know how to code can create new scripts or modify existing object behavior without modifying your source code! If you want to know more reasons of what makes Lua great read the beginning of this article.

I wrote about how you can develop your own binding library in previous chapters. But this library is very basic, it doesn’t have the power some other libraries have. It’s really hard to write your own binding because it usually involves lots of templates magic and meta-programming.

I’ve tested lots of libraries and found LuaBridge to be the most awesome. It has MIT license, doesn’t have any dependencies(like Boost which some libraries use) and it doesn’t require C++11. You don’t need to build it. Just drop LuaBridge folder in your project folder, include one header and you’re ready to go!

Minimal project setup

Download LuaBridge from the repository

You also need to download Lua.

Add Lua include/ directory and LuaBridge to include directories and link lua52.lib to your project.

Create script.lua with this code in it:

testString = "LuaBridge works!" number = 42

Add main.cpp to your project:

// main.cpp #include <LuaBridge.h> #include <iostream> extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } using namespace luabridge; int main() { lua_State* L = luaL_newstate(); luaL_dofile(L, "script.lua"); luaL_openlibs(L); lua_pcall(L, 0, 0, 0); LuaRef s = getGlobal(L, "testString"); LuaRef n = getGlobal(L, "number"); std::string luaString = s.cast<std::string>(); int answer = n.cast<int>(); std::cout << luaString << std::endl; std::cout << "And here's our number:" << answer << std::endl; }

Compile and run this program. You should see the following output

LuaBridge works! And here's our number:42

Note: if your program doesn’t compile and you get something like “error C2065: ‘lua_State’ : undeclared identifier” in luahelpers.h, you need to do the following

1) Add this to the beginning of LuaHelpers.h

extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" }

2) Change 460th line of Stack.h from this

lua_pushstring (L, str.c_str(), str.size());

to this

lua_pushlstring (L, str.c_str(), str.size());

Okay, here’s how the code works.

#include <LuaBridge.h> #include <iostream> extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" }

We include all important headers first. Nothing special here.

using namespace luabridge;

LuaBridge has its own namespace (you should probably put it only where you use luabridge, though)

lua_State* L = luaL_newstate();

We create lua_State with this line.

luaL_dofile(L, "script.lua");

This line opens our script.

You don’t need to create new lua_State each time you open new file. You can load scripts using just one state.

Then we load Lua’s libraries and call the script (you can find more explanation in the first part):

luaL_openlibs(L); lua_pcall(L, 0, 0, 0);

We can then create LuaRef object which can hold everything Lua variable can: int, float, bool, string, table, etc.

LuaRef s = getGlobal(L, "testString"); LuaRef n = getGlobal(L, "number");

Then we can convert LuaRef to C++ string like this:

std::string luaString = s.cast<std::string>(); int answer = n.cast<int>();

Error checking

But some things can go wrong and you should handle errors gracefully. Let’s check some of the most important errors

What if Lua script is not found?

if (luaL_loadfile(L, filename.c_str()) || lua_pcall(L, 0, 0, 0)) { ... // script is not loaded }

What if variable is not found?

You can easily check if the variable you want to access is nil or not

if (s.isNil()) { std::cout << "Variable not found!" << std::endl; }

Variable is not of the type we expect to get

If you want to be sure that the variable is string, for example, you can check this:

if(s.isString()) { luaString = s.cast<std::string>(); }

Tables

Tables are not just arrays: they’re amazing structures that can hold every Lua type and even tables!

Let’s write this in script.lua

window = { title = "Window v.0.1", width = 400, height = 500 }

And this code in C++:

LuaRef t = getGlobal(L, "window"); LuaRef title = t["title"]; LuaRef w = t["width"]; LuaRef h = t["height"]; std::string titleString = title.cast<std::string>(); int width = w.cast<int>(); int height = h.cast<int>(); std::cout << titleString << std::endl; std::cout << "width = " << width << std::endl; std::cout << "height = " << height << std::endl;

You should see the following output:

Window v.0.1 width = 400 height = 500

As you can see we can access table elements using operator []. We can even write shorter:

int width = t["width"].cast<int>();

You can also change table contents like this:

t["width"] = 300

It doesn’t change the script, but if you try to get this variable again it will have another value:

int width = t["width"].cast<int>(); // 400 t["width"] = 300 width = t["width"].cast<int>(); // 300

Suppose you have a table like this:

window = { title = "Window v.0.1", size = { w = 400, h = 500 } }

How can we get value of window.size.w?

Here’s how:

LuaRef t = getGlobal(L, "window"); LuaRef size = t["size"]; LuaRef w = size["w"]; int width = w.cast<int>();

(This is the shortest way of doing it I’ve found. If someone knows how to do this better, shoot me an e-mail or write a comment, it will be very appreciated)

You can go to the first part of my tutorial and find my implementation more convenient for getting data from tables because you can do something like this:

int width = script->get<int>("window.size.w");

Functions

Let’s write a simple function in C++

void printMessage(const std::string& s) { std::cout << s << std::endl; }

Write this in script.lua

printMessage("You can call C++ functions from Lua!")

Now we need to register the function in C++:

getGlobalNamespace(L). addFunction("printMessage", printMessage);

Note: You must do this before loading the script using luaL_dofile.

Note: C++ and Lua functions can have different names

You just registered printMessage function. If you want to register it in some namespace you can do something like this:

getGlobalNamespace(L). beginNamespace("game") .addFunction("printMessage", printMessage) .endNamespace();

You then need to call it from Lua like this:

game.printMessage("You can call C++ functions from Lua!")

Lua namespaces have nothing to do with C++ namespaces. They’re used for logical grouping and convenience mostly.

Let’s now call Lua function from C++

-- script.lua sumNumbers = function(a,b) printMessage("You can still call C++ functions from Lua functions!") return a + b end

// main.cpp LuaRef sumNumbers = getGlobal(L, "sumNumbers"); int result = sumNumbers(5, 4); std::cout << "Result:" << result << std::endl;

You should see the following output:

You can still call C++ functions from Lua functions! Result:9

Well, isn’t that cool? You don’t need to tell LuaBridge function arguments and what you expect to get returned.

There are some thing to consider, though.

You can’t have more that 8 arguments for one function.

If you pass more arguments than function expects, LuaBridge will silently ignore them

However, if something goes wrong LuaBridge will throw a LuaException. Be sure to catch and handle it!

Here’s the full code of the functions example:

-- script.lua printMessage("You can call C++ functions from Lua!") sumNumbers = function(a,b) printMessage("You can still call C++ functions from Lua functions!") return a + b end

// main.cpp #include <LuaBridge.h> #include <iostream> extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } using namespace luabridge; void printMessage(const std::string& s) { std::cout << s << std::endl; } int main() { lua_State* L = luaL_newstate(); luaL_openlibs(L); getGlobalNamespace(L).addFunction("printMessage", printMessage); luaL_dofile(L, "script.lua"); lua_pcall(L, 0, 0, 0); LuaRef sumNumbers = getGlobal(L, "sumNumbers"); int result = sumNumbers(5, 4); std::cout << "Result:" << result << std::endl; system("pause"); }

What? There’s even more?

Yes. There are some neat things which I’ll cover in next part: classes, object creation, object lifetime… That’s a lot of stuff!

I also recommend you to check out my re:Creation dev logs which show how I use Lua scripts in the development. Lua makes my life as a gamedev much easier. Try it, maybe it will make yours easier too?

Week #0: Preparation

Week #1: Things are getting bigger

Follow me on twitter to get updates! @EliasDaler