Basic scipting

In tutorial 1.4 you've seen how to control application through user input. It is not the only way of controlling your apps though. In the last few years a lot of attention received another one - scipting. Scripting is based on the concept of data driven programming ie. logic is separated from data. In other words, description of weapons should not be hardcoded in game sources but preferrably stored in some text file. This way changing weapon characteristics doesn't require program compilation and so time of the programmers could be saved and yet designers have more freedom and control of the game. However, this description is rather outdated regarding modern scripts. More often than not scripts are nowadays used to control games logic eg. behaviour of NPCs, dialogues, description of special effects (all shaders could be thought as some very specific scripts).

Even though scripting was not supported in the very first release of nGENE, it is now. In this tutorial we will see how to use its scipting capabilities on the example of Lua. However, concepts behind this language won't be covered, so for a thorough understanding it is advised to visit Lua homepage.

In this application we will control the camera position using scripts. This application will be build up upon code from tutorial 1.3 so be sure to check it before going any further.

We will start with creating a simple script. Create file simple_script.lua containing following lua script code:


now = getEngine():getTimer():getMilliseconds()
camera = getEngine():getActiveCamera()

angle = 0.0
axisX = Vector3(1.0, 0.0, 0.0)
axisY = Vector3(0.0, 1.0, 0.0)

quatX = Quaternion(axisX, 0.3)

while(1) do
    diff = getEngine():getTimer():getMilliseconds() - now
    angle = angle + diff * 0.001 * math.pi
    quatY = Quaternion(axisY, angle)
    quatY = quatY * quatX



What this script does is basically printing hello message and rotating a camera around specified point. Note infinite while loop and nGENE.WaitFrame(1) function call. Scripts in nGENE work in a way similar to threads. You can pause and resume them at any time. However lua threads differs from real system threads in that if you don't yield them, they will halt application execution until they are finished running. So to give application opportunity to render geometry, update physics and so on you have to manually inform script to pause and for that you can use one of the following functions:
  • nGENE.WaitFrame() - orders script to wait for a specified number of frames
  • nGENE.WaitMilis() - orders script to wait for a specified number of miliseconds
  • nGENE.WaitTime() - orders script to wait until given timestamp (provided in miliseconds)

Another important thing is call to hello function at the very beginning of the script. This call invokes hello() function defined in our .cpp source code! We will see shortly how to make this function available in scripts.

After certain number of frames or miliseconds pass, the script will be automatically resumed by nGENE Tech.

Also it is important to know that you can use only two kind of functions in your lua scripts:

  • ones exposed through the API and related to nGENE code (like hello() or getEngine())
  • ones defined in your lua scripts

Now to the C++ app. First we have to add this line:
to C/C++ -> General -> Additional Include Directories project setting,
to C/C++ -> Preprocessor -> Preprocessor Definitions and this one:
lua51.lib luabind.lib
to Linker -> Input -> Additional Dependencies one. Of course in case of debug build it should be luabind.x86.debug.lib. Also for VS 9: luabind.x86.release.vs9.lib and luabind.x86.debug.vs9.lib. This way we can use lua and luabind without compilation and linking errors.

The application code didn't change much. First let's add this code at the beginning of App.cpp file:

#include "ScriptLua.h"
void hello() {
  cout << "Hello world from tutorial 1.7!" << endl;

It simply includes "ScriptLua.h" file defining lua scripting and defines hello() function mentioned a while before.

The most important changes involve using third person camera instead of first person used up to now. Code in App.cpp file responsible for creating camera is presented below:

CameraThirdPerson* cam;
cam = (CameraThirdPerson*)sm->createCamera(L"CameraThirdPerson", L"Camera");
cam->setPosition(Vector3(0.0f, 10.0f, -10.0f));
cam->setTarget(Vector3(0.0f, 0.0f, 7.0f));
sm->getRootNode()->addChild(L"Camera", cam);

Also we have to change code in MyInputListener.cpp a bit as we don't want user to move camera with mouse or keyboard:
#include "MyInputListener.h"
#include "nGENE.h"
#include "App.h"
using namespace nGENE;
void MyInputListener::handleEvent(const MouseEvent &_evt)
void MyInputListener::handleEvent(const KeyboardEvent& _evt)

Also add this line:

ScriptLua* pScript;

to App class definition in App.h file. ScriptLua is a class representing Lua scripts.

Ok, so finally we can load and run our script. Add following code at the end of App::createApplication() method:

    luabind::def("hello", &hello)
pScript = Engine::getSingleton().getLuaScripting()->createScript();

And that's all. First we export our hello() function thus making it visible for scripts. Note that exporting functions, classes and so on have to precede running or loading scripts using them or else you will get errors. You can also use namespaces, export classes and much more. For full list of features of luabind visit its online documentation.

Then we obtain lua scripting system and retrieve pointer to the newly created ScriptLua object. The second line opens and runs script located in simple_script.lua file, ie. our script!

One more thing. Sometimes it might be preferrable not to use threads or run scripts instantly but simply to open a file and run its contents at the specified moment. A good example of this are NPCs in RPG games. You can load scripts for all of them at once when the level is being processed but fire them when certain conditions are met (eg. you get a quest). To achieve this you can go with something like this:

// And later...
pScript->callFunction <ReturnValueType>("SomeFunction", returnBuffer, sizeOfTheBufferInBytes);