Hack 27 Time-Controlled Movement 
All scripted subanimations do not need to run
at the frame rate. Make a subanimation run at a rate independent of
the frame rate by using timed events.
You can change the rate of both
scripted and unscripted time-based motion in several ways. To change
the speed of an animation, you can tween it out over more (or fewer)
frames or change the frame rate. You can insert additional frames in
the timeline using F5. You can delete frames by right-clicking
(Windows) or -clicking (Mac) in the timeline and
choosing Remove Frames from the pop-up menu.
You can control scripted animations in a time-based manner by
increasing the frame rate and performing screen updates in the
onEnterFrameFrame( ) event handler. You can rely
on other event handlers, such as onMouseMove(
) [Hack #26], to be more
judicious in screen updating, which allows you to increase animation
smoothness without hogging the processor.
But in some cases, we want the animation to be time based, not based
on some event such as mouse movement. Rather than use very high frame
rates when you require extremely smooth animation, it is better to
use the setInterval( )
function to create an interval (i.e., a timed
event). The advantage of this is three-fold:
Flash doesn't waste time by redrawing the entire
screen at a high frame rate (as would happen if you simply increased
the frame rate). Different portions of the animation can be run at different speeds. Animation speed can be timed relatively precisely, independent of the
frame rate or length of the animation in the timeline.
A standard interval is created like this:
intervalID = setInterval(eventHandler, period, arguments);
where:
- intervalID
-
Is the interval ID returned by the call to setInterval(
). You need to know this to remove (a.k.a. stop or clear)
an existing interval.
- eventHandler
-
Is the name of the function you want to use as the event handler (the
function to trigger at each interval).
- period
-
Specifies how often (in milliseconds) you want Flash to invoke the
event handler.
- arguments
-
Specify zero or more arguments to be passed to the event handler. If
you want more than one argument, separate them by commas
(argument1,
argument2,
...argumentn).
An event handler invoked by setInterval( ) is
not the same as a normal instance event handler, such as
onMouseMove( ) or onEnterFrame(
), because when it is invoked, the scope is not that of an
instance method. To invoke a method on an object (i.e., to invoke a
method within the scope of an instance), you can use the alternative
form of setInterval( ):
intervalID = setInterval(object, "method", period, arguments);
where:
- object
-
Is the object, such as a MovieClip instance, on
which to invoke the method specified by
"method"
- "method"
-
Is the method name, as a string, to invoke on
object at each interval
The remaining arguments are the same as in the previous form of
setInterval( ).
If you pass this as the
object parameter, then the method
invocation will have the scope of the current instance [Hack #10], meaning it can access the
current instance's properties.
You can also invoke a function within the scope of a clip by
specifying a target instance as the first argument. The following
setInterval( ) call creates an interval for a
movie clip instance, myClip, that will attempt to
invoke the clip's myEvent( )
method every millisecond (or as close as Flash can get to it):
intervalID = setInterval(myClip, "myEvent", 1);
The function is invoked repeatedly until the interval is cleared. To
clear an interval, use clearInterval( ):
clearInterval(intervalID)
You need to make sure that the intervalID variable
is in scope from wherever you clear the interval. Typically, the
function invoked by setInterval( ) clears the
interval after some condition is met (or the first time it is called,
in the case of a one-time event).
Another way to create a setInterval( ) event is
to make it invoke a method of the movie clip instance. This ensures
that the handler is scoped to the movie clip being controlled. The
next example defines mover( ) as a method of
movie clip myClip. The mover(
) function will run every 1 ms (or as close as Flash can
get to this) and will increase the myClip._x
property to animate the clip until the x position exceeds 300.
There is a problem though: the mover( ) function
has no way of knowing the interval identifier
(intervalID), which is needed to properly clear
the interval. So we make the interval ID a property of the movie
clip, in such a way that it is available as
this.intervalID within the interval handler,
mover( ):
function mover( ) {
this._x++;
if (this._x > 300) {
clearInterval(this.intervalID);
}
updateAfterEvent( );
}
// Store the interval ID as a property of the clip
myClip.intervalID = setInterval(myClip, "interval", 1);
myClip.interval = mover;
The Code
The following code demonstrates the differences between and benefits
of using setInterval( ) instead of an
onEnterFrame( ) handler to update the animation.
The code creates two movie clips and makes them move across the Stage
in 1-pixel steps. The puck1 clip appears to move
much more quickly and smoothly because it is being animated as fast
as the Flash Player can run. The puck2 clip moves
at the current frame rate, which will be 12 fps by default.
mover = function( ) {
this._x += speed;
if (this._x > 550) {
clearInterval(this.interval);
}
updateAfterEvent( );
};
function enterFrameMover( ):Void {
this._x += speed;
if (this._x > 550) {
delete this.onEnterFrame;
}
}
function drawBall(clip:MovieClip, x:Number, y:Number):MovieClip {
var mc:MovieClip = this.createEmptyMovieClip(clip.toString( ),
this.getNextHighestDepth( ));
mc.lineStyle(40, 0xCCCCCC, 100);
mc.moveTo(-1, 0);
mc.lineTo(1, 0);
mc._x = x;
mc._y = y;
return mc;
}
var speed:Number = 1;
var puck1:MovieClip = drawBall(puck1, 20, 200);
var puck2:MovieClip = drawBall(puck2, 20, 300);
puck1.intervalMover = mover;
puck1.interval = setInterval(puck1, "intervalMover", 1);
puck2.onEnterFrame = enterFrameMover;
Note the way setInterval( ) is created:
The interval ID returned by setInterval( ) is of
type Number. The interval ID has to be known so that we can remove the interval to
which it refers at the end of the animation. The interval ID could be
passed as an argument, but in this case we add it as a timeline
variable, interval, on the
puck1 clip.
Of course, making the frame rate really high, such as 95 fps, would
ensure that both movie clips moved quickly and smoothly, but that
technique has two limitations. First, if all the animations are
dependent on the frame rate, it is harder to vary the speed of
different graphic elements. Second, if you try to make everything
move too fast, the Flash Player won't be able to
achieve the requested frame rate. The setInterval(
) technique allows you to selectively target the critical
areas of your motion graphics that must run quickly [Hack #71] . It is worth noting that if
you make the whole SWF run faster, the maximum frame rate you can
achieve is considerably less than if only portions run faster.
Final Thoughts
Despite complaints that the Flash Player isn't fast
enough, you can do a lot to make your code more efficient. Choosing
your event handling [Hack #26] so
that you run code only when it is required improves animation
smoothness and apparent performance. See Chapter 9 for more performance-related
hacks.
|