Writing an iPhone Game Engine (Part 3- Scripting)

Adding script support to an engine has many benefits such as writing game play code without recompile the engine source code which may takes a long time. It also can draw a much clear boundary between the game code and the engine code. I chose to use Lua(ver. 5.1.4) as it is easy to embed and its size is small. As I am no expert on Lua, I would like to write about what I have learnt on how to bind Lua and C/C++.

Calling C function from Lua
First, you need to create a lua_State*, where all Lua operations are done within it, by lua_open() (or you may use lua_newState() if you need to hook up your memory allocator to Lua.)

  1. lua_State* luaState= lua_open();
Lua and C can exchange data through a virtual stack. Both Lua and C can push and pop data from or to the stack. Say, we have already register a C function for Lua to use with function signature:

  1. function drawText(str, screenPosX, screenPosY);
then in Lua side, when the following Lua script is executed:

  1. textWidth= drawText("Ready Go~",  240, 160);
3 values will be pushed to the Lua Stack:
We can get the values from the stack in C using an absolute index counting from the bottom of the stack (start from 1) or a relative index to the top of the stack (start from -1).

Then we can retrieve the values in stack inside the C function called by Lua with the following code:

  1. int drawText(lua_State* luaState)
  2. {
  3. float screenPosY = (float) lua_tonumber(luaState, -1); // get the value 160
  4. float screenPosX = (float) lua_tonumber(luaState, -2); // get the value 240
  5. const char* str = lua_tostring(luaState, -3); // get the value "Ready Go~"
  6. printf("Text '%s' draw at (%f, %f)\n", str, screenPosX, screenPosY);

  7. int textWidth= strlen(str);
  8. lua_pushnumber(luaState, textWidth); // return a value to Lua
  9. return 1; // number of values return to Lua
  10. }
when the C function exit, the stack will look like:

After Lua get the return value from the C function, the parameter and the return value of the C function will be popped out of the stack. 
But before Lua can execute the drawText(), remember to register it to the lua_State* by:

  1. lua_register(luaState, "drawText", drawText);


Calling Lua functions from C
We can also call Lua functions from C. For example, we have declare a function in Lua script for initializing the engine configurations:

  1. function initEngineConfig(date)
  2. print('initialize engine on ' .. date);
  3. end
As Lua is a typeless language, it also treats functions as variables. So we need to push the Lua variable 'initEngineConfig' into the stack with its arguments with the following C code:

  1. lua_getglobal(luaState, "initEngineConfig"); // get the function to the stack
  2. lua_pushstring(luaState, "18th Aug, 2011"); // push the argument of the function
  3. lua_call(luaState, 1, 0); // execute the function

(You may also use lua_pcall() instead of lua_call() to get more debug info  when error occurs)
The above C codes is equivalent to calling a function in Lua:

  1. initEngineConfig("18th Aug, 2011");

We can also use similar technique to execute an object's method in C. For instance, we can call an object's method in Lua:

  1. gameObjectA:update(timeSlice);
We can do this in C too. In Lua, the colon syntax is just a short form for writing the statement:

  1. gameObjectA.update(gameObjectA, timeSlice);
So, we need to push the 'update' function of 'gameObjectA' onto the Lua stack with 2 arguments:
  1. lua_getglobal(luaState, "gameObjectA"); // for getting the 'update' function of 'gameObjectA'
  2. lua_getfield(luaState, -1, "update"); // get the 'update' function of 'gameObjectA'
  3. lua_insert(luaState, -2); // swap the order of "gameObjectA" and "update"
  4. // so that "gameObjectA" becomes an argument
  5. lua_pushnumber(luaState, 1.0f/30.0f); // push the timeSlice argument on the stack.
  6. lua_call(luaState, 2, 0); // execute the functions.
This is basically how Lua interacts with C, but you also need to know how to represent C structure as user data or light user data. You may also need to know LUA_REGISTRY_INDEX for creating a variable in C without worrying conflicts in variable name. After knowing these things you may want to try some binding library to generate the binding. But I hope these little binding methods can help someone who want to bind Lua and C on their own.


Reference:
[1] Lua manual: http://www.lua.org/manual/5.1/manual.html
[2] Lua user data: http://www.lua.org/pil/28.1.html
[3] Lua light user data: http://www.lua.org/pil/28.5.html
[4] LUA_REGISTRY_INDEX: http://www.lua.org/pil/27.3.2.html

沒有留言:

發佈留言