Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4384

How to Create an Interactive Children’s Book for the iPad

$
0
0
Learn how to create an interactive children's book for the iPad!

Learn how to create an interactive children’s book for the iPad!

With the iPad, it’s never been a better time to be a kid!

The iPad allows developers to create beautiful interactive children’s books that simply cannot be replicated in any other medium. For some examples, check out The Monster at the End of This Book, Bobo Explores Light, and Wild Fables.

In the old days, developers had to use third party frameworks like Cocos2d to get the job done. But now, Apple has provided their own 2D framework called Sprite Kit, which is perfect for creating these types of books.

In this tutorial, you’ll create an interactive children’s book using the Sprite Kit framework, where you’ll learn how to add objects to scenes, create animation sequences, allow the reader to interact with the book and even how to add sound and music to your book!

This tutorial assumes you are familiar with at least the basics of Sprite Kit. If you are new to Sprite Kit, please check out our Sprite Kit Tutorial for Beginners first.

Getting Started

First download the starter project for this tutorial. Extract the project to a convenient location on your drive, then open it up in Xcode for a quick look at how it’s structured.

The project’s contents are collected in four main folders, as shown below:

The Seasons - Project Layout

  1. Other Resources: this contains all resources from third-party sources.
  2. Sounds: this contains most of the project’s sound files.
  3. Art: this contains all of the creative image assets.
  4. Scenes: as you may have guessed, this contains all the classes that control the various scenes of the project.
Note: The Seasons was written and illustrated by myself (Tammy L Coron). This tutorial uses the first few pages from that book. Please do not reproduce any of its contents in your own projects. The narration is provided by Amy Tominac, and again cannot be used in your own projects.

This tutorial also uses music from Kevin MacLeod and sound effects from FreeSound. These are both great resources for your project, but you must provide attribution. See attribution.txt for more details.

Remember, kids… only you can prevent copyright theft. Oh, and forest fires too, according to Smokey the Bear.

Close the Other Resources, Sounds, and Art groups; you won’t be making any changes in those areas. You’ll only be working with the “Scene*” files located in the Scenes group.

Like any good book, it’s best to start at the beginning. The next section will walk you through the “title” scene for your book.

Adding the Title Page

The starter project provides stub versions of the methods used in this scene — it’s your job to add the code. Your first steps will be to initialize the scene and add a background image.

Open the Scene00.m file and add the following block of code to initWithSize: just after the comment that reads /* add setup here */:

SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:@"bg_title_page"];
background.anchorPoint = CGPointZero;
background.position = CGPointZero;
 
[self addChild:background];

The code above creates an SKSpriteNode object and initializes it using the spriteNodeWithImageNamed: class method. It then sets the background‘s anchorPoint to CGPointZero which is the lower left corner of the scene.

Next, it sets the background‘s position to CGPointZero, which is the lower left corner of the screen. Finally, it uses the addChild: class method to add the newly created node as a child of the SKScene.

Note: SKSpriteNode subclasses SKNode. SKNodes are responsible for most of the content that you will add to your Sprite Kit apps.

Still working in the same file, add the following two lines of code to initWithSize: immediately after the call to addChild::

[self setUpBookTitle];
[self setUpSoundButton];

The two methods called here are only shells at the moment; you’ll flesh them out in the next section.

Build and run your project; you should see the title page as illustrated below:

tc_spritekit_build1

Adding the Book Title

As you saw in the code above, it’s a straightforward operation to add an SKSpriteNode to your scene. With just a few lines of code, you can add a textured sprite to any scene.

Now that the background image has been added, you’ll need to add the book title. You could use an SKLabelNode to add your title, or even a UIView, but instead I’ve opted to use a graphic.

Still working in the Scene00.m file, add the following block of code to setUpBookTitle:

SKSpriteNode *bookTitle = [SKSpriteNode spriteNodeWithImageNamed:@"title_text"];
bookTitle.name = @"bookTitle";
 
bookTitle.position = CGPointMake(421,595);
[self addChild:bookTitle];

In the above code, you first create an SKSpriteNode and then initialize it using spriteNodeWithImageNamed:. You then name the node “bookTitle” for ease of reference later, and set the node’s position to something other than CGPointZero.

Why? Since the background image takes up the entire view, you can get away with positioning it in the bottom-left corner of the screen. The title image, on the other hand, must be positioned precisely where you want it.

Build and run your project; you’ll see your background image with the graphic title superimposed over it, as shown below:

tc_spritekit_build2

That looks pretty neat. But wouldn’t it be great if you could add a little bit of action to your title screen?

Adding Animation to Objects

Sprite Kit makes it easy to add animation to the objects in your scene. Instead of simply having the title appear on the screen, you can make it slide on to the screen and bounce a little bit before it comes to rest.

To do this, you’ll use SKAction objects with predefined sequences.

Head back to Scene00.m and add the following code to the end of setUpBookTitle:

SKAction *actionMoveDown = [SKAction moveToY:600 duration:3.0];
SKAction *actionMoveUp = [SKAction moveToY:603 duration:0.25];
SKAction *actionMoveDownFast = [SKAction moveToY:600 duration:0.25];
 
[bookTitle runAction:[SKAction sequence:@[actionMoveDown, actionMoveUp, actionMoveDownFast]]];

The above code creates three actions using SKAction‘s class method moveToY:duration:; this specifies an action that moves a node vertically from its original position to the specified y position. Next, it creates a new sequence action using SKAction‘s sequence: class method; this instructs bookTitle to run the specified array of actions in order.

The net result of these actions is that the sprite will move the sprite down, then up, and then down again before it comes to rest.

Next, modify the line in setUpBookTitle that sets bookTitle‘s position property as shown below:

bookTitle.position = CGPointMake(425,900);

This simply modifies the start position of the title image so that it starts off-screen.

Build and run your project; the title now slides in slowly and bounces a little before it comes to rest at the position specified in actionMoveDownFast.

Actions are great, but sounds are an integral part of any interactive children’s book. Sprite Kit makes this tremendously easy as well!

Adding Sound to Your Story

There are several methods to add music and other sounds to your book, but in this tutorial you’ll just focus on two methods, one for adding the background music, and the other for adding the narration and sound effects.

Head back to Scene00.m and add the following import statement:

@import AVFoundation;

Add the following private instance variables to Scene00.m at the spot indicated by the comment /* set up your instance variables here */:

AVAudioPlayer *_backgroundMusicPlayer;
SKSpriteNode *_btnSound;
BOOL _soundOff;

Next, add the following two lines of code just before the line that creates background in locate initWithSize::

_soundOff = [[NSUserDefaults standardUserDefaults] boolForKey:@"pref_sound"];
[self playBackgroundMusic:@"title_bgMusic.mp3"];

This code retrieves the BOOL value that represents the user’s preference for having the sound on or off and assigns it to the _soundOff instance variable.

Now add the following block of code to playBackgroundMusic::

NSError *error;
NSURL *backgroundMusicURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
_backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
_backgroundMusicPlayer.numberOfLoops = -1;
_backgroundMusicPlayer.volume = 1.0;
[_backgroundMusicPlayer prepareToPlay];

playBackgroundMusic: plays the background music using a AVAudioPlayer object.

Note: AVAudioPlayer isn’t specific to Sprite Kit, so it won’t be covered in detail here. For more detail on AVAudioPlayer, check out the Audio Tutorial for iOS.

Okay — you have some code to handle the user preference for enabling or disabling sound in your book. You should probably add a control to let the user set this preference! :]

Still working in Scene00.m, add the code below to setUpSoundButton:

if (_soundOff)
{
  // NSLog(@"_soundOff");
 
  [_btnSound removeFromParent];
 
  _btnSound = [SKSpriteNode spriteNodeWithImageNamed:@"button_sound_off"];
  _btnSound.position = CGPointMake(980, 38);
 
  [self addChild:_btnSound];
  [_backgroundMusicPlayer stop];
}
else
{
  // NSLog(@"_soundOn");
 
  [_btnSound removeFromParent];
 
  _btnSound = [SKSpriteNode spriteNodeWithImageNamed:@"button_sound_on"];
  _btnSound.position = CGPointMake(980, 38);
 
  [self addChild:_btnSound];
  [_backgroundMusicPlayer play];
}

The above code sets the texture of the _btnSound SKSpriteNode to the appropriate image depending on the user’s stored preference. It then checks _soundOff and, regardless of its value, removes the sprite from the screen and initializes a new sprite with the appropriate on or off image. Then it sets the sprite’s position, adds it to the parent view, and finally plays or stops the music depending on the user’s preference.

There are a number of ways to handle changing a sprite’s image. The above method is only one way to accomplish this; the next section shows you another way to manage sprite images. The next section shows you another way to accomplish the same thing.

For now, build and run the app, and the background music should play. Tapping the button won’t do anything yet though.

Detecting Touch Events

The reason you can’t currently toggle the control is that the sprite doesn’t have any touch event handlers. So let’s add that next.

Add the following code to touchesBegan:withEvent: in Scene00.m:

/* Called when a touch begins */
for (UITouch *touch in touches)
{
  CGPoint location = [touch locationInNode:self];
  // NSLog(@"** TOUCH LOCATION ** \nx: %f / y: %f", location.x, location.y);
 
  if([_btnSound containsPoint:location])
  {
    // NSLog(@"xxxxxxxxxxxxxxxxxxx sound toggle");
 
    [self showSoundButtonForTogglePosition:_soundOff];
  }
}

touchesBegan:withEvent: is part of the UIResponder class which all SKNodes extend. It tells the receiver when one or more fingers begin touching the view. In this method you’re able to capture the location of the touch and respond to it accordingly.

Note: As every SKNode extends UIResponder, you can implement touch handling directly in your sprites. This tutorial performs all touch event handling in the scene nodes simply to keep the number of classes more manageable. However, in a production app it would likely make more sense to create an SKSpriteNode subclass, such as SoundToggleButton, and have that subclass handle its own touch events.

Look at the if statement above; you’ll see that if the touch location is on the sound button, you call showSoundButtonForTogglePosition:.

Now would be a great time to write that method! :]

Add the following code to showSoundButtonForTogglePosition: in Scene00.m:

// NSLog(@"togglePosition: %i", togglePosition);
 
if (togglePosition)
{
  _btnSound.texture = [SKTexture textureWithImageNamed:@"button_sound_on"];
 
  _soundOff = NO;
  [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"pref_sound"];
  [[NSUserDefaults standardUserDefaults] synchronize];
 
  [_backgroundMusicPlayer play];
}
else
{
  _btnSound.texture = [SKTexture textureWithImageNamed:@"button_sound_off"];
 
  _soundOff = YES;
  [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"pref_sound"];
  [[NSUserDefaults standardUserDefaults] synchronize];
 
  [_backgroundMusicPlayer stop];
}

The method above sets the appropriate texture for the SKSpriteNode that is held in _btnSound — much as you did in setUpSoundButton. Once the texture has been set, you can store the user’s selection and either play or stop the music accordingly.

Note: An SKTexture object stores a reference to the graphical data in memory that a sprite renders.

Build and run, and now you should be able to tap the sound button to turn the background music on and off.

Adding the Next Scene

Add the following import to the top of Scene00.m:

#import "Scene01.h"

You’ll need this to present the next scene; this is Page 1 of the book and is controlled by the Scene01 class.

Still working in Scene00.m, add the following code to setUpBookTitle just after the line that creates actionMoveDownFast, but before the line that calls runAction: on bookTitle:

SKAction *wait = [SKAction waitForDuration:0.75];
SKAction *showButton = [SKAction runBlock:^{
 
  SKSpriteNode *buttonStart = [SKSpriteNode spriteNodeWithImageNamed:@"button_read"];
  buttonStart.name = @"buttonStart";
 
  buttonStart.position = CGPointMake(425,460);
  [self addChild:buttonStart];
 
  [buttonStart runAction:[SKAction playSoundFileNamed:@"thompsonman_pop.wav" waitForCompletion:NO]];
}];

The code above creates a new action block which creates a sprite for the start button and plays a sound. By putting the code to create the sprite and play a sound inside an action, you can easily make this code run in a particular point in a sequence of actions.

As you may have figured out by now, there is usually more than one way to skin a cat — or code your children’s book!

Replace the line in setUpBookTitle that calls runAction: on bookTitle with the following code:

[bookTitle runAction:[SKAction sequence:@[actionMoveDown, actionMoveUp, actionMoveDownFast, wait, showButton]]];

This simply adds the new action to the sequence that bookTitle runs when called.

The next step is to modify the touch handling in touchesBegan:withEvent:.

Add the following line to the top of touchesBegan:withEvent:

SKNode *startButton = [self childNodeWithName:@"buttonStart"];

This assigns the button – added earlier in the showButton action – to the node.

Add the following else if clause after the if statement in touchesBegan:withEvent::

else if([startButton containsPoint:location])
{
  [_backgroundMusicPlayer stop];
 
  // NSLog(@"xxxxxxxxxxxxxxxxxxx touched read button");
 
  Scene01 *scene = [[Scene01 alloc] initWithSize:self.size];
  SKTransition *sceneTransition = [SKTransition fadeWithColor:[UIColor darkGrayColor] duration:1];
  [self.view presentScene:scene transition:sceneTransition];
}

The above code adds a new scene and presents it with a new transition when it detects a user tap on the “Read Story” button.

Build and run your project. After the initial animation, you should see the new “Read Story” button, as shown below:

tc_spritekit_build3

Tap the “Read Story” button and the next scene presents itself on-screen. You won’t see a whole lot at this point — your next task is add some content to the first page.

Adding Content to the First Page

In this section, you’ll add the first page to the book along with some physics simulation and some touch response logic. Before you do that you’ll need to set up the scene.

Open Scene01.m and add the following import statements to the top of the file:

#import "Scene00.h"
#import "Scene02.h"
#import "SKTUtils.h"

The first two lines above give you access to other scenes so you can implement page forward and back controls. The third line allows you to use all of the goodies in SKTUtils, which is an external resource developed for iOS Games by Tutorials and won’t be covered in detail in this tutorial.

Add the following private variables to the implementation section of Scene01.m:

SKSpriteNode *_footer;
SKSpriteNode *_btnLeft;
SKSpriteNode *_btnRight;
Note: From this point forward, all code dealing with background sound has been included in the starter files for you. You can review the previous section to see how sound is implemented in this project.

Just as before, you’ll need to initialize the scene before you can do anything with it. There are several ways to accomplish this, but this tutorial takes the approach of creating separate methods for each scene instead of handling everything within a single method.

Add the following block of code to Scene01.m, just after the comment that reads /* additional setup */:

[self setUpText];
[self setUpFooter];
[self setUpMainScene];

Those are your basic setup methods — your job is to fill them out. The good news is that the implementation of setUpText setUpFooter is identical for each scene in your project, so you’ll only need to write it once for all scenes.

Add the following code to setUpText in Scene01.m:

SKSpriteNode *text = [SKSpriteNode spriteNodeWithImageNamed:@"pg01_text"];
text.position = CGPointMake(300 , 530);
 
[self addChild:text];
 
[self readText];

Here you create an SKSpriteNode, set its location, and add it to the scene. The sprite above holds an image for the text of the page; you’ll have one sprite for the text of each page in your book.

This method also calls readText which plays the narration for the book provided by the very talented Amy Tominac.

Add the following code to readText in Scene01.m:

if (![self actionForKey:@"readText"])
{
  SKAction *readPause = [SKAction waitForDuration:0.25];
  SKAction *readText = [SKAction playSoundFileNamed:@"pg01.wav" waitForCompletion:YES];
 
  SKAction *readSequence = [SKAction sequence:@[readPause, readText]];
 
  [self runAction:readSequence withKey:@"readText"];
}
else
{
  [self removeActionForKey:@"readText"];
}

Here you create a new SKAction, much like you did earlier for the bouncing title text. The difference this time — and it’s an important difference — is that you also assign a key name of readText to your action. You’ll discover why a bit later.

You’re also using SKAction’s playSoundFileNamed:waitForCompletion: class method, which is a really simple method for playing sounds. While this method works great for quick sound effects, it’s probably not the best choice for read-along text because you can’t interrupt the sound when it’s playing, among other reasons. You’re using it in this tutorial for ease-of-use only and to become familiar with the framework.

In a production-level app, you should consider using something with a little more functionality such as the ability to stop sounds while they are playing, such as one of Apple’s other audio technologies.

Build and run, and now you will see and hear the narrated text for page 1!

iOS Simulator Screen shot Jan 13, 2014, 2.42.55 PM

Adding the Navigation Controls

Next let’s add the navigation controls for your app, which will all be located along the bottom of the screen.

Add the following code to setUpFooter in Scene01.m:

/* add the footer */
 
_footer = [SKSpriteNode spriteNodeWithImageNamed:@"footer"];
_footer.position = CGPointMake(self.size.width/2, 38);
 
[self addChild:_footer];
 
/* add the right button */
 
_btnRight = [SKSpriteNode spriteNodeWithImageNamed:@"button_right"];
_btnRight.position = CGPointMake(self.size.width/2 + 470, 38);
 
[self addChild:_btnRight];
 
/* add the left button */
 
_btnLeft = [SKSpriteNode spriteNodeWithImageNamed:@"button_left"];
_btnLeft.position = CGPointMake(self.size.width/2 + 400, 38);
 
[self addChild:_btnLeft];
 
/* add the sound button */
 
if (_soundOff)
{
  // NSLog(@"_soundOff");
 
  [_btnSound removeFromParent];
 
  _btnSound = [SKSpriteNode spriteNodeWithImageNamed:@"button_sound_off"];
  _btnSound.position = CGPointMake(self.size.width/2 + 330, 38);
 
  [self addChild:_btnSound];
  [_backgroundMusicPlayer stop];
}
else
{
  // NSLog(@"_soundOn");
 
  [_btnSound removeFromParent];
 
  _btnSound = [SKSpriteNode spriteNodeWithImageNamed:@"button_sound_on"];
  _btnSound.position = CGPointMake(self.size.width/2 + 330, 38);
 
  [self addChild:_btnSound];
 
  [_backgroundMusicPlayer play];
}

The code above initializes _footer with the footer area’s background image, sets its position, and adds it to the scene. It also adds sprites for both the page back and page forward buttons. Finally, it adds the sound toggle button, which should look familiar since it’s essentially the same code you used to set up the button in the previous scene.

Build and run, and you will now see the footer along the bottom of the scene (although tapping it won’t do anything yet):

Book footer

Now that the basic scene setup is complete, it’s time to add the main character.

Adding The Main Character

Still working in the Scene01.m file, add the following private variable to the Scene01 implementation:

SKSpriteNode *_kid;

This variable holds a reference to your main character’s sprite.

Next, add the following two lines to setUpMainScene of Scene01.m:

[self setUpMainCharacter];
[self setUpHat];

These methods simply call other methods to keep your code nice and tidy. You’ll populate those next.

Add the following code to setUpMainCharacter in Scene01.m:

_kid = [SKSpriteNode spriteNodeWithImageNamed:@"pg01_kid"];
_kid.anchorPoint = CGPointZero;
_kid.position = CGPointMake(self.size.width/2 - 245, 45);
 
[self addChild:_kid];
[self setUpMainCharacterAnimation];

This should be familiar territory to you by now; you create an SKSpriteNode, set its anchorPoint and position, and add it to the scene. Then you call setUpMainCharacterAnimation to set up the main character animation.

setUpMainCharacterAnimation only exists as a shell — time to add some madness to your method! :]

Add the following code to setUpMainCharacterAnimation in in Scene01.m:

NSMutableArray *textures = [NSMutableArray arrayWithCapacity:2];
 
for (int i = 0; i <= 2; i++)
{
  NSString *textureName = [NSString stringWithFormat:@"pg01_kid0%d", i];
  SKTexture *texture = [SKTexture textureWithImageNamed:textureName];
  [textures addObject:texture];
}
 
CGFloat duration = RandomFloatRange(3, 6);
 
SKAction *blink = [SKAction animateWithTextures:textures timePerFrame:0.25];
SKAction *wait = [SKAction waitForDuration:duration];
 
SKAction *mainCharacterAnimation = [SKAction sequence:@[blink, wait, blink, blink, wait , blink, blink]];
[_kid runAction: [SKAction repeatActionForever:mainCharacterAnimation]];

Animations are performed using a series of images. In the code above, the main character blink animation uses two images to achieve this effect.

The first line creates an array to hold your images. Following that, you generate the image name, create an SKTexture object for each image in the animation, then add that object to your array.

Next you create the animation sequence using SKAction‘s animateWithTextures:timePerFrame: class method which expects an array of textures.

Finally, you instruct the _kid sprite to perform its action. By using the repeatActionForever method, you tell the node to run this action continuously.

Build and run your project; hit the “Read Story” button and you’ll see the main character appear on-screen and blink his eyes while the narration plays in the background, as so:

tc_spritekit_build4

An Introduction to Physics

You can really improve the appeal of your book by adding some interactivity. In this section, you’re going to create a hat for the main character which the reader can drag around the screen and place on the main character’s head.

Still working in Scene01.m, add the following instance variable to the Scene01 implementation:

SKSpriteNode *_hat;

Add the following code to setUpHat in Scene01.m:

SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Thonburi-Bold"];
label.text = @"Help Mikey put on his hat!";
label.fontSize = 20.0;
label.fontColor = [UIColor yellowColor];
label.position = CGPointMake(160, 180);
 
[self addChild:label];
 
_hat = [SKSpriteNode spriteNodeWithImageNamed:@"pg01_kid_hat"];
_hat.position = CGPointMake(150, 290);
_hat.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_hat.size];
_hat.physicsBody.restitution = 0.5;
 
[self addChild:_hat];

The first half of the code above creates an SKLabelNode object and adds it to the scene. An SKLabelNode is similar to a UILabel; in Sprite Kit, it’s a node used to draw a string.

The second half of the code adds the physics to the scene. You created the SKSpriteNode and assigned it to the _hat variable as well as an SKPhysicsBody which lets you apply a number of physical characteristics to your objects such as shape, size, mass, and gravity and friction effects.

Calling the SKPhysicsBody‘s bodyWithRegtangleOfSize: class method sets the shape of the body to match the node’s frame. You also set the restitution to 0.5 which means your physics body will bounce off objects with half of its initial force.

Build and run your project; tap the “Read Story” button and…huh? Where did the hat go?

If you were watching the screen closely, you may have noticed the hat falling off the screen. That’s because there was no opposing body to stop it from falling.

You can fix that by adding a physics body to act as the ground.

Add the following code to setUpFooter in Scene01.m, just after the line that calls addChild: to add _footer to the scene:

self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:_footer.frame];

Build and run your project again; this time, the hat has something to land on — the physics body you set up in the footer. As well, you can see the yellow text label that you created with SKLabelNode, as shown below:

tc_spritekit_build5

Okay, you’ve added some physics properties to the hat — but how do you go about adding interactivity?

Handling Touches and Moving the Hat

This section implements the touch handling for the hat so that you can move it around the screen, as well as touch handling for the Next, Previous and sound preference button.

touchesBegan:withEvent: will end up as one of the longest methods you’ll write; it already includes the processing you wrote for Scene00 to toggle the background music, and you’ll add supporting code for the next/previous page buttons and for moving the hat in the next few code blocks.

Add the following block of code immediately after the existing if statement of touchesBegan:withEvent: in Scene01.m:

else if ([_btnRight containsPoint:location])
{
  // NSLog(@">>>>>>>>>>>>>>>>> page forward");
 
  if (![self actionForKey:@"readText"]) // do not turn page if reading
  {
    [_backgroundMusicPlayer stop];
 
    SKScene *scene = [[Scene02 alloc] initWithSize:self.size];
    SKTransition *sceneTransition = [SKTransition fadeWithColor:[UIColor darkGrayColor] duration:1];
    [self.view presentScene:scene transition:sceneTransition];
  }
}
else if ([_btnLeft containsPoint:location])
{
  // NSLog(@"<<<<<<<<<<<<<<<<<< page backward");
 
  if (![self actionForKey:@"readText"]) // do not turn page if reading
  {
    [_backgroundMusicPlayer stop];
 
    SKScene *scene = [[Scene00 alloc] initWithSize:self.size];
    SKTransition *sceneTransition = [SKTransition fadeWithColor:[UIColor darkGrayColor] duration:1];
    [self.view presentScene:scene transition:sceneTransition];
  }
}

Here you check to see if the Next or Previous page buttons were the target of the touch event, much like you did for the sound toggle button. You then handle the touch event by stopping the background music and moving to the appropriate scene.

However, there is one small difference here. Recall the key that you set for the action that narrates the text?

[self runAction:readSequence withKey:@"readText"];

The block of code you added above uses that key to check if the readText action is currently playing. If it is, do nothing. If it’s not, turn the page.

But why check at all?

The reason is that when you start an SKAction that plays a sound, it’s impossible to interrupt that sound. This is why you can’t turn the page while the text is being read. As was mentioned earlier, you’ll probably want to use something more robust to narrate the text in your production-level app.

It would be nice to give the reader a way to jump back to the first scene, no matter where they are in the book.

Add the following code right below the code that you added previously, which will take you back to the first scene when the user touches the book title in the footer:

else if ( location.x >= 29 && location.x <= 285 && location.y >= 6 && location.y <= 68 )
{
  // NSLog(@">>>>>>>>>>>>>>>>> page title");
 
  if (![self actionForKey:@"readText"]) // do not turn page if reading
  {
    [_backgroundMusicPlayer stop];
 
    SKScene *scene = [[Scene00 alloc] initWithSize:self.size];
    SKTransition *sceneTransition = [SKTransition fadeWithColor:[UIColor darkGrayColor] duration:1];
    [self.view presentScene:scene transition:sceneTransition];
  }
}

The above code tests the touch location a little differently than the other checks you’ve made. Because the book’s title is part of the footer image, this simply checks to see whether or not the touch location falls within the area where you know the book title appears. It’s usually not a good idea to have “magic numbers” like this strewn about your app, but it serves to keep things simple in this tutorial.

The rest of the above code is exactly like the code that handles the Previous Page button touch events.

Finally, you’ll need to handle touch events on the hat. To do so, you’ll need to store some data between events.

Add the following two instance variables to the Scene01 implementation of Scene01.m:

BOOL _touchingHat;
CGPoint _touchPoint;

In the above code, _touchingHat stores whether the user is currently touching the hat, while _touchPoint stores the most recent touch location.

To ensure the hat catches any touch events that occur both over it and another target area, check the hat first.

To do this, change the first if in touchesBegan:withEvent: to an else if so that it looks like the following:

else if([_btnSound containsPoint:location])

Next, add the following code immediately above the line you just changed in Scene01.m:

if([_hat containsPoint:location])
{
  // NSLog(@"xxxxxxxxxxxxxxxxxxx touched hat");
  _touchingHat = YES;
  _touchPoint = location;
 
  /* change the physics or the hat is too 'heavy' */
 
  _hat.physicsBody.velocity = CGVectorMake(0, 0);
  _hat.physicsBody.angularVelocity = 0;
  _hat.physicsBody.affectedByGravity = NO;
}

When the user first touches the hat, the code above sets the _touchingHat flag to YES and stores the touch location in _touchPoint. It also makes a few changes to the hat’s physics body. These changes are necessary because without them it’s virtually impossible to drag the hat around the screen as you’re constantly fighting with the physics engine.

You’ll need to track the touch as it moves across the screen, so add the following code to touchesMoved:withEvent: in Scene01.m:

_touchPoint = [[touches anyObject] locationInNode:self];

Here you update the most recent touch location stored in _touchPoint.

When the user stops touching the screen, you need to reset any hat-related data.

Add the following code to both touchesEnded:withEvent: and touchesCancelled:withEvent::

_touchingHat = NO;
_hat.physicsBody.affectedByGravity = YES;

The above code sets the _touchingHat flag to NO and re-enables gravity for the hat so that it will fall back to the floor when the user releases it.

There’s just one more thing to do to get the hat to track the user’s finger as it moves on the screen.

Add the following code to update::

if (_touchingHat)
{
  _touchPoint.x = Clamp(_touchPoint.x, _hat.size.width / 2, self.size.width - _hat.size.width / 2);
  _touchPoint.y = Clamp(_touchPoint.y,
                        _footer.size.height +  _hat.size.height / 2,
                        self.size.height - _hat.size.height / 2);
 
  _hat.position = _touchPoint;
}

update invokes before each frame of the animation renders. Here you check to see if the user is dragging the hat; if it is, change _hat‘s current location to the position stored in _touchPoint. You’re using the Clamp function from SKTUtils to ensure the hat doesn’t move off the screen or below the footer.

Build and run your project; hit the “Read Story” button, and play around with the hat on the screen a little.

Note: Try building the app both with and without the changes to the hat’s physics bodies to see the difference it makes. You should be able to move the hat around with your finger easily when the changes to the physics body are applied, but it’s a little more difficult to move it around when you comment out the changes.

Moving the hat around is cool, but there isn’t any feedback as to whether or not the hat is on top of the main character’s head. It’s time to add that feedback.

Modify touchesEnded:withEvent: so that it looks like the code below:

if (_touchingHat)
{
  CGPoint currentPoint = [[touches anyObject] locationInNode:self];
 
  if ( currentPoint.x >= 300 && currentPoint.x <= 550 && 
       currentPoint.y >= 250 && currentPoint.y <= 400 )
  {
    // NSLog(@"Close Enough! Let me do it for you");
 
    currentPoint.x = 420;
    currentPoint.y = 330;
 
    _hat.position = currentPoint;
 
    SKAction *popSound = [SKAction playSoundFileNamed:@"thompsonman_pop.wav" waitForCompletion:NO];
    [_hat runAction:popSound];
  }
  else
    _hat.physicsBody.affectedByGravity = YES;
 
  _touchingHat = NO;
}

With the above bit of code you can determine if the user is touching the hat and where they attempted to release it. This is why you want to use the end event and not the begin event.

If the user releases the hat close enough to the kid’s head, your code re-positions the hat to an exact location as defined by currentPoint.x and currentPoint.y.

Additionally, a sound plays using SKAction‘s playSoundFileNamed: class method to alert the user that the hat is now firmly placed on the main character’s head – which is important! Did you see all that snow outside the window? Brrrrr!

Build and run your project; grab the hat and plunk it down on your character’s head, like so:

tc_spritekit_build7

Adding Page Two

The code for page two is roughly the same as that for page one; to this end, it’s been pre-populated for you in Scene02.m. The code is heavily commented for your reading pleasure, and since it’s very similar to Scene01.m it won’t be covered in detail here.

One thing to note is that Scene02 has an instance variable — _catSound — that references an SKAction that plays a sound. This illustrates that you can store SKActions and reuse them, but it also improves the user experience in the case of actions that play sounds.

Your app loads the sound into memory when you create a sound action, so initializing the sound action before it’s needed eliminates any potential pauses your users might experience.

Build and run your project; tap “Read Story” and use the navigation buttons to move back and forth in the story (after the text is done reading), like so:

tc_spritekit_build8

As an added bonus, tap on the cat to hear him meow!

Ok, enough messing around with pre-written code and kitties. Back to work, lazy pants! :]

Adding Page Three

Hopefully you enjoyed your short break to play with the kitty, but don’t think that just because you didn’t write the code for Page Two that it’s not in your best interest to review it! There’s lots to be learned by reviewing code written by others.

In the interest of keeping this tutorial focused, Scene03.m also provides a lot of the code you’ll need, but there’s still a little to add.

Add the following code to setUpMainScene in Scene03.m:

/* add the kid and the cat */
 
SKSpriteNode *cat = [SKSpriteNode spriteNodeWithImageNamed:@"pg03_kid_cat"];
cat.anchorPoint = CGPointZero;
cat.position = CGPointMake(self.size.width/2 - 25, 84);
 
[self addChild:cat];
 
/* add Snowflakes */
 
float duration = 1.25f; // determines how often to create snowflakes
[self runAction:[SKAction repeatActionForever:[SKAction sequence:@[[SKAction performSelector:@selector(spawnSnowflake) onTarget:self],[SKAction waitForDuration:duration]]]]];

Just as before, you create a new SKSpriteNode, set its anchorPoint and position and add it to the scene. Nothing surprising there.

Next you create an SKAction sequence that runs forever. You can use SKAction’s performSelector:onTarget: to run any method of your choosing. In this case, the action runs spawnSnowflake; you’ll be writing that method in a moment.

The above sequence ends with a waitForDuration: action; since the sequence repeats forever, it calls spawnSnowflake, pauses for the specified number of seconds as stored in duration, then repeats those two actions forever.

Add the following code to spawnSnowflake in Scene03.m:

// here you can even add physics and a shake motion too
 
CGFloat randomNumber = RandomFloatRange(1, 4);
CGFloat duration = RandomFloatRange(5, 21);
 
NSString *snowflakeImageName = [NSString stringWithFormat:@"snowfall0%.0f",randomNumber];
SKSpriteNode *snowflake = [SKSpriteNode spriteNodeWithImageNamed:snowflakeImageName];
snowflake.name = @"snowflake";
 
snowflake.position = CGPointMake(RandomFloatRange(0, self.size.height), self.size.height + self.size.height/2);
[self addChild:snowflake];
 
SKAction *actionMove = [SKAction moveTo:CGPointMake(snowflake.position.x + 100, -snowflake.size.height/2) duration:duration];
 
SKAction *actionRemove = [SKAction removeFromParent];
[snowflake runAction:[SKAction sequence:@[actionMove, actionRemove]]];

The block of code above executes continuously because the SKAction calling this method was instructed to run forever. Talk about willing to make a commitment. Men, please take note. ;]

So what does that code actually do?

  1. It generates an SKSpriteNode using a random snowflake image.
  2. It creates and executes an SKAction sequence that includes an action to move the sprite down the screen using SKAction‘s moveTo: class method.
  3. Finally, it removes the sprite from the scene using SKAction‘s removeFromParent class method.
Note: This would be the perfect place to add some physics and a method that creates a snow globe effect when the user shakes the device. I’ll leave that job to you, dear reader!

Build and run your project; you should see snowflakes start to appear and move down the screen, as shown below:

tc_spritekit_build9

Note: Just for fun, adjust the duration variable to 0.15. The value is inversely proportional to the number of snowflakes on the screen. Build and run your project and watch a blizzard unfold right before your very own eyes! When you’re done creating the snowpocalypse, move along — that’s enough playing in the snow for now!

Eagle-eyed readers will have noticed that the snowflakes run right over your footer. Time to fix that!

Add the following code to checkCollisions in in Scene03.m:

[self enumerateChildNodesWithName:@"snowflake" usingBlock:^(SKNode *node, BOOL *stop)
{
  SKSpriteNode *snowflake = (SKSpriteNode *)node;
  CGRect footerFrameWithPadding = CGRectInset(_footer.frame, -25, -25); // set padding around foot frame
 
  if (CGRectIntersectsRect(snowflake.frame, footerFrameWithPadding))
  {
    [snowflake removeFromParent];
  }
}];

Using enumerateChildNodesWithName:, you’re able to detect nodes with the specified name. Since you gave each snowflake a node name of ‘snowflake’, you can do two things here: one, apply the collision detection on matching nodes only, and two, remove the node only if you detect a collision. You detect collisions using CGRectIntersectsRect.

The code above looks and works great, but how do you call it? How does the program know when to run it? That’s where the infamous “game loop” comes in handy. You may have noticed it already, but it’s explained in detail below if you didn’t follow through the code.

Sprite Kit uses a traditional rendering loop — often referred to as a game loop — to process the contents of each frame before its rendered and presented. Sprite Kit calls update: for each frame; it’s in this method that you will write the code to call the collision detection method you created above.

Add the following code to update: in Scene03.m:

[self checkCollisions];

Build and run your project; you now should see the snowflake sprites disappear as they approach the top edge of the footer, as shown below:

tc_spritekit_lastpage

Note: Collision detection is important in game development. For example, a routine like this would be useful to detect when your hero ship fired at an enemy ship in a space adventure game. If you detect a hit, then remove the enemy ship from the screen. In this tutorial, the collision detection is simplified, but the same basic principles apply.

Where To Go From Here?

At this point, the rest of the story is up to you!

Note that the Scene04.m file has been populated with a generic “to be continued” scene. I want you to create Page Four using everything you’ve learned in this tutorial. Here’s a mockup of what your page could look like:

tc_spritekit_comp

Everything you need is already included with the project. Have fun!

I hope you enjoyed working through this tutorial as much as I have enjoyed writing it. To compare notes, download the complete sample project herehere.

If you’re looking to learn more about Sprite Kit, be sure to check out our book, iOS Games by Tutorials.

If you have any questions or comments, feel free to join in the discussion below!

How to Create an Interactive Children’s Book for the iPad is a post from: Ray Wenderlich

The post How to Create an Interactive Children’s Book for the iPad appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4384

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>