Tweens & Jugglers unleashed

Daniel Sperl on October 1, 2010

You think you know all about Sparrow’s animation system after the last two parts of this series? But we have merely scratched the surface! Get ready for the last part of the series to find uses of the juggler you have probably never thought of!

Tween events

First, we’ll discuss a fact that we have shamefully ignored in the last two articles: tweens dispatch events. Three types of events, to be exact:

  • SP_EVENT_TYPE_TWEEN_STARTED
  • SP_EVENT_TYPE_TWEEN_UPDATED
  • SP_EVENT_TYPE_TWEEN_COMPLETED

The best way to understand those events should be an example. So, back to Ruth Lezz and her blast door!

// create our door object
BlastDoor *door = [[BlastDoor alloc] init];
[self addChild:door];

// now move it down!
SPTween *tween = [SPTween tweenWithTarget:blastDoor time:4.0];
[tween animateProperty:@"y" targetValue:100.0f];
[juggler addObject:tween];

// add events
[tween addEventListener:@selector(onTweenStarted:) atObject:self 
  forType:SP_EVENT_TYPE_TWEEN_STARTED];
[tween addEventListener:@selector(onTweenUpdated:) atObject:self 
  forType:SP_EVENT_TYPE_TWEEN_UPDATED];
[tween addEventListener:@selector(onTweenCompleted:) atObject:self 
  forType:SP_EVENT_TYPE_TWEEN_COMPLETED];
  
// here are the corresponding event listeners
- (void)onTweenStarted:(SPEvent *)event
{
  NSLog(@"The door starts to move now.")
}

- (void)onTweenUpdated:(SPEvent *)event
{
  NSLog(@"The door is moving.");
}

- (void)onTweenCompleted:(SPEvent *)event
{
  NSLog(@"The door is now closed.")
}

When you add that code to your project and run it, the output will be similar to this:

The door starts to move now.
The door is moving.
The door is moving.
The door is moving.
...
The door is moving.
The door is moving.
The door is now closed.

As you can see, the START- and END-events are the first and the last events of the tween, respectively. The UPDATE-event, however, is dispatched every time the tween is advanced (in other words: once per frame).

You can use these events to trigger some action in certain stages of a tween’s life cycle. This can come in handy in a lot of situations.

For most tasks, however, you can use simpler techniques than events. I’ll prove that in the following sections.

Chaining Tweens

A common task is having two or more tweens to be executed one after the other.

Let’s say you want to move the door down and then up again. To achieve this, you could first create a tween that moves the door down, then add an event listener that fires when the door arrives at the bottom, and finally create the upward moving tween in this event listener. But, frankly, that’s a lot of code to write for such a simple thing, isn’t it? Well, the ‘delay’-property comes to the rescue! Look at this:

// move down
SPTween *downTween = [SPTween tweenWithTarget:door time:1.0];
[downTween animateProperty:@"y" targetValue:100.0f];
[juggler addObject:downTween];

// move up
SPTween *upTween = [SPTween tweenWithTarget:door time:1.0];
upTween.delay = downTween.time; // <- !
[upTween animateProperty:@"y" targetValue:0.0f];
[juggler addObject:upTween];

It’s simple, isn’t it? The second tween is started with a delay that is as long as the first tween’s duration. With this technique, you can chain as many tweens as you want.

Delayed calls

The juggler contains a very useful method to delay calls. Its syntax takes a little time to get used to, but once you’ve got the hang of it, you will find many uses for this technique.

Let’s start with a simple example. You’ve got a text field and want to change its text. Easy:

[textField setText:@"Blah"];

Now you want to do the same thing - but only 2 seconds from now. The juggler allows you to do that in the following way:

[[mJuggler delayInvocationAtTarget:textField byTime:2.0] 
  setText:@"Blah"];

I know it looks weird - so let’s see if it makes more sense if we write the same thing in 2 lines:

id futureTextField = [mJuggler delayInvocationAtTarget:textField 
                                                byTime:2.0];
[futureTextField setText:@"Blah"];

Think of it like this: ‘delayInvocationAtTarget:byTime:’ returns a future version of the text field (perhaps it uses a Flux Capacitor™, but who knows?). And everything that you do to this text field will only happen 2 seconds from now.

[A short warning though: you cannot use ‘release’, ‘copy’ and other methods of Objective C’s NSObject base class - but that’s rarely a problem.]

Let’s use a real-life example to show the power of this method. We want to display a countdown - a text field that counts: “3 - 2 - 1 - Go!”. With the juggler’s delayed calls, you can do that easily!

// create a text field with the text "3"
SPTextField *textField = [SPTextField textFieldWithText:@"3"];
[self addChild:textField];

// now start the countdown!
[[juggler delayInvocationAtTarget:textField byTime:1.0] setText:@"2"];
[[juggler delayInvocationAtTarget:textField byTime:2.0] setText:@"1"];
[[juggler delayInvocationAtTarget:textField byTime:3.0] setText:@"Go!"];

// and get rid of it later.
[[juggler delayInvocationAtTarget:textField byTime:4.0] removeFromParent];

Note that we remove the text field from the screen at the end - which makes this countdown-method completely self contained (there’s no need to save that object in a member variable just to remove it from the screen later).

Animate Everything!?

You already know that you can use a tween to animate every numerical property of an object (integer, float, double). This allows us to do a lot of animations: movement, fading, scaling, rotation, etc.

But this fact can be interpreted in a different way, too: As soon as you can describe something with a number, you will be able to animate it.

Again, here’s an example. Many games contain a score display - a text field that shows the player his current score. When the player achieves something, you raise the score, probably like this:

mScore += 100; // mScore is a member variable (int)
scoreTextField.text = [NSString stringWithFormat:@"%d", mScore];

Now the score changes immediately from, say, 400 to 500. But wouldn’t it be nice if it displayed the intermediate values, too? Quickly rising from 400 to 401, 402, 403, etc., until it reaches 500?

To do that, we create a subclass of SPTextField, that adds - a numerical property! Remember, any numerical property can be animated. So we better create one!

// NumberField.h

@interface NumberField : SPTextField
{
  int mValue;
}

@property (nonatomic, assign) int value;

@end

// NumberField.m

#import "NumberField.h"

@implementation NumberField

@synthesize value = mValue;

- (void)setValue:(int)value
{
  mValue = value;
  self.text = [NSString stringWithFormat:@"%d", value];
}

@end

Voilà, we’ve just created the ‘NumberField’ class. It’s no more than a text field than has a special property for displaying numbers. With that class in our tool belt, we can animate the score as we wish:

NumberField *score = [[NumberField alloc] init];
score.value = 400;

SPTween *raiseScore = [SPTween tweenWithTarget:score time:0.2];
[raiseScore animateProperty:@"value" targetValue:500];
[juggler addObject:raiseScore];  

[Disclaimer: use the ‘alloc/init’ approach to create instances of the NumberField class. If you want to use the ‘textFieldWith…‘-methods, you have to overwrite them in the NumberField class.]

Conclusion

I hope that you find one or another of these techniques useful for your projects! If you’ve read through all three articles of this series, you are on the best way to become a master Sparrow animator. :-)

Good luck with your projects!