Scripting iOS Games With Lua - Part II

| Comments

In part I of this three part post, I introduced a simple game framework to which we are going to add Lua scripting. Now we are going to get right to it and cover all the steps necesary to get Lua scripting working. This will include embedding the Lua interpreter in the Xcode project, running Lua scripts from Objective C code, and accessing game state (ship position) in Lua scripts via a C function we will write and bind to Lua.

The first thing we need to do is grab a copy of the Lua source here. I’m using version 5.1. Version 5.2 was just released at the end of 2011, but the C binding system has changed somewhat so I am not using it here.

Once you have downloaded and untarred the archive, rename the src directory lua. Then drag the lua directory from Finder into your Xcode project. Be sure to uncheck the “create external build system box” and have Xcode copy the files and create a group:

Now open the lua group and delete the Makefile, lua.c, and luac.c files. We wont be using the Makefile to build the source and we don’t need lua.c or luac.c as these are the code for the standalone interpreter.

Now build the project using Xcode. It should build without issues. Congratulations! You’ve just embedded Lua in your iOS project! Of course, it isn’t actually doing anything yet, so let’s get to that.

One of the nice things about embedded Lua is that you can choose just how tightly you want to bind your code to Lua. Lua provides a C API that lets you control this. In the simplest case, you can call Lua scripts from C and get the results back. At the next level, you can have your Lua scripts invoke C functions that you bind to Lua. Finally, you can define new Lua types that not only invoke C functions, but instantiate C++/Objective C objects and delegate processing to them. We will look at each of these levels in turn.

First, let’s just invoke the embedded interpreter to execute a simple Lua script. I won’t cover Lua syntax here as it is oustide the scope of this blog post, but suffice it to say that its syntax is similar enough to C that the reader should have no problem following along. Please refer to The Lua Programming Language or another reference for a comprehensive introduction.

As I mentioned, Lua provides a C API for interacting with the interpreter. Actually, it provides two APIs, the basic API and the auxilary API. The auxilary API adds convenience methods to the basic API that make it a bit easier to use.

We need to add a few imports and a declaration to our ViewController.h file.

ViewController.h with Lua Support
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <UIKit/UIKit.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

@interface ViewController : UIViewController{
    lua_State *L;
    NSTimer *timer;
}

-(IBAction)pressLeftButton:(id)sender;
-(IBAction)pressRightButton:(id)sender;
-(IBAction)pressTopButton:(id)sender;
-(IBAction)pressBottomButton:(id)sender;

@end

One of the nice things about the Lua code is that it’s designed to be reentrant. Therefore it maintains no internal state. Instead, the interpreter state is maintained externally in a C struct called, appropriately, lua_State. The main program creates these and passes them to the interpreter with every call in the APIs. For this application, we will only need one instance and we add it as an instance variable for our ViewController. Ideally we would probably create a separate class to handle all of our Lua interaction, but in this example we are simply going to do everything in the ViewController.

Add the following lines to the viewDidLoad method of ViewController.m before the call that creates the NSTimer:

ViewController.m Lua Init and Test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// initialize Lua and our load our lua file
L = luaL_newstate(); // create a new state structure for the interpreter
luaL_openlibs(L); // load all the basic libraries into the interpreter

lua_settop(L, 0);

int err;

err = luaL_loadstring(L, "print(\"Hello, world.\")");
if (0 != err) {
    luaL_error(L, "cannot compile lua file: %s",
               lua_tostring(L, -1));
  return;
}


err = lua_pcall(L, 0, 0, 0);
if (0 != err) {
  luaL_error(L, "cannot run lua file: %s",
               lua_tostring(L, -1));
  return;
}

All of the methods in the basic API have names that start with lua_, while all methods in the auxillary API have names that start with luaL_. The first thing we need to do is inovke luaL_newstate which instantiates a lua state structure for us and returns a pointer to it. The next call is to luaL_openlibs, which opens all the basic Lua libraries – we will use this to inject our own C methods into Lua later.

The next call to lua_settop brings up a very important point that needs to be made. All passing of data from C to Lua and from Lua to C is done via a stack. When C code invokes a Lua scripted function it passes the arguments to the Lua function on the stack. It also reads values returned by the function from the stack.

Similarly, when a Lua script invokes a bound C function, it passes its arguments on the stack and reads its return values from the stack. The call to lua_settop simply tells Lua that the stack contains zero values, i.e., it’s empty.

The next call to luaL_loadstring compiles our one line “Hello, world.” Lua script and returns a nonzero error code if something went wrong. We check this code and use luaL_error to get a more meaningful error message if something went wrong. For now we are just providing the script in a hard-coded string. Later we will read this script from a file.

Finally, we execute the Lua script using lua_pcall. The p in lua_pcall stands for protected, which essentially means that if something goes wrong the interpreter will trap the error and return an error code instead of propagating the error upwards.

After adding these lines, run the program and you should see output like this:

Hello, world.
2012-02-05 15:23:07.899 TestLua[29603:f803] Player ship at (100.000000,100.000000)
2012-02-05 15:23:08.899 TestLua[29603:f803] Player ship at (100.000000,100.000000)

More progress! We have just run our first Lua code from within our iOS project! Now let’s do something a bit more useful. We are going to demonstrate how to bind a C method to Lua so we can call it from our scripts.

Let’s extend Lua so that it knows where our player ship is. Add the following function to ViewController.m.

ViewController.m Binding C Function to Lua
1
2
3
4
5
6
static int player_ship_position(lua_State *L){
    lua_pushnumber(L, playerShipController.x);
    lua_pushnumber(L, playerShipController.y);

    return 2;
}

This C funciton is declared static so it cannot be used outside of our ViewController and therefore does not need to be declared in a header. Remember, this is NOT an instance method. It implements the basic prototype for C functions that are bound to Lua – it takes a single lua_State argument and returns an int. This method does two things; it pushes the ship’s x and y coordinates on the stack and returns the number of items it has placed on the stack, 2. Objective C 2.0 properties work even inside a C function, so we can access the ship’s position using its properties x and y.

Now we have a function that will return the ship’s position to Lua via the stack, but we must first bind it to Lua before we can use it in a script. There are a couple of different ways to do this, but the most useful one if you are going to bind many functions (and we will) is to put them in an array and pass this array to the luaL_register function. So we add the following code to ViewController.m.

ViewController.m Binding Code
1
2
3
4
5
6
7
8
9
10
11
static const struct luaL_Reg shiplib_f [] = {
    {"player_ship_position", player_ship_position},
    {NULL, NULL}
};

int luaopen_mylib (lua_State *L){

    luaL_register(L, "ship", shiplib_f);

    return 1;
}

We declare an array of struct luaL_Reg which are structures containing a function to be bound and a name to which to bind it. This array is terminated by an entry with NULL values. We pass this array to the luaL_register function (macro, really) along with a lua_State and a name for the table that will contain the functions in the array.

We need to call the luaopen_mylib function before we run our script. The easy way to do that is to add it to the list of libs that automatically get loaded when the Lua interpreter starts. This can be done by modifying the linit.c file in our lua directory like so:

linit.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define linit_c
#define LUA_LIB

#include "lua.h"

#include "lualib.h"
#include "lauxlib.h"

extern int luaopen_mylib (lua_State *L);


static const luaL_Reg lualibs[] = {
  {"", luaopen_base},
  {LUA_LOADLIBNAME, luaopen_package},
  {LUA_TABLIBNAME, luaopen_table},
  {LUA_IOLIBNAME, luaopen_io},
  {LUA_OSLIBNAME, luaopen_os},
  {LUA_STRLIBNAME, luaopen_string},
  {LUA_MATHLIBNAME, luaopen_math},
  {LUA_DBLIBNAME, luaopen_debug},
  {"ship", luaopen_mylib},
  {NULL, NULL}
};


LUALIB_API void luaL_openlibs (lua_State *L) {
  const luaL_Reg *lib = lualibs;
  for (; lib->func; lib++) {
    lua_pushcfunction(L, lib->func);
    lua_pushstring(L, lib->name);
    lua_call(L, 1, 0);
  }
}

The only changes we have made here are to add {"ship", luaopen_mylib}, to the lualibs array and to add the extern int luaopen_mylib (lua_State *L) declaration so the linker can do its thing.

Now we are going to modify our Lua script to call our new method. At the same time, we will load our script from a file instead of a hard-coded string. First, let’s make the changes to ViewController.m. Replace the call to luaL_loadstring with the following.

ViewController.m Loading Lua Script From File
1
2
3
NSString *luaFilePath = [[NSBundle mainBundle] pathForResource:@"ship" ofType:@"lua"];

err = luaL_loadfile(L, [luaFilePath cStringUsingEncoding:[NSString defaultCStringEncoding]]);

This will load and execute the script in the file ship.lua. Now let’s create this file.

ship.lua
1
2
3
4
5
6
7
8
9
function print_player_ship_position()

    -- get the location of the player's ship
    player_x, player_y = ship.player_ship_position()

    -- print position to console
    print(string.format("player ship is at (%f, %f)", player_x, player_y))

end

This file defines a single lua function which will use our bound player_ship_position method to get the x,y position of the ship and print it out.

Now we need to execute this method in our run loop:

ViewController.m run loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-(void)runLoop:(id)sender {

    NSLog(@"Player ship at (%f,%f)", playerShipController.x, playerShipController.y);
    // put the pointer to the lua function we want on top of the stack
    lua_getglobal(L,"print_player_ship_position");

    // call the function on top of the stack
    int err = lua_pcall(L, 0, 0, 0);
  if (0 != err) {
      luaL_error(L, "cannot run lua file: %s",
                   lua_tostring(L, -1));
      return;
  }
}

Running the program and pushing the up button a couple of times produces the following:

2012-02-05 20:09:52.370 TestLua[32429:f803] Player ship at (100.000000,100.000000)
Lua says player ship is at (100.000000, 100.000000)
2012-02-05 20:09:52.892 TestLua[32429:f803] Top button pressed for ship player_ship
2012-02-05 20:09:53.310 TestLua[32429:f803] Top button pressed for ship player_ship
2012-02-05 20:09:53.370 TestLua[32429:f803] Player ship at (100.000000,104.000000)
Lua says player ship is at (100.000000, 104.000000)
2012-02-05 20:09:54.370 TestLua[32429:f803] Player ship at (100.000000,104.000000)
Lua says player ship is at (100.000000, 104.000000)

Now we’re getting somewhere! We can access our player ship’s state from within a Lua script, which opens up a lot of possibilities. We could create an entire C API that would let us manipulate our ship by calling methods on our ShipController instances. In fact, we will do something similar to this in Part III, but we will wrap it into a nice object oriented layer that will make our scripting cleaner.

Comments