Moose Tutorial Series #2:
Time-based Scripted Animation – Part II
Frame-Based Motion in a Time-Based World
In the first part of this tutorial, you were introduced to the Moose ActionScript Library, an ActionScript framework to make your job as a Flash developer easier. If you haven't done so already, I recommend that you read that tutorial before starting on this one as we will dive right in from where we left off. Last time, we began exploring the wonderful world of time-based scripted motion and you saw how you can easily create time-based scripted animations in Moose using the Move and MoveScript classes. In this, the second part, we're going to delve deeper into time-based motion and learn how to incorporate frame-based animations into a time-based movie. You will also see how, using Moose, you can play back the timeline of any movie clip at a frame rate that is independent of the main movie's frame rate.
Before we start on the tutorial, make sure you download the source files and unzip them into your working directory. There you will find all the necessary Moose Modules, as well as finished versions of the FLAs we'll be creating here.
Note: Since Moose is an evolving library, you may notice a few new things. In addition to the new classes that we'll be covering this week, the names of some of the include files will be different. The Moose library has moved to implement a mixed case filenaming scheme in favor of underscore-separated filenames (eg. frameEvent.as instead of frame_event.as). This allows you to know at a glance whether you are importing an object (if the filename begins with a lowercase letter) or a class (if the filename begins with an uppercase letter.) This also means that the filenames of files are now exactly the same as the object or class that they contain. eg. DoInNumFrames.as contains the class DoInNumFrames. Since it's a class you know that you need to create an instance of it to invoke it, e.g., new DoInNumFrames(1, this, "someAction"); Don't worry about this change but keep it in mind as you read through the examples.
Delay and Repetition
While creating our scripted animations last week, you saw how you could make a MoveScript loop using its setLoop method. One thing we didn't touch upon was how to introduce delay into your animations. Let's take our synchronous motion example from Part I (sync_motion.fla) where we had three blue balls animate in sequence from one side of the screen to another. Let's say that after the second ball's animation is complete, we want the animation to pause for two seconds. This is actually very easy to do and you don't even have to learn anything new: All we need to do is create an empty Move object (which I'll call a Delay Move) and add it to our MoveScript: Here's the code for that doing that:
// add a Move that does nothing for two seconds
ball2Delay = new moose.Move();
ball2Delay.setDuration(2);
// ...
ballScript = new moose.MoveScript(); // create a new MoveScript instance
ballScript.addMove( ball1Move ); // add the first ball move
ballScript.addMove( ball2Move ); // add the second ball move ballScript.addMove( ball2Delay ); ballScript.addMove( ball3Move ); // add the third ball move
ballScript.runScript(); // run the scripted animation
You can see it working in the completed FLA from source files, sync_motion_with_delay.fla.
All right, so its easy to incorporate a delay into a MoveScript, but what if you want to delay the execution of any given function in a Flash movie? Similarly, wouldn't it be useful if we could repeatedly call a function at a given interval? We can do both of these using two new classes from Moose, DoIn and DoEvery.
DoIn and DoEvery
DoIn and DoEvery are very simple to use yet quite powerful. Let's tackle them in order with some simple examples:
Create a new Flash movie and save it as do_in_test.as
On Frame 1, Layer 1 of the movie, enter the following script:
// include the necessary Moose files
#include "moose/init.as"
#include "moose/doIn.as"
// create a function to be called
function test ( a, b, c)
{
trace( "Running! Arguments: a=" + a + ", b=" + b + ", c=" + c );
}
// create a DoEvery instance that passes three arguments to the test function
myDoIn = new moose.DoIn(5, this, "test","wow", "this", "works!");
Test the movie and count five seconds: One one-thousand, two one-thousand...
When you reach five seconds, you should see the following string in the Output window: Running! Arguments: a=wow, b=this, c=works!
The DoIn class is very straightforward to use: You create a new DoIn instance and pass the constructor the number of seconds to wait and the function to call. Any parameters you pass after this are merely passed to the function as arguments when it is called (in the above example, this comprises the three strings "wow", "this" and "works!".
If you need to, you can cancel a DoIn instance before it has runs, by calling its cancel method:
e.g., myDoIn.cancel();
Also, instead of specifying the arguments all at once, you can set up a DoIn instance by calling its setter methods. e.g.,
// create a DoIn instance without specifying any arguments
myDoIn = new moose.DoIn();
// set the duration to five seconds
myDoIn.setDuration (5);
// set the function to call
myDoIn.setCallback (this, "test");
// set the arguments to be passed to the callback function when it is run
myDoIn.setArgs ("wow", "this", "works");
// you need to start the timer manually
myDoIn.run();
The above code is functionally equivalent to the first example.
Congratulations, you now know how to time the execution of functions! Now let's learn how to call functions repeatedly at a given interval. The DoEvery class allows you to do this and lets you, optionally, cap the number of executions at a given limit.
Create a new Flash movie and save it as "do_every_test.as" (without quotes)
On Frame 1, Layer 1 of the movie, enter the following script:
Test the movie. You should see the message trace out in the Output window every second and stop after the message has displayed three times. Just like DoIn, you create a DoEvery instance by passing all the arguments at once to the constructor. For example, the six lines of code used to construct the DoEvery instance above can be boiled down to:
These two classes, DoIn and DoEvery, form the basis of the two new classes that we're going to be learning about next: AnimGif and FPS. (Note: There is a third class that is related to DoIn and DoEvery, called DoInNumFrames, that is used by AnimGif. This is used to execute a callback function after a specified number of enterFrame events have occured. I haven't covered it here since it is frame-based instead of time-based, and hence slightly off-topic, but it is included in the download for you to peruse if you so desire.)
Emulating Animated GIF functionality in Flash
You have, no doubt, encountered animated GIFs at some point and perhaps even created a couple. Animated GIFs are very simple frame-based animations that you create by specifying a sequence of frames and assinging a duration for each frame to determine how long that frame will display. The AnimGif class allows you to emulate the behavior of animated GIFs in Flash. Although an animated GIF animation is frame-based (in that it is comprised of a sequence of frames), it is not tied to a given frame rate (any frame can be set to display for any duration, independent of the durations of the other frames.) Here's a quick example so you can see it working:
Open anim_gif_test.fla from your downloaded source files.
Open up your Library if it isn't open and double click on the simpleAnimation movie clip to open up its timeline for editing.
Notice how the clip is structured. Each frame has the artwork for that frame, along with an actions frame that contains a variable called "d." D stands for "duration" and specifies, in seconds, the amount of time that the frame it is specified for should stay visible. Thus, it works exactly like an animated GIF. You can create an animated GIF in a program like Adobe ImageReady, export each frame, import it into Flash and enter the d values from ImageReady to recreate the exact same animation in Flash!
Test the movie and notice how the animation plays back. Try changing the frame durations to see how it affects the animation.
Look at the script in Frame 1 of the Actions layer to see how the AnimGif instance is created:
myAnimGif = new moose.AnimGif (a_mc, 1, 5, true, true);
The instance name of our simpleAnimation clip on the stage is a_mc and that's the first argument we pass to the AnimGif class constructor. Next, we tell it to start the animation with Frame 1 of the movieclip and end it on Frame 5. The fourth argument tells the animation to loop and the last one tells the animation to start running straight away (otherwise, we'd have had to call the run method of the myAnimGif AnimGif instance manually at a later time.)
Now, how about we create a Moose AnimGif ourselves, step by step? In the process, we'll see how easy it is to convert a regular animated GIF into a Moose AnimGIF. For this, we'll use a free animated gif of a fawn from Animation Factory. You can see the actual animated GIF below:
Find the file in your downloaded materials (it's called fawn.gif) and open it up in Adobe ImageReady (if you don't have Photoshop/ImageReady, you can use any freeware or shareware graphics editor that shows you (and allows you to alter) the duration for each frame in an animated GIF. A quick search on Google turned up Easy GIF Animator, which appears to do just that and has a free trial.
Note down the frame durations listed in the timeline in ImageReady.
Once you've noted the frame durations, set the duration of each frame to No Delay by clicking on the small arrow next to the duration text for each frame.
Save the fawn.gif file. That's it, you're done with ImageReady.
Fire up Flash and start a new Movie.
Create a new movie clip and call it fawn (Insert -> New Symbol; Ctrl-F8) This should create the fawn movie clip and place you within its timeline.
Double-click the label of Layer 1 to edit it and rename it to frames
Click the first frame within the fawn timeline to select it and choose File -> Import (Ctrl-R). On the resulting dialog, select the fawn.gif file.
Flash should import the file and place each frame within a single frame on your timeline (this is why we set the durations of the frames on the animated gif to No Delay in ImageReady.
Take a look at the screenshot below, which shows you how the timeline would have looked if we had not set the frames to No Delay in ImageReady. As you can see, Flash translates the durations set in the Animated Gif to a certain number of frames, based on the current frame rate. Needless to say, this binds you to the movie's frame rate and is far from ideal. You could massage the timeline manually to make sure each frame of the animation takes a single frame in the timeline but that would be ardious for long animations.
Create a new layer using either the Insert Layer button (circled, below) or Insert -> Layer. Name the layer durations and use the Insert -> Keyframe (F6) option to create a keyframe for each frame of the animation.
Click on each empty keyframe in the durations layer in order and, in the Actions Panel, enter the duration that you noted for that frame in Step 2. Thus, the scripts in frame 6 and 12 of the animation would be:
// duration
d = 1.5;
And for all the other frames, you would enter the script:
// duration
d = 0.15;
Now that our fawn movie clip is ready, click on the Scene 1 link in Flash to return to the main timeline.
On the main timeline, rename Layer 1 and call it fawn mc (since we're going to put our fawn movie clip there.)
Drag an instance of the fawn movie clip from your library to the stage and give it the instance name fawn_mc using the Property Inspector.
Create a new Layer (Insert -> Layer) and call it actions.
On frame one of the actions layer, enter the following script:
animFawn = new moose.AnimGif (fawn_mc, null, null, true, true);
Our script is almost exactly the same as the one in the previous example. The only difference here is that we've chosen not to specify the starting and ending frames for the animation manually. Instead, we're passing null for those two arguments. The class interprets this as "start the animation from the beginning of the movie clip's timeline" and "end the animation on the last frame of the movie clip's timeline", respectively. The last two arguments tell the animGIF instance to loop and start playing right away. Here is the original animated GIF and our Flash version, side-by-side:
Animated GIF
Flash AnimGIF
Complete Control over Timelines For Control Freaks
Although a very simple concept, frame rate sems to be a source of confusion in Flash. Most developers meet the frame rate setting in the Document Properties (Modify -> Dialog) dialog on their first day learning Flash but few take their manipulation or understanding of the frame rate further than the very basics outlined in introductory books. This is unfortunate because the concept of frame rate is central to how Flash works and, perhaps more than any other single feature, affects the playback of your Flash movies.
First, let's define what the frame rate setting is: The frame rate setting determines the number of times a second that Flash tries to update the screen. Each of these updates is what we lovingly refer to as a "frame" (the terminology comes from film, where a frame is nothing more than a photograph; a frozen moment in time. One of many, which, when viewed in rapid succession give us the sense of motion or "animation" thanks to the little trick our eyes play on us called persistence of vision.) Notice a subtlety in my definition of frame rate: I say that Flash "tries to update" instead of "updates". This is because Flash does not always manage to reach the frame rate that you set for it. Depending on how much it has to do, it may find itself unable to refresh the screen at a given rate, especially if the rate is very high and/or what it needs to accomplish is very computationally intensive. (For example, animating lots of objects with gradient fills will choke up Flash as will animating very large bitmaps.) In these cases, Flash tries hard and does the best it can. This means that it turns into a resource hungry hog from hell, making your CPU indicator hit 100%. It also means that it draws fewer frames per second than the frame rate calls for. We call the frame rate you set the ideal frame rate and the frame rate Flash achieves, the actual frame rate. Keep this distinction in mind as it is an important one.
So, on to controlling frame rate like a true master! We know that all movie clips, if left to their own devices, will playback at the frame rate set for the main movie. This applies equally to movies that have been loaded in externally. For example, let's say that you have two movies and that your main movie's frame rate is set at 12 fps. The second movie has its frame rate set at 60fps. If the first movie loads the second movie (inside a movie clip, say) using loadMovie and issues a gotoAndPlay() on its timeline, it will play back at 12fps (since it is, essentially, playing back from within the first movie -- the one that loaded it.) This can be a problem.
A single frame-rate isn't ideal on large projects where different animators and individual animations may call for different frame rates (or, you may have just completed tweaking the timing on your masterpiece 3000 frame animation only to realize that you used the wrong frame rate -- hands up everyone who has experienced this one. What!? It's just me?) In any case, with out handly little FPS object, you're not limited by this constraint any longer.
With FPS, you can play back any timeline at any frame rate within a movie. The caveat is that you cannot play a timeline at a frame rate that is higher than the main movie's frame rate. The solution to this is to set your movie's frame rate equal or slightly higher than the highest frame rate you plan to use in the movie. As long as you use FPS for all of your frame-based animations, you can alter the frame rate of your movie at any point in your project without affecting the playback of your animations.
All right then, enough talk, let's have an example.
Open up fps_test.fla and run the movie. You should see the following:
The movie's main frame rate has been set to 120 fps. We have five copies of a movie clip that contains a frame animation with the numbers 1 to 5 on subsequent frames. On the first clip we did not apply FPS control and it speeds by at 120fps. The other clips have custom frame rates applied to them. Tthe script that does this is on the first frame of the FLA:
Couldn't be simpler, could it? You simply call the method setFPS on the movie clip you want to use a custom frame rate for and pass the frame rate as the only argument. You then need to call the runFPS() method on the movie clip in order to make the new frame rate take effect. That's it!
Here's another example (fps_horse.fla, in your downloads) that demonstrates AnimGif and FPS working together: The horses are converted animated gifs and the movie is set to run at 120 fps. Each of the horse clips has been wrapped in another movie clip and motion tweened for size and position. These motion tweens originally do not have FPS controlling them (so they run at 120 fps.) Click the button to activate FPS on the motion tweens and see the difference.
A Final Example: Time-based Shape Tween with Easing
Since we're talking about integrating frame-based animations into a time-based movie, let's end with a novel little example of a time-based shape tween. This example is more complex than the ones we have covered so far. The shape-tween, of course, is a frame-animation. To transform it into a time-based animation we use the Move class in a novel way (akin to the Color transform example in Part I of this tutorial.)
Open up control_shape_tween.fla
Take a look at the code on Frame 1 of the Actions layer:
// update function
updateFrame = function ( anim, the_mc ){
var currentFrame = Math.floor ( frameCounter.currentFrame ) + 1;
the_mc.gotoAndStop ( currentFrame );
}
// clickHandler for the "Play Animation" button
onPlayAnimation = function ()
{
// reset the movie clip, if already played
a_mc.gotoAndStop(1);
// reset the frame counter
frameCounter.currentFrame = 1;
// create the frame animation
var frameChange = new Moose.Move();
frameChange.setMovementFn(Math.easeInQuad);
frameChange.setTarget(this, "frameCounter"); // change counter
framechange.setDuration(1.5);
frameChange.addDeltaProp("currentFrame", 200); // increase the current frame by 200 frames
frameChange.setUpdateFn(this, "updateFrame", a_mc);
frameChange.startMove();
}
As I mentioned in last week's tutorial, the Move class can be used for vary any property over time. In this case, we're varying a property called currentFrame that we've attached to an object called frameCounter. We've also set up a function to be called each time the property updates. This function, updateFrame, is what actually carries out the animation. It reads the currentFrame property from the frameCounter object and, after rounding it off to an integer, makes the movie clip go to and stop on that frame. You can see the result below.
In Conclusion
With the classes presented here and in the first part of this tutorial, you have a complete solution for creating time-based sites and applications in Flash, including a time-based scripting engine, general time-based callbacks, animated GIF functionality and complete control over the frame-rate of your movie clips, independent of the frame-rate of your movie.
As usual, I'd love to hear of the wonderful things you will no doubt create using the Moose library. I already have quite a few links that people have sent since Part I was published and, if we receive enough of them, perhaps we can place them up here, on Ultrashock, as a showcase.