====== Animation ======
Animations are a fundamental part of any game. Sparrow helps you by making animations as simple as possible.
If you think about it, there are two kinds of animations. There are the animations where you know from the beginning exactly what will happen --- e.g. when you fade in a message box, or when you move some interface element out of the screen. Then there are very dynamic animations, like enemies moving toward the player. The movement of the enemies can change in any frame, as it depends on the actions of the player.
Let's first look at the latter type, dynamic animations.
===== SPEnterFrameEvent =====
In some game engines, you have what is called a "run-loop". That's an endless loop which constantly updates all elements of the scene. In Sparrow, due to the display tree architecture, such a run loop would not make much sense. You separated your game in numerous different custom display elements, and each should know for itself what to do when some time has passed.
That's what the EnterFrameEvent is for. It's available in any display object, and is dispatched once in every frame. Here is how you use it:
// e.g. in the init-method
[self addEventListener:@selector(onEnterFrame:) atObject:self forType:SP_EVENT_TYPE_ENTER_FRAME];
// the corresponding event listener
- (void)onEnterFrame:(SPEnterFrameEvent *)event
{
NSLog(@"Time passed since last frame: %f", event.passedTime);
[enemy moveBy:event.passedTime * enemy.velocity];
}
The method "onEnterFrame:" is called once per frame, and you receive the time that has passed since the last frame. With that information, you can move your enemies, let snowflakes fall down just a little further, etc.
===== Tween =====
Now to predefined animations. They are very common and have names such as "Movement", "Fading", "Rotation", etc. Sparrow has a very simple yet powerful approach for those kinds of animations. Basically, you can animate any property of any object, as long as it is numeric (int, float, double).
Those animations are described in an object called "SPTween". The term "Tween" comes from hand drawn animations, where a lead illustrator would draw important key frames, while the rest of the team drew the frames in be**tween** those frames.
Enough theory, let's go for an example:
SPTween *tween = [SPTween tweenWithTarget:msgBox time:0.5f];
[tween animateProperty:@"y" targetValue:20.0f];
[tween animateProperty:@"scaleX" targetValue:2.0f];
[tween animateProperty:@"scaleY" targetValue:2.0f];
[tween animateProperty:@"alpha" targetValue:0.0f];
This tween describes an animation that scales the msgBox to twice its size, and simultaneously reduces its opacity till it is invisible. The animation takes half a second. If you are missing the start values: the animation will start using the current values of the specified properties.
As you can see, you can animate arbitrary properties of an object, and you can do multiple animations in one tween object.
Apropos: since scaling, fading and movement are done so frequently, the SPTween class provides specific methods for that, too. So you can write the following instead:
[tween moveToX:0.0f y:20.0f]; // animating "x" and "y"
[tween scaleTo:2.0f]; // animating "scaleX" and "scaleY"
[tween fadeTo:0.0f]; // animating "alpha"
There is more to tweens: you can change the way the animation is executed -- e.g. letting it start slowly and get quicker and quicker. That's done by specifying a transition type.
These are the available transition types:
(the default transition type, 'linear', was omitted)
{{ :manual:transitions.png |Tween transitions}}
Tweens offer some additional features, like delaying their execution, repeating them, or executing certain blocks during the life-time of a tween.
SPTween *tween = [SPTween tweenWithTarget:msgBox time:0.5f transition:SP_TRANSITION_EASE_IN];
// delay by 2 seconds
tween.delay = 2.0f;
// repeat 4 times in yoyo-style
tween.repeatCount = 4;
tween.reverse = TRUE;
// execute blocks while the tween is running
tween.onStart = ^{ NSLog(@"Tween started"); };
tween.onUpdate = ^{ NSLog(@"Tween updated"); };
tween.onComplete = ^{ NSLog(@"Tween completed"); };
Now we have created the tween --- but nothing will happen yet. A tween object describes the animation, but it does not execute it. That's what the juggler is for.
Find a comprehensive description of tweens in [[tutorials:More about Tweens|this tutorial]].
===== The Juggler =====
The juggler takes anything that can be animated (that is, anything implementing the SPAnimatable protocol), and executes the animation. There is always a default juggler available in Sparrow.
The easiest way to execute an animation is by the line below --- just add the animation (tween) to the default juggler, and you are done. When the tween is finished, it will automatically be removed.
[Sparrow.juggler addObject:tween];
In many cases, that simple approach will be fine.
Sometimes, though, you want to group your animations into different contexts. Let's say your stage contains a game area, where the game action takes place. When the user clicks on the exit button, you want to pause the game and show an animated message box (this thing won't stop popping up in any chapter!), asking the player if he is sure that he wants to exit the game.
In such a case, it makes sense to give the game area its own juggler. As soon as the exit button is pressed, this juggler should just stop animating anything. The game will freeze in its current state. The message box, however, uses the default juggler. Thus, it fades in just fine.
When you create a custom juggler, all you have to do is to call its "advanceTime:" method in every frame. I recommend using jugglers the following way:
// In your main game class, listen to EnterFrameEvents:
- (void)onEnterFrame:(SPEnterFrameEvent *)event
{
if (activeMsgBox)
// message box is visible, wait for user input
else
[gameArea advanceTime:event.passedTime];
}
// the game area class advances its juggler in the
// "advanceTime:" method
- (void)advanceTime:(double)seconds
{
[gameJuggler advanceTime:seconds];
}
That way, you have neatly separated the animations of the game and the message box.
By the way: the juggler is not constricted to Tweens. As soon as your class implements the SPAnimatable protocol, you can add it to the juggler. The protocol has only one method:
- (void)advanceTime:(double)passedTime;
This method will automatically be called once per frame. This is how your animation is executed.
If your animated object should be removed from the juggler, you can do so manually through the "removeObject:" method of the juggler. However, if your animated object should be removed automatically once it's finished, simply let it dispatch an event with the type "SP_EVENT_TYPE_REMOVE_FROM_JUGGLER". The juggler listens to those events and will remove the object right away.
Find a comprehensive description of [[tutorials:More about Jugglers/|jugglers]] and [[tutorials:advanced animation techniques|how experts use them]] in the tutorials section.
===== Delayed calls =====
There is another type of animation that is not that obvious. Sometimes, you want to do something in the future. Let's say the player just lost all his energy. Now, you don't want to stop the game immediately --- that would end the game too abruptly. So, you want to call your "gameOver" method with a delay of, say, 3 seconds.
The juggler can do this just as well. The advantage of letting the juggler do this becomes obvious when the game is paused, like in the example above. In that case, you not only want to stop all animations; you want this delayed "gameOver" call to be delayed even more.
==== The Classic Way ====
To do that, make a call like the following:
[[juggler delayInvocationAtTarget:self byTime:3.0f] gameOver];
Confused? This syntax takes a little time to get used to --- but it's very powerful. To understand it, just imagine that first part of the call (the ''[juggler delay...]'' part) returns the "target" argument (in our case: self), but only after 3 seconds (!). Thus, three seconds from now, the following code will be executed:
[self gameOver];
That syntax makes it easy to call arbitrarily complex methods. It's perfectly fine to do something like this:
[[juggler delayInvocationAtTarget:self byTime:1.0f]
doXYZ:@"text" withA:someObject andB:1234];
==== Using Blocks ====
Alternatively, you can delay the execution of a block. The syntax for that is as follows.
[juggler delayInvocationByTime:2.0 block:^
{
[self gameOver];
};
-----
//Next section: [[Movie Clips]]//