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
Old-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.
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.
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 SKAction
s, 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.
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.
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.
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
Leave a Reply