As we discussed, we will describe the whole game in scripts, and the core functionality
we will define in the core. In this chapter we will be adding Lua to our application.
You do not need to download Lua itself - you’d better install it with your system’s
package manager (
apt or whatever your Linux uses,
brew for OSX…).
The only thing you need to download from Internet this time is Lua wrapper called luacppinterface. So go and get it from Github.
And unpack it… right to the
source directory of our project! That’s right! That’s
really small library so it will not pollute your project with tons of files.
Now, I mentioned dependency managers earlier. This is how we will handle them in our C++ application - we will simply put the sources of all the libraries we depend on, with the versions we depend on, right in our project. Given that, you may put Irrlicht there as well - you are free to do anything with our project!
To build our project we will need to change our
CMakeLists.txt file to fetch
our new dependency:
And here’s the thing: if you try to compile our project on another machine, you will not need to install any other libraries than Lua on that machine! That supposed to sound like “sweet, huh?”, except that one little “but…“… Bittersweet…
Back to our busines…
luacppinterface needs to be tweaked a bit to fit our project -
we will hack its
CMakeLists.txt file to make it depend on system Lua libraries.
Just make it look like this:
It barely differs from the original file, but it makes a compilation pleasant - you do not need to specify paths to Lua libs anymore!
Injecting some Lua
Our application now uses C++ code to place some 3D objects in a scene. Let’s move, say, sphere creation, to the script.
First of all, add
luacppinterface headers to our
Now let’s look at some of Irrlicht’ conventions:
- it uses
irr::video::IVideoDriverfor rendering operations
- it uses
irr::scene::ISceneManagerfor scene management
So why not to define a
ScriptManager to handle scripts? Our requirements
for this class (for now) are:
- it should load and evaluate scripts
- it should provide simple API to our scripts
Let’s get coding!
This is just a skeleton - we will fill it out in a minute. Just catching up:
- this class depends on
ISceneManagerto handle 3D objects and the scene
- it contains
Lua luaStatefield to store the current state of our script running
- it stores all the nodes as a
<string, ISceneNode*>map to allow access to our nodes from scripts
- it exposes three methods as an API to Lua scripts:
getNodePositionso we will be able to make some manipulations in our scripts
- it provides really short and simple interface to our C++ core:
The main principle, each and every programmer breaks every day is KISS (Keep It Stupidly Simple). And that principle should guide us through this whole tutorial to not overthink and override ourselves as well as the project we are making. That is why our APIs are that simple.
But let’s get back to our
ScriptManager. It shows how things will look like, but never
defines how they will actually work. So here are the key points to Lua API:
LuaTableis an array-like structure in Lua, representing both indexed as well as key-value arrays in Lua. This type is a way to pass variables between Lua script and C++ program. You may use both
table.Get<value_type>("key")methods to access its values.
To bind our
ScriptManagermethods to Lua functions, we need to use pointers to those functions. And as it is not that simple in usual C++, we will use C++11x lambdas:
- All the functions and variables you want to pass to Lua scripts should be global. And since
we have our pretty
luaStatemember, we may set global members through its methods:
- We will be using just a map of a Irrlicht’ nodes and its name to bypass those nodes between scripts and core:
Given those, we have our API and are able to create and run our first Lua script.
Add one in the
Note: paths in the script will be used by C++ core, relatively to the binary file, which is… generated by our C++ code! So all the paths in the scripts are just the same as they are in C++ core.
And add the
ScriptManager initialization code:
Now you may remove the code, creating sphere in the
main() function. And run the code.
You should see exactly the same picture as before:
Your task is: try to move all the other “factory” functions (creating cube, ninja,
circle animator for cube and fly animator for Ninja) to Lua script, adding API for them
We will now advance our script and add some convention to it. These will be our tasks for the rest of this chapter:
- move keyboard events handling to script
- create two function in script so we may call them by convention, not by configuration
The last phrase I took from Ember.js introduction. It says “prefer convention over configuration”, meaning we’d better call the functions of same name on different scripts, instead of setting somehow which function to call.
That is, we will define
handleFrame() function in our script, which will be called
onFrame event in our C++ core and the
main() function, which will be called right
after script has been loaded.
Moreover, we will define a global keyboard state table for each of scripts we load and will be updating it as user presses keys on his keyboard. And this variable will be shared with script, but as read-only one. So changes in that table will have no effect on the application itself.
Variables are added to a
GlobalEnvironment just as function do:
Lua-defined functions are found by their names and called with
Let’s add some simple interaction to our script now. I’ll help you a bit:
This is how nodes could be moved relatively to their current position in Irrlicht.
And here’s how our Lua script may look like now:
If you run our application now, you should be able to control sphere with w and s keys: