Atmosfall – Managing Game Progress with Coroutines

by Nevin Flanagan | June 2013 | Games

In this article by Nevin Flanagan, author of Corona SDK Hotshot, we look at managing information with the use of physics engine. If you're a long-time or low-level programmer, you may be accustomed to controlling the core of your game loop; if you're more used to recent game engines, you may have learned to juggle complex information about what actors in your game are doing at any given moment. In Corona, the combination of a separately-tracked physics engine and the versatility of Lua will, when used correctly, manage this information for you.

<p style="margin-left: 40px; margin-right: 40px;" align="center"><i>(For more resources related to this topic, see <a href="#more">here</a>.)</i></p> <h1>What do we build?</h1> <p>We'll explore this idea of managing progress through a game schedule by completing a scrolling shooter in the tradition of games such as Xevious or River Run, where the player maneuvers a ship around the screen, avoiding enemy fire and destroying enemy ships until he/she reaches a boss target. Enemies will include ships that fly various patterns across the screen, turrets that follow the scrolling background and turn to face the player's ship, and a boss at the end that takes many hits to destroy and moves in various directions, firing multiple weapons. The player's ship will be able to fire at points on the screen that the player touches.</p> <h2>What does it do?</h2> <p>The player maneuvers their ship around the screen as the ground scrolls under them from the top of the screen downward. As the scrolling background reaches certain points, enemies appear and fly or scroll around the screen, firing bullets as they go; the player must avoid the bullets as well as the ship itself while shooting back.</p> <p>Like the <i>Deep Black</i> game, this project will be based on Corona's Box2D-based physics library. In this version, most objects will be "sensors", meaning they only detect collisions, and do not bounce off of each other or transfer momentum. We'll also use Box2D's collision filters so that we don't need to process enemy ships colliding with each other, or bullets hitting each other.</p> <p>In the <i>TranslationBuddy</i> project, we got a taste of coroutines and how they can be used to bookmark a task that you're in the middle of and come back to it later. In this project, we'll take that concept much further, using coroutines to create scripted behavior that's carried out over time, which is the simplest part of what game players and developers usually refer to as AI. We'll create computer-controlled fighters that follow flight assignments which are predictable at run time, but easily customized by the designer.</p> <h2>Why is it great?</h2> <p>In addition to coroutines, we'll also use a Lua feature called environments to create a minimal language of very simple functions controlling intervals and enemy actions. Features like this can become useful in larger projects, where programmers and designers must collaborate on a project. In such projects, programmers are responsible for the code that carries out the actions which the enemies will take, but the decisions of what the enemies should do, when, and in what order, are made by game designers, and frequently have to be adjusted for best balance and fun. For this reason, it's good to let the designers edit these schedules and scripts themselves. Designers are usually not experienced programmers, although they often have a little knowledge of programming and scripting, so this simple language will make it much easier for the designers.</p> <h2>How are we going to do it?</h2> <p>For this project, we'll review the design, rather than creating it. While an indie programmer will often be designing their own games and then coding from their designs, any programmer at a studio is likely to be coding from a document given to them by a designer or design team. We will be managing game progress with the help of the following game coroutines:</p> <ul> <li> <p>Founding the framework</p> </li> <li> <p>Moving the player</p> </li> <li> <p>Scheduling enemies</p> </li> <li> <p>Scripting behavior</p> </li> <li> <p>Controlling the boss</p> </li> <li> <p>Cleaning up and making the game playable</p> </li> </ul> <h2>What do I need to get started?</h2> <p>First, open the file <i>design.txt</i> from the project pack and read through it, noting the [NYI] tags that indicate features still pending. In full development projects, this sort of status tracking will usually be carried out by a more complex database or dedicated tracking program, but even in small projects, a simple record of what has yet to be accomplished can be very useful.</p> <p>At this point, the game has files describing various ships (a broad category which also includes the ground-based, immobile turrets) as well as the various weapons with which the ships are equipped, the bullets they fire, and the explosions when they land. It also includes code that handles things taking damage&mdash;including events that can be tracked by user interface elements or gameplay progress tracking, processing user input into commands, and a long background made up of large tiles. A splash screen is already completed and appears when the game is launched.</p> <p>What the game still needs is actual level design. Enemies need to appear as time advances in the level, and carry out various plans of attack against the player. This means that there are two kinds of schedules required; the schedule of which enemies appear when, and the individual schedules of the enemies that dictate how each one flies and attacks after it is created. To make this happen, we'll not only create scheduling behaviors, but also modules that attach these behaviors and other suitable characteristics, such as orientation, to our predefined ships to make them into bosses and enemies.</p> <p style="margin-left:40px;margin-right:40px"><i> This distinction between units in a game that have statistics and behaviors, and the actors that represent them in the game world, is very powerful for scaling projects up in complexity.</i></p> <p>Once that's done, create a new project folder, <i>Atmosfall</i>, and copy the contents of the <i>version </i>0 folder in the project pack directory. You should be able to load this project into the simulator and advance the game past the splash screen, seeing a ship in the middle of a swamp background. In the simulator, you can click anywhere on the game screen and watch the ship fire double bullets at the point at which you click. You can also build the game for a device and watch the ship slide around the screen as you tilt the device. The prototype responds to user input and is ready to start adding the scheduling and AI enemy control.</p> <h1>Tracking progress through the level</h1> <p>Games like this typically trigger enemy appearances and events based on how far the background has scrolled past the screen. To focus on the challenges of the project, we'll import the background itself and just add the scrolling logic to issue events that track this progress.</p> <h2>Getting ready</h2> <p>You should have already copied the partly completed project from the <i>version 0</i> folder into your new project directory; if you haven't, do that now.</p> <h2>Getting on with it</h2> <p>We'll start by loading the new <i>marsh </i>background into the <i>Ground </i>layer of the game's view, instead of the blank rectangle that the project uses by default. Open the game.lua file and change the <i>createScene</i> function to load this module as the new background, as shown in the following code snippet:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">local group = self.view<br /><b>self.Ground = require "level.marsh"(group)</b><br />self.Mobs = display.newGroup()</p></pre> <p>Then we adjust the scale of the background to make it fit into the width of the screen:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">self.Ground = require "level.marsh"(group)<br /><b>local scale = display.contentWidth / self.Ground.width<br />self.Ground.xScale, self.Ground.yScale = scale, scale</b><br />self.Mobs = display.newGroup()</p></pre> <p>If you test the code at this point, you should see a static background filling the screen behind the player ship.</p> <h3>Sliding the background</h3> <p>Pinning a moving rectangle to another moving rectangle so that it doesn't slide too far and show the back side of the world requires a little bit of math, but we can offload this math onto Corona with a little ingenuity.</p> <p>First, when the scene starts, we'll make sure that in the <i>willEnterScene </i>responder, the background is lined up, with its bottom edge along the bottom edge of the screen.</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">physics.setGravity(0, 0)<br /><b>self.Ground:setReferencePoint(display.BottomCenterReferencePoint)<br />self.Ground.xOrigin, self.Ground.y = display.contentCenterX,<br />display.contentHeight</b><br />for _, coordinates in ipairs(walls) do</p></pre> <p>Before we can start the background moving, we need to know how long it's supposed to take. This could vary from level to level in a real game, so we will let the schedule we load provide this value (the file in question doesn't exist yet; creating it will be part of our next task, so make a note that it will need to return its total length) as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">physics.setGravity(0, 0)<br /><b>self.Duration = require "level.marsh-enemies" (self)</b><br />self.Ground:setReferencePoint(display.BottomCenterReferencePoint)</p></pre> <p>Now that we know the desired length of the background scroll, we can start the transition in response to the <i>enterScene </i>event, once the scene has finished loading as shown in the following code snippet. This is where the magic happens and the explanation will follow once you've had a chance to scan it.</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">self.Exit = nil<br /><b>local bounds = self.Ground.contentBounds<br />self.Ground.Pan = transition.to(self.Ground,<br />{<br />time = self.Duration * 1000;<br />y = 0, yReference = bounds.yMin - bounds.yMax;<br />onComplete = function(object)<br />object.enterFrame, object.Pan = nil, nil<br />end<br />}</b><br />)<br />self.Lives = 2</p></pre> <p>The key here is that <i>transition.to</i> (and <i>transition.from</i>) can tween any value on any object that can be indexed, even though it usually gets applied to the visible properties of display objects. The <i>yReference </i>value has no visible effect on an object by itself; changing an object's <i>yReference </i>value changes what value its y position ends up being in its parent coordinates, but doesn't cause anything to move on the screen. However, when you combine this with continually adjusting the y position, it has the effect of changing the scale of the motion; setting the y position moves the object so that the point designated internally as its <i>yReference </i>value sits at the specified coordinate in its parent system.</p> <p>Image</p> <p>We also needed to know the distance to move the reference point in the group; the <i>marsh </i>module builds a group with its zero point, or origin, at its bottom. So eventually, we need to move the <i>yReference </i>value to a negative value equal to the functional height of the background. We figure this out from the height of its bounding rectangle.</p> <p>If you want to test this out, you'll have to temporarily replace <i>self.Duration = require "marsh-enemies" (group)</i> with <i>self.Duration = 60</i>. To temporarily sub out values like this, I often create an end-of-line comment, making the line look like the following:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>self.Duration = 60 -- require "level.marsh-enemies" (self)</b></p></pre> <p>Then, to replace the old code, I can just delete the part that says 60 &ndash;&ndash;. Once you've inserted this place-holder, you can test the code and you'll see the background crawl past. However, if you're using a tall profile device, such as an iPhone 5, you may see that the bottom edge of the background first creeps down across the empty bar at the bottom of the screen, before filling it completely, and at the end, it will expose a bar of black at the top as it creeps into place. This is because when Corona uses <b>letterbox </b>alignment, it only positions the reference frame for drawing to leave space at the edges; it doesn't actually crop out anything that was hanging over the edges of the screen. But we can do this fairly easily by adding a mask to the display stage, the way we did on scenes in Project 3 to hide bits of overhanging text during transitions. To enforce this letterboxing globally throughout the program, we can add the mask in <i>main.lua</i>.</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">require "input"<br /><b>display.currentStage:setMask(graphics.newMask("effect/masking-frame.<br />png"))<br />display.currentStage.maskX, display.currentStage.maskY = display.<br />contentCenterX, display.contentCenterY</b><br />local storyboard = require "storyboard"</p></pre> <h2>Tracking the background progress</h2> <p>We need to track how far the schedule for the level will have advanced. We want this to be as fine-grained as we can manage, so we'll check the value as frequently as Corona will do, which is every frame. This means that an <i>enterFrame </i>listener is the logical vector which is shown in the following code snippet:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>Runtime:addEventListener('enterFrame', self.Ground)<br />function self.Ground:enterFrame(event)<br />end</b><br />self.Lives = 2</p></pre> <p>For the schedule, we need to track time elapsed since the schedule starts. We'll make a note of the time when the scene begins, and post the <i>Progress </i>events that indicate what time the schedule has reached at, as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>self.Ground.Start = system.getTimer()</b><br />Runtime:addEventListener('enterFrame', self.Ground)<br />function self.Ground:enterFrame(event)</p></pre> <p>The other thing we want to track is the actual motion of the background. The first enemy we'll create is a turret, so if it doesn't appear to move in sync with the ground, the user will find that very distracting. This is a little trickier, since the ground's position and reference are both moving on the same sliding scale. Fortunately, there is another point that moves more predictably&mdash;the origin.</p> <p style="margin-left:40px;margin-right:40px"><i>The origin of an object is whichever point is considered (0, 0) for placing that object's reference point. For most objects, it's fixed at the object's center; for groups, it's (0, 0) in the group's coordinates, wherever the group's children happen to be placed in relation to it. In our case, what we're really concerned with is that the object's origin is fixed with respect to its visible contents in a way that the reference point isn't. So if the group moves down by three pixels on the screen, you can say confidently that its yOrigin value also increased by three (assuming that it's not parented to a group with a different yScale value).</i></p> <p>We can track the position of the ground's <i>yOrigin </i>value from frame to frame in order to determine how much it has visibly moved. We can include that information in the events we dispatch to supply the progress information as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">self.Ground.Start = system.getTimer()<br /><b>self.Ground.oldY = self.Ground.yOrigin</b><br />Runtime:addEventListener('enterFrame', self.Ground)<br />function self.Ground:enterFrame(event)<br /><b>scene:dispatchEvent{name = 'Progress'; time = (event.time - self.<br />Start) / 1000; delta = self.yOrigin - self.oldY}<br />self.oldY = self.yOrigin</b><br />end</p></pre> <p>Our <i>schedule </i>module will feed off these events to update its progress and trigger actions. First, however, we need something for the schedule module to do; <i>schedules </i>for levels will consist of spawning enemies at different intervals.</p> <h1>Constructing the enemy behavior</h1> <p>For the schedule to spawn new enemies, we need one to exist. We'll start with a simple one, that just moves in sync with the ground to start with. Once that works, we'll add tracking and weapon fire..</p> <h2>Getting on with it</h2> <p>Add a new file,<i> turret.lua</i>, and open it. This file will add <i>turret </i>behavior to a <i>ship </i>sprite and physics description specified in the starting project.</p> <h3>Creating an enemy</h3> <p>Add the basic description of the turret object's appearance and physics as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>local ship = require "ship.ship"<br />local category = require "category"<br />local groundFilter = {<br />groupIndex = category.enemy;<br />}<br />return function(game, x, y)<br />local self = ship.turret(game, game.Mobs.Ground, groundFilter)<br />self.x, self.y = x, y<br />self.bodyType = 'static'<br />return self<br />end</b></p></pre> <p>This creates a turret that just sits perfectly still and does nothing, which isn't very interesting. The next thing we'll do is make it move in sync with the ground by following the <i>Progress </i>events that we added to the game in the last section as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">self.bodyType = 'static'<br /><b>game:addEventListener('Progress', self)<br />function self:Progress(event)<br />if self.y then<br />self.y = self.y + event.delta<br />else<br />game:removeEventListener('Progress', self)<br />end<br />end</b><br />return self</p></pre> <p>Since each <i>Progress </i>event contains the amount the screen moved, we can move the turret by the same amount. Now that this object has some basic behavior, it's time to start linking it into the game and at aching the scheduling mechanism. Save and switch to game.lua, and add a simple table before the event definitions using the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">local scene = storyboard.newScene()<br /><b>scene.Spawn = {<br />turret = require "turret";<br />}</b><br />function scene:createScene( event )</p></pre> <p>This means that we can call <i>game.Spawn.turret(game, x, y)</i> to create a new turret at x, y in the world for <i>game</i>. However, we're not going to call it directly. We'll create a schedule that contains functions to spawn enemies in a common, shared context (the game) to save the code having to contain the same values repeated an awful lot.</p> <h1>Creating a schedule</h1> <p>In order to have our turret appear at the right point, we'll create a schedule that spawns new enemies as the level progresses.</p> <h2>Getting on with it</h2> <p>Save <i>game.lua</i> for the moment and create a new file in the <i>level </i>folder called <i>marsh-enemies.lua</i>. This file will define a <i>schedule </i>module that's 60 seconds long, so that's the first thing we'll define using the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>return function(game)<br />local duration = 60<br />return duration<br />end</b></p></pre> <p style="margin-left:40px;margin-right:40px"><i>At this point, if you previously put a placeholder duration in game.lua to test the background scroll, you can revert that to the code that uses this file.</i></p> <p>Next we'll use the schedule function (from a module that isn't created yet) to start our custom schedule function against the current game. This will take a few steps to make it fully clear, but showing you how it will be used in the following code should help you see why this setup is worth engineering:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">local duration = 60<br />schedule(game,<br />function()<br /><b>at (0.3) spawn.turret(50, -20)<br />end<br />)</b><br />return duration</p></pre> <p>Now here's the catch&mdash;the at and <i>spawn.turret</i> functions haven't been defined anywhere yet, and they won't be made local in this file or defined as globals, even though this function uses them as global. Our <i>schedule </i>function will create them in a custom environment.</p> <p>So for each <i>schedule </i>function, we'll create an environment that contains simplified actions on the game the schedule is for, and use it for the function that defines the schedule. We'll combine this with making the schedule function part of a coroutine, so that it can suspend itself, such as when it is waiting for a particular time to come up in the schedule.</p> <p>So, before moving on, load the module you're about to create into <i>marsh-enemies.lua</i> using the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>local schedule = require "schedule"</b><br />return function(game)<br />local duration = 60</p></pre> <h3>Building a schedule framework</h3> <p>Save <i>marsh-enemies.lua</i> and create the file<i> schedule.lua</i> at the top of your project. This le won't actually be very long. The core is a function that starts each newly created coroutine, at aching the environment supplied to the schedule, running that schedule until it's complete, and finally disconnecting the schedule from the game so that it won't throw errors or take up processing time as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>local function bind(game, listener, actions, schedule)<br />setfenv(schedule, actions)<br />schedule()<br />game:removeEventListener('Progress', listener)<br />end</b></p></pre> <p>The rest of the module will be a function that does the work of setting it up. It'll create an environment that contains bridge actions to the main game, start a coroutine using our glue function, and start that coroutine with the <i>schedule </i>function and environment. This coroutine will wake up every time the <i>game </i>object receives a <i>Progress </i>event to see if there are any enemies it needs to spawn, create those required, and go back to waiting.</p> <p>We'll start by creating a blank environment and our coroutine:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">game:removeEventListener('Progress', listener)<br />end<br /><b>return function(game, schedule)<br />local actions = {}<br />local self = coroutine.wrap(bind)<br />end</b></p></pre> <p style="margin-left:40px;margin-right:40px"><i>Unlike coroutine.create, coroutine.wrap returns a function that resumes the new coroutine each time it's called. It's usually a little more convenient, but be a little careful with coroutine.wrap because if any error is thrown inside the coroutine, it will bubble right up and affect the code calling the resume function.</i></p> <p>We'll then connect the new coroutine to be resumed for each Progress event sent to the game using the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">local self = coroutine.wrap(bind)<br /><b>game:addEventListener('Progress', self)</b><br />end</p></pre> <p>We'll create a listener that stops feeding new Progress events into the schedule module when the game ends, such as when the player loses all of his or her lives as shown in the following code. This efiectively terminates the schedule; there's no way anymore to resume it and it will get garage-collected.</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">game:addEventListener('Progress', self)<br /><b>local function close(event)<br />if event.action == 'ended' then<br />game:removeEventListener('Progress', self)<br />game:removeEventListener('Game', close)<br />end<br />end<br />game:addEventListener('Game', close)</b><br />end</p></pre> <p>Then, we'll start the coroutine with its schedule and environment, as well as the information it needs to clean itself up, and return the new coroutine in case the calling code has some use for it:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">game:addEventListener('Game', close)<br />self(game, self, actions, schedule)<br />return self<br />end</p></pre> <h3>Building the scheduled actions</h3> <p>Of the two functions we've described, the at action is the simpler one. It checks the elapsed time in the <i>schedule </i>module; if it's not enough, it yields to keep waiting, but if its designated time has arrived, it returns from its loop and lets the schedule advance. This means the code is very straightforward as follows:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">local actions = {}<br /><b>function actions.at(time)<br />repeat<br />local progress = coroutine.yield()<br />until progress.time &gt;= time<br />end</b><br />local self = coroutine.wrap(bind)</p></pre> <p style="margin-left:40px;margin-right:40px"><i>Calling the function at may seem a little strange, but it allows the schedule calls to read much more like normal language. We could have called the function waitUntil and written the function waiting for it on another line, but Lua's loose syntax allows us to use this very compact format.</i></p> <p>The <i>spawn </i>functions are a little more complex. We could simply build them all like the following:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">function actions.spawn.turret(x, y)<br />return game.Spawn.turret(game, x, y)<br />end</p></pre> <p>However, since each one would follow the same pattern, we can use another pattern based on metatables and the <i>__index </i>lookup, the <b>self-populaing table</b>. <i>__index</i> on a table's metatable is only used when the requested key isn't in the original table; this means that the function that creates the requested value can store it in the table, and next time, it will simply be retrieved from the table instead of being looked up in the <i>__index</i> table again. This makes the spawn action family easy to summarize in one block.</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>actions.spawn = setmetatable({},<br />{<br />__index = function(t, name)<br />local function cue(...)<br />return game.Spawn[name](game, ...)<br />end<br />t[name] = cue<br />return cue<br />end<br />}<br />)</b><br />local self = coroutine.wrap(bind)</p></pre> <p>That's actually all there is to the schedule system! You can test it out and watch the turret appear in the upper-left hand slide-down corner across the screen, mindlessly facing right and doing nothing.</p> <h2>Bringing an enemy to life</h2> <p>Making the turret work requires only two main steps. The first is to have it track the player. Since the <i>game </i>object keeps a reference to the <i>Player </i>object, this boils down to basic trigonometry.</p> <p>Open <i>turret.lua</i> and add a line to the <i>Progress </i>handler as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">if self.y then<br />self.y = self.y + event.delta<br /><b>self.rotation = math.deg(math.atan2(game.Player.y - self.y,<br />game.Player.x - self.x))</b><br />else</p></pre> <p>The arctangent of the y and x distances gives the facing direction from the turret to the player, which we can use as the rotation angle to make the turret point at the player. If you try the code again you should see the turret stay pointed at the player as it scrolls down and the player moves. The last step is to give the turret a weapon. While we have the turret file open, have it start firing as soon as it is created:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">end<br /><b>self.Weapons.AntiAir:start(self, 100, 0)</b><br />return self</p></pre> <p>This depends on the turret having a weapon called <i>AntiAir</i>, which in this case was already created in the existing partial code.</p> <p>The turret is complete for the time being. If you test the code, it should shoot at the player, and bullets fired at it should vanish when they hit it. Currently, however, nothing gets destroyed no matter how many bullets hit it.</p> <h2>What did we do?</h2> <p>A lot of stuff happened in this section! Using coroutines to track a continuously advancing schedule of new enemies, using Lua environments to wrap up some complicated actions in some simple wrappers, and adding an advancing series of progression data to drive everything else.</p> <h2>What else do I need to know?</h2> <p>Environments are a powerful feature of Lua; each function has an associated environment which is just a table linked to that function. Whenever a function needs to use a global, it looks up the global name as a string key in that table. By changing a function's environment, we can give it access to a totally different set of global functions. The real power here will come from the fact that the environment that we link to our schedule will be stocked with functions that use the normal environment, and can therefore take actions whose particulars are hidden from the code using them.</p> <p style="margin-left:40px;margin-right:40px"><i>Lua creates a default environment to link to its code when it's started, containing all the standard global functions, and stores a link to that environment in that environment under the name _G, which you've probably used already.</i></p> <h1>Scripting behavior</h1> <p>An enemy that just sits and shoots predictably quickly stops presenting much of a challenge. Our next enemy will be capable of steering around the screen according to a predesigned path, but since the path will be managed through a schedule similar to the one we use to spawn enemies, a different path can be given to each enemy.</p> <h2>Getting ready</h2> <p>This new enemy will be a fighter craft equipped with a single forward-facing machine gun that can be turned on and off. It can face a given direction, set its speed, and adjust its facing over time to create more naturally curved paths. We'll use the third entry in ship/sprite. lua to represent this ship.</p> <h2>Getting on with it</h2> <p>Prepare a new file in the top level of the project called <i>dogfighter.lua</i>. It will start much like the <i>player.lua</i> and <i>turret.lua</i> files. Notice that this function takes an extra argument&mdash;the function that describes its orders as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>local ship = require "ship.ship"<br />local category = require "category"<br />local enemyFilter = {<br />groupIndex = category.enemy;<br />}<br />return function (game, x, y, path)<br />local self = ship.fighter(game, game.Mobs.Air, enemyFilter)<br />self.x, self.y = x, y<br />return self<br />end</b></p></pre> <p>Because the fighters will face down by default, we'll rotate it in the opposite direction from the player. To keep the shadows and highlights on the sprite still appearing to come more or less from the same side, we'll reverse the y scale.</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">self.x, self.y = x, y<br /><b>self.rotation = 90;<br />self.yScale = -1;</b><br />return self<br />end</p></pre> <p>We'll set a Speed value that will hold the ship's desired scalar velocity, without regard to which direction it's going in or how that velocity will be broken down into the x and y speeds:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">self.rotation = 90;<br />self.yScale = -1;<br /><b>self.Speed = 0</b><br />return self<br />end</p></pre> <p>We'll use a <i>plan </i>module, to be created next, to generate a coroutine that will carry out the requested orders, similar to the <i>schedule </i>module:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">self.Speed = 0<br /><b>local plan = require "plan" (self, path)</b><br />return self</p></pre> <p>Finally, we'll start a timer to update this coroutine according to the passage of time, and store this timer so that we can stop it when the ship is destroyed or removed:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">local plan = require "plan" (self, path)<br /><b>self.Guidance = timer.performWithDelay(1000/application.fps, plan,<br />0)</b><br />return self</p></pre> <h3>Writing a ship control script</h3> <p>To get a notion of what commands our plan module needs to support, let's lay out a control script first. The ship will start by facing down across the screen, move forward at a set speed, and start firing as it travels down. After a second, it will veer off to the left and stop firing. This function will be laid out in the <i>marsh-enemies.lua</i> module, so save <i>dogfighter</i>. lua and reopen that module to add the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">function()<br />at (0.3) spawn.turret(50, -20)<br />at (0.5) spawn.defender(140, -10,<br />function ()<br />face(90)<br />go(100)<br />fire("MachineGun", 50, 0)<br />after (1) turn(60, 0.75)<br />release("MachineGun")<br />end<br />)<br />end</p></pre> <p>So, this gives us at least six functions to support:</p> <ul> <li> <p><i>face()</i> will instantly set the ship to face the specified direction.</p> </li> <li> <p><i>go()</i> will set the ship's forward velocity to the specified speed.</p> </li> <li> <p><i>turn()</i> will adjust the ship's facing by the specified angle relative to its current facing, over the specified amount of time.</p> </li> <li> <p><i>after()</i> will wait until the specified amount of time has passed since <i>after()</i> was called, then proceed. It's not quite the same as <i>at()</i> in the level schedule, because that waits for specific intervals from the start of the level.</p> </li> <li> <p><i>fire()</i> and <i>release()</i> will start or stop the specified weapon. The same key should be used that was used to identify the template in the ship's Weapons table.</p> </li> </ul> <p>The <i>dogfighter </i>module uses the <i>plan </i>module to support these functions, so save <i>marsh-enemies</i>.lua and create the new module, <i>plan.lua</i>, in the top level of the project. This file will start off with a skeleton similar to that of <i>schedule.lua</i>, as shown in the following snippet, with which it has a great deal in common:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;"><b>local function bind(path, actions)<br />end<br />return function(self, path)<br />end</b></p></pre> <p>The plan will have the same sort of glue function, which will at ach a set of actions to the plan function in the same way:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">local function bind(path, actions)<br /><b>setfenv(path, actions)<br />path()</b><br />end</p></pre> <p>However, where schedules are intended to run for a fixed amount of time, a ship's orders need to be carried out as long as it's alive, which could be highly variable. But since we expect plans to run off of a timer that will repeat indefinitely, this makes them more uniform to cancel once the plan function is exhausted. We just wait for one more firing of the timer so that we can get the timer source to cancel it.</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">path()<br /><b>local finalize = coroutine.yield()<br />timer.cancel(finalize.source)</b><br />end</p></pre> <p>The code to start a new plan from the glue function and arguments will be almost identical to that of <i>schedule.lua</i>:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">return function(self, path)<br /><b>local actions = {facing = self.rotation}<br />local plan = coroutine.wrap(bind)<br />plan(path, actions)<br />return plan</b><br />end</p></pre> <p style="margin-left:40px;margin-right:40px"><i>The ship control code will be able to refer to <i>facing</i> as a global variable to determine the angle at which the ship is pointing, although setting this global will not turn the ship. This is convenient for scripts that might want smarter behavior.</i></p> <h3>Defining ship actions</h3> <p>The easiest actions to implement will actually be the fire control ones:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p>We'll put a sanity check in just so that if a designer (or programmer) misspells a weapon name or otherwise tries to fire a non-existent weapon, it doesn't crash the ship's entire plan:</p> <p>We'll take a similar approach with the <i>release</i> function, but as a convenience, it can be called without any name to stop all firing weapons:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p>The <i>go</i> function is one of the functions that I was referring to when I mentioned that some functions get simpler when the base ship images are pointed to the right. It also stores the scalar speed to facilitate turning functions:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p style="margin-left:40px;margin-right:40px"><i>The Corona rotation properties are measured in degrees, but Lua math functions return and expect angles in radians. The math.deg and math.rad functions help plug this gap.</i></p> <p>Finally, we get to use turn(). This is a slightly more complicated function because it keeps control of the ship until the turn is complete. For convenience, it expects durations in seconds, not milliseconds, as shown in the following code:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p>It needs to know how much time has passed since the last time it was checked, so it starts by noting the time it started:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p>The function will continue reducing the duration by the elapsed time whenever it gets notified of time passing, until the complete duration has passed:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p>To turn the ship smoothly, it will see what portion of the turn duration has just elapsed (maxing out at <i>all of it</i>), and turn by that large a slice of the remaining angle:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p>Finally, it will reduce the remaining angle to turn by the amount it just turned by:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <h3>Adding a new ship to the level</h3> <p>Open <i>game.lua</i> and add the <i>dogfighter</i> module to the list of elements the game can create:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p>If you test the code at this point, you will see a ship fly down across the screen and bank, although it should not fire yet.</p> <h3>Adding weapon fire</h3> <p>Save and move to the file <i>ship.lua</i> in the <i>ship</i> folder of your project directory. Find the section where it declares the <i>fighter</i> description, and fill a new entry into the Weapons table:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <p>If you test the code again, the enemy ship should fire as it comes down from the top of the screen.</p> <h2>What did we do?</h2> <p>We expanded the principles used for the <i>schedule</i> module to run actions in an even more specific context by using a single ship as the basis for the environment applied to the new plan. This system opens up a lot of options for producing varied ship behaviors to challenge and engage the player. Ships can fly in formation, appear in waves, and use various fire patterns to spray the screen with bullets.</p> <h2>What else do I need to know?</h2> <p>There's one important caveat to using functions with environments like this, in Lua, functions are actually objects; this means that if you set different environments on a function, the environment more recently set is used. And if you have multiple references to a function, setting the environment on one of them changes all of them. This means that if you try to use the same function as a plan for more than one ship at once, very strange things may happen, as if all the guidance systems were interfering.</p> <p>The easiest way to deal with this is to make plan factories, functions that return a new copy of a function each time they're called. Each such closure, or instance of function code, has its own upvalues and its own distinct environment. By the end of the project, we'll have constructed several of these.</p> <h1>Controlling the boss</h1> <p>No game like this is complete without an awe-inspiring boss craft or base that offers a more complex fight. Because boss crafts don't typically fly around in circles, a new action is called for in <i>plan.lua</i>; hover. It's very similar to <i>turn</i> in its implementation, so we won't cover it in too much detail.</p> <h2>Getting on with it</h2> <p>First, open <i>plan.lua</i> and add the hover function to the environment:</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <h2>Creating the boss object</h2> <p>Now that the AI script actions are complete, we'll create the file that will accept these actions and advance the game when a boss is destroyed. Make a copy of dogfighter.lua in the top level of the project and name it boss.lua. The following are the steps to create a boss object:</p> <ol> <li> <p>The <i>module</i> function will accept a flight <i>plan</i> function like the <i>dogfighter</i> module does, but also accepts a function that dictates what will happen when the boss is defeated:</p> <pre><p style="white-space: pre;">code1</p></pre> </li> <li> <p>Record the <i>advance</i> function in the boss's table so that we can retrieve it and run it when the boss dies:</p> <pre><p style="white-space: pre;">code1</p></pre> </li> </ol> <p>Using a supplied function gives us choices about how bosses appear. For instance, you could make a level where a boss appeared halfway through, and its defeat started a new schedule to proceed through the rest of the level.</p> <h3>Making the boss module available</h3> <p>In order for the schedule to be able to spawn a new boss object, the game's spawn table needs a reference to the module. Open game.lua and add a new line to the table near the top of the file.</p> <pre style="margin-left: 40px;"><p style="white-space: pre;">code1</p></pre> <h3>Driving the boss's behavior</h3> <p>To launch the boss at the end of the level, we'll modify the level schedule. Open <i>marsh-enemies.lua</i> and find the end of the function being passed to the schedule.</p> <ol> <li> <p>We'll add an entry a second or two before the schedule runs out to create the <i>boss</i> object:</p> <pre><p style="white-space: pre;">code1</p></pre> </li> <li> <p>Because it's easy in this case, we'll specify what happens when the boss is defeated first:</p> <pre><p style="white-space: pre;">code1</p></pre> </li> <li> <p>Then we'll create a function that returns a set of flight orders moving in a figure-eight pattern; first, the boss will move down from its spawning point at the top of the screen and slide over to one corner of its loop:</p> <pre><p style="white-space: pre;">code1</p></pre> </li> <li> <p>Then, its flight plan will go into a loop that will last until it is destroyed, moving down, then up and over, and then the same on the other side.</p> <pre><p style="white-space: pre;">code1</p></pre> </li> <li> <p>As the boss moves, it will switch some of its guns on and off as it changes direction. Because these calls are repetitive, the flight plan can define reusable functions for them.</p> <pre><p style="white-space: pre;">code1</p></pre> <p style="margin-left:40px;margin-right:40px"><i>This ability to keep using loops, functions, and other programming semantics inside a sandbox like this makes it especially powerful.</i></p> </li> <li> <p>Once it calls those functions appropriately, the flight plan is complete.</p> <pre><p style="white-space: pre;">code1</p></pre> </li> </ol> <h3>Handling the boss's defeat</h3> <p>The default <i>onDestroy</i> handler falls short in two regards where the boss is concerned. It needs to end the level (as determined by the boss's advance function), and frankly, one little explosion isn't impressive enough for a boss's victory. The following are the steps to handle the boss's defeat:</p> <ol> <li> <p>Open <i>ship.lua</i> and frame in an <i>onDestroy</i> override for the <i>dreadnought</i> entry:</p> <pre><p style="white-space: pre;">code1</p></pre> </li> <li> <p>Most of the functions are still the same as the default <i>onDestroy</i> function, so we'll start by bringing them in. However, we'll hold off on removing the <i>boss</i> sprite as shown in the following code:</p> <pre><p style="white-space: pre;">code1</p></pre> </li> <li> <p>First, we'll start a repeating timer to set off explosions continuously on top of the <i>boss</i> sprite:</p> <pre><p style="white-space: pre;">code1</p></pre> </li> <li> <p>Then, we'll start a one-use timer that will clean things up. It will stop the endless explosions, remove the boss, and trigger the boss's <i>Advance</i> function.</p> <pre><p style="white-space: pre;">code1</p></pre> </li> </ol> <p>Test things out! Once you make it past the other enemies and dodge the boss's guns, you'll see the boss going down in a blaze of glory. Having your character be invulnerable during development makes things like this much easier to test out.</p> <h2>What did we do?</h2> <p>We expanded the selection of actions available to the enemy's AI code. We created a new object type with a continuously repeating set of controls and internal logic to its actions. We also used one timer to regulate another, in order to create a visually interesting effect with a fixed duration.</p> <h2>What else do I need to know?</h2> <p>If you want to create functions for your control coroutines, they need to be defined inside the function that contains the instructions. Otherwise, they'll be created without the special environment that provides their instructions, and they won't respond properly.</p> <h1>Summary</h1> <p>This was a serious endeavor! This system has several complex elements and uses all three major elements of advanced Lua: metatables, environments, and coroutines. It combines the special controls of smartphones with a classic retro style of arcade game play.</p> <h2>Resources for Article :</h2> <p>&nbsp;</p> <hr size="1" noshade="noshade" /> <p><b><a name="more"><span style="color: #000000;">Further resources on this subject:</span></a></b></p> <ul> <li><a href="http://www.packtpub.com/article/installing-alfresco-software-development-kit-sdk" target="_blank">Installing Alfresco Software Development Kit (SDK)</a> [Article]</li> <li><a href="http://www.packtpub.com/article/getting-started-with-flash-and-facebook" target="_blank">Getting Started with Flash and Facebook</a> [Article]</li> <li><a href="http://www.packtpub.com/article/inserting-updating-deleting-sorting-grouping-displaying-data-grid-silverlight" target="_blank">Data Manipulation in Silverlight 4 Data Grid</a> [Article]</li> </ul> <hr size="1" noshade="noshade" />

About the Author :


Books From Packt

<hr /> <table id="table42" style="border-collapse: collapse;" border="0" bordercolor="#cc6600" cellpadding="3" width="588"> <tbody> <tr> <td valign="top" width="68"> <p align="center"><span style="font-family: Verdana; font-size: xx-small;"> <a href="http://www.packtpub.com/drupal-6-javascript-and-jquery/book/sl/typoext-abr/1108"> <img style="border: 1px solid #000000;" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://images.packtpub.com/images/50x61/1847196160.png" border="0" alt="Drupal 6 JavaScript and jQuery: RAW" title="Drupal 6 JavaScript and jQuery: RAW" width="49" height="61" /></a></span><br /> <a href="http://www.packtpub.com/drupal-6-javascript-and-jquery/book/sl/typoext-abr/1108"> <span style="font-family: Verdana; color: #ff0000; font-size: xx-small;">Drupal 6 JavaScript and jQuery: RAW</span></a></p> </td> <td valign="top" width="68"> <p align="center"><span style="font-family: Verdana; font-size: xx-small;"> <a href="http://www.packtpub.com/wordpress-theme-design/book/sl/typoext-abr/1108"> <img style="border: 1px solid #000000;" class="style7" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://images.packtpub.com/images/50x61/1847193099.png" border="0" alt="WordPress Theme Design" title="WordPress Theme Design" width="50" height="62" /></a><br /> <span><span class="style1"> <a href="http://www.packtpub.com/wordpress-theme-design/book/sl/typoext-abr/1108"> <span style="color: #ff0000;">WordPress Theme Design</span></a></span></span></span></p> </td> <td valign="top" width="68"> <p align="center"><a href="http://www.packtpub.com/wordpress-for-business-bloggers/book/sl/typoext-abr/1108"> <img style="border: 1px solid #000000;" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://images.packtpub.com/images/50x61/1847195326.png" border="0" alt="WordPress for Business Bloggers" title="WordPress for Business Bloggers" width="49" height="61" /></a><span style="font-family: Verdana; font-size: xx-small;"><br /> <span> <span class="style1"><span style="color: #ff0000;"> <a href="http://www.packtpub.com/wordpress-for-business-bloggers/book/sl/typoext-abr/1108"> <span style="color: #ff0000;">WordPress for Business Bloggers</span></a></span></span></span></span></p> </td> <td valign="top" width="68"> <p align="center"><span style="font-family: Verdana; font-size: xx-small;"> <a href="http://www.packtpub.com/wordpress/book/sl/typoext-abr/1108"> <img style="border: 1px solid #000000;" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://images.packtpub.com/images/50x61/1904811892.png" border="0" alt="WordPress Complete" title="WordPress Complete" width="50" height="61" /></a><br /> </span> <a href="http://www.packtpub.com/wordpress/book/sl/typoext-abr/1108"> <span style="font-family: Verdana; color: #ff0000; font-size: xx-small;">WordPress Complete</span></a></p> </td> <td valign="top" width="68"> <p align="center"><a href="http://www.packtpub.com/drupal-6-create-powerful-websites/book/sl/typoext-abr/1108"> <img style="border: 1px solid #000000;" class="style7" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://images.packtpub.com/images/50x61/1847192971.png" border="0" alt="Building Powerful and Robust Websites with Drupal 6" title="Building Powerful and Robust Websites with Drupal 6" width="49" height="61" /></a><br /> <span style="font-family: Verdana; color: #ff0000;"> <span class="style9"> <a href="http://www.packtpub.com/drupal-6-create-powerful-websites/book/sl/typoext-abr/1108"> <span class="style1"><span style="color: #ff0000; font-size: xx-small;">Building Powerful and Robust Websites with Drupal 6</span></span></a></span></span></p> </td> <td valign="top" width="68"> <p align="center"><span style="font-family: Verdana; font-size: xx-small;"> <a href="http://www.packtpub.com/joomla-version-1-5/book/sl/typoext-abr/1108"> <img style="border: 1px solid #000000;" class="style7" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://images.packtpub.com/images/50x61/184719530X.png" border="0" alt="Building Websites with Joomla! 1.5" title="Building Websites with Joomla! 1.5" width="50" height="62" /></a><br /> <span><span class="style8"> <a href="http://www.packtpub.com/joomla-version-1-5/book/sl/typoext-abr/1108"> <span class="style1"><span style="color: #ff0000;">Building Websites with Joomla! 1.5</span></span></a></span></span></span></p> </td> <td valign="top" width="68"> <p align="center"><a href="http://www.packtpub.com/drupal-6-themes/book/sl/typoext-abr/1108"> <img style="border: 1px solid #000000;" class="style7" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://images.packtpub.com/images/50x61/1847195660.png" border="0" alt="Drupal 6 Themes" title="Drupal 6 Themest" width="50" height="62" /></a><br /> <span style="font-family: Verdana; color: #ff0000;"> <span class="style9"> <a href="http://www.packtpub.com/drupal-6-themes/book/sl/typoext-abr/1108"> <span class="style1"><span style="color: #ff0000; font-size: xx-small;">Drupal 6 Themes</span></span></a></span></span></p> </td> <td valign="top" width="68"> <p align="center"><a href="http://www.packtpub.com/Professional-Plone-web-applications-CMS/book/sl/typoext-abr/1108"> <img style="border: 1px solid #000000;" class="style7" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="http://images.packtpub.com/images/50x61/1847191983.png" border="0" alt="Professional Plone Development" title="Professional Plone Development" width="50" height="62" /></a><br /> <span style="font-family: Verdana; color: #ff0000;"> <span class="style9"> <a href="http://www.packtpub.com/Professional-Plone-web-applications-CMS/book/sl/typoext-abr/1108"> <span class="style1"><span style="color: #ff0000; font-size: xx-small;">Professional Plone Development</span></span></a></span></span></p> </td> </tr> </tbody> </table> <hr />
No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
T
G
j
H
6
1
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software