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
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 firstname.lastname@example.org: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,
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:
1 2 3 4 5
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
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:
1 2 3 4 5
1 2 3 4 5
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.
1 2 3 4 5 6 7 8 9 10
We have variables
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:
1 2 3 4 5
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:
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
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
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).
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
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
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.
runLoop method is given here:
1 2 3 4
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.