How to Create an Interactive Children’s Book for the iPad
Learn how to create your own beautiful animated interactive children’s book for the iPad! By Tammy Coron.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Create an Interactive Children’s Book for the iPad
45 mins
- Getting Started
- Adding the Title Page
- Adding the Book Title
- Adding Animation to Objects
- Adding Sound to Your Story
- Detecting Touch Events
- Adding the Next Scene
- Adding Content to the First Page
- Adding the Navigation Controls
- Adding The Main Character
- An Introduction to Physics
- Handling Touches and Moving the Hat
- Adding Page Two
- Adding Page Three
- Where To Go From Here?
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:
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.
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: