Sprite Kit Tutorial: Making a Universal App: Part 2

Nicholas Waynik
Whack that laugh off this mole's face!

Whack that laugh off this mole's face!

Note from Ray: Tutorial Team member Nick Waynik has ported this tutorial from Cocos2D to Sprite Kit as part of the iOS 7 Feast. We hope you enjoy!

This article is the second part of a two-part series on how to create a mole whacking game with Sprite Kit. This series brings together a lot of concepts from other Sprite Kit tutorials on this site, and introduces some new concepts along the way as well.

In the first part of the series, we created the basics of the game – cute little moles popping out of holes. We spent a lot of time thinking about how to organize the art and coordinates so that the game would look good on the iPhone 3.5-inch, iPhone 4-inch, iPad, and iPad Retina – and be efficient too!

In this article, we’ll add some cute animations to the mole as he laughs and gets whacked, add gameplay so you can do the whacking and earn points, and of course add some gratuitous sound effects as usual.

If you don’t have it already, grab a copy of the project where we left things off in the last Sprite Kit tutorial.

Defining Animations: Practicalities

To make the game a little more fun, we’re going to give the mole two animations. First, he’ll laugh a little when he pops out of the hole (to make you really want to whack him!), then if you do manage to whack him he’ll make a “just got whacked” face.

But before we begin, let’s talk about the practicalities of defining our animations in code.

Our mole’s laugh animation is going to be these images in this order: mole_laugh1.png, mole_laugh2.png mole_laugh3.png, mole_laugh2.png, mole_laugh3.png, mole_laugh1.png.

So we could hard-code a bunch of lines to set up our animation, like this:

[animFrames addObject:
    [SKTexture textureWithImageNamed:@"mole_laugh1.png"]];
[animFrames addObject:
    [SKTexture textureWithImageNamed:@"mole_laugh2.png"]];
[animFrames addObject:
    [SKTexture textureWithImageNamed:@"mole_laugh3.png"]];
[animFrames addObject:
    [SKTexture textureWithImageNamed:@"mole_laugh2.png"]];
// And so on...

But that would make our code kind of ugly. To make things a bit cleaner, instead of defining the images in the animation in code, we’ll bring them out to a property list instead.

Property Lists

If you haven’t used property lists before, they are special files you can create in XCode to contain data like arrays, dictionaries, strings, numbers, and so on in a hierarchial format. It’s extremely easy to create these, and just as easy to read them from code.

Let’s see what I mean by trying this out in XCode. Right click on WhackAMole, choose “New File…”, choose “iOS\Resource\Property List”, and click “Next”. Name the new file “laughAnim.plist”, and click Create. At this point the property list editor for laughAnim.plist should be visible, as shown below:

XCode's Property List Editor

Every property list has a root element. This is usually either an array or a dictionary. This property list is going to contain an array of image names that make up the laugh animation, so click on the second column for the root element (Type, currently set to Dictionary), and change it to Array.

Next, click the small plus sign button to the right of the word Root – this adds a new entry to the array. By default, the type of the entry is a String – which is exactly what we want. Change the value to “mole_laugh1.png” for the first entry in the animation.

Click the + button to add a new row, and repeat to add all of the frames of the animation, as shown below:

Setting up Laugh Animation in Property List Editor

Next, repeat the process for the animation to play when the mole is hit. Follow the same steps as above to create a new property list named hitAnim.plist, and set it up as shown below:

Setting up Hit Animation in Property List Editor

Now, time to add the code to load these animations. Start by opening up MyScene.h and add property for each animation action, as shown below:

// Inside @interface MyScene
@property (strong, nonatomic) SKAction *laughAnimation;
@property (strong, nonatomic) SKAction *hitAnimation;

These will be used to keep a handy reference to each SKAction so it can be easily found and reused in the code.

Next add a method to create a SKAction based on the images defined in the property list, as follow:

- (SKAction *)animationFromPlist:(NSString *)animPlist
{
    NSString *plistPath = [[NSBundle mainBundle] pathForResource:animPlist ofType:@"plist"]; // 1
    NSArray *animImages = [NSArray arrayWithContentsOfFile:plistPath]; // 2
    NSMutableArray *animFrames = [NSMutableArray array]; // 3
    for (NSString *imageName in animImages) { // 4
        [animFrames addObject:[SKTexture textureWithImageNamed:imageName]]; // 5
    }
    
    float framesOverOneSecond = 1.0f/(float)[animFrames count];
    
    return [SKAction animateWithTextures:animFrames timePerFrame:framesOverOneSecond resize:NO restore:YES]; // 6
}

This is important to understand, so let’s go through it line by line.

  1. The property list is included in the project file, so it’s in the app’s “main bundle”. This helper method gives a full path to a file in the main bundle, which you’ll need to read in the property list.
  2. To read a property list, it’s as easy as calling a method on NSArray called arrayWithContentsOfFile and passing in the path to the property list. It will return an NSArray with the contents (a list of strings for the image names in the animation, in this case). Note this works because we set the root element to be an NSArray. If we had set it to a NSDictionary, we could use [NSDictionary dictionaryWithContentsOfFile…] instead.
  3. Creates an empty array that will store the animation frames.
  4. Loops through each image name in the array read from the property list.
  5. Gets the texture for each image and adds it to the array.
  6. Returns a SKAction based on the array of textures.

Next, add the code to the end of your init method to call this helper function for each animation:

self.laughAnimation = [self animationFromPlist:@"laughAnim"];
self.hitAnimation = [self animationFromPlist:@"hitAnim"];

One last step – let’s use the animations (just the laugh one for now). Modify the popMole method to read as the following:

- (void)popMole:(SKSpriteNode *)mole
{
    SKAction *easeMoveUp = [SKAction moveToY:mole.position.y + mole.size.height duration:0.2f];
	easeMoveUp.timingMode = SKActionTimingEaseInEaseOut;
	SKAction *easeMoveDown = [SKAction moveToY:mole.position.y duration:0.2f];
	easeMoveDown.timingMode = SKActionTimingEaseInEaseOut;
    
    
    SKAction *sequence = [SKAction sequence:@[easeMoveUp, self.laughAnimation, easeMoveDown]];
    [mole runAction:sequence];
}

The only difference here is that instead of delaying a second before popping down, it runs the laughAnimation action instead. The laughAnimation action uses textures from the laughAnim.plist, and sets restore to YES so that when the animation is done, it reverts back to the normal mole face.

Compile and run your code, and now when the moles pop out, they laugh at you!

Mole with Laugh Animation

Time to wipe that smile off their faces and start whacking!

Adding Game Logic

We’re now going to add the gameplay logic into the game. The idea is a certain number of moles will appear, and you get points for each one you whack. You try to get the most number of points you can.

So we’ll need to keep track of the score, and also display it to the user. And when the moles are finished popping, we’ll need to tell the user about that as well.

So start by opening MyScene.h, and add the following instance variables under the actions you added earlier:

@property (strong, nonatomic) SKLabelNode *scoreLabel;
@property (nonatomic) NSInteger score;
@property (nonatomic) NSInteger totalSpawns;
@property (nonatomic) BOOL gameOver;

These will keep track of the score label, the current score, the number of moles popped so far, and whether the game is over or not.

Next add the following to the end of your initWithSize: method in MyScene.m:

// Add score label
float margin = 10;

self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
self.scoreLabel.text = @"Score: 0";
self.scoreLabel.fontSize = [self convertFontSize:14];
self.scoreLabel.zPosition = 4;
self.scoreLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
self.scoreLabel.position = CGPointMake(margin, margin);
[self addChild:self.scoreLabel];

This code creates a label to show the score. The label is placed in the lower left corner of the screen by using the margin of 10 points from the left and bottom. Setting the label’s horizontalAlignmentMode property to SKLabelHorizontalAlignmentModeLeft will position the label’s text so that the left side of the text is on the node’s origin.

Also note that rather than passing the font size directly, it goes through a helper function to convert the font size first. This is because the font size will need to be larger on the iPad and iPad Retina, since it has a bigger screen. So implement convertFontSize next as the following:

- (float)convertFontSize:(float)fontSize
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        return fontSize * 2;
    } else {
        return fontSize;
    }
}

This is very simple – on the iPad and iPad Retina the font size is doubled, otherwise it’s left alone.

Next we want to add the touch detection code to see if a touch has hit a mole. But before we can do that, we need to add a flag to the mole to the game knows whether the mole is currently tappable or not. The mole should only be able to be tapped while it’s laughing – while it’s moving or underground it’s “safe.”

We could create a subclass of SKSpriteNode for the mole to keep track of this, but because we only need to store this one piece of information, we’ll use the userData property on the SKSpriteNode instead. So modify popMole one more time to the following:

- (void)popMole:(SKSpriteNode *)mole
{
    if (self.totalSpawns > 50) return;
    self.totalSpawns++;
    
    // Reset texture of mole sprite
    mole.texture = self.moleTexture;
    
	SKAction *easeMoveUp = [SKAction moveToY:mole.position.y + mole.size.height duration:0.2f];
    easeMoveUp.timingMode = SKActionTimingEaseInEaseOut;
    SKAction *easeMoveDown = [SKAction moveToY:mole.position.y duration:0.2f];
    easeMoveDown.timingMode = SKActionTimingEaseInEaseOut;
    
    SKAction *setTappable = [SKAction runBlock:^{
        [mole.userData setObject:@1 forKey:@"tappable"];
    }];
    
    SKAction *unsetTappable = [SKAction runBlock:^{
        [mole.userData setObject:@0 forKey:@"tappable"];
    }];
    
    
    SKAction *sequence = [SKAction sequence:@[easeMoveUp, setTappable, self.laughAnimation, unsetTappable, easeMoveDown]];
    [mole runAction:sequence completion:^{
        [mole removeAllActions];
    }];
}

The changes to popMole are as follows:

  • The method immediately returns if there has been 50 or more spawns, since 50 is the limit for this game.
  • It next resets the display frame of the sprite to the base image (“mole_1.png”) at the beginning of the method, since if the mole was hit last time, it will still be showing the “hit” image and will need to be reset.
  • Right before the mole laughs, it runs an action to run code specified in a block. This block sets a userData dictionary object named tappable to an NSNumber of 1, which you’ll use to indicate whether the mole is tappable.
  • Similarly, after the mole laughs, it runs code specified in the block for the unsetTappable action, which sets the tappable flag back to 0.

Ok, now that the sprite has a userData flag indicating whether it can be tapped or not, you can finally add the touchesBegan: method back to your code. Add the following to the end of your method:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInNode:self];
    
    SKNode *node = [self nodeAtPoint:touchLocation];
    if ([node.name isEqualToString:@"Mole"]) {
        SKSpriteNode *mole = (SKSpriteNode *)node;
        
        if (![[mole.userData objectForKey:@"tappable"] boolValue]) return;
        
        self.score += 10;
        
        [mole.userData setObject:@0 forKey:@"tappable"];
        [mole removeAllActions];

        SKAction *easeMoveDown = [SKAction moveToY:(mole.position.y - mole.size.height) duration:0.2f];
        easeMoveDown.timingMode = SKActionTimingEaseInEaseOut;
        
        // Slow down the animation by half
        easeMoveDown.speed = 0.5;
        
        SKAction *sequence = [SKAction sequence:@[self.hitAnimation, easeMoveDown]];
        [mole runAction:sequence];
    }
}

The touchesBegan:method get the location of a touch, then it finds the SKNode at the touch location. If the node’s name is equal to Mole, it proceeds to check if the sprite is tappable.

If the mole is hit, it sets the mole as no longer tappable, and increases the score. It then stops any running actions, plays the “hit” animation, and moves the mole immediately back down the hole.

One final step – add some code to update the score and check for the level complete condition at the beginning of update:

if (self.gameOver) return;

if (self.totalSpawns >= 50) {
    
    SKLabelNode *gameOverLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
    gameOverLabel.text = @"Level Complete!";
    gameOverLabel.fontSize = 48;
    gameOverLabel.zPosition = 4;
    gameOverLabel.position = CGPointMake(CGRectGetMidX(self.frame),
                                         CGRectGetMidY(self.frame));
    
    [gameOverLabel setScale:0.1];
    
    [self addChild:gameOverLabel];
    [gameOverLabel runAction:[SKAction scaleTo:1.0 duration:0.5]];
    
    self.gameOver = YES;
    return;
}

[self.scoreLabel setText:[NSString stringWithFormat:@"Score: %d", self.score]];

That’s it! Compile and run your code, and you should be able to whack moles and increase your score! How high of a score can you get?

Showing the score in the game

Gratuitous Sound Effects

As usual, let’s add even more fun to the game with some zany sound effects. Download these sound effects Ray made with Garage Band and Audacity, unzip the file, and drag the sounds to your WhackAMole folder. Make sure that “Copy items into destination group’s folder” is selected, and click Finish.
Add the following import statement to the top of MyScene.h:

#import <AVFoundation/AVFoundation.h>

Now add the following properties before @end:

@property (strong, nonatomic) AVAudioPlayer *audioPlayer;
@property (strong, nonatomic) SKAction *laughSound;
@property (strong, nonatomic) SKAction *owSound;

Then make the following changes to MyScene.m:

// Add at the bottom of your initWithSize: method
// Preload whack sound effect
self.laughSound = [SKAction playSoundFileNamed:@"laugh.caf" waitForCompletion:NO];
self.owSound = [SKAction playSoundFileNamed:@"ow.caf" waitForCompletion:NO];

NSURL *url = [[NSBundle mainBundle] URLForResource:@"whack" withExtension:@"caf"];
NSError *error = nil;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];

if (!self.audioPlayer) {
    NSLog(@"Error creating player: %@", error);
}

[self.audioPlayer play];

// Add at bottom of popMole method, change the sequence action to:
SKAction *sequence = [SKAction sequence:@[easeMoveUp, setTappable, self.laughSound, self.laughAnimation, unsetTappable, easeMoveDown]];

// Add inside touchesBegan: method, change the sequence action to:
SKAction *sequence = [SKAction sequence:@[self.owSound, self.hitAnimation, easeMoveDown]];

Compile and run your code, and enjoy the groovy tunes!

Where To Go From Here?

Here is a sample project with all of the code we’ve developed so far in this Sprite Kit tutorial series.

That’s it for this article series (at least for now) – but if you want, why not play with this project some more yourself? I’m sure you can come up with some good ideas for how to make things even better!

If you want to learn more about Sprite Kit, check out our book iOS Games by Tutorials where you’ll learn how to make 5 complete games – from a zombie action game to a racing game to a space shooter!

In the meantime, if you have any questions or comments, please join the forum discussion below!

Nicholas Waynik

Nicholas Waynik is an independent iOS developer, and has done everything from network administration to web development. He started writing iOS apps when the iPhone SDK was first released. Since then he has gone on to start his own business focusing on iOS development. He loves spending his free time with his family, and sometimes playing golf.

He can be found on Twitter as @n_dubbs, or at his website: http://www.nicholaswaynik.com.

Other Items of Interest

Black Friday Sale

50% OFF All Swift & iOS Books

Ends in…

0
:
0
:
0

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

iOS Team

... 78 total!

Android Team

... 27 total!

Unity Team

... 12 total!

Articles Team

... 15 total!

Resident Authors Team

... 20 total!

Podcast Team

... 7 total!

Recruitment Team

... 9 total!