Make a Clock in Sprite Kit: Adding Animation to the Clock

In our first installment, we added a label and set up a clock.  We could easily do that with a story board and a wired up label. The point of series this is to have a lot of cool animation running in Sprite Kit for a non game UI. We will begin to add animation today.

Add a Grid Method

Before we do, as I discussed in another post, I grid my work. Instead of a whole class, I’ll only need a quick method here. Add this method just under the @implementation:

-(CGPoint)gridPointX:(float)xPoint pointY:(float)yPoint{
    CGFloat xDivision = CGRectGetMaxX(self.frame) /5.0;
    CGFloat yDivision = CGRectGetMaxY(self.frame)/5.0;
    return CGPointMake(xPoint * xDivision, yPoint * yDivision);
}

This will give us coordinates on a 6×6 grid for any point. Since the anchor points are the center of the node, there is a 4×4 grid of completely visible points. For this project, that is all we need.

Make a Pendulum

solidcircleOld-time clocks had a pendulum. We will use a flat design for a pendulum with a simple circle. Download the circle on the left by right clicking and using save as or its equivalent in your browser. Name it solidCirlce.png. Drag the downloaded file into your project, and in the dialog box, check on  copy to project . We now have a graphic, called a texture, for our project.

Add the following under the code [self addChild:myTimeLabel];

//Make the ball
        CGPoint tick = [self gridPointX:1 pointY:1];
        CGPoint tock = [self gridPointX:4 pointY:1];
        SKSpriteNode *bouncingBall = [SKSpriteNode spriteNodeWithImageNamed:@"solidcircle"];
        bouncingBall.position = tock;
        bouncingBall.name=@"bouncingBall";
        [self addChild:bouncingBall];

Lines 2 and 3 set coordinates (1,1) and (4,1) on our grid for the swing of the clock. Line 4 initialized a Sprite Node from the bouncing ball image. We set the position and name in line 5 and 6 and add the child node to the node tree in line 7. Compared to the formatting and text options for a label, sprite nodes are usually less code to get started. Build and run this. There will be a solid circle in our app.
Screenshot 2014-05-09 06.15.56

Move the Pendulum

We have an object, now to move the object using animation. The SKAction class governs animation. We use one of three runAction:(SKAction) methods to associate an action with a node. Add the following code just under [self addChild:bouncingBall]; :

//simple animation to the ball
SKAction *bounceBall = [SKAction moveToX:tick.x duration:1.0];
[bouncingBall runAction:bounceBall withKey:@"bounceBall"];

Line 2 Creates a SKAction. Using a class method moveToX: it moves from tock.x to tick.x. The duration is in seconds, and we want to move in once second from tock to tick. Line 3 adds the action to the node, giving it a key bounceBall. Build and run this.
Screenshot 2014-05-09 06.16.27
The ball moves from the right to the left in one second and stays there. We want a constantly moving ball. Replace the animation code above with this:

//add animation to the ball
SKAction *bounceBall = [SKAction sequence:@[
  [SKAction moveToX:tock.x duration:1.0],
  [SKAction moveToX:tick.x duration:1.0],
  ]];
[bouncingBall runAction:[SKAction repeatActionForever:bounceBall] withKey:@"bounceBall"];

In line 2, we add the SKAction class method sequence:, which creates does actions from an array one after the other. We set up a literal array of two SKActions, moving back and forth from tick to tock. In line 6, we added to our run action another class method repeatActionForever: to form an infinite motion. Build and run. We have a working pendulum.
Screenshot 2014-05-09 06.29.06

Add the Date Label

The counterpart of sequence: is group:. While sequence: performs actions one at a time, group: performs actions at the same time. For example,  add another label after [self addChild:myTimeLabel];:

self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
myDateLabel = [SKLabelNode labelNodeWithFontNamed:@"HelveticaNeue"];
myDateLabel.text = @"Date Goes Here";
myDateLabel.fontSize = 40;
myDateLabel.alpha = 0.20;
myDateLabel.position = [self gridPointX:2.5 pointY:2.0];
myDateLabel.name = @"myDateLabel";
[self addChild:myDateLabel];

Add another instance variable, under myTimeLabel:

SKLabelNode *myDateLabelNode;

Build and run. We now have a label just above the pendulum.
Screenshot 2014-05-09 06.21.22
Add some code in update: to give us a date. Much of the code we did last time.  Make a formatted string and set the text property to make a date. Add this to the end of the code on update:

NSString *formattedDateString =[NSString stringWithFormat:@"%04d %02d %02d", (int)currentDate.year, currentDate.month, currentDate.day];
myDateLabel.text = formattedDateString;

Build and run. The date is now on the bottom half of the app, faded out.
Screenshot 2014-05-09 06.32.32

Animate the Label with a Touch

We will toggle the between the date and time using a tap to the screen. Add the following touchesBegan: method to our scene implementation file:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    CGPoint topDisplay = [self gridPointX:2.5 pointY:4];
    CGPoint bottomDisplay = [self gridPointX:2.5 pointY:2];
    if ([touches count]>0){
        SKAction *labelHideAction = [SKAction group:@[
                                [SKAction fadeAlphaTo:0.2 duration:3.0],
                                [SKAction moveToY:bottomDisplay.y duration:3.0]
                        ]];
        SKAction *labelShowAction = [SKAction group:@[
                                [SKAction fadeInWithDuration:3],
                                [SKAction moveToY:topDisplay.y duration:3.0]]];

        //remove previous action
        [myTimeLabel removeActionForKey:@"timeLabelAction"];
        [myDateLabel removeActionForKey:@"dateLabelAction"];

        //toggle action
        if(isShowingTime){
            [myTimeLabel runAction:labelHideAction withKey:@"timeLabelAction"];
            [myDateLabel runAction:labelShowAction withKey:@"dateLabelAction"];
        }else{
            [myTimeLabel runAction:labelShowAction withKey:@"timeLabelAction"];
            [myDateLabel runAction:labelHideAction withKey:@"dateLabelAction"];
        }
        isShowingTime = !isShowingTime;
    }
}

Line 4 checks for any touches to the frame. If there are some, we run the animation code, which toggles between two states. Line 5 and 9 set up two actions: moving up and fading in and moving down and fading out. After removing our previous actions, we set the actions for the two labels to either move up or down.

We need one more instance variable for a toggle. Place this under myDateLabel:

BOOL isShowingTime;

Add this to initialize it at the end of initWithSize:

isShowingTime=YES;

Build and run.

We now have a working clock which changes the date through animation. We quickly overrode the touchesbegan: method. Next time, we’ll explore this more and extend it to add some buttons to our clock.

The Clock Code for MyScene.m

Here is the complete implementation file MyScene.m for the clock. This one file contains all the code for the clock, so it should cut and paste easily for those interested in doing so.

//
//  MPMyScene.m
//  SpriteTimeClock
//
//  Created by Steven Lipton on 5/6/14.
//  Copyright (c) 2014 Steven Lipton. All rights reserved.
//

#import "MPMyScene.h"

@implementation MPMyScene{
    SKLabelNode *myTimeLabel;
    SKLabelNode *myDateLabel;
    BOOL isShowingTime;
}

-(CGPoint)gridPointX:(float)xPoint pointY:(float)yPoint{
    CGFloat xDivision = CGRectGetMaxX(self.frame) /5.0;
    CGFloat yDivision = CGRectGetMaxY(self.frame)/5.0;
    return CGPointMake(xPoint * xDivision, yPoint * yDivision);
}

-(id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
        /* Setup your scene here */
        myTimeLabel = [SKLabelNode labelNodeWithFontNamed:@"HelveticaNeue"];
        myTimeLabel.text = @"00:00:00";
        myTimeLabel.fontSize = 40;
        myTimeLabel.position = CGPointMake(CGRectGetMidX(self.frame),
                                       CGRectGetMaxY(self.frame)*0.80);
        myTimeLabel.name = @"myTimeLabel";

         [self addChild:myTimeLabel];

        self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];

        myDateLabel = [SKLabelNode labelNodeWithFontNamed:@"HelveticaNeue"];
        myDateLabel.text = @"Date Goes Here";
        myDateLabel.fontSize = 40;
        myDateLabel.alpha = 0.20;
        myDateLabel.position = [self gridPointX:2.5 pointY:2.0];
        myDateLabel.name = @"myDateLabel";

        [self addChild:myDateLabel];

        //Make the ball
        CGPoint tick = [self gridPointX:1 pointY:1];
        CGPoint tock = [self gridPointX:4 pointY:1];
        SKSpriteNode *bouncingBall = [SKSpriteNode spriteNodeWithImageNamed:@"solidcircle"];
        bouncingBall.position = tock;
        bouncingBall.name=@"bouncingBall";
        [self addChild:bouncingBall];

/*
        //simple animation to the ball
        SKAction *bounceBall = [SKAction moveToX:tick.x duration:1.0];
        [bouncingBall runAction:bounceBall withKey:@"bounceBall"];

*/
        //add animation to the ball
        SKAction *bounceBall = [SKAction sequence:@[
                                                    [SKAction moveToX:tock.x duration:1.0],
                                                    [SKAction moveToX:tick.x duration:1.0],
                                                    ]];
        bounceBall.timingMode = SKActionTimingEaseInEaseOut;
        [bouncingBall runAction:[SKAction repeatActionForever:bounceBall] withKey:@"bounceBall"];

        isShowingTime = YES;
    }

    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    CGPoint topDisplay = [self gridPointX:2.5 pointY:4];
    CGPoint bottomDisplay = [self gridPointX:2.5 pointY:2];
    if ([touches count]>0){
        SKAction *labelHideAction = [SKAction group:@[
                                [SKAction fadeAlphaTo:0.2 duration:3.0],
                                [SKAction moveToY:bottomDisplay.y duration:3.0]
                        ]];
        SKAction *labelShowAction = [SKAction group:@[
                                [SKAction fadeInWithDuration:3],
                                [SKAction moveToY:topDisplay.y duration:3.0]]];

        //remove previous action
        [myTimeLabel removeActionForKey:@"timeLabelAction"];
        [myDateLabel removeActionForKey:@"dateLabelAction"];

        //toggle action
        if(isShowingTime){
            [myTimeLabel runAction:labelHideAction withKey:@"timeLabelAction"];
            [myDateLabel runAction:labelShowAction withKey:@"dateLabelAction"];
        }else{
            [myTimeLabel runAction:labelShowAction withKey:@"timeLabelAction"];
            [myDateLabel runAction:labelHideAction withKey:@"dateLabelAction"];
        }
        isShowingTime = !isShowingTime;

    }
}

-(void)update:(CFTimeInterval)currentTime {
    /* Called before each frame is rendered */
    CFGregorianDate currentDate = CFAbsoluteTimeGetGregorianDate(CFAbsoluteTimeGetCurrent(), CFTimeZoneCopySystem());
    CFRelease(CFTimeZoneCopySystem());
    NSString *formattedTimeString = [NSString stringWithFormat:@"%02d:%02d:%02.0f", currentDate.hour, currentDate.minute, currentDate.second];
    myTimeLabel.text = formattedTimeString;

    NSString *formattedDateString =[NSString stringWithFormat:@"%04d %02d %02d", (int)currentDate.year, currentDate.month, currentDate.day];
    myDateLabel.text = formattedDateString;

}

@end

4 Replies to “Make a Clock in Sprite Kit: Adding Animation to the Clock”

  1. Thanks for the tutorial. :)

    Small typo error when you add the label for the date.
    It should be “SKLabelNode *myDateLabelNode;” instead of “SKLabelNode *myDateLabelNode;”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s