RSS

Game Template Part 5 – Level Selection

02 Aug


This section will expand on the level selection scene provided by the existing LevelSelect class.  Our level selection will allow the player to select a level to play based on the currently selected chapter.  The level select scene will have the following features:

  • Each chapter of levels will have its own xml file.
  • Each chapter of levels will have a customised level icon based on the selected chapter.
  • Each level will be locked or unlocked depending on the related xml.
  • Each level will have 0, 1, 2 or 3 stars depending on player progress (also stored in the xml).
  • Each level will have some sample data (in the form of a string) which could be where you store your actual level configuration information in the future.

Preparation

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

Universal File Names

There will be many references to file names in the LevelSelect code.  You might need to load a different file for an iPhone or iPad so you need to cater for that in the code. In order to minimise double coding, add the following to the LevelSelect.h file, just beneath the existing BOOL property for the iPad:

@property (nonatomic, assign) NSString *device;

Also ensure you synthesise the new device variable in the implementation file LevelSelect.m:

@synthesize iPad, device;

Finally, update the following code in the init method of LevelSelect.m:

     // Create variables to help with layout:
 
        self.iPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
 
        if (self.iPad) {
            self.device = @"iPad";
        }
        else {
            self.device = @"iPhone";
        }

You can now call self.device from anywhere in the class and have iPad or iPhone inserted automatically.

Level Data

Just like we did for Chapter Data in Game Template – Part 4 we’ll take a similar approach to the Level Data.

Using finder create Level Data from the data template as follows:

  1. Copy all the files from Testing/Data/Template to Testing/Data/Levels
  2. Delete TemplateData.xml from Testing/Data/Levels
  3. Rename TemplateData.h to Level.h
  4. Rename TemplateData.m to Level.m
  5. Rename TemplateDataParser.h to LevelParser.h
  6. Rename TemplateDataParser.m to LevelParser.m
  7. Copy Level.h to Levels.h
  8. Copy Level.m to Levels.m
  9. Download and extract Levels-ChapterX.zip to Testing/Data/Levels
  10. Download and extract LevelsProgress.zip to Testing/Resources/Images
  11. Add the new files to Xcode

Your Level Data in Xcode should now look something like this:

Levels-ChapterX.xml

I’ve opted for 5 chapters, each with 18 levels.  The xml structure is so repetitive you’ll work out the pattern pretty quickly.  Essentially each level is enclosed by Level tags and then five other variable settings are enclosed within.  You could put whatever level specific data you need here later on.

Level.h

Follow the template guides and you’ll end up with changes like this to the Level header file:

//
//  Level.h
//
 
#import
 
@interface Level : NSObject {
 
    // Declare variables with an underscore
    NSString *_name;
    int _number;
    BOOL _unlocked;
    int _stars;
    NSString *_data;
}
 
// Declare variable properties without an underscore
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int number;
@property (nonatomic, assign) BOOL unlocked;
@property (nonatomic, assign) int stars;
@property (nonatomic, copy) NSString *data;
 
// Custom init method interface
- (id)initWithName:(NSString *)name
            number:(int)number
          unlocked:(BOOL)unlocked
             stars:(int)stars
              data:(NSString *)data;
 
@end

Level.m

Same story for the Level implementation file:

//
//  Level.m
//
 
#import "Level.h"
 
@implementation Level
 
// Synthesize variables
@synthesize name = _name;
@synthesize number = _number;
@synthesize unlocked = _unlocked;
@synthesize stars = _stars;
@synthesize data = _data;
 
// Custom init method takes a variable
// for each class instance variable
- (id)initWithName:(NSString *)name
            number:(int)number
          unlocked:(BOOL)unlocked
             stars:(int)stars
              data:(NSString *)data {
 
    if ((self = [super init])) {
 
        // Set class instance variables based
        // on values given to this method
        self.name = name;
        self.number = number;
        self.unlocked = unlocked;
        self.stars = stars;
        self.data = data;
    }
    return self;
}
 
- (void) dealloc {
    [super dealloc];
}
 
@end

Levels.h

Just like we did in Part 4 for chapters, we need to smoosh each level into an array before we give it to the parser.  This means we need a Levels class.  I cheated and copy/pasted the code from Chapters.h and put it in Levels.h.  A quick bunch of renames from chapters to levels and we’re done.

//
//  Levels.h
//
 
#import
 
@interface Levels : NSObject {
 
    // Declare variables with an underscore in front
    NSMutableArray *_levels;
}
 
// Declare variable properties without an underscore
@property (nonatomic, retain) NSMutableArray *levels;
 
@end

Levels.m

Again, use the same trick from Levels.h:

//
//  Levels.m
//
 
#import "Levels.h"
 
@implementation Levels
 
@synthesize levels = _levels;
 
-(id)init {
 
    if ((self = [super init])) {
 
        self.levels = [[[NSMutableArray alloc] init] autorelease];
    }
    return self;
}
 
- (void) dealloc {
    [super dealloc];
}
 
@end

LevelParser.h

The header file is easy however note that I’ve extended the methods to take an int variable based on chapter. This variable will be used to determine what xml file to read and write to.

//
//  LevelParser.h
//
 
#import
 
@class Levels;
 
@interface LevelParser : NSObject {}
 
+ (Levels *)loadLevelsForChapter:(int)chapter;
+ (void)saveData:(Levels *)saveData
      forChapter:(int)chapter;
 
@end

LevelParser.m – Multiple Files

Firstly fix up the top of LevelParser.m to import the following:

#import "LevelParser.h"
#import "Levels.h"
#import "Level.h"
#import "GDataXMLNode.h"

As we’re going to be loading and saving to multiple xml files we next need to specify what one we want to use. The loading and saving methods have been modified to take an int to represent this. We need to modify the dataFilePath method to take this chapter specific variable and stick it on the end of the xml file name:

+ (NSString *)dataFilePath:(BOOL)forSave forChapter:(int)chapter {
 
    NSString *xmlFileName = [NSString stringWithFormat:@"Levels-Chapter%i",chapter];
 
    /***************************************************************************
     This method is used to set up the specified xml for reading/writing.
     Specify the name of the XML file you want to work with above.
     You don't have to worry about the rest of the code in this method.
     ***************************************************************************/
 
    NSString *xmlFileNameWithExtension = [NSString stringWithFormat:@"%@.xml",xmlFileName];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *documentsPath = [documentsDirectory stringByAppendingPathComponent:xmlFileNameWithExtension];
    if (forSave || [[NSFileManager defaultManager] fileExistsAtPath:documentsPath]) {
        return documentsPath;
        NSLog(@"%@ opened for read/write",documentsPath);
    } else {
        NSLog(@"Created/copied in default %@",xmlFileNameWithExtension);
        return [[NSBundle mainBundle] pathForResource:xmlFileName ofType:@"xml"];
    }
}

LevelParser.m – Loading

Again, you’ll see that the loading method takes a chapter.  Just like in Part 4 we create our variables and an instance of Levels to return.

+ (Levels *)loadLevelsForChapter:(int)chapter {
 
    /***************************************************************************
     This loadData method is used to load data from the xml file
     specified in the dataFilePath method above.
 
     MODIFY the list of variables below which will be used to create
     and return an instance of TemplateData at the end of this method.
     ***************************************************************************/
 
    NSString *name;
    int number;
    BOOL unlocked;
    int stars;
    NSString *data;
    Levels *levels = [[[Levels alloc] init] autorelease];
 
    // Create NSData instance from xml in filePath
    NSString *filePath = [self dataFilePath:FALSE forChapter:chapter];
    NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
    NSError *error;
    GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
    if (doc == nil) { return nil; NSLog(@"xml file is empty!");}
    NSLog(@"Loading %@", filePath);
 
    /***************************************************************************
     This next line will usually have the most customisation applied because
     it will be a direct reflection of what you want out of the XML file.
     ***************************************************************************/
 
    NSArray *dataArray = [doc nodesForXPath:@"//Levels/Level" error:nil];
    NSLog(@"Array Contents = %@", dataArray);
 
    /***************************************************************************
     We use dataArray to populate variables created at the start of this
     method. For each variable you will need to:
        1. Create an array based on the elements in the xml
        2. Assign the variable a value based on data in elements in the xml
     ***************************************************************************/
 
    for (GDataXMLElement *element in dataArray) {
 
        NSArray *nameArray = [element elementsForName:@"Name"];
        NSArray *numberArray = [element elementsForName:@"Number"];
        NSArray *unlockedArray = [element elementsForName:@"Unlocked"];
        NSArray *starsArray = [element elementsForName:@"Stars"];
        NSArray *dataArray= [element elementsForName:@"Data"];
 
        // name
        if (nameArray.count > 0) {
            GDataXMLElement *nameElement = (GDataXMLElement *) [nameArray objectAtIndex:0];
            name = [nameElement stringValue];
        }
 
        // number
        if (numberArray.count > 0) {
            GDataXMLElement *numberElement = (GDataXMLElement *) [numberArray objectAtIndex:0];
            number = [[numberElement stringValue] intValue];
        }
 
        // unlocked
        if (unlockedArray.count > 0) {
            GDataXMLElement *unlockedElement = (GDataXMLElement *) [unlockedArray objectAtIndex:0];
            unlocked = [[unlockedElement stringValue] boolValue];
        }
 
        // stars
        if (starsArray.count > 0) {
            GDataXMLElement *starsElement = (GDataXMLElement *) [starsArray objectAtIndex:0];
            stars = [[starsElement stringValue] intValue];
        }
 
        // data
        if (dataArray.count > 0) {
            GDataXMLElement *dataElement = (GDataXMLElement *) [dataArray objectAtIndex:0];
            data = [dataElement stringValue];
        }
 
        Level *level = [[Level alloc] initWithName:name
                                            number:number
                                          unlocked:unlocked
                                             stars:stars
                                              data:data];
        [levels.levels addObject:level];
    }
 
    [doc release];
    [xmlData release];
    return levels;
}

LevelParser.m – Saving

Saving is all about building the xml file from scratch.  The only thing we have to go off is the Levels class that’s given to this saveData method and the chapter variable so we know what filename to write.  You can see in the commented code where I make the xml elements and put other elements inside them.

+ (void)saveData:(Levels *)saveData
      forChapter:(int)chapter {
 
    /***************************************************************************
     This method writes data to the xml based on a TemplateData instance
     You will have to be very aware of the intended xml contents and structure
     as you will be wiping and re-writing the whole xml file.
 
     We write an xml by creating elements and adding 'children' to them.
 
     You'll need to write a line for each element to build the hierarchy //      ***************************************************************************/
 
    // create the  element
    GDataXMLElement *levelsElement = [GDataXMLNode elementWithName:@"Levels"];
 
    // Loop through levels found in the levels array
    for (Level *level in saveData.levels) {
 
        // create the  element
        GDataXMLElement *levelElement = [GDataXMLNode elementWithName:@"Level"];
 
        // create the  element
        GDataXMLElement *nameElement = [GDataXMLNode elementWithName:@"Name"
                                                         stringValue:level.name];
        // create the  element
        GDataXMLElement *numberElement = [GDataXMLNode elementWithName:@"Number"
                                                           stringValue:[[NSNumber numberWithInt:level.number] stringValue]];
        // create the  element
        GDataXMLElement *unlockedElement = [GDataXMLNode elementWithName:@"Unlocked"
                                                             stringValue:[[NSNumber numberWithBool:level.unlocked] stringValue]];
        // create the  element
        GDataXMLElement *starsElement = [GDataXMLNode elementWithName:@"Stars"
                                                           stringValue:[[NSNumber numberWithInt:level.stars] stringValue]];
        // create the  element
        GDataXMLElement *dataElement = [GDataXMLNode elementWithName:@"Data"
                                                         stringValue:level.data];
        // enclose variable elements into a  element
        [levelElement addChild:nameElement];
        [levelElement addChild:numberElement];
        [levelElement addChild:unlockedElement];
        [levelElement addChild:starsElement];
        [levelElement addChild:dataElement];
 
        // enclose each  into the  element
        [levelsElement addChild:levelElement];
    }
 
    // put the  element (and everything in it) into the XML doc
    GDataXMLDocument *document = [[[GDataXMLDocument alloc]
                                   initWithRootElement:levelsElement] autorelease];
 
    NSData *xmlData = document.XMLData;
 
    // overwrite the existing file, being sure to overwrite the proper chapter
    NSString *filePath = [self dataFilePath:TRUE forChapter:chapter];
    NSLog(@"Saving data to %@...", filePath);
    [xmlData writeToFile:filePath atomically:YES];
}

Using Level Data

Lets go test if the Level Data setup works.  Open up LevelSelect.m and ensure you configure the following imports just under the existing #import LevelSelect.h:

#import "Level.h"
#import "Levels.h"
#import "LevelParser.h"
#import "GameData.h"
#import "GameDataParser.h"

Add the following two lines of code to the init method in LevelSelect.m

GameData *gameData = [GameDataParser loadData];
[LevelParser loadLevelsForChapter:gameData.selectedChapter];

Run the app, tap Play, select a chapter then look at the log window. You should be able to see the xml data for that level being displayed:

Double Check Saving Works

We won’t need to save anything to the Levels-ChapterX files until the game scene is created and the player earns stars and unlocks levels. Still, it’s probably a good idea to double check that saving data to the level xml files actually works.  Change the init method of LevelSelect.m as follows:

- (id)init {
 
    if( (self=[super init])) {
 
        // Determine Device
        self.iPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
 
        GameData *gameData = [GameDataParser loadData];
        Levels *selectedLevels = [LevelParser loadLevelsForChapter:gameData.selectedChapter];
 
        // Iterate through the array of levels
        for (Level *level in selectedLevels.levels) {
 
            // look for currently selected level
            if (level.number == gameData.selectedLevel) {
 
                // record that the player earned 3 stars!
                [level setStars:3];
            }
        }
 
        // Write the new xml
        [LevelParser saveData:selectedLevels forChapter:gameData.selectedChapter];
 
        //  Put a 'back' button in the scene
        [self addBackButton];
    }
    return self;
}

Run the app in the iPhone simulator. From the menu at the top of your screen click iOS Simulator then Reset Content and Settings. This will ensure the GameData.xml file is copied from Xcode.  Stop the app.  Go back to Xcode and edit GameData.xml and change SelectedLevel to 7.  Save the xml and run your app again, press Play and select a Chapter. When it runs it will display results to the log window.  It will tell you where the updated saved XML file is.  For me it was something like:

Saving data to /Users/tim/Library/Application Support/iPhone Simulator/5.0/Applications/A53B7DDB-59A4-4DF0-A7C3-32D8F4CCC705/Documents/Levels-Chapter2.xml

Open that file from finder and review the contents.  You should see that the player has 3 stars for level 7!

Making it Pretty

Our Level Select engine is ready yet the screen is still blank!  Lets fix this.  Firstly you’ll need to import some more stuff at the top of LevelSelect.m so that you can use Chapter Data:

#import "Chapter.h"
#import "Chapters.h"
#import "ChapterParser.h"

Next it’s time to heavily modify the init method of LevelSelect.m in order to create a bunch of buttons, stick a label on them showing the level number and also overlay stars or a padlock.  The code is commented heavily again for clarity:

- (id)init {
 
    if( (self=[super init])) {
 
     // Create variables to help with layout:
 
        self.iPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
 
        if (self.iPad) {
            self.device = @"iPad";
        }
        else {
            self.device = @"iPhone";
        }
 
        int smallFont = [CCDirector sharedDirector].winSize.height / kFontScaleSmall;
 
     // read in selected chapter number:
        GameData *gameData = [GameDataParser loadData];
        int selectedChapter = gameData.selectedChapter;
 
     // Read in selected chapter name (use to load custom background later):
        NSString *selectedChapterName = nil;
        Chapters *selectedChapters = [ChapterParser loadData];
        for (Chapter *chapter in selectedChapters.chapters) {
            if ([[NSNumber numberWithInt:chapter.number] intValue] == selectedChapter) {
                CCLOG(@"Selected Chapter is %@ (ie: number %i)", chapter.name, chapter.number);
                selectedChapterName = chapter.name;
            }
        }
 
     // Read in selected chapter levels
        CCMenu *levelMenu = [CCMenu menuWithItems: nil];
        NSMutableArray *overlay = [NSMutableArray new];
 
        Levels *selectedLevels = [LevelParser loadLevelsForChapter:gameData.selectedChapter];
 
     // Create a button for every level
        for (Level *level in selectedLevels.levels) {
 
            NSString *normal =   [NSString stringWithFormat:@"%@-Normal-%@.png", selectedChapterName, self.device];
            NSString *selected = [NSString stringWithFormat:@"%@-Selected-%@.png", selectedChapterName, self.device];
 
            CCMenuItemImage *item = [CCMenuItemImage itemFromNormalImage:normal
                                                           selectedImage:selected
                                                                  target:self
                                                                selector:@selector(onPlay:)];
            [item setTag:level.number]; // note the number in a tag for later usage
            [item setIsEnabled:level.unlocked];  // ensure locked levels are inaccessible
            [levelMenu addChild:item];
 
            if (!level.unlocked) {
 
                NSString *overlayImage = [NSString stringWithFormat:@"Locked-%@.png", self.device];
                CCSprite *overlaySprite = [CCSprite spriteWithFile:overlayImage];
                [overlaySprite setTag:level.number];
                [overlay addObject:overlaySprite];
            }
            else {
 
                NSString *stars = [[NSNumber numberWithInt:level.stars] stringValue];
                NSString *overlayImage = [NSString stringWithFormat:@"%@Star-Normal-%@.png",stars, self.device];
                CCSprite *overlaySprite = [CCSprite spriteWithFile:overlayImage];
                [overlaySprite setTag:level.number];
                [overlay addObject:overlaySprite];
            }
        }
 
        [levelMenu alignItemsInColumns:
          [NSNumber numberWithInt:6],
          [NSNumber numberWithInt:6],
          [NSNumber numberWithInt:6],
          nil];
 
     // Move the whole menu up by a small percentage so it doesn't overlap the back button
        CGPoint newPosition = levelMenu.position;
        newPosition.y = newPosition.y + (newPosition.y / 10);
        [levelMenu setPosition:newPosition];
 
        [self addChild:levelMenu z:-3];
 
     // Create layers for star/padlock overlays & level number labels
        CCLayer *overlays = [[CCLayer alloc] init];
        CCLayer *labels = [[CCLayer alloc] init];
 
        for (CCMenuItem *item in levelMenu.children) {
 
         // create a label for every level
 
            CCLabelTTF *label = [CCLabelTTF labelWithString:[NSString stringWithFormat:@"%i",item.tag]
                                                        fontName:@"Marker Felt"
                                                        fontSize:smallFont];
 
            [label setAnchorPoint:item.anchorPoint];
            [label setPosition:item.position];
            [labels addChild:label];
 
         // set position of overlay sprites
 
            for (CCSprite *overlaySprite in overlay) {
                if (overlaySprite.tag == item.tag) {
                    [overlaySprite setAnchorPoint:item.anchorPoint];
                    [overlaySprite setPosition:item.position];
                    [overlays addChild:overlaySprite];
                }
            }
        }
 
     // Put the overlays and labels layers on the screen at the same position as the levelMenu
 
        [overlays setAnchorPoint:levelMenu.anchorPoint];
        [labels setAnchorPoint:levelMenu.anchorPoint];
        [overlays setPosition:levelMenu.position];
        [labels setPosition:levelMenu.position];
        [self addChild:overlays];
        [self addChild:labels];
        [overlays release];
        [labels release];
 
     // Add back button
 
        [self addBackButton];
    }
    return self;
}

You’ll also have to create a new method at the top of LevelSelect.m so selecting a level writes the selected level to a file and the game scene is loaded:

- (void) onPlay: (CCMenuItemImage*) sender {
 
 // the selected level is determined by the tag in the menu item
    int selectedLevel = sender.tag;
 
 // store the selected level in GameData
    GameData *gameData = [GameDataParser loadData];
    gameData.selectedLevel = selectedLevel;
    [GameDataParser saveData:gameData];
 
 // load the game scene
    [SceneManager goGameScene];
}

If all goes well you should see different coloured level selection buttons based on the chapter you selected. All of the levels will be locked so play with the XML file and see how that affects what stars and padlocks show up.  Don’t forget to reset content and settings of the device every time you update the master XML or your changes may not flow through to the device.  Try the iPad too and notice the larger images are used.

Here’s the end result:

You’ll notice that padlocked levels are un-clickable.  Awesome huh.

As to the unlocked levels, when you do click them you’re sent to the Game Scene.  In order to prove that the correct level data is being loaded by the game scene #import GameData.h and GameDataParser.h in GameScene.m.  Also change the code in the init method of GameScene to the following:

- (id)init {
 
    if( (self=[super init])) {
 
        // Determine Device
        self.iPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
 
        // Determine Screen Size
        CGSize screenSize = [CCDirector sharedDirector].winSize;
 
        // Calculate Large Font Size
        int largeFont = screenSize.height / kFontScaleLarge;
 
        GameData *gameData = [GameDataParser loadData];
 
        int selectedChapter = gameData.selectedChapter;
        int selectedLevel = gameData.selectedLevel;
 
        Levels *levels = [LevelParser loadLevelsForChapter:selectedChapter];
 
        for (Level *level in levels.levels) {
            if (level.number == selectedLevel) {
 
                NSString *data = [NSString stringWithFormat:@"%@",level.data];
 
                CCLabelTTF *label = [CCLabelTTF labelWithString:data
                                                       fontName:@"Marker Felt"
                                                       fontSize:largeFont];
                label.position = ccp( screenSize.width/2, screenSize.height/2);
 
                // Add label to this scene
                [self addChild:label z:0];
            }
        }
 
        //  Put a 'back' button in the scene
        [self addBackButton];
 
    }
    return self;
}

Congratulations if you’ve made it this far, you now have a data driven chapter and level selection system with stars and everything else =)

Here’s the complete source code so far

Thanks for reading, leave a comment if you found this post useful!

-Tim

Requested Update: Adding More Chapters/Levels

To add more chapters you will have to do a few things.  Firstly you’ll need to add your new chapter to the existing Chapters.xml file.  Lets say I wanted to add a new chapter called ‘New‘, I would edit Chapters.xml as shown via the green highlight below. The exact name of the ‘New‘ chapter is important to note as you use it later on when creating images.

When a chapter has been selected in the game the LevelSelect scene loads.  The LevelSelect scene looks for images based on the name of the chapter that has been selected.  If you look in your project you will see a Resources/Images/Levels folder which has normal/selected iphone/ipad images for every chapter name. You guessed it, you now have to make sure images for the ‘New‘ chapter exist with these exact names:

  • New-Normal-iPad-hd.png
  • New-Normal-iPad.png
  • New-Normal-iPhone-hd.png
  • New-Normal-iPhone.png
  • New-Selected-iPad-hd.png
  • New-Selected-iPad.png
  • New-Selected-iPhone-hd.png
  • New-Selected-iPhone.png

You could either copy and rename image sets from an existing chapter or create your own from scratch.

Bonus: If you have photoshop, here’s my master PSD file for the level images specific to a chapter.  With that you can create your own level selection images by doing the following in Photoshop:

  1. Right Click an existing chapter’s layer group and select Duplicate Layer Group. I called my new layer group Example New Chapter as you can see below:
  2. Double click the gradient overlay and change the gradient to something you like, remembering that the text colour of the level numbers is white.
  3. When saving files from this master image you need to click File, Save for Web and Devices and ensure you save as a png file so the file has transparency.
    1. To create New-Selected-iPad-hd.png you should unhide the Outer Glow and Inner Glow before saving as a 256×256 file.
    2. To create New-Selected-iPad.png you should unhide the Outer Glow and Inner Glow before saving as a 128×128 file
    3. To create New-Selected-iPhone-hd.png you should unhide the Outer Glow and Inner Glow before saving as a 128×128 file
    4. To create New-Selected-iPhone.png you should unhide the Outer Glow and Inner Glow before saving as a 64×64 file
    5. To create New-Normal-iPad-hd.png you should hide the Outer Glow and Inner Glow before saving as a 256×256 file.
    6. To create New-Normal-iPad.png you should hide the Outer Glow and Inner Glow before saving as a 128×128 file.
    7. To create New-Normal-iPhone-hd.png you should hide the Outer Glow and Inner Glow before saving as a 128×128 file.
    8. To create New-Normal-iPhone.png you should hide the Outer Glow and Inner Glow before saving as a 64×64 file.
  4. Now that you have your ‘New’ chapter files drag them into the Resources/Images/Levels folder with all the other chapter based level images.

If you don’t have photoshop, you can just download this and extract this file instead.  Drag the 8 images in to the Resources/Images/Levels folder of your Xcode project.

Before you will be able to see your handiwork on the LevelSelect screen you will need to add a new Levels-Chapter6.xml file to the Data/Levels folder in Xcode.  You can either copy/paste another chapter and do a search/replace or download one I’ve prepared earlier and use that instead ;-)

Here’s the end result:

I hope this updated explanation helps you apply this game template to your needs a little easier.

Thanks to Dav2011 for requesting this info on the Tutorials Index page. Let me know if this post was useful to you so I have motivation to keep writing!

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 6 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.
    58 Comments

    Posted by on August 2, 2011 in iOS Tutorials

     

    58 Responses to Game Template Part 5 – Level Selection

    1. dav2011

      August 12, 2011 at 2:07 am

      Hi Tim, that absolute makes sense. Thank you very much for that, I’m at work at moment but I will try this soon as I get in. Then on to the hard stuff, Im going to attempt to add Ray Winderlechs Simple iphone game tutorial. I have a feeling I may be back here very soon lol Thank you so much Tim.

      p.s. My tutor will noe be a happy person

       
    2. dav2011

      August 12, 2011 at 2:50 am

      Hey Tim, I just wanted to get something right in my head. Am I right in thinking that I would use these image sizes. I am currently using inkscape for my imagaes and exporting to .png. So when I export would I export in pixels to these sizes?

      320 – 480 pxls iphone
      640 – 960 pxls iphone hd
      1280 – 1920 pxls ipad hd

      unsure for standard ipad…

      Thank you

       
      • Tim Roadley

        August 12, 2011 at 6:39 am

        Hi Dav2011, have you read the ‘Universal Support’ section of Game Template Part 1? It covers pixels and points for all iOS devices. If you have read it and it is unclear, please let me know. I assume you’re creating a png file as the background and as such it needs to match the pixel count on the device? Please note there’s no such thing as a hd iPad unfortunately just yet, iPad 1 and iPad 2 have the same resolution screen and co-ordinates systems. You’re safe to use 1024×768 points and pixels for any iPad.

         
    3. Manni

      October 8, 2011 at 8:24 pm

      Hi Tom,

      Great tutorials. Can you please update the XCode project to also run on Simulator rather than only on device. It makes debugging and analysis of code lot easier.

       
      • Tim Roadley

        October 10, 2011 at 7:56 am

        Hi Manni

        What version of xcode are you running? I think I may have written the project on a beta version of xcode that supports iOS 5. Most of my testing was on the simulator.

        When iOS5 and the final version of xcode that goes with it is released can you try again?

        Cheers

         
      • adrian phillips

        December 6, 2011 at 2:33 am

        manni

        to resolve your issue click on the target for the project. in this case testing. then make sure the summery is selected and you see the window that has the game summary. click on the arrow beside deployment target and from the drop down menu select 4.3. then you will have the option to run the testing project on the simulator.

        hope that helps if you have not figure it out already.:)

         
    4. Manni

      October 12, 2011 at 7:55 am

      Hi Tim,

      I am using XCode 4.0 with iOS 4.3 (that is the latest simulator I see in my projects). When I load your xcode project the only option I see is of device.

      Also I need to ask you if I can use the sprites (images) you have used in these tutorials in the App I am developing?

      Cheers

       
      • Tim Roadley

        October 12, 2011 at 8:10 am

        Hmm interesting. I would just wait for iOS 5 to be released and when you upgrade Xcode at that time you should be able to use the simulator again.

        Which sprites in particular?

        – The sticky note I got from http://www.psdgraphics.com/page/2/?s=sticky – The ninjastar, level selection backgrounds and arrows I made myself so you can use it no problems for free. – The baby image I found on my PC so have no idea where that came from (best not to use)

        If there are other ones that I can’t remember off the top of my head let me know and I’ll let you know where they came from.

        Cheers

         
    5. Paul D

      October 25, 2011 at 5:05 pm

      Hi Tim,

      Thanks very much for these tutorials, it’s a really useful addition to what’s already out there. One thing I am having trouble with is memory leaks. I downloaded your project after part 5….and when I profile it with the leaks instrument, there are leaks for each time chapter is initialised and each time level is initialised. I’ve tried a bunch of stuff to fix this, but no joy, wondered if you had any thoughts?

      Also, the returned array in the GameDataParser is not autoreleased, so each instantiation would need to be released (I just added auto release to the relevant line and now GameData is not leaking).

      Thanks again,

      Paul.

       
      • Tim Roadley

        October 25, 2011 at 9:34 pm

        Ha! Well found Paul. I plan to do a post on instruments when I finish the game I’m making in a couple of months. I know exactly the leak you’re talking about and you are right, simply adding a autorelease fixes it :)

        Thanks for reading!

        Cheers, Tim

         
        • Tim Roadley

          October 26, 2011 at 7:51 am

          Just to expand on that specifically, you’ll need to change this in GameDataParser.m:

          GameData *Data = [[GameData alloc] initWithSelectedChapter:selectedChapter
          selectedLevel:selectedLevel
          sound:sound
          music:music];

          to this:

          GameData *Data = [[[GameData alloc] initWithSelectedChapter:selectedChapter
          selectedLevel:selectedLevel
          sound:sound
          music:music] autorelease];

           
        • Tim Roadley

          October 26, 2011 at 7:54 am

          You will also need to do the same to the ChapterParser.m

          Chapter *chapter = [[Chapter alloc] initWithName:name number:number];

          becomes

          Chapter *chapter = [[[Chapter alloc] initWithName:name number:number] autorelease];

          and also the LevelParser.m

          Level *level = [[Level alloc] initWithName:name
          number:number
          unlocked:unlocked
          stars:stars
          data:data];

          becomes

          Level *level = [[[Level alloc] initWithName:name
          number:number
          unlocked:unlocked
          stars:stars
          data:data] autorelease];

           
    6. Paul D

      October 26, 2011 at 4:26 pm

      Nice work Tim! Thanks again, this series of guides has been very useful.

       
    7. Avinas

      December 8, 2011 at 4:12 pm

      Hi Tim, Great tutorial. This is what i was looking for my next game. Thanks a lot :) . Although in my implementation instead of giving two screens(chapters and levels) i want to directly scroll levels scene (with some heading). Can you suggest me any basic step to put levels in chapters and handle selection directly. I am also trying out some changes. Thanks in advance.

       
    8. Lars Svensson

      January 18, 2012 at 5:49 pm

      Hi Tim, just wanted to say thanks for this awesome tutorial!!!

      much appreciated and I hope you keep the good work up!

      Lars
      Sweden

       
    9. adrian phillips

      January 23, 2012 at 4:20 am

      hi tim,

      i hope everything is going well with your mac. i have managed to remove most of the errors from the game play file except for two. when i incorporate the unlock code i get two errors it says that Use of undeclared identifier ‘GameDataParserloadData’ and also Use of undeclared identifier ‘LevelParserloadLevelsForChapter’.
      now i have imported all the xml data file headers and also level and chapter data and header files. can you help me with these errors please.

      adrian

       
    10. adrian phillips

      January 23, 2012 at 11:56 pm

      hi tim,

      thanks a million for resolving my issues and teaching me how to fix them. i have to admit that these series of tutorials has thought me more in such short time than whole a lot of other tutorials i have been reading for months. just know this that all of the guys out there are appreciating the efforts you put in these in order to help us. i know i do.:)

      keep up the great work mate.

      adrian

       
      • Tim Roadley

        January 24, 2012 at 3:01 am

        Thanks for your kind words Adrian, makes it worth it!

        Cheers

         
    11. Alex

      March 2, 2012 at 5:55 pm

      Tim, have a question also. Based on your tutorials I’ve created a Game and an Item class and its loaded for each selected Chapter and Level.

      Using the same technique how should I proceed for a Level which has an amount of games? Want to create array an array for each game in order to add items to it.

      Thanks again for your tutorials! ;)

       
    12. Tim Roadley

      March 3, 2012 at 10:30 am

      Hi Alex,

      I’m not sure I understand your question fully so forgive me if I misunderstand what you’re asking.

      From what I’m reading you have two classes, Game and Item.

      Each Chapter has many Levels (in an array).

      If each Level has many games then yes you need to use the same approach used to put many levels in a chaper.

      Does that make sense?

       
    13. Alex

      March 5, 2012 at 7:33 pm

      Thanks, but right now I’m not wrong you’re using replacing scene using goLevelSelect method. But what I need is to load them all of them into a Level.

       
    14. Tim Roadley

      March 5, 2012 at 8:38 pm

      Do you have code samples that might help explain your problem a little better? When you say “what I need is to load them all of them into a Level” what exactly are you trying to load .. and into what? I have no idea what your game or item classes are or even do. Happy to help however I need more information.

       
    15. Alex

      March 5, 2012 at 9:22 pm

      Yes, sure;
      Like you’ve build in the tutorial parts (4 & 5 ) I’ve build Game class and Item class

      The logic of the games is that a Level has multiple games and a game contains couple of items

      So in GameScene I load them like:


      Levels *levels = [LevelParser loadLevelsForChapter:selectedChapter];
      Games *games = [GameParser loadGamesForLevel:selectedLevel fromChapter:selectedChapter];
      Items *items = [ItemParser loadItemsForLevel:selectedLevel fromChapter:selectedChapter];

      The xml structure of a game would is:

      1
      0
      Text of Game 1
      Game 2
      00:59
      01:00

      and for Item XML:

      1
      1
      picture.png
      sound.mp3
      Text of item
      0
      280
      280
      1
      2

      What I’d like to know how to create arrays of games with items in order to remove it and know which one has been removed

      Thank you! :)

       
      • Alex

        March 5, 2012 at 9:29 pm

        *sorry but I’ve included the xml tags into code tag but didn’t displayed them, so I’ll enumerate them in the exact above order:
        Game: GameNumber, isFinished, Text, completionTime and Time

        Item: IsEnabled, IncludedInGameNumber, Picture, Text, isFound, xCoord, yCoord, tag, zOrder

         
    16. Mars

      March 10, 2012 at 3:32 am

      I have a doubt…In Angry Birds you get 1, 2 or 3 stars if you reach a certain amount of points. Then you move on to the next chapter. But those aren’t really different levels, right?

       
      • Tim Roadley

        March 10, 2012 at 12:54 pm

        They are scores for different levels which is just one thing you might want to store on a per level basis. If you want to store say the position of all the pigs, birds, wood and glass in a level you could also store that too. Likely you would use a tool like Tiled to create a TMX file per level. You would simply refer to the name of the TMX file from the level parser when loading a level and all of its specific attributes.

        Does that make sense?

         
    17. Mars

      March 10, 2012 at 1:32 pm

      But what I mean is this. You can complete a level with 1, 2 or 3 stars. The stars just depend on whether you make more than 50k points, more than 100k points or 150k points for example, right?

       
      • Tim Roadley

        March 10, 2012 at 1:45 pm

        It can mean whatever you want it to mean. For iSoccer my iPad app the star count you win depends on how many goals you score versus how many the computer.

         
    18. Marcio Valenzuela

      March 13, 2012 at 7:49 am

      Gr8 tutorial!

      I noticed that by the time you got to Part 5, your detailed step by step description of the code you added had diminished. Im interested more up to Part 5 because Im very interested in this part where we add the level and chapter functionality.

      Up to the hard coded “code” i was fine, but the last Part where you added the xml for levels and chapters I was kind of lost. Could you run through what happens as you go through a chapter and level selection from the very beginning up to when you save the data and exit the game?

       
      • Tim Roadley

        March 14, 2012 at 9:05 pm

        What was the heading of the section you get lost at?

         
    19. Marcio Valenzuela

      March 14, 2012 at 1:42 am

      Also, where does the LevelParser saveData get called?

       
      • Tim Roadley

        March 14, 2012 at 9:06 pm

        You’d only use that later down the track when you want to save someones progress, for example someone earns 3 stars and you want to unlock the next level

         
    20. Marcio Valenzuela

      March 15, 2012 at 7:05 am

      Ok this is where Im lost, in the LevelSelect init method:

      I understand we load our GameData which we must have saved somewhere in the game when we were playing but since we are running it for the first time, its just 1,1,1,1.

      GameData *gameData = [GameDataParser loadData];

      Then we create a Levels object which is simply an array.

      Levels *selectedLevels = …

      This is here I’m confused, this following line calls the loadLevelsForChapter (our selectedChapter which is currently 1).

      …[LevelParser loadLevelsForChapter:gameData.selectedChapter];

      As i understand this class method, it returns a Levels Object which has an NSarray property names levels. It takes our selectedChapter passed to it, it loads the corresponding Level-Chapter1.xml file which holds the values:

      Name:Chapter1Level1
      Number:1
      Unlocked:1
      Stars:3 (but lets say originally it is 1)
      Data:SomeDataC1L1

      —NO—Actually its just reading the Level values for the chapter selected in the ChapterSelect scene, isn’t it?

      So for each one of these 5 elements in the xml, it creates an NSArray of the same name, and populates it with that element’s value. So it creates as many names’ arrays as there are Levels for that chapter. That’s why I’m confused; is it used for user advancement in the game or for reading an xml file to present an array of chapters and levels?

      It then iterates thru the array of levels? Why the entire array of levels?

      // Iterate through the array of levels
      for (Level *level in selectedLevels.levels) {

      // look for currently selected level
      if (level.number == gameData.selectedLevel) {

      // record that the player earned 3 stars!
      [level setStars:3];
      }
      }

      // Write the new xml
      [LevelParser saveData:selectedLevels forChapter:gameData.selectedChapter];

      Im trying to go through in my head what happens when you call the :
      + (Levels *)loadLevelsForChapter:(int)chapter

      method.

       
      • Tim Roadley

        March 15, 2012 at 8:55 am

        Use loadLevelsForChapter to get an array of levels in a chapter.

        Later on when you call saveData the XML file is written from scratch. This is why you need all the levels in a temporary array so you can save them again later.

        The example code…

        Levels *selectedLevels = [LevelParser loadLevelsForChapter:gameData.selectedChapter];

        …puts all the Level objects for the selected chapter into a temporary Levels array called selectedLevels.

        Once selectedLevels is populated from the XML we can then begin to edit data such as the amount of stars that have been earned or whatever.

        When we’re finished making changes to the data we have to write out the file (from scratch) by putting everything in the selectedLevels array back into the XML file. When we are saving the XML we need to also specify what chapter we’re writing the levels for. In this case it’s the selected chapter:

        [LevelParser saveData:selectedLevels forChapter:gameData.selectedChapter];

        Does this help at all?

        Cheers

         
    21. Mars

      March 15, 2012 at 9:51 am

      So we are dumping the Levels-Chapter1.xml file into an array, modifying some values (maybe sometimes all the values) and rewriting the complete file with any changed values?

       
    22. Zishan

      March 29, 2012 at 7:51 pm

      Hi Tim, How can I use different images in chapters and levels. I have read the part-4 Chapter selection comment about this issue, but I am not clear about this.

       
    23. Natalie

      April 12, 2012 at 6:58 pm

      Thanks so much for taking the time to write these there wonderful!

       
    24. TheBoss

      April 15, 2012 at 6:29 am

      Hello Tim,
      These are some greats tuts you have here but I have a problem. I downloaded the source code at the end of this part of the tutorial and when I build the project i get like 300 erros telling me that Box2D files are not included, so I followed the path they indicated me in Xcode and it appears that these files are included in the project so I dont see the problem. Any suggestions please?
      Thanks in advance

       
      • Tim Roadley

        April 25, 2012 at 8:35 am

        This is usually due to the path the project is in, I might have missed relative pathing. See if it works when you move the app directory to the root documents directory. Either that or verify the search paths

         
    25. Thomas

      April 18, 2012 at 7:53 pm

      Once again Fantastic!

       
    26. Zishan

      May 17, 2012 at 6:12 pm

      Hi Tim, I want to show 5 levels in a chapter, so I delete remain levels data from Levels-Chapter1.xml . but now my app is crashed after select first chapter.

      debug window show me this

      “GDataXMLElement 0xe44fe90: {type:1 name:Level xml:\”Chapter1Level5500Some Chapter 1 Level 5 Data\”}”
      )
      2012-05-17 20:08:42.168 KillTheDevil[973:10a03] *** Assertion failure in -[CCMenu alignItemsInColumns:vaList:], /Volumes/MY_ODESK/Kill-The-Devil/KillTheDevil/KillTheDevil/libs/cocos2d/CCMenu.m:390
      2012-05-17 20:08:42.169 KillTheDevil[973:10a03] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Too many rows/columns for available menu items.’
      *** First throw call stack:
      (0x1a47052 0x1bd8d0a 0x19efa78 0x135b2db 0x37e81 0x37890 0x10164c 0x3fae1 0x103d12 0xff76c 0x19ad51d 0x19ad437 0x39985 0x36d42 0x1a48ec9 0x91df1 0x9293f 0x947f1 0x9faa30 0x9fac56 0x9e1384 0x9d4aa9 0x281ffa9 0x1a1b1c5 0x1980022 0x197e90a 0x197ddb4 0x197dccb 0x281e879 0x281e93e 0x9d2a9b 0xf68ef 0x1f25 0x1)
      terminate called throwing an exception(gdb)

      would you please tell me, where i need to change for solve this problem?

      Thank you

       
      • Zishan

        May 20, 2012 at 12:28 am

        Hi, I have solve that issue my-self. Thanks for this great template tutorial. I have another quick question, how can i run 30 different scenes in 30 levels of 5 chapter? Now I am using

        switch (gameData.selectedLevel) {
        case 1:

        [SceneManager goChapter1Level1Scene];
        break;
        …………………………..
        …………………………..
        case 30:

        [SceneManager goChapter5Level6Scene];
        break;

        default:
        break;
        }

        this method. But it work only for first 6 levels.
        Please help me…

        Thank you

         
    27. Tim Roadley

      May 20, 2012 at 8:01 am

      Hi Zishan,

      You are using a reasonable approach. What’s the difference in what is called by chapter 6 and 7?

      Cheers

       
    28. adrian phillips

      June 8, 2012 at 12:43 am

      hi tim,

      hope you’re doing alright man. i know I’ve been a pain since i found your site but I’m stuck and i have a question.

      i have added the score variable to the game data as int and followed the path of the tutorials in adding the score variable to the level parser and the related xml files. everything looks good but when i try to write the execution code in the unlock method for the levels it does not save it to xml file so when the player goes to the next level the score still is zero to start with and it does not add the score from the previous level.
      previously i had the the score added through a score label CClabelTTF and tried to update it through a string but that did not work too. here is the part that i added the score label:

      in the gameplaylayer.h i just declared the score and old score as int and added the label. after i added the score to the game data and the parser and xml i did add the score and the old score to the level and level parser and its xml files as well and i tried using

      in the unlock method, but no change. the score still is zero when the player goes to the next level.

      could you please please help me with this i am stuck and have been trying to resolve this issue for some time.

      thank you in advance for your help man.
      adrian

       
    29. adrian phillips

      June 8, 2012 at 12:46 am

      sorry the code did not get uploaded somehow.
      _score = 0;
      _oldScore = -1;
      self.iPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
      if (self.iPad) {
      self.scoreLabel = [CCLabelTTF labelWithString:@"00000000" dimensions:CGSizeMake(100, 50) alignment:UITextAlignmentRight fontName:@"Marker Felt" fontSize:52];
      _scoreLabel.position = ccp(900, 700);
      }
      else {
      self.scoreLabel = [CCLabelTTF labelWithString:@"00000000" dimensions:CGSizeMake(100, 50) alignment:UITextAlignmentRight fontName:@"Marker Felt" fontSize:32];
      _scoreLabel.position = ccp(400, 290);
      }
      [self addChild:_scoreLabel z:1];
      and here is what i tried in the unlock method
      level.score = _score;

       
    30. adrian phillips

      June 8, 2012 at 12:48 am

      i forgot to tell you that the score increases as the collision happens within the level its just it does not carry to the next level.

      adrian

       
    31. Matt Bennett

      June 12, 2012 at 6:08 am

      Hi Tim,
      Just wanted to say thanks for these tutorials. I have been working through them over the past few days. Very very helpful.

      I’m planning to try to integrate this with LevelHelper.

      Again – thanks for the great tutorials.

      Matt

       
    32. Hayley

      September 12, 2012 at 3:18 am

      Hey Tim,

      Thanks for the tutorials :)
      I was wondering how would you go about making this page into CCScrollLayer . So instead of having only 15 buttons on one page with 15 levels in xml, you have two menus with 15 buttons on each on one page, another 15 on another so you would be calling 30 levels from the xml? If that makes any sense?

      Thanks In Advance :)

       
    33. Sebastián Coronado Alvarado

      November 10, 2012 at 4:54 am

      Hello Tim , My saving is working fine but, It just updates the file on the simulator, and when I load the scene again I dont know why it has the same values, maybe when I test it on device it will work, can you help me please

       
      • Tim Roadley

        November 10, 2012 at 8:29 am

        If you’re saving fine and your interface isn’t updating, perhaps you need to performFetch?

         
    34. Umair

      January 1, 2013 at 5:45 pm

      can you tell us how to have horizontal scroll in levels?

       
    35. Matt

      August 13, 2013 at 2:56 pm

      Love the tutorial! However when I edit the unlocked XML value on chapter 1 levels 6 & 7 it works when I run the app the first time. Im using the actual iPhone5 device not the simulator. Then randomly (and I haven’t been able to consistently reproduce so it seems to have a mind if its own) it will reset back to the original values of locked for these 2 levels. It won’t go back unless I re-run the build after it changed. then it ends up changing back. I haven’t implemented any save code yet what could be the cause?

       
    36. Angie

      January 2, 2014 at 9:22 am

      hello

       

    Leave a Reply