Today’s tutorial comes to us courtesy of Jeff Fulton, a 16-year veteran of the web and game development industries. He cut his teeth as a core developer, engineer, and technical producer at Mattel for 13 years, creating hundreds of games and web applications for Barbie.com, Hotwheels.com, PollyPocket.com, Myscene.com, and many other high-traffic branded web sites.
During the height of the indie Flash gaming boom, Jeff and his brother, Steve, ran the influential blog at www.8bitrocket.com, covering Flash and retro game development as well as reviewing the latest games and technologies. While Jeff occasionally updates 8bitrocket.com, he can be more easily found on the @8bitrocket Twitter feed while he works as the Chief Technology Officer for Producto Studios. In this position, Jeff uses Corona SDK every day to solve a multitude of application and game projects problems and opportunities.
One traditional method that game and application developers use to update content is to upload a new version of their application to the various marketplaces. In this tutorial we are going to examine another method: the use of remote data files to update content!
The game content we will be updating is a simple tile sheet. It will be updated using Alexander Makeev’s Lua-only XML parser. We will also make use of Corona’s powerful network.download() function and a remote server to store the files.
The Game Content Without On-The-Fly Updates
First, let’s look at the simple example that will make up our game content. It is a green tank from the WidgetWorx royalty free SpriteLib. It contains eight frames which, when played in order, simulate the movement of tank treads. Here is the tile sheet we will be using:
We can easily configure this tile sheet in Corona as follows:
This example uses the green_tanks.png file located in the system.ResourceDirectory (this is application main folder).
If we want to update the frames of this animation, we can simply swap out the green_tanks.png for a new file that is constructed in the same manner. For this simple example, we would need another 8 frame 32×32 file with dimensions of 256×32.
When we run this example in the simulator, it should look something like below (but of course it will be animating through the eight green tank frames):
Updating the Sprite Sheet From a Remote Source
We can easily update the tile sheet from a remote source by using Corona’s network.download() API. We have placed a new file with a set of blue tanks on our server, called blue_tanks.png. It looks like this:
We can simply load a different file into the tilesheetFileName local variable and display it. If the file does not exist (“404” is returned) or if there is another network error, we resort to using the original tile sheet. The following code shows how this is done.
Obviously, in your implementation, you would specify your web server in filepath on Line 20:
local filepath = "http://myWebServer.com/blue_tanks.png"
Also note that we did not define our functions with a local scope. Normally we would do this, but for simplicity of demonstration in this tutorial, we’ll keep them global.
If the remote asset can be loaded, then the result will be reflected in the Simulator.
Making it “Production Ready”
The above example is fine for a demo, but in most cases we don’t want to load a new tile sheet every time the user runs the application. To make this demo at least modestly “production ready,” we’ll add some checks and balances to our code. We’ll do this by adding a remote XML file that loads before we load in any other remote assets — and of course, you can use a text or JSON file if you prefer.
To update this demo application, we just need a simple XML file. Our file will be called gamedata.xml and it will be constructed like this:
The lastupdate XML node contains a date formatted as yyyymmdd (an American version of a serialized date). This represents the last time this file was updated. Our code will read this XML file first and check this date field against a date — stored in the same yyyymmdd format — inside a text file in the system.DocumentsDirectory. This file will be called filesupdate_date.txt. If it does not exist, it will be created and the current date will be written to it. If it does exist, the date will be read and compared to the XML lastupdate node. If the lastupdate date is greater than the stored date, the file in tilesheet XML node will be read and used to replace the current tile sheet (green_tanks.png) in the resources folder.
If the XML file can not be read because of a 404 or network error, or if there is no tilesheet node in the XML file, the code will revert to the green_tanks.png file. We do this to ensure that the game is still playable, even if there is no way to load in the the new tile sheet (i.e. no active network connection).
If the lastupdate in the XML is not newer than the date stored in the filesupdate_date.txt file, we can assume that there is a new, current file named like the tilesheet node and it will be used as a default. Of course, if the XML file no longer contains the tilesheet node, the code needs to use the tile sheet from the system.resourceDirectory. Finally, if the XML file cannot be loaded or the tile sheet data does not exist in the XML, we will set two more “flag” variables to ensure that the default data is used instead.
Data Check Flags
We have already encountered one of our data check flags in the second code example above. The loadedTankData was set to true only if the remote tile sheet resource was loaded. We will add two more flags to this — loadedXML and foundTankData.
- loadedXML will be set to true if an the remote XML file was loaded properly.
- foundTankData will be set to true if the tilesheet was found in the XML file.
In the final version of the displayTank() function, all three flags will need to be true to trigger usage of the new tile sheet rather than the one delivered with the application. In a more advanced example, we might organize this in a slightly different manner, such as writing the latest remote asset name to a file in the documents directory and using that instead of the file in the resources folder.
Final Remote Asset Update Code
We’ve described what we want our final code to do, but before we examine the code listing, let’s summarize exactly what each of our functions will do:
- The first function we’ll call is checkForLocalUpdateDataFile(). This function will check to see if we have downloaded these assets before. If we have, a file will exist with the last date they were downloaded. If the file does not exist, we create one with today’s date.
- The next function we’ll call is writeCurrentDateToLastUpdatedFile(). If there is no filesupdate_date.txt file, we create one with today’s date.
- The loadXML() function will attempt to load remote XML file.
- The networkListenerRequest() function is called after the attempt to download the XML file. If the file exists and there is no network error, we parse the XML file. If either process fails, we’ll display the default tile sheet.
- The parseXML() function attempts to parse the lastupdate and tilesheet nodes. First, it compares the lastupdate field in XML with the last updated date in the text file. If the XML date is newer than the locally stored date, we will load the new tile sheet and write the current date to the text file. If not, there’s no need to load the tile sheet — just set all flags to true and call the displayTank() function.
- The loadTankSheet() function will start network download of the tank tile sheet specified in the XML.
- The networkListenerRequestRemoteGameAsset() function is called after the attempt to download the tile sheet file. First we check to see if there is a network error or 404 on the tank tile sheet. If there is, we set loadedTankData to false; otherwise, we set loadedTankData to true.
- The displayTank() function displays the tank from tile sheet using the new or current sprite asset. If there were any errors loading the remote tile sheet, the default sheet is displayed. Otherwise, the new remote tile sheet is used.
Where From Here?
Most games will have more than a single asset that can be loaded. If we were to create a more complex version of this code, we would read in a series of files from the XML document and create a manifest list of files to load in. If a file did not exist on the remote server, we could check for a version in the documents folder. If it did not even exist there, then we could check for a version in the resources folder.
Also, since the XML file is stored in the system.DocumentsDirectory, if it can’t be downloaded, we could use a pre-existing, previously downloaded version already stored in that folder to create the list of assets to download. There are even more things that we could add such as multiple attempts to download a file before declaring it “unavailable.”
For purposes of testing and working through this tutorial, you can download the source project files here. Hopefully this tutorial has shown you a new and interesting method to get “new content” into your app remotely and on demand.