Hack 27 Time-Controlled Movement

figs/expert.gif figs/hack27.gif

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 figs/command.gif-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.