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.

           
      • Zwelfe

        March 21, 2012 at 10:28 am

        Hi, I really like this tutorial and it helped me a lot…
        I would like to know if there is any answer or suggestion to solve the problem of overwriting the game progress in the users xml files when upgrading the game as mentioned in the second question of David Cabrera? Thank you a lot in advance.

         
    8. Manraj

      February 5, 2012 at 10:41 pm

      Hey tim, great tutorials, just one question, does this tutorial also deal with saving the game state, lets say i am playing the game, and a phone call comes, i answer it, then when i go back to the game, it begins where i left off? Thank you.

       
    9. Marcio Valenzuela

      March 11, 2012 at 12:45 am

      GDXML.zip file is missing and the SceneManager in part 2 is also missing…I mean the links are broken.

       
      • Tim Roadley

        March 11, 2012 at 6:55 am

        Thanks for letting me know. It’s strange, the links were working previously and for some reason they became case sensitive. As soon as I matched the case of the files exactly the links work again.

         
    10. Marcio Valenzuela

      March 15, 2012 at 12:57 am

      2 questions:

      1) When we add chapterSeclected & levelSelected int variables in the loadData method, I’m unsure if these are meant to store the number of levels there are in the game, or the level where we last left off while playing the game? Its the former, isn’t it?

      2) So then when we saveData a little later on, how come we say gameDataElement addChild:selectedChapter or level? Are we adding a new node in the xml with new values or replacing the values of the existing nodes in the xml?

       
    11. Tim Roadley

      March 15, 2012 at 6:18 am

      1) It’s the latter. We store the selected chapter so we know what chapter’s levels to display to the user on the level select screen. We store the selected level so we know what level to load later when the game starts.

      2) The whole XML is re-written when we save so we create it from scratch.

       
    12. adrian phillips

      April 15, 2012 at 5:38 am

      hi tim,

      i have a quick question. can this xml files save score as well and if so how do you implement that. and also how do you call the score from the xml files.

      adrian

       
    13. adrian phillips

      May 20, 2012 at 1:48 pm

      hi tim,

      i was wondering if you got a chance to look at my question in regard with the score saving through the xml files. i e mailed you what i did and wanted to see if you could help me with the method to save and call the score from the xml file.

      if you ever get a chance to look at it i’ll be a very happy man.

      thanks mate

      adrian

       
    14. Josh

      May 23, 2012 at 9:16 am

      Are there benefits to using XML and another library to parse it vs. using a plist file and using the built-in iOS functions to read and write from that?

       
    15. java80

      May 23, 2012 at 2:03 pm

      great tutorial

       
    16. basic

      June 27, 2012 at 3:14 am

      Awesome tutorials!
      One thing about the loadData function (in GameDataParser.m) – It should end with:
      return [Data autorelease];
      instead of:
      return Data;
      [Data release];

      Otherwise the memory is not getting released.

       
    17. Adam J

      July 7, 2012 at 12:27 pm

      Amazing tutorials. Thanks so much! This is exactly what I’ve been looking for.

      An addition, at the end of 6b you hadn’t explicitly mentioned to rename “TemplateData” to “GameData” for the following (although, it’s pretty intuitive at that point of this tutorial to rename it):

      GameData *Data = [[GameData alloc] initWithExampleInt:exampleInt
      exampleBool:exampleBool
      exampleString:exampleString];
      [doc release];
      [xmlData release];
      return Data;
      [Data release];

      ….

      And yes I agree with “basic”‘s comment to use:
      return [Data autorelease];

      … Unless: “return Data; [Data release]” actually works, and I’ve just never seen that technic.

      Again, amazing tutorials!

       
    18. tyler Reynolds

      July 27, 2012 at 8:06 am

      Let me start by saying this was an awesome tutorial :)

      One question though, for a boolean value in the .xml file
      Do you use a TRUE or FALSE basis there, or is it 0 or 1?

       
    19. P@nix

      July 27, 2012 at 6:02 pm

      Wow great tutorials…I just gound this website…i dont know how i ever missed it all this time. Very clear tutorial…thankx a lot Tim.

       
    20. P@nix

      July 27, 2012 at 8:12 pm

      I have read and gone through the entire tutorial. I would like one small clarification if possible. I am trying to develop a game which is going to be depended quite a lot on xml files. One part that is going to be using an xml file is the achievments section of the game. I am going to store all data regarding each achievment (whether it has been completed or not, title, description etc) in an xml file. The xml file is prepopulated with all this information and for every achievment the complition part in the xml is set to NO value. At several part of the game a check is going to happen changing this value to YES in case he completed an achievment.

      My question is therefore the following. If i understood correctly, the initial prepopulated xml file is something like read only and a way to retrieve data when the game has been initialized for the first time and then recreate this xml file somewhere in the device? This recreated xml file is then able to be edited but not the first one ? I am a bit confused with this.

      Any clarifications would be greatly appreciaterd and thanks in advance for any response.

       

    Leave a Reply