Sunday, September 30, 2012

Tutorial 19 - Integrating Game Centre (Part 1)

19.1 Game Center

   
Game Center is Apple’s social gaming network. Integrating Game Center functionality into your Codea App has to be done in Xcode. There are three main areas that we need to concern ourself with to implement Game Center:

  1. Players;
  2. Scores; and
  3. Achievements.

All of your metadata for Game Center functionality is set up and managed in iTunes Connect, allowing you to test your Game Center features before submitting your app to the App Store. You use iTunes Connect to enable your app for Game Center testing, and set up your leaderboards and achievements. Then use the Game Kit framework in your app to add Game Center functionality.
   
Apple suggests that you should consider scores and achievements as part of your initial game design (as opposed to tacking it on at the end). This is a legitimate observation in most cases, for example passing other players in DoodleJump is arguably why it is so popular.
  
Be aware that once your Game Center assets are published to the live servers, some assets become more difficult to change because they are already in use by players and on live versions of your game. For example, leaderboard scores are formatted using the leaderboard assets you created. If you change your scoring mechanism and change your leaderboard assets to match, older scores would still be posted on Game Center and would be inconsistent with the newer scores. For this reason, some assets you create cannot be modified after the game ships.
    

19.2 Why Bother?

   
Many iOS games use Game Center, but not all of them use every feature. Apps can choose to include any or all of the following features supported by Game Center:
  • Leader-boards – compares scores with the player's friends and with other players from around the world
  • Achievements – shows goals that can be accomplished by the player and also allows the player to compare with friends' achievements
  • Multiplayer – the game can host matches in real time, either between the player's friends or by "auto-matching" with random players from around the world.
Some of the reasons that you may want to include Game Centre are:
  1. Improve the longevity and replayability of your game by adding challenges (aka Achievements) or multiplayer capability.
  2. Being able to see usage of your Apps via leaderboard activity.
  3. Improve the discoverability of your Apps through players challenging their friends and the new facebook "like" button.
  4. Allows players to rate your App from Game Center (if your App is rubbish this may not be a good thing).
To setup Game Centre there is code that we need to add to the App and then metadata which we need to add to iTunes Connect for the App. This tutorial will deal with the App side changes.
      

19.3 Step 1 - Authenticate the Player

     
To demonstrate the implementation of Game Center, we will use the MineSweeper App developed in previous tutorials. We are assuming that you are familiar with the Codea runtime and have got your App running in Xcode. If you haven't then read Tutorials 12 and 13 first. 
     
    
To start, open up your App in Xcode. The first thing we need to do is to link the GameKit framework. In the navigator area of Xcode 4, select the project name in the top left, it should be “CodeaTemplate”. Next, select the current target (“MineSweeper” in our case), and then select the “Build Phases” tab. Expand the “Link Binary With Libraries” option, and then click the “+” button to add a new framework. Type “g″ into the search box, and select the GameKit.framework framework that appears in the list. Click “Add” to link this framework to  your project. So far so good.

Once again in the Project Navigator pane, Under Classes -> Supporting Files, click on the CodifyAppDelegate.h file and add:
  
#import <GameKit/GameKit.h>
  
Your App will now recognise the Game Center methods and variables. If we weren't using Codea, setting up Game Center is pretty simple, to illustrate:
 
Click on CodifyAppDelegate.m and add the following to the end of the didFinishLaunchingWithOptions method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Existing code here...

    // Authenticate Player with Game Center
    
    GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
    
    // Handle the call back from Game Center Authentication
    
    [localPlayer authenticateWithCompletionHandler:^(NSError *error)
    {
        if (localPlayer.isAuthenticated)
        {
            // Player was successfully authenticated.
            // Perform additional tasks for the authenticated player.
        }
        else if (error != nil)
        {
            NSLog(@"error : %@", [error description]);
        }
    }];

    // ...and back to the existing code.

    Return YES;
 
}

This is the code for pre-iOS 6 (since I have an iPad 1) devices. If you are developing for iOS 6, note that authenticateWithCompletionHandler is deprecated (use localPlayer.authenticateHandler instead).
  
Running this code in the Xcode simulator should present you with the pop up shown in Figure 1. Log in with your usual App ID to test.
  
Figure 1. Game Center Sign In.
  
If you haven't enabled your game for Game Center, once you log in you will get the error shown in Figure 2.
  
Figure 2. Game Center not enabled Error.

The problem is that we need to be able to submit scores and achievements from within our Codea App and Codea doesn't know about Game Center. However, one of the gun coders, @juaxix over on the Codea Forums has built a bridge between Objective C and Lua. We will extend this and use it to enable Game Center for MineSweeper. So delete the authentication code above if you added it to your App and we will show you the correct way to bring Game Center functionality to your Codea App.

In the next tutorial we will show you how to add leader boards and achievements in iTunes Connect. We will need these before we can make the changes required to our Lua and Objective C code.

Saturday, September 29, 2012

Tutorial 18 - Saving and Loading Complicated Tables



18.1 Recap

   
Tutorial 18 brings together elements from a number of the previous Tutorials. It is an example of where asking the right question makes finding the answer much simpler. Our aim is to be able to load and save level data from our Tower Defence level generator. Most of the data we need is stored in a table. The tricky part is that Codea only allows us to save data as strings at the moment (some smart folks over at the Codea Forum have worked out how to jam data into image files and save those).
  
Tutorial 16 examined a technique for saving simple table structures like a one dimensional array. Initially we thought that saving a more complicated table wouldn't be that tough. All we needed was one of the existing Lua table to string parsers like Pickle / UnPickle. WRONG!
    

18.2 Limitations of Table Serialisation Parsers

  
We discussed Lua tables in Interlude 6. They are simple in concept but capable of representing very complicated structures. Tables can contain all the simple Lua types (booleans, strings, nil, and number), functions, other tables, closures, etc. The most complete table to string parser that we could find was Data Dumper. But even Data Dumper can not handle tables that contain:
  • Both light and full userdata
  • Coroutines
  • C functions
And of course our level data contains userdata (e.g. vec2 is represented using userdata). So where to from here?
    

18.3 The Modified Cell Class

   
This is where asking the right question makes all the difference. Looking at which data we actually need to save, it becomes apparent that we don't need to save the entire table (including functions) as this can all be reconstituted. All we really need from the Grid table is what is contained in each cell. So rather than trying to convert a two dimensional array of cell objects (which contain userdata) into a string, we extend the cell class to provides its contents (state) as a string. We also provide the inverse function which sets the cell state from a string so we can load the level back in. The extended cell class is shown below.
  
  
Once we can get the grid cell contents out as a string it is easy to write a function to create a simple two dimensional table holding each of these strings. Data Dumper makes short work of converting this table to a string which we can save to global data. Even better, Data Dumper saves the table in a format that loadstring can use to rebuild the original table. The updated Main class for dGenerator details these save and load functions.
  

We updated the File Manager class from Tutorial 17 to allow us to select the file to be loaded. As part of this update we got rid of the submenus because we didn't need them and they meant that the user had to do an extra tap to load. In addition, Delete was right next to Load which is poor design. One slip of the finger would be potentially disastrous (particularly because there is no confirmation for delete and no undo). The other improvement to this version of File Manager is we truncate keys and values which are larger than the ListScroll widths. You can see the upgraded version in the screenshot below. The About menu item doesn't do anything at this stage.
     

18.4 Loading & Saving Data

   
Tapping the Save button on the main screen of dGenerator will call the saveLevel() function in Main(). The function starts off by saving the simplified table data in saveGrid. It then generates the key which contains a header "dGen" (to indicate when loading if it is the right data type), the game name and level number, a boolean indicating if the start cell has been selected and its co-ordinates and then a boolean indicating if the end cell has been selected and its co-ordinates. Finally we use Data Dumper to convert saveGrid to a string and save the key and value to global data.
  
Loading a level is the reverse. Tapping the Load button on the dGenerator main screen will call the loadLevel() function in Main. This prints out some debug data but its only compulsory action is to set the App state to stateFileManager. This will draw the File Manager instead of the main screen and handle its touches.
  
Once the user has navigated to global data and selected an appropriate key containing level data, tapping load on the File Manager menu bar will call the loadFile() function in Main. This function splits the key back into its component parts using the explode() helper function and then uses loadstring() to create a function which rebuilds the data table. The createGrid() function was updated so that it can be loaded using this data table.
  
And that is all there is to it. You can use this link to download the entire code including the updated classes.
  
Next up we will look at adding creeps to your levels in a number of waves.

Thursday, September 20, 2012

Tutorial 17 - A Simple File Manager for Codea



17.1 The Main Class

         
Tutorial 16 explained the various ways that data can be stored using Codea. In order to facilitate the loading and storing of data for our level generator we created a simple file management class.
    
This version handles Local Data, Project Data, Project Information and Global Data. Remember that all of this data is stored in string format using a pList which is accessed by a unique string called the key.
    
The File Manager is constructed using two ListScroll objects (a class written by sanit over on the Codea Forum), a text box showing the value of the selected key and a MenuBar object (a class written by dave1707 also from the Codea Forum).
   
The Main class is used to load some test data into the four data stores and then instantiate the FileManager class. We have provided stubs for the functions called when the MenuBar items are tapped. The only menu item which has been implemented is delete file. WARNING - file deletion can NOT be undone and there is no confirmation required to delete a file so back up your data and use this class with care (or delete this functionality). Feel free to delete the test data which is loaded in setup(), this will be recreated every time you run the App.
  
The data you need to take particular care with is Global Data as this may have been stored by another Codea App. For example, if you have used Spritely (the sprite creation example program which comes with Codea) then any sprites that you have created will be stored in global data.

The delete function has not been implemented for Project Info because there is no way to determine what keys are in there (i.e. there is no listProjectInfo function).

To delete a file we just assign the value of the selected key to nil. We will provide examples of the load and save function when we incorporate FileManager into our Tower Defence Level Generator (dGenerator).
   
The line FileManager:init() will setup your FileManager class and prefetch the keys for each persistent data store.
       
The class handles both portrait and landscape orientation. The orientationChanged() function adjusts the FileManager position when the iPad is rotated. It works but not very well as ListScroll wasn't designed to handle this. You may have to drag the ListScroll box to show the contents. We haven't had a chance to modify this.
      

17.2 The FileManager Class

             
Most of the hard work is done in this class. We start off by defining variables to represent the four supported Directory Types to make the code easier to read. The init() function creates the two ListScrolls (one for the list of Directories called directoryList and one for the keys it contains called dataKeyList).
     
We then pre-load the keys for Project Data, Local Data and Global Data (we already know the Project Info keys since we loaded them). We set the current directory to Project Info and set the current key to the first one. There is a variable called valueString which we use to contain the value of the selected string. Only the first 150 characters of the saved value are displayed. The last thing we do in init() is setup the MenuBar.
     
One of the modifications we had to make to the MenuBar class was to prevent touches on the ListScroll when a sub menu is displayed (otherwise you could accidentally delete the wrong file).
       

17.3 Download the Code

          
You can download all of the code for this program using the following links:
  1. FileManager Complete Code v1.1 - All of the code in one file.
  2. Main.lua - The Main tab class only.
  3. FileManager.lua - The FileManager v1.1 class only.
  4. ListScroll.lua - The ListScroll class from sanit.
  5. TextItem.lua - The TextItem class used in ListScroll from sanit.
  6. MenuBar.lua - The MenuBar class v1.1 from dave1707 (modified) .
Next up we will incorporate FileManager into our Tower Defence Level Generator.

Tuesday, September 11, 2012

Interlude 12 - MineSweeper "Sales"

It has been ten days since MineSweeper was released into the wilds of the App Store so I thought I would provide an update on how downloads are going. In summary, downloads are better than I expected, given it is a clone and there are a bunch of established clones already in the store. Figure 1 shows downloads per day (you will probably have to click on the image to see a version big enough to read). 
  
Figure 1. MineSweeper Downloads By Day.
   
It shows the usual trend with a large hump on the day after release followed by a rapid fall over the next four days. You then enter a gentle oscillating phase which will gradually decline if you do nothing else. The small bump on the far right is the weekend kick which I see on most of my App sales. There is many a developer who saw their second day sales, multiplied this by their App price and then by 365 and thought they were on the road to retirement. Alas, my experience is that the post ten day average is a better indicator of long term sales.
   
Figure 2. MineSweeper Download Detail.
    
My guess is that sales will settle down around 50-100 per day, which if I was making any money from them would be fantastic. It does suggest that it would be worth adding iAds to try and extract some return from the effort of writing the App (apart from the educational benefit of course).
  
Figure 3. Early Ranking Data.
  
Looking at how the App ranks is very interesting and illustrates the mysterious Apple ranking algorithm at work. You can see that rank roughly follows downloads (with a slight lag). However, if we look over the ten day period we see a curious result.
  
Figure 4. Ranking Data at the end of the 10 day period.
      
Figure 4 shows that even though sales are not as high as day 2, the rank has continued to climb suggesting that there is some sort of cumulative effect. There is definitely a feedback mechanism whereby the higher you rank, the higher your downloads, which means the higher you rank, etc. The other observation is that it is easy to make progress when you are ranking between 200-300. Getting sub 100 is tougher and top 10 is virtually impossible (unless you call your App Angry Birds).
    
Figure 5. Downloads by Country.
   
Downloads by country contain a couple of surprises. I always expect to do best in America given it is the biggest English speaking market and the UK at number 3 and Australia at number 5 are equally predictable. Germany at number 2 is unexpected and Russia at number 4 is also a pleasant surprise. So thank you to all the German and Russian downloaders. France and China are normally up there and I don't tend to have a lot of luck with the Canadians. Where this sort of information is useful is in deciding what countries to localise your App for. There isn't a lot of text in MineSweeper so localisation would be a fairly simple matter. I have read that localisation can have a dramatic effect on downloads in that market but I havent tried it myself.
    
Figure 6. Reefwing Software Downloads by Product.
     
Figure 6 graphs downloads by product for all of my Apps over a one month period. You can see that the free Apps (MIneSweeper, Personality, LifeAudit, and Number Converter) dominate downloads, however the majority of my revenue comes from LifeGoals. Note that MineSweeper has only been available for 10 days of the month reported on in Figure 6. I will give another update after MineSweeper has been out for a month.
   
Figure 7. Early MineSweeper Reviews.
    
A factor in driving sales is review comments and the number of stars people rate your App at. Trying to bootstrap these by getting your Mum to write a review is always a strategy but in reality unless you have a LOT of friends in MANY countries, real reviews and ratings will swamp any attempt to game the system. BTW - thanks to all the folks from the Codea forum for their positive reviews!
  
Figure 8. Reefwing Software iAds Revenue.
     
The next step in this grand experiment will be to attempt to convert the MineSweeper downloads into cash. Theoretically, iAds should be a fairly good mechanism for this. However, looking at Figure 8 you will see that my current revenue from iAds in the Personality App can only be called pathetic. 
       
Let's see what MineSweeper can do...

Saturday, September 8, 2012

Tutorial 16 - Convert String to Table and Table to String

16.1 Why do we need this?

  
In our last tutorial we developed a simple level editor. This is not much use unless you can save and then load the level data produced. Codea provides a function to saveProjectData and one to saveGlobalData(). Since we would like to save our level data from dGenerator and then read it into our game program, saveGlobalData() is the function we need, as saveProjectData() saves data which is only accessible by the program which saved it.
  
Unfortunately it is not as simple as saying saveGlobalData("Level1", ourTableData) as you can only use this function to save a number, string or nil. Consequently, we need to convert our table to a string in order to save it. To load the data we need to reverse this process and reconstitute our table from the saved string.
  
We used a similar technique in MineSweeper to save and load high scores, albeit we were only concatenating three strings rather than converting a table, but the theory is the same.
     

16.2 Codea Data Persistence Under the Hood

      
There are four primary ways to save and load data in Codea (excluding image data, which we wont discuss in this tutorial). Namely:
  1. clearLocalData(), saveLocalData() and readLocalData() - used for storing information which is unique to the program and the device. Other programs and devices running a copy of the same program don't have access to this data. It is useful for things like high scores and perhaps user preferences. For each project, a key of the form Project Name_DATA is used to store local data in the com.twolivesleft.Codify.plist which may be found in the Library -> Preferences sub folder (i.e. the same location as global data).
  2. clearProjectData(), saveProjectData and readProjectData() - gets bundled with your program and is not device specific. It can be used for level data, maps, and other program specific data which doesn't change with different users. Project Data is stored in the Data pList in the Documents directory of your App.
  3. saveProjectInfo() and readProjectInfo - has similar access to Project Data (bundled with program and is not device specific) but is used for saving metadata about your App. There are two data keys which are natively supported by Codea, "Description" and "Author". The data associated with the "Description" key will show in the Codea Project Browser when this App is selected. If you are planning on using the Codea runtime, be aware that this uses the "Version" key and expects a string. If you use a number like we do then you will get an error and the runtime wont compile. The fix for this is described in our earlier tutorial on submitting an application to App store. If you whip out iExplorer you will see that all of the project information is stored in Info.plist which is part of your application bundle. This pList also includes a key called "Buffer Order" which tells the runtime which order your tabs should be loaded in.
  4. saveGlobalData() and readGlobalData() - is data available between all projects on a device. The subfolder Preferences of folder Library contains files including com.twolivesleft.Codify.plist which contains any global data that you have saved (under the key "CODEA_GLOBAL_DATA_STORE"). If you have used the sprite generator Spritely which comes with Codea, you will see that it stores some data in the global data store.
You can read more about what is happening under the hood on the Codea Wiki. Since version 1.4.3 of Codea three additional functions have been provided: listLocalData(), listProjectData() and listGlobalData(). These return the keys already saved in these data stores.
    

16.3 A Solution for Single Dimension Arrays & Tables

    
The simplest version of a Lua table is an array so lets work out a solution for that first. It is an easy matter to concatenate the elements of a table together to form a string, as long as the elements of that table are something that can be converted to a string (i.e. this wont work on a table of tables). It is easy because Lua provides a function to do this very thing. Even better you can specify a delimiter to be placed between each element in the resulting string.
  
The function looks like table.concat(yourArray, "delimiter") so:
  
table.concat({ "one", "two", "three", "four", "five" }, ",") will produce a string "one,two,three,four,five"
   
Assuming we pass in the array to our saveData() function, it will look like:
  
function saveData(mArray)
   
    local saveString = table.concat(mArray, ",")
    saveGlobalData("dataKey", saveString)
     
end
   
Obviously you could just as easily use a global array, in which case you wont need to pass the array to the function. Loading the data is a bit more work, but as with a lot of common problems, it is one which has already been solved (credit to http://richard.warburton.it for writing the explode function which does all the heavy lifting).
      
function explode(div,str) 
    
    if (div=='') then return false end
    local pos,arr = 0,{}
    -- for each divider found
    for st,sp in function() return string.find(str,div,pos,true) end do
        table.insert(arr,string.sub(str,pos,st-1)) -- Attach chars left of current divider
        pos = sp + 1 -- Jump past current divider
    end
    table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
    return arr
   
end
    
Using the above function the loadData() function is trivial, and will return your original array:
   
function loadData()
     
    local stringArray = readGlobalData("dataKey")
    return explode(",", stringArray)
     
end
     

16.4 A Solution for Multidimension Arrays & Tables

   
Once you get past the simple array or non-nested table things get complicated quickly. As you can stick just about anything in a table (e.g. another table, closures / functions, Userdata and Metatables) a comprehensive conversion function would need to take all of the possible cases into account. These table serialisation libraries exist but they are overkill for our purposes.
  
Instead we will use the charmingly named Pickle and UnPickle functions written by Steve Dekorte.
    
To be continued...

Saturday, September 1, 2012

Tutorial 15 - A* Path Finding


15.1 The Starting Point


In the previous Tutorial we implemented a very simple path finding algorithm which added or subtracted from our current (x,y) co-ordinates until we ended up at our target co-ordinates. As pointed out at the time, this approach has its limitations (e.g. it can't handle obstacles or differing terrain). There is a better way.

A* (pronounced A star) is an algorithm for calculating an efficient path between two points. This is useful for a variety of games like the real time strategy and the tower defence genre (not to mention route finding for robots and GPS devices). To illustrate how it works, assume we want to get from point A to point B. We want to be able to handle obstacles and different terrains. In order to work out the best route we divide the search area into a two dimensional grid, this allows us to represent the search area by a two dimensional array. Each item in the array represents a square on the grid which can have a state of walkable or un-walkable (if it contains an obstacle). Our path can then be represented by a list of the squares which we take to get from A to B. We could then use this in our SpaceWar! game to move the ship along the centre of each square until it reaches the destination. The centre of the square is defined as a node.
     

15.2 The Search Algorithm

  
A* uses a best-first search and finds a least-cost path from our starting node A to the target node B. This is called a distance plus cost heuristic. To do this it uses two functions:
  1. g(x) - To calculate the cost of moving between two adjacent squares (which can simulate terrain effects i.e. the movement cost); and
  2. h(x) - To calculate the distance from the current node to the target node along the path.
By overlaying a grid on the search area we have reduced the problem to a manageable number of nodes. The simplest approach to selecting a square size is to choose the same dimensions as your game sprites. This is not essential, it all depends on how much resolution you want your path to have and this will be related to how quickly the algorithm can traverse the paths and deliver a solution. The bigger the grid squares, the less precision in the path but the faster the algorithm will return a result. As with most engineering problems it will be a tradeoff (in this case between precision and speed). Note that you don't have to use a square grid, hexagon or rectangles are equally valid and the nodes can be placed at any point in your grid. Our test App uses squares which are 32 x 32 pixels in size on a 15 x 15 grid and the corresponding search is very quick even on an iPad1.
  
Starting at A, the algorithm searches outwards until it finds B, all the while keeping track of the path lengths so it knows which are the shortest and quickest. We will use two tables to keep track of things:
  1. openList - the surrounding nodes that we need to check as possible path waypoints; and
  2. closedList - the list of nodes already checked which are part of the current path.
Initially the start cell A gets added to the open list. The algorithm then examines all of the adjacent cells and if they are passable (i.e. not a wall or some other illegal terrain), they get added to the open list. Cell A then gets removed from the open list and added to the closed list as the first step along our path.
    
We then select the cell with the lowest f(x) score (which is referred to as the current square i.e curSquare in the code) and repeat the process. Note that f(x) is defined as:
  
f(x) = g(x) + h(x)
   
The path is thus generated by repeatedly going through the openList and selecting the cell with the lowest f(x) score. As mentioned above, h(x), is an estimate of the distance from the current cell to the end cell along the shortest path. There are many different ways to calculate h(x), we will use the simple approach of adding remaining distance in the y direction to the remaining distance in the x direction. Our algorithm currently only allows horizontal and vertical movement (not diagonal - which will be added in a subsequent tutorial).
  
We have ported across a Corona implementation of the A* algorithm which was a trivial exercise since it already uses Lua. Adapting it to the MineSweeper cell class was simply a matter of changing their board table variable from isObstacle to using our cell state variable.
      
         

15.3 dGenerator - A Level Editor with Path Finding

  
To illustrate the A* algorithm in action we will produce a simple level editor which could be used for a tower defence, RTS or dungeon crawler type game. To represent the game board we will reuse the MineSweeper grid code to save us some time. A collateral benefit of this tutorial is that it will demonstrate one way to use pre-rendered sprites as buttons.
  
You can use this App as follows:
  1. When dGenerator fires up, the "Start" button will be preselected and you will be shown a solid grid of bricks which represent obstacles. The four buttons enclosed by the recessed rectangle are sprites which you can place on the grid. You can only select one of these buttons at a time. With Start pressed you can select where you want your path to begin by tapping anywhere on the grid. Only one cell can be selected as the start cell and one as the end cell. The "Path" button will place blue walkable cells. These cells are areas that your character or creep can traverse. The "Wall" button will allow you to place obstacles on the grid.
  2. The "Grid" button toggles a grid overlay to assist with sprite placement. The way we implemented this grid was to have a boolean in the cell class which if true draws a rectangle around the cell. 
  3. The "A*" button will try and find a path using the A* algorithm from the start cell to the end cell (assuming you have placed these). If it can't find a path then the function will return nil. If a path is found, the co-ordinates of the path will be shown in the Output pane and a red dot will be drawn on each cell which is on the path.
We created the sprites using Sprite Something on the iPad and really recommend this as a tool for sprite development. Sprites can be saved directly to DropBox which is great for use with Codea.  
    

15.4 Downloading the Code and Graphic Assets

     
The following links will download all the code and sprites you need to get dGenerator up and going:
  1. The complete code in one file - dGenerator v1.lua
  2. The main class - Main.lua
  3. The Cell class which represents each square on the grid - Cell_v1.lua
  4. The sprite for Button implementation class - Button.lua
  5. The A* Find Path functions - FindPath.lua
  6. Our standard UIColor list - Colors v1_1.lua
  7. A handy function which creates strings from strings, numbers, booleans and tables - ToString.lua
  8. All the graphic assets sprites for the grid and button images - dGenerator Sprites

15.5 What Next?

    
The dGenerator level generator is ok for demonstrating the A* path finding algorithm but not much else at this stage. The next step is to allow the levels to be saved and then loaded by your game. Some extra sprite types would also be handy as would the ability to move diagonally. We will create a simple tower defence game to illustrate this functionality.