RSS

Game Template Part 3 – Game Data Persistence

19 Jul


This tutorial will explain how to enable your iOS game app to read and write xml files.  The reason we need to be able to do this is to store data that persists even after the app is terminated.  A lot of people might use Core Data for this purpose, that’s what I did when I made iHeals.  I think Core Data has a steeper learning curve than an XML Parser.  Core Data is really powerful however so it really comes down to your needs.  I’m going to base my tutorial on GDataXML which is discussed by Ray Wenderlich in this tutorial.

Preparation

We continue off by using the Testing project source code from Part 2.  Once you’ve downloaded it, open it up in Xcode.

Integrating GDataXML

GDataXML is one of the fastest XML parsers you can read and write with.  It’s written by Google so I guess they know what they’re doing.  Just as Ray explained in his tutorial we need to integrate GDataXML with our project before we can use it.  Here’s how:

  1. Download and extract GDataXML.zip
  2. Drag the XMLSupport folder into the Testing project’s Resources directory in Xcode.
  3. Double check there are two files GDataXMLnode.h and GDataXMLnode.m in the XMLSupport folder.

Before it will work, GDataXML needs some special project build settings configured:

  1. Select your project, build settings, all.
  2. Scroll down and find Header Search Paths in the Search Paths section
  3. Add /usr/include/libxml2 to the Header Search Paths list as shown below:
  4. Scroll up again, this time to the Linking section. Add -lxml2 to Other Linker Flags as shown below:

Introducing the Data Template

Before you go down the xml path you should consider if you need things like table relationships.  If you do then you should probably learn Core Data instead.  If you’re happy with simple persistent data then read on.

I’ve created a template you can use to form the basis of a new data class set. Download & extract Data.zip. Put the extracted Data folder into your Testing project as shown in the picture below.  Note the extra folders for Chapters, GameData and Levels are included for later use.

The data template is made up of three parts:

  1. An xml file – for persistent data.
  2. A data class – for the data structure usable within your app.
  3. A data parser class –  for creating an instance of the data class from the xml file.  Also used to save data back into the xml file again.

1. XML File (TemplateData.xml)

XML is a pretty well known standard for storing application data.  The example xml included shows three simple elements ExampleInt, ExampleBool and ExampleString within a root element called ExampleData:

Being an XML file each of the values are all actually string values and not integers or boolean.  That’s where the Data Class can set a few things straight before we attempt to use this data in our app.

2. Data Class (TemplateData.h/m)

Before we try reading in data from the xml we need to establish what variables we want from it.  The template includes an integer, boolean and string variable example.  When you adapt this code to your own needs you might want to use additional variable types.  I’ve highlighted in orange the parts you would expect to change when customising this file for your own use later on:

Note: The underscores in front of some variables are only used to stop a warning regarding hiding of the instance variables.  We get this warning because there are two variables of the same name in play here.  The first is the variable taken by the method.  The second is the class variable we populate with the method variable.

The sole purpose of the TemplateData class is to specify the data types we will use with our xml. Again, I’ve highlighted in orange the parts you would expect to change when customising this file for your own use:

3. Data Parser Class (TemplateDataParser.h/m)

The Data Parser Class is where all the action happens. It uses the variables defined by TemplateData to save/load data to/from the specified XML file.  At first glance the parser can look intimidating.  If you deleted out my heavy commenting you’ll find it’s not that big or bad.  The parser class does three things, which is obvious by the three methods:

  1. Prepares an XML file for use:  + (NSString *)dataFilePath:(BOOL)forSave
  2. Loads data from xml, returning a TemplateData instance: + (TemplateData *)loadData
  3. Saves data to xml from a TemplateData instance: + (void)saveData:(TemplateData *)saveData

The best way to explain the various parts of the parser is to use it in practice.

Creating GameData from Data Template

This section will detail step-by-step how you use the data template to enable reading and writing to a new XML file called GameData.xml.  The point of GameData.xml will be to house the following variables for our game, albeit in the form of a string.

  • selectedChapter will be an integer value representing the currently selected chapter.  This will be useful to keep track of what level selection screen customisations to load.
  • selectedLevel will be an integer value representing the currently selected level.  This will be useful to keep track of what level to load when we launch the game scene.
  • sound will be a BOOL value representing whether the sound is on or off.
  • music will be a BOOL value representing whether the music is on or off.

Step 1 – Copy Data Template

  • Using finder copy the 5 files from the Testing/Data/Template folder into the Testing/Data/GameData folder.
  • Rename each file replacing the word Template with the word Game.
  • Add the files to xcode.  They should show up as follows:

Step 2 – Modify GameData.XML

Replace example data in GameData.xml with the following:

<GameData>
    <SelectedChapter>1</SelectedChapter>
    <SelectedLevel>1</SelectedLevel>
    <Sound>1</Sound>
    <Music>1</Music>
</GameData>

Step 3 – Configure GameData.h

The template should guide you pretty well through the customisation process.  It will instruct you to perform the following:

  • Rename TemplateData to GameData where requested (highlighted below in red)
  • Declare variables with an underscore in front (highlighted below in green)
  • Declare your variable properties without an underscore (highlighted below in blue)
  • Put in a custom init method interface based on the variables you’re going to pass to the init method (highlighted below in yellow). If you’re unsure how to structure this method please read the section titled Passing Multiple Variables to Methods in my previous iOS Newbie Part 5 – Method Man tutorial.

Step 4 – Configure GameData.m

Much like the interface customisation the implementation customisation is as simple as following the template guides.  The guides will instruct you to:

  • Rename TemplateData to GameData (highlighted below in red)
  • Synthesize your variables (highlighted below in green).  Note: you could synthesize all the variables on one line if you wanted.  It looks neater for the tutorial on multiple lines.  Also notice each variable is set to equal _variable.
  • Put in your custom init method to match the one in the interface header file (highlighted below in blue)
  • Set all class instance variables to the values passed to the method (highlighted below in yellow)

Step 5 – Configure GameDataParser.h

The header is easy, just rename stuff highlighted in red and yellow below:

Step 6 – Configure GameDataParser.m

Customising the implementation is more involved so I’ll break it into parts 6a, 6b and 6c.

Step 6a – Configure GameDataParser.m xmlFileName

Rename stuff highlighted in red and yellow below, including setting the xmlFileName (without .xml on the end)

Step 6b – Configure GameDataParser.m for Loading Data

In the loadData method you’ll need to rename TemplateData to GameData and also put in variables appropriate to the GameData class (highlighted below in green):

Next, put in the appropriate root element name based on what is in GameData.xml:

Now for the fun part.  For every variable you will need to:

  1. Create a new array (highlighted below in red)
  2. Base the new array on the appropriate element in GameData.xml (highlighted below in yellow)
  3. Create new element (highlighted below in orange)
  4. Set the variable (highlighted below in green)
Note: I’ve only highlighted new variables involved in the selectedChapter variable

Step 6c – Configure GameDataParser.m for Saving Data

Saving to the xml is done by passing an instance of GameData to the saveData method.  For the saveData method to work we need to:

  1. Rename some things, including the parent element (highlighted below in red)
  2. Create child elements for all variables (selectedChapter example highlighted below in orange)
  3. Set the element name for all variables (selectedChapter example highlighted below in yellow)
  4. Set the value of the string that will go into the element for all variables (selectedChapter example highlighted in green below)
  5. Double check the variable types you’re converting from are appropriate (highlighted in blue below)
  6. Set up the gameDataElement to include each variable element & add it to the document to be saved (highlighted in pink below)

Bringing it all together (using GameData)

Now all the annoying stuff has been set up we can easily read and write to GameData.xml from anywhere in our app.

  1. Open up the MainMenu.m scene
  2. Import GameData.h and GameDataParser.h
  3. Put the following code at the end of the init method:
// Testing GameData
GameData *gameData = [GameDataParser loadData];
CCLOG(@"Read from XML 'Selected Chapter' = %i", gameData.selectedChapter);
CCLOG(@"Read from XML 'Selected Level' = %i", gameData.selectedLevel);
CCLOG(@"Read from XML 'Music' = %i", gameData.music);
CCLOG(@"Read from XML 'Sound' = %i", gameData.sound);
gameData.selectedChapter = 7;
gameData.selectedLevel = 4;
gameData.music = 0;
gameData.sound = 0;
[GameDataParser saveData:gameData];

Before you run the project note the original values in GameData.xml.  When you run the app for the first time GameData.xml will be copied to your device (or the simulator).  Note: Unless you delete the app the copy of GameData.xml now lives on your device or the simulator.  It won’t be overwritten by changes you make to the original xml in Xcode.  Be careful with this because once you release an app on the app store people don’t delete their app in order to upgrade it.

When you run the project you will see the variable values from the xml show up in the log window.  The other thing the code does is write some new values to the xml file now found on the device.  To check the new values have been written properly you’ll need to use finder to go to the address listed in the log window.  For me, the location was shown as :

Saving data to /Users/tim/Library/Application Support/iPhone Simulator/5.0/Applications/5C25E098-9CA1-4252-B099-49053334A2FC/Documents/GameData.xml

Phew! That wraps up part 3.  Here’s the complete source code

Thanks for reading!

If you liked this tutorial or found something wrong with it please let me know!

If you want to support my work and have an iPad please consider purchasing iSoccer *wink*

-Tim

Go to Part 4 or visit Tutorials Index


Be Sociable, Share!
     

    About Tim Roadley

    I'm a senior analytics software consultant at eMite Pty Ltd primarily focused on delivering business intelligence dashboards, currently for one of Australia’s major banks. I'm also working on a revamped version of eMite's iOS App for release under iOS 7. Prior to eMite, I was Infrastructure Manager at Cuscal Pty Ltd where I was heavily involved in designing and implementing a payments switch that drives 1300+ ATM’s Australia wide. I have several apps on the App Store, including Teamwork, iSoccer and now Grocery Dude and Grocery Cloud. In my down time I enjoy spending time with my wonderful wife Tracey and two lovely children Tyler and Taliah.
    30 Comments

    Posted by on July 19, 2011 in iOS Tutorials

     

    30 Responses to Game Template Part 3 – Game Data Persistence

    1. Lord Zadow of Eek.

      August 21, 2011 at 2:00 am

      REally enjoying the tutes, very clear, really like the step guidance in the templates. Very well done. Had a bit of trouble with the libxml/header search paths. I had over 300 errors so I added

      #import “GDataXMLNode.h”

      to the appdelegate which reduced the errors to 8

      then in the header search for both the project and target I added

      $(SDK_DIR)/usr/include/libxml2

      Which seemed to work. Good work fella, keep it up

       
    2. scottapotamas

      September 7, 2011 at 9:10 pm

      Really nice set of tuts you have here… Im a bit beyond most of these, but I found some useful little tips here and there!

      Maybe think about a tutorial about something like having a character/sprite, and having different animation states which show the corresponding animation. There is definately a shortage of these tutorials. I could never find any, so I just hacked it together.

      Keep up the good work and Im looking forward to more tutorials!

       
      • Tim Roadley

        September 14, 2011 at 8:38 am

        Thanks Scott! I’ve gone quiet recently working on getting a 3D illusion of a ball rolling. It’s made up of a box2d circle and animation of the sprite that overlays the circle. It’s fundamental to a game I’m building at the moment however when I release the game in a couple of months I’ll publish the code :)

         
    3. Dan Meza

      October 29, 2011 at 9:34 am

      This is a really great tutorial!, i have learned so much from this, everything is so well documented and easy to follow. i have already parsed a normal XML file like you did in this part of the tutorial.
      I am just starting coding with Cocos2d and I want to parse a XML file that has attributes, something like this:

      And i want to get the value of these coordinates to set a sprite in the game.
      I was wondering if you could any hints with this? or do you know any tutorial that could help me do this in GDataXML?

      Thx in advance

       
      • Tim Roadley

        October 30, 2011 at 11:23 am

        Hi Dan,

        I use an XML to store x and y co ordinates for the starting point of a character in a game I’ve just finished making.

        I use two different values startX and startY which I convert to integers once they are out of the XML. Once you have the 2 integers you can create a cgpoint from them pretty easily. Maybe floats would be better if you need fine positioning.

        I won’t have access to my computer for a couple of days until I finish moving house. I’ll give a proper response as soon as I can!

        You are on the right track

        Cheers, Tim

         
        • Dan Meza

          October 31, 2011 at 3:15 am

          Thank you for your reply and your great work with all of this, i really appreciate these kind of tutorials.

          I’ll keep trying to see if i can do it, trying what you told me and wait for your response.

           
    4. Dan Meza

      October 29, 2011 at 9:40 am

      Im sorry the XML example did not paste
      it was something like this:

      Coordinates XCoordinate=”100″ YCoordinate=”100″

      i didn’t put the tags because it won’t show them, but its just 1 tag.

       
    5. Charlie Martin

      December 26, 2011 at 9:50 am

      These tutorials are really awesome. I’ve been trying to get into iOS for awhile. I’m pretty comfortable with Obj C, but was having a hard time getting a good understanding of making games with Cocoa. This is exactly what I needed. Thanks!

       
      • Tim Roadley

        December 27, 2011 at 9:11 am

        Thanks Charlie, glad you found them useful :)

         
    6. gaDi

      January 12, 2012 at 8:13 pm

      Undefined symbols for architecture i386:
      “_inflateInit2_”, referenced from:
      _inflateMemoryWithHint in ZipUtils.o
      “_inflate”, referenced from:
      _inflateMemoryWithHint in ZipUtils.o
      “_inflateEnd”, referenced from:
      _inflateMemoryWithHint in ZipUtils.o
      “_gzopen”, referenced from:
      _ccInflateGZipFile in ZipUtils.o
      “_gzread”, referenced from:
      _ccInflateGZipFile in ZipUtils.o
      “_gzclose”, referenced from:
      _ccInflateGZipFile in ZipUtils.o
      “_uncompress”, referenced from:
      _ccInflateCCZFile in ZipUtils.o
      ld: symbol(s) not found for architecture i386
      collect2: ld returned 1 exit status

      What does it mean this error ..? plz help me…

       
      • Tim Roadley

        January 16, 2012 at 8:48 am

        Are you receiving this error when you try to use the sample code? Perhaps try clicking Product -> Clean

         
    7. David Cabrera

      January 20, 2012 at 12:01 pm

      Great tutorials, Tim, really learning a lot. However, I have one question in regards to the XML method to storing game data: Say, after initial launch, I decide to add a few more levels, etc. When I release the new version, will upgrading overwrite the user’s XML files?

       
      • Tim Roadley

        January 20, 2012 at 1:16 pm

        Yeah unfortunately it would overwrite the user’s XML. To handle this I would suggest releasing separate xml files of levels (per chapter). Does that make sense?

         
        • David Cabrera

          January 21, 2012 at 4:19 am

          Definitely makes sense. I actually have followed your tutorial series through to the end already, and have configured my levels as you’ve suggested. I used the levels as an example, but really what I am trying to do is save a “credits” int value in the GameData.xml. I’ve configured a non-consumable in-app purchase to add to this int value. I realize that “credits” are usually associated with in-game currency and, thus, are considered consumable. In my case, these are not public facing credits, and are used solely as a method to unlock X amount of levels. I’d actually gone as far as to implement all of this, and it was working great… until I came to the unfortunate realization that these xml files would just be overwritten when I release an upgrade. Consequently, doesn’t this also mean that all of the “score” and “unlocked” fields saved in the level xml files would also be overwritten? If a user upgrades, I don’t want them to have to re-unlock all of the levels or have issues with the credits they’ve purchased. I am not new to iPhone programming, but very new to game programming. Is there a standard approach to this that I am just overlooking? Seems like a rather common requirement. I realize this is beyond the scope of these tutorials, but I’m curious to know how you would remedy this if you were in the same predicament.