Irrational Exuberance!

Creating Slideshows with Cocos2d iPhone

October 29, 2008. Filed under cocos2diphone

I've been slowly growing my Cocos2d iPhone related entries, and in this entry I want to display some real usable code to solve a general problem for iPhone game developers: how can I create a how-to guide that teaches new users to play my game?

The code below implements a simple slideshow player, which will cycle through an arbitrary number of images and text labels, and then pop itself off the stack of scenes. Along the way we'll implement a full example of handling touch detection in Cocos2d, and see how quick and downright pleasant Cocos2d iPhone development can be.

Usage of the SlideShowLayer class looks like this:

// Initialize scene and slide show layer.
Scene * s = [Scene node];
SlideShowLayer * ssl = [SlideShowLayer node];

// Set positions for the background and labels.
// Generally you'll be using full screen backgrounds
// (screenshots), and positioning them at the center.
[ssl setBackgroundXPosition:100];
[ssl setBackgroundYPosition:100];
[ssl setDescriptionXPosition: 200];
[ssl setDescriptionYPosition: 200];
[ssl setDescriptionWidth:100];
[ssl setDescriptionHeight:300];
[ssl setFontName:@"Helvetica"];      // optional
[ssl setFontSize:24];                // optional

// Add slides in order they appear.
[ssl addSlideWithBackground:@"pic1.png" andDescription:@"Yada yada one."];
[ssl addSlideWithBackground:@"pic2.png" andDescription:@"Yada yada two."];
[ssl addSlideWithBackground:@"pic3.png" andDescription:@"Yada yada 3."];
[ssl addSlideWithBackground:@"pic4.png" andDescription:@"Yada yada 4."];
[ssl addSlideWithBackground:@"pic5.png" andDescription:@"Yada yada 5."];
[ssl addSlideWithBackground:@"pic6.png" andDescription:@"Yada yada six."];

// Start and display the slide show.
[ssl displayFirstSlide];
[scene add:ssl];
[[Director sharedDirector] pushScene:scene];

Currently there are some limitations (all labels and images must be at the same location and all labels must be of the same height and width), but improving upon the code in that regard shouldn't be terriably difficult. Beyond that, it should be a fairly reusable and flexible tool for displaying the simplest of simple slide shows.

I'll be posting the code in its entirety as large chunks (one chunk for SlideShowLayer.h and one chunk for SlideShowLayer.m), to promote copy and pastability. Commentary will be inlined in the code as Objective-C comments.

First SlideShowLayer.h.

/* 
 *  SlideShowLayer by Will Larson and Luke Hatcher. 10/29/2008.
 *  Released under MIT License.
 *  Please check out from repository for full license detail.
 *
 *  Nothing too exiting happening here.
 *  Apologies that it is not in Obj2.0
 *  syntax, but I'm still ambivalent about
 *  the ambiguousness of properties. 
 */

#import <UIKit/UIKit.h>
#import "CocosNode.h"
#import "Layer.h"
#import "Label.h"
#import "Sprite.h"

@interface SlideShowLayer : Layer {
    int slidePosition;
    int backgroundXPosition;
    int backgroundYPosition;
    int descriptionXPosition;
    int descriptionYPosition;
    int descriptionHeight;
    int descriptionWidth;
    int fontSize;
    NSString * fontName;
    NSMutableArray * backgrounds;
    NSMutableArray * descriptions;
    Sprite * background;
    CocosNode * description;
}

/*
 *  This is the only method for adding slides to the slide show.
 */
-(void)addSlideWithBackground: (NSString *)imageString
               andDescription: (NSString *)descString;
/*
 *  Methods for advancing, retreating, and starting the slideshow.
 *  There currently isn't a UI mechanism for retreating to already
 *  seen slides, but you could rig something up if you tried hard 
 *  enough. ;)
 */
-(void)displayFirstSlide;
-(void)displayNextSlide;
-(void)displayPreviousSlide;
-(void)displaySlide: (int)slideNumber;
-(BOOL)hasPreviousSlide;
-(BOOL)hasNextSlide;

/*
 *  Mutators and Accessors
 */
-(NSMutableArray *)backgrounds;
-(NSMutableArray *)descriptions;
-(Sprite *)background;
-(void)setBackground: (Sprite *)aSprite;
-(CocosNode *)description;
-(void)setDescription: (CocosNode *)aCocosNode;
-(NSString *)fontName;
-(void)setFontName: (NSString *)aFontName;
-(int)fontSize;
-(void)setFontSize: (int)anInt;
-(int)slidePosition;
-(void)setSlidePosition: (int)anInt;
-(int)backgroundXPosition;
-(void)setBackgroundXPosition: (int)anInt;
-(int)backgroundYPosition;
-(void)setBackgroundYPosition: (int)anInt;
-(int)descriptionXPosition;
-(void)setDescriptionXPosition: (int)anInt;
-(int)descriptionYPosition;
-(void)setDescriptionYPosition: (int)anInt;
-(int)descriptionWidth;
-(void)setDescriptionWidth: (int)anInt;
-(int)descriptionHeight;
-(void)setDescriptionHeight: (int)anInt;
@end

And now for SlideShowLayer.m.

/* 
 *  SlideShowLayer by Will Larson and Luke Hatcher. 10/29/2008.
 *  Released under MIT License.
 *  Please check out from repository for full license detail.
 */
#import "SlideShowLayer.h"

@implementation SlideShowLayer

-(id)init {
    self = [super init];
    if (self) {
        /*
         * setting isTouchEnabled is the magic step that
         * allows the layer to be registered for UI events.
         * forgetting this step will lead to great confusion :-/
         */
        isTouchEnabled = YES;
        slidePosition = -1;
        backgrounds = [[NSMutableArray alloc] init];
        descriptions = [[NSMutableArray alloc] init];
        [self setFontName:@"Helvetica"];
        [self setFontSize:24]; 
    }
    return self;
}

- (void) dealloc {
    [backgrounds release];
    backgrounds = nil;
    [descriptions release];
    descriptions = nil;
    [background release];
    background = nil;
    [description release];
    description = nil;
    [fontSize release];
    fontSize = nil;
    [super dealloc];
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    /*
     *  We are using pushScene/popScene instead of replaceScene
     *  (which has superior memory usage characteristics), because
     *  it makes integrating SlideShowLayer much simpler, and helps
     *  avoid binding the code to one application without requiring
     *  us to use the delegate pattern.
     */
    if ([self hasNextSlide]) [self displayNextSlide];        
    else [[Director sharedDirector] popScene];
}

-(void)addSlideWithBackground: (NSString *)imageString
               andDescription: (NSString *)descString {
    [[self backgrounds] addObject:imageString];
    [[self descriptions] addObject:descString];
}

-(void)displayFirstSlide {
    if ([[self backgrounds] count] > 0) {
        [self setSlidePosition:0];
        [self displaySlide:[self slidePosition]];
    }
}

-(void)displayNextSlide {
    if ([self hasNextSlide]) {
        [self setSlidePosition:[self slidePosition]+1];
        [self displaySlide:[self slidePosition]];
    }
}

-(void)displayPreviousSlide {
    if ([self hasPreviousSlide]) {
        [self setSlidePosition:[self slidePosition]-1];
        [self displaySlide:[self slidePosition]];
    }
}

-(void)displaySlide: (int)slideNumber {
    /*
     * Remove existing slide and description.
     */
    if ([self background]) {
        [self remove:[self background]];
    }
    if ([self description]) {
        [self remove:[self description]];
    }
    
    /*
     * Retrieve and generate necessary details for next slide.
     */
    NSString * bgString = [[self backgrounds] objectAtIndex:slideNumber];
    NSString * descString = [[self descriptions] objectAtIndex:slideNumber];
    Sprite * bg = [Sprite spriteWithFile:bgString];
    bg.position = cpv([self backgroundXPosition], [self backgroundYPosition]);
    CocosNode * desc = [Label labelWithString:descString
                                   dimensions:CGSizeMake([self descriptionWidth],
                                                         [self descriptionHeight])
                                    alignment:UITextAlignmentCenter
                                     fontName:[self fontName]
                                     fontSize:[self fontSize]];
    desc.position = cpv([self descriptionXPosition], [self descriptionYPosition]);
    
    // Add the background and desc to the layer.
    [self setBackground:bg];
    [self setDescription:desc];
    [self add:bg];
    [self add:desc];    
}

-(BOOL)hasNextSlide {
    return ([self slidePosition]+1 < [[self backgrounds] count]);
}

-(BOOL)hasPreviousSlide {
    return ([self slidePosition] > 0);
}

-(Sprite *)background {
    return background;
}
-(void)setBackground: (Sprite *)aSprite {
    if (background) [background release];
    background = [aSprite retain];
}

-(CocosNode *)description {
    return description;
}
-(void)setDescription: (CocosNode *)aCocosNode {
    if (description) [description release];
    description = [aCocosNode retain];
}

-(NSMutableArray *)backgrounds {
    return backgrounds;
}

-(NSMutableArray *)descriptions {
    return descriptions;
}

-(NSString *)fontName {
    return fontName
}

-(int)fontSize {
    return fontSize;
}

-(void)setFontSize: (int)anInt {
    fontSize = anInt;
}

-(void)setFontName: (NSString *)aFontName {
    if (fontName) [fontName release];
    fontName = [aFontName retain];
}

-(int)slidePosition {
    return slidePosition;
}

-(void)setSlidePosition: (int)anInt {
    slidePosition = anInt;
}

-(int)backgroundXPosition {
    return backgroundXPosition;
}

-(void)setBackgroundXPosition: (int)anInt {
    backgroundXPosition = anInt;
}

-(int)backgroundYPosition {
    return backgroundYPosition;
}

-(void)setBackgroundYPosition: (int)anInt {
    backgroundYPosition = anInt;
}

-(int)descriptionXPosition {
    return descriptionXPosition;
}
-(void)setDescriptionXPosition: (int)anInt {
    descriptionXPosition = anInt;
}

-(int)descriptionYPosition {
    return descriptionYPosition;
}

-(void)setDescriptionYPosition: (int)anInt {
    descriptionYPosition = anInt;
}

-(int)descriptionWidth {
    return descriptionWidth;
}

-(void)setDescriptionWidth: (int)anInt {
    descriptionWidth = anInt;
}

-(int)descriptionHeight {
     return descriptionHeight;
}

-(void)setDescriptionHeight: (int)anInt {
    descriptionHeight = anInt;
}

@end

Hopefully this is a starting point that you can work from, and use in your applications. I'll be cleaning this up and putting it into a repository in the near future. I'll post a link here afterwards.

Let me know if you have any questions or comments.