In reality, tweens and jugglers are very simple objects. If you understand how they work, you can handle all time-based stuff in your games very elegantly.
Before we look at the juggler, however, we need to understand how tweens work internally. Let's look at the security door tween we've created above:
// create the door and display it SPImage *door = [SPImage imageWithContentsOfFile:@"door.png"]; door.y = 0; [self addChild:door]; // create a tween that moves the door down SPTween *tween = [SPTween tweenWithTarget:door time:4.0]; [tween animateProperty:@"y" targetValue:480];
Notice that I did not add the tween to the juggler — we want to manipulate the tween manually in this sample. Look at this:
// starting with: door.y == 0 [tween advanceTime:1.0]; // door.y = 120, tween.isComplete = NO [tween advanceTime:1.0]; // door.y = 240, tween.isComplete = NO [tween advanceTime:1.0]; // door.y = 360, tween.isComplete = NO [tween advanceTime:1.0]; // door.y = 480, tween.isComplete = YES
As you can see, every time I call the method “advanceTime:” on the tween, it updates the door's y-value. There's no magic behind a tween — all you have to do is call the “advanceTime:” method regularly (read: once per frame).
I guess now you see where I'm going. In theory, you don't need a juggler at all — you could create an enter frame event listener and update your tween once per frame, like this:
- (void)onEnterFrame:(SPEnterFrameEvent *)event { [tween advanceTime:event.passedTime]; }
In reality, however, that's burdensome. Work like this should be delegated to a cheap assistant that does not ask any dumb questions — and that's why you hire the juggler for that job!
If you use the stage juggler (accessible to any display object by calling “self.stage.juggler”), you just have to add the tween to the juggler, and it will carry out the animation for you. We demonstrated that already. However, we won't use the stage juggler now, but create our own juggler.
But why? I'll tell you a secret: I rarely use the stage juggler. In my games, I always create my own jugglers. On the long run, that makes life easier. You will see why that is at the end of this tutorial.
The juggler is a really small class. It does one thing, and one thing only: advancing all tweens you give it. Look at this:
// create a custom juggler SPJuggler *juggler = [SPJuggler juggler]; // reset the door's position door.y = 0; // create another tween SPTween *tween = [SPTween tweenWithTarget:door time:4.0]; [tween animateProperty:@"y" targetValue:480]; // now, give it to the juggler [juggler addObject:tween];
However, the door won't move yet! Just like the tween, the juggler needs to be advanced:
// door.y == 0 [juggler advanceTime:1.0]; // door.y = 120, tween.isComplete = NO [juggler advanceTime:1.0]; // door.y = 240, tween.isComplete = NO [juggler advanceTime:1.0]; // door.y = 360, tween.isComplete = NO [juggler advanceTime:1.0]; // door.y = 480, tween.isComplete = YES
Bummer. We have not gained much, have we? That's just as much work as before!
Well, in this simple example, the juggler really does not help much. But in a typical game, you have a lot of animations, and then it suddenly makes sense. We'll see that later.
BTW, have you noticed the “isComplete” property of the tween in the code above? That information is crucial to the juggler, because it tells the juggler when an animation is finished. As soon as that happens, the juggler can throw away the tween.
Let's look at an example that comes closer to real life.
When I create some building block of my game that is bound to become more complex, I always start with a scaffold like this:
@interface PlayingField : SPSprite { SPJuggler *mJuggler; } @end @implementation PlayingField - (id)init { if (self = [super init]) { mJuggler = [[SPJuggler alloc] init]; } return self; } - (void)advanceTime:(double)seconds { [mJuggler advanceTime:seconds]; } - (void)dealloc { [mJuggler release]; [super dealloc]; } @end
It's just a sprite with a juggler and a method that is supposed to be called once per frame. This class will become the playing field, a typical building block of a game. I might also have a class “Dashboard” (showing the health and number of lives of the player) and a class “MessageBox”. All those major building blocks have some animations, so they all look start like the class above.
Whenever an animation is started from within those classes, I add the corresponding tweens to its juggler (mJuggler). Thus, the animations of each class are all separated.
Now somebody has got to handle those classes! That's the stage, normally. Look at this:
@interface Game : SPStage { PlayingField *mPlayingField; Dashboard *mDashboard; MessageBox *mMessageBox; } @end @implementation Game - (id)initWithWidth:(float)width height:(float)height { if (self = [super initWithWidth:width height:height]) { [self addEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME]; // create building blocks of the game mPlayingField = [[PlayingField alloc] init]; [self addChild:mPlayingField]; [mPlayingField release]; mDashboard = [[Dashboard alloc] init]; [self addChild:mDashboard]; [mDashboard release]; // that one will be created on demand -- e.g. when the // player hits the pause button or is game over. mMessageBox = nil; } return self; } - (void)onEnterFrame:(SPEnterFrameEvent *)event { double passedTime = event.passedTime; if (mMessageBox) [mMessageBox advanceTime:passedTime]; else [mPlayingField advanceTime:passedTime]; [mDashboard advanceTime:passedTime]; } @end
Now we get to the point where it all comes together. The reason that all those classes have their own juggler is that we want to control them separately! Look at the “onEnterFrame:” method. As long as a message box is active, the playing field will not move one pixel. It's, in effect, frozen where it is! As soon as the message box has disappeared (it's nil
), it continues where it left of. The dashboard, however, is animated continuously.
To see why this is so useful, look at the alternatives. Say you had used one juggler (probably the stage juggler) for all animations. Now the user hits the pause button. How in hell do you stop all animations? You'd have to remember each and every tween and remove it from the stage juggler. Then, when the game continues, you'd have to add them again. That's a lot of work! Besides, I guarantee that you'll forget one tween sometime, perhaps creating a hard to find bug.
Thus, my recommendation is to construct your games just like the sample above:
Another interesting trivia about the juggler is that it can handle more than just tweens. Every class that implements the “SPAnimatable” protocol can be added to a juggler. That allows you to create your own animated objects, just like Sparrow's SPMovieClip class.
Next section: Advanced Animation Techniques