This article is a translation of my
tutorial , which I originally wrote in English. However, this translation contains additions and improvements over the original.
Tutorial does not require knowledge of Lua, but C ++ needs to be known at a level slightly above basic, but there is no complex code here.
I once wrote
an article about using Lua with C ++ using the Lua C API . While writing a simple wrapper for Lua that supports simple variables and functions is not difficult, writing a wrapper that will support more complex things (functions, classes, exceptions, namespaces) is already difficult.
Quite a lot of vrappers for using Lua and C ++ are written. Many of them can be found
here .
I tested many of them, and most of all I liked LuaBridge. There is a lot in LuaBridge: a convenient interface, exceptions, namespaces and so much more.
But let's start in order, why bother to use Lua with C ++?
Why use Lua?
')
Configuration files Getting rid of constants, magic numbers and some define'ov
These things can be done using simple text files, but they are not so easy to handle. Lua allows you to use tables, mathematical expressions, comments, conditions, system functions, etc. This can be very useful for configuration files.
For example, you can store data in this form:
window = { title = "Test project", width = 800, height = 600 }
You can get the system variables:
homeDir = os.getenv("HOME")
You can use mathematical expressions to set parameters:
someVariable = 2 * math.pi
Scripts, plugins, functionality extension
C ++ can call Lua functions, and Lua can call C ++ functions. This is a very powerful feature that allows you to put some of the code into scripts or allow users to write their own functions that extend the functionality of the program. I use Lua functions for various triggers in the game that I am developing. This allows me to add new triggers without recompilating and creating new functions and classes in C ++. Very comfortably.
A little bit about Lua. Lua is a language with a MIT license that allows you to use it in both non-commercial and commercial applications. Lua is written in C, so Lua works on most operating systems, which allows using Lua in cross-platform applications without problems.
Installing Lua and LuaBridge
So let's get started. Start by downloading
Lua and
LuaBridgeAdd the include Lua folder and LuaBridge itself in the Include Directories of your project
Also add lua52.lib to the list of link libraries.
Create a
script.lua file with the following content:
Add
main.cpp (this code is just to check that everything works, the explanation will be slightly lower):
Compile and run the program. You should see the following:
LuaBridge works!
And here's our number: 42Note : if the program is not compiled and the compiler complains about the error
“error C2065: 'lua_State': undeclared identifier” in the file
LuaHelpers.h , then you need to do the following:
1) Add these lines to the beginning of the LuaHelpers.h file.
extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" }
2) Change the 460th line to
Stack.h with this:
lua_pushstring (L, str.c_str(), str.size());
On this:
lua_pushlstring (L, str.c_str(), str.size());
Done!
And now more about how the code works.
We include all the necessary headers:
#include <LuaBridge.h> #include <iostream> extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" }
All LuaBridge functions and classes are placed in the namespace luabridge, and in order not to write “luabridge” many times, I use this construct (although it is better to put it in those places where LuaBridge itself is used)
using namespace luabridge;
Create lua_State
lua_State* L = luaL_newstate();
Open our script. For each script, you do not need to create a new lua_State, you can use one lua_State for multiple scripts. Thus it is necessary to take into account the collision of variables in the global namespace. If variables with the same name are declared in script1.lua and script2.lua, problems may arise
luaL_dofile(L, "script.lua");
Open the main Lua libraries (io, math, etc.) and call the main part of the script (i.e. if actions in the global namespace were specified in the script, they will be executed)
luaL_openlibs(L); lua_pcall(L, 0, 0, 0);
Create a LuaRef object that can store everything that the Lua variable can store: int, float, bool, string, table, etc.
LuaRef s = getGlobal(L, "testString"); LuaRef n = getGlobal(L, "number");
Converting LuaRef to C ++ types is easy:
std::string luaString = s.cast<std::string>(); int answer = n.cast<int>();
Error checking and correction
But some things can go wrong, and it is worth checking and handling errors. Consider the most important and common errors.
What if the Lua script is not found?
if (luaL_loadfile(L, filename.c_str()) || lua_pcall(L, 0, 0, 0)) { ...
What if a variable is not found?
The variable may not be declared, or its value is nil. This is easy to check with the isNil () function.
if (s.isNil()) { std::cout << "Variable not found!" << std::endl; }
The variable is not of the type we expect to receive.
For example, it is expected that the variable has the type string, then you can do such a check before you make a caste:
if(s.isString()) { luaString = s.cast<std::string>(); }
Tables
Tables are not just arrays: tables are a wonderful data structure that allows you to store Lua variables of any type in them, other tables and assign keys of different types to values ​​and variables. Tables allow you to submit and receive configuration files in a beautiful and easy to read form.
Create a
script.lua with this content:
window = { title = "Window v.0.1", width = 400, height = 500 }
C ++ code to retrieve data from this script:
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 on the screen:
Window v.0.1
width = 400
height = 500As you can see, you can get the various elements of the table using the operator []. You can write shorter:
int width = t["width"].cast<int>();
You can also change the contents of the table:
t["width"] = 300
This does not change the value in the script, but only the value that is contained during the execution of the program. Those. the following happens:
int width = t["width"].cast<int>();
To save the value, you need to use table serialization, but this tutorial is not about that.
Let now the table looks like this:
window = { title = "Window v.0.1", size = { w = 400, h = 500 } }
How can I get the value of
window.size.w ?
Like this:
LuaRef t = getGlobal(L, "window"); LuaRef size = t["size"]; LuaRef w = size["w"]; int width = w.cast<int>();
Functions
Let's write a simple function in C ++
void printMessage(const std::string& s) { std::cout << s << std::endl; }
And write this in the Lua script:
printMessage("You can call C++ functions from Lua!")
Then we register the function in C ++.
getGlobalNamespace(L). addFunction("printMessage", printMessage);
Note 1 : this must be done before the “luaL_dofile” call, otherwise Lua will try to call an undeclared function
Note 2 : Functions in C ++ and Lua may have different names.
This code registered the function in the global namespace Lua. To register it, for example, in the namespace "game", you need to write the following code:
getGlobalNamespace(L). beginNamespace("game") .addFunction("printMessage", printMessage) .endNamespace();
Then the printMessage function in scripts will need to be called in this way:
game.printMessage("You can call C++ functions from Lua!")
Namespaces in Lua have nothing to do with C ++ namespaces. They are rather used for logical combination and convenience.
Now call the Lua function from C ++.
You should see the following:
You can still call C ++ functions from Lua functions!
Result: 9Isn't it great? You do not need to specify LuaBridge how many and what arguments the function has, and what values ​​it returns.
But there is one limitation: a single Lua function cannot have more than 8 arguments. But this restriction is easy to get around by passing the table as an argument.
If you pass more arguments to the function than are required, LuaBridge silently ignores them. However, if something goes wrong, LuaBridge will throw a LuaException exception. Do not forget to catch it! Therefore it is recommended to surround the code with try / catch blocks.
Here is the complete code of the function example:
What? Is there something else?
Yes. There are some more wonderful things that I will write about in the next parts of the tutorial: classes, creating objects, the lifetime of objects ... A lot of things!
I also recommend reading
this dev log , in which I talked about how to use scripts in my game, practical examples are always useful.