Scripting iOS Games With Lua - Part I

| Comments

A while back @celsiusgs published a blog entry discussing using C++ polymorphism for pluggable AI in his upcoming game Drifter. In a nutshell, each ship in the game is controlled by a ShipObject that has the ship’s state as well as methods (“inputs”) to control the ship (throttle, steering, etc.). This class provies a process method that updates ship state (presumably based on some sort of physical model and the current state and inputs). For player controlled ships, the ShipObject inputs are driven by touch controls. For instance, when the player touches the throttle control the throttleControl method(s) would be called (presumably).

This separation of the physical controls that respond to user touches from the ship controller makes it easy to plug in AI code to control NPC ships. For NPC ships, the ShipController object is assigned an AI object that accesses the ShipController to control the ship’s state. This AI object implements a process method that is called from the ShipObject’s update method once per game loop.

To make this as flexible as possible, @celsiusgs has created a generic AI class that provides a virtual process method that is implemented by classes inheriting from this base class. This allows him to simply attache different AI implementations to each NPC ship to get different behaviour.

The decoupled nature of this approach provides another benefit that @celsiusgs mentions: it provides an easy entry point for replacing C++ code with a scripting system.

I won’t get into a discussion here of the pros and cons of using an embedded scripting system. I like to think that there are more reasons supporting using one than reasons against it. For this appliction, however, there is one huge benefit that definitely makes it worth doing.

Implementing even simple AI is nontrivial and often involves a lot of iterations as you try to weed out “dumb” behavior. An NPC ship repeatedly bumping it’s “head” into an asteroid tends to spoil a games immersiveness. Executing test runs over and over again is particular painful on the iOS platform if the code under test needs to be compiled every run.

If the code is run from a script, however, these scripts can be read from a webserver every time they are executed, so the main program does not need to be rebuilt every run. In fact, if the code is written to support it, it may not even need to be restarted, but merely reset. This allows the AI coder to make changes to his/her scripts and rapidly test the effects.

By far the most popular embedded scripting language for game development is Lua. I won’t describe Lua in much detail here, instead I’ll refer the reader to the main Lua site. Suffice it to say, it has three properties that make it a good choice for embedding scripting in a game:

  • It was designed as an embedded language from the ground up
  • It is based on standard C/C++ code and is therefore runs on many platforms
  • It is lightweight and very fast

In this three part blog entry I am going to demonstrate how to embed Lua into an iOS app, with particular emphasis on a system like the one in Drifter, albeit using much simpler game mechanics. If you want to follow along in Xcode the project can be cloned from github at git@github.com:indiejames/TestLua.git.

First things first, we need a game platform in which to embed our lua interpreter. For this demonstration, I will create a very simple sample program using a single view. I want to keep the main program as simple as possible to focus on the lua portion, so this program won’t have any OpenGL or other graphics and only a rudimentary user interface.

I’ll skip using storyboards or generating tests as these are outside the scope of this discussion.

At this point we have a simple single view application with the standard boilerplate code. Aside from the startup code and support files, this consists of just two Objective C classes, AppDelegate and ViewController. These should be familar to anyone working in iOS, so I won’t discuss their core functionality here. If you don’t know how these work, check at one of the many resources related to iOS development.

I won’t be changing the AppDelegate class at all for this example so it is not shown here. All our changes to the boilerplate code will be done in ViewController. The bare bones code for this class is shown here:

ViewController.h
1
2
3
4
5
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
ViewController.m
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#import "ViewController.h"

@implementation ViewController

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
  [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
  [super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

@end

This is where we will load and invoke our Lua interpreter, but for now let’s set up our simple game mechanics. For this we will create a new class that we will call ShipController, which will manage the state of our ship and respond to inputs.

So now we have an empty class as shown here:

ShipController.h
1
2
3
4
5
#import <Foundation/Foundation.h>

@interface ShipController : NSObject

@end
ShipController.m
1
2
3
4
5
#import "ShipController.h"

@implementation ShipController

@end

I want to keep the game mechanics simple so I can focus on the scripting, so I am going to create a game in which ships move in two dimensions. We will use the OpenGL convention of the y-axis pointing “up”. All ships will have an intrinsic “speed” which determines how far they move in response to inputs. The player will have four buttons (Left, Right, Up, Down) that can be pressed to control the ship. When the player presses one of the buttons, the ship will move a certain number of units in that direction, where this number is given by the ship’s intrinsic speed. For instance, if the player’s ship has a speed of four, pressing the Up button twice will increase the y value of the ship’s postion by eight units. Note that we won’t be modeling velocity or accelaration here – the ship simply changes its position based on its inputs and its intrinsic speed.

We add the following code to the ShipController.h file to add instance variables to hold the state of ship, along with property declarations to make them available to other classes.

ShipController.h State Variables
1
2
3
4
5
6
7
8
9
10
@interface ShipController : NSObject {
    float x;
    float y;
    float speed;
    NSString *name;
}

@property (readonly) float x;
@property (readonly) float y;
@property (readonly) NSString *name;

We have variables x and y to store our ship’s position, speed which defines the instrinsic speed of the ship (NOT its velocity) and a name member by which we will distinguish different ships in our output.

Our property declarations will allow us to output the position of the ships by name periodically so we may observe their behaviour. We will not be generating any graphical output in this example.

In addition to the state for our ships, we need to provide methods to represent the “inputs” with which we can control it. We add the following to the header file:

ShipController.h Methods
1
2
3
4
5
-(id) initWithX:(float)x Y:(float)y Speed:(float)speed Name:(NSString *)name;
-(IBAction)pressLeftButton;
-(IBAction)pressRightButton;
-(IBAction)pressTopButton;
-(IBAction)pressBottomButton;

The first method is our init method and it sets the initial state (x,y) for a ship as well as it’s intrinsic speed and its name. The next four methods are the “input” methods that will be called whenever someone presses one of the buttons. Notice that there is no process method. Because we are using such a simple physics model, I am going to simply update the ships position directly every time one of the button press methods are called. A more sophisticated simulation would implement a process method ala Drifter to update the ships state once every game loop.

The implementation for these methods are shown here:

ShipController.m
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
34
35
36
37
38
39
40
41
#import "ShipController.h"

@implementation ShipController

@synthesize x, y, name;

-(id) initWithX:(float)_x Y:(float)_y Speed:(float)_s Name:(NSString *)_name{
    self = [super init];
    if (self) {
        x = _x;
        y = _y;
        speed = _s;
        name = _name;
        [name retain];
    }

    return self;
}

-(void) pressLeftButton {
    NSLog(@"Left button pressed for ship %@", self.name);
    x = x - speed;
}


-(void) pressRightButton {
    NSLog(@"Right button pressed for ship %@", self.name);
    x = x + speed;
}

-(void) pressTopButton {
    NSLog(@"Top button pressed for ship %@", self.name);
    y = y + speed;
}

-(void) pressBottomButton {
    NSLog(@"Bottom button pressed for ship %@", self.name);
    y = y - speed;
}

@end

The init method sets the initial state (x,y) the only model parameter, speed, and the name for the ship. The button press methods log the action and direcctly manipulate the ship’s state.

Now we modify our ViewController to instantiate a ShipController for the player’s ship. First we import the header for or ShipController class, then declare a global variable to hold a pointer to our player’s ship controller. Obviously using a global here is bad practice, but we do so now to keep things simple. Finally we instantiate the global in the viewDidLoad method of the ViewController.

ViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "ShipController.h"

ShipController *playerShipController;

//////////////
//////////////
...

- (void)viewDidLoad
{
    [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.

    // create the ship for the player
    playerShipController = [[ShipController alloc] initWithX:100 Y:100 Speed:2.0 Name:@"player_ship"];

}

Now that we have our ship’s controller we need to add a way for the user to manipulate it. To this end we add four simple buttons to our interface and wire them to our view controller. First we add four actions to the view controller (one for each button).

ViewController.h
1
2
3
4
5
6
7
8
9
10
11
12
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController{
    NSTimer *timer;
}


-(IBAction)pressLeftButton:(id)sender;
-(IBAction)pressRightButton:(id)sender;
-(IBAction)pressTopButton:(id)sender;
-(IBAction)pressBottomButton:(id)sender;
@end
ViewController.m Actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# pragma mark - Button actions

-(void) pressLeftButton:(id) sender {
    [playerShipController pressLeftButton];
}

-(void) pressRightButton:(id) sender {
    [playerShipController pressRightButton];
}

-(void) pressTopButton:(id) sender {
    [playerShipController pressTopButton];
}

-(void) pressBottomButton:(id) sender {
    [playerShipController pressBottomButton];
}

These actions simply delegate to the ShipController class by calling the corresponding pressButton actions. We have also added an NSTimer to our ViewController. This will be used later to set up the game loop.

Now we use Interface Builder to add four buttons two our view and connect them to our actions.

We can test our game by executing it and pressing a few buttons. The output from this run is given here:

2012-02-04 22:27:46.348 TestLua[24376:f803] Top button pressed for ship player_ship
2012-02-04 22:27:47.028 TestLua[24376:f803] Right button pressed for ship player_ship
2012-02-04 22:27:47.676 TestLua[24376:f803] Bottom button pressed for ship player_ship
2012-02-04 22:27:48.260 TestLua[24376:f803] Left button pressed for ship player_ship

The last thing we’ll do before adding our Lua interpreter is to set up a game loop. We simply initialize the timer in the viewDidLoad method of our ViewController:

ViewController.m Game Loop
1
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runLoop:) userInfo:nil repeats:YES];

This timer will execute the runLoop method every second. Not exactly blazing, but since we aren’t going to be doing graphics, this is more than enough.

The runLoop method is given here:

Game Run Loop
1
2
3
4
-(void)runLoop:(id)sender {

    NSLog(@"Player ship at (%f,%f)", playerShipController.x, playerShipController.y);
}

This simply writes out he position of the player’s ship every time it executes. So now if we run the game and press the up button a couple of times, we get the following:

2012-02-04 22:52:08.802 TestLua[25770:f803] Player ship at (100.000000,100.000000)
2012-02-04 22:52:09.508 TestLua[25770:f803] Top button pressed for ship player_ship
2012-02-04 22:52:09.801 TestLua[25770:f803] Player ship at (100.000000,102.000000)
2012-02-04 22:52:10.166 TestLua[25770:f803] Top button pressed for ship player_ship
2012-02-04 22:52:10.801 TestLua[25770:f803] Player ship at (100.000000,104.000000)
2012-02-04 22:52:11.801 TestLua[25770:f803] Player ship at (100.000000,104.000000)
2012-02-04 22:52:12.802 TestLua[25770:f803] Player ship at (100.000000,104.000000)
2012-02-04 22:52:13.801 TestLua[25770:f803] Player ship at (100.000000,104.000000)

Now we have our (very simple) game working to the point that we can steer our ship around using our buttons. In Part II we will get to the heart of the matter and add our Lua interpreter and some bindings that will allow us to use Lua scripts to interact with our game. Finally, in part III, we will wrap this up in a new Lua type that will allow us to easily control NPC ships in our scripts.

Comments