Integrating Lua into Ogre

We've already seen how to integrate Lua into vanilla C. Now we can look at integrating lua into an Ogre project.

Command-Line Interpreter

Using scanf (or the equivalent) in a game just won't work -- we don't want to wait on input, and always need to be running the game in the background. What to do? We can have a interpreter class that receives keyboard events (that is, key presses) and builds up a string one character at a time, as follows:

void 
DebugInterface::HandleKeystroke(OIS::KeyEvent e)
{
        if (e.key == OIS::KC_RETURN)
        {
            LuaWrapper::getSingleton()->doString(mLuaCommand.c_str());
            mHistory.push_back(mLuaCommand);
            mHistoryIndex = (int) mHistory.size();
            mLuaCommand = "";
            mOverlayText->setCaption("Lua >" + mLuaCommand);
        }
        else if (e.key == OIS::KC_BACK || e.key == OIS::KC_LEFT)
        {
            if (mLuaCommand.size() > 0)
            {
                mLuaCommand.erase(mLuaCommand.size()-1);
                mOverlayText->setCaption("Lua >" + mLuaCommand);
            }
        }
        else if (e.key == OIS::KC_UP)
        {
            mHistoryIndex--;
            mHistoryIndex = std::max(mHistoryIndex,0);
            if (mHistoryIndex < mHistory.size())
            {
                mLuaCommand = mHistory[mHistoryIndex];
                mOverlayText->setCaption("Lua >" + mLuaCommand);
            }
        }
        else if (e.key == OIS::KC_DOWN)
        {
            mHistoryIndex++;
            mHistoryIndex = (int) std::min(mHistoryIndex,mHistory.size()-1);
            if (mHistoryIndex < mHistory.size())
            {
                mLuaCommand = mHistory[mHistoryIndex];
                mOverlayText->setCaption("Lua >" + mLuaCommand);
            }
        }

        else if (e.key != OIS::KC_LSHIFT && e.key != OIS::KC_RSHIFT && e.key != OIS::KC_LCONTROL && e.key != OIS::KC_RCONTROL)
        {
            mLuaCommand.push_back((char) e.text);
            mOverlayText->setCaption("Lua >" + mLuaCommand);
        }
}

Note that there are some hidden details -- we are assuming the existence of a LuaWrapper singleton class, that handles all of the Lua state for us, and we are assuming that there is an input handler that captures keyboard input and sends it along to us. The full code is available in the zip file at the bottom of this page.

Exposing Functions to Lua

Even if you aren't using scripting in your final code, exposing functions to lua is handy for debugging.  There are one or two wrinkles to consider.  One is that lua can't easily call C++ methods, only C functions (at least without a more extensive lua wrapper than the basic one we've been using thus far.)  What if you want your lua functions to call methods in clases?  One option is to make the class you want lua to access a singleton class.  That way, the C functions that Lua calls can just call up the singleton, and then call the appropriate method from that class.  For instance, let's say we have a world class which has an ApplyForce method, which takes as input the name of an object and a vector representing a force,  and applies that force to the object.  We could expose the lua function like so:

int applyForce(lua_State *L)
{
    int numArgs = lua_gettop(L);

    if (numArgs < 4)
    {
        return 0;
    }
    Ogre::String name = lua_tostring(L,1);
    PhysicsWorld::getSingleton()->applyForce(name, Ogre::Vector3(lua_tonumber(L,2),lua_tonumber(L,3), lua_tonumber(L,4)));
    return 0;
}

An alternate solution is to have a singleton lua wrapper code that has a pointer to the world (or anything else that we might need to access from lua.) We would need to set up the appropriate pointers in the lua wrapper before we began, of course.

int setPosition(lua_State *L)
{
    int numArgs = lua_gettop(L);

    if (numArgs < 4)
    {
        return 0;
    }
    const char* nodeName = lua_tostring(L,1);
    float x = (float) lua_tonumber(L,2);
    float y = (float) lua_tonumber(L,3);
    float z = (float) lua_tonumber(L,4);
    LuaWrapper::getSingleton()->getWorld()->setPosition(nodeName, Ogre::Vector3(x,y,z));
    return 0;
}

Lua Scripts

What if you want a script that takes more than one frame to complete?  For instance, if you want to write a script that slowly opens a door, or controls moving platforms, or even controls some simple AI?  Your script will need some processor cycles every frame.  How can we easily manage all of our scripts, in a way that does not require recompiling our code when we add or remove scripts?  The easiest method is to have a global script manager (written in lua) that gets a think call every frame.  The script manager can store a table of all active scripts.  The script manager can go through its list of active scripts, calling the think method on each one in turn.



File scriptManager.lua
GlobalScrpts = { }

function ScriptManagerThink(time)
   for k,v in GlobalScripts do
      v:think(time)
   end
end

Then, to make a script active, all we need to do is add it to the table GlobalScripts.  Each script is a class which contains various data members (strings representing the objects that the script can manipulate, internal script state, and so on)  a think method, and any other methods that are required for good program decomposition.  To make a script inactive, we can just remove it from the table GlobalScripts.

Somewhere in our main loop, we will call ScriptManagerThink:


    lua_getglobal(L, "ScriptManagerThink");
    lua_pushnumber(L, timeSinceLastFrame);
    lua_call(L, 1, 0);
	

Practical Exercise

Download a the sample solution, which contains both a lua project and a simple Ogre project

Once the program is running, nothing much seems to happen -- it is just a blank screen. Type in the tilde character ~ to bring up the Lua Interpreter. Now you can start typing in things

Lua > createObject("MyCube.mesh", "c1")
Lua > createObject("car.mesh", "car")
Lua > setPosition("c1", 0, 10, 0)

For your in-class lab, you are going to add some functionality to this simple lua interpreter as follows:

If you get all of this done, add lua to your own project. It is really not that hard, and can make your life much easier in the long run. The lua source can be found at the lua website here. We'll be using the latest version, 5.2.3 (though other recent versions of Lua will behave similarly). You can always just extract the Lua project from the above OgreLua.zip file if you don't want to recreate the project, but it really is not that difficult and is a good exercise