Hack 70 Adjust the Animation Complexity Dynamically 
Measure the Flash Player's
runtime performance in order to adjust the complexity of an effect or
animation dynamically to run optimally on both low-end and high-end
machines.
In the preceding
hacks, we've seen ways to improve performance,
regardless of the playback system, by optimizing a Flash
movie's assets at authoring time. Optimization of
that sort is essentially a lowest common denominator affair. This
hack addresses the need to maximize animation and other effects for
faster machines without creating a movie that bogs down on slower
machines.
Although Flash Player 7 performs better than previous versions, it is
still not a speed demon. The underlying goal of Flash is to optimize
filesize (i.e., reduce download time) rather than provide
breathtaking performance. Although we can improve performance by
lowering the render quality [Hack #67], in many cases, turning off
antialiasing can result in an unacceptably poor appearance for your
site.
A better way to improve performance is to adjust the complexity
(i.e., how much is happening per frame) of a
graphical effect rather than its quality or frame rate. This approach
can yield better results over a wider range of user systems with
varying processing power than a one-size-fits-all optimization.
Adjusting the complexity is often an iterative process based on
experimentation (with time it will become second nature and require
fewer iterations):
Start with a target frame rate, such as 18 fps. Measure the achieved frame rate, as covered shortly. If the desired frame rate is not achievable on the test machine,
reduce the complexity of the effect. If the effect is rendered quickly enough, you have the leeway to
increase the complexity of the effect.
Now that you understand the basics of the approach,
let's automate it so that Flash decides dynamically
whether to reduce or increase the complexity of the animation. This
approach protects against sluggish performance on slower machines and
(when done well) enhances the effect on faster machines.
We do not intend to change the frame rate, but rather change the
complexity of the effect, in such a way that it always completes just
before the next frame starts.
Calculate the Achieved Frame Rate
The following code creates an empty movie
clip called
perfMon
(performance monitor) and attaches an onEnterFrame(
) event handler to it. This event handler calculates the
average frame duration (in milliseconds) using the last two timer
measurements, creating a rolling average. This rolling average,
time, is compared against the expected duration,
FRAME_DUR, and a Boolean flag,
slow, is set to true or
false based on whether the Flash Player is
achieving the target frame rate.
function performanceMonitor( ) {
this.createEmptyMovieClip("perfMon", 10000);
perfMon.onEnterFrame = function( ) {
time = (getTimer( ) - lastTime) / 2;
lastTime = getTimer( );
// Set slow to a Boolean based on whether the
// elapsed time exceeds the allowed frame duration
slow = time > FRAME_DUR;
};
}
// Set FRAME_RATE to match the movie's target frame rate
var FRAME_RATE:Number = 18;
var FRAME_DUR:Number = (1 / FRAME_RATE) * 1000;
var time:Number = 0;
var lastTime:Number = getTimer( );
performanceMonitor( );
A slow value of false indicates
that we can make the Flash Player do more without compromising
performance because it is completing all its tasks before the start
of the next frame. Ideally, we would want the Flash Player to finish
all tasks for the current frame just as the next frame starts. We
know this is happening when our rolling average,
time, is equal to the expected frame duration,
FRAME_DUR.
Adjust the Complexity Based on Performance
Let's see how to make Flash increase or decrease the
animation's complexity based on the performance
monitor's calculation. One simple approach is to
draw more or fewer movie clips depending on the value of our
slow flag. For the sake of example,
let's use a star field particle effect [Hack #33] . The following code assumes a
default Stage size of 550 400 pixels, with a dark background color.
The effect works best with a frame rate of 18-24 fps. To set the
background color and frame rate, select Modify Document and
adjust the properties as desired. The following code assumes a frame
rate of 24 fps, and if you choose any other rate, you will need to
change the following line to reflect this:
var FRAME_RATE:Number = 24;
Note that you have to do this manually because there is no property
or method in Flash that returns the movie's target
frame rate (strange but true!).
We've modified the starfield(
) function from [Hack #33] to draw a single star at a
time. Instead of setting the slow flag when the
animation is running slowly, we use an if
statement to adjust the animation's complexity. If
the animation is running quickly enough, it draws additional stars by
calling starfield( ); otherwise, it deletes
stars to maintain performance.
Again, it is important to realize that we are not varying the frame
rate (and doing so would not be transparent to the rest of the SWF,
in any case), but rather the complexity of the animation so that the
time taken to render each frame in the animation is equal to all the
available time per frame.
function performanceMonitor( ) {
var perfMon:MovieClip = this.createEmptyMovieClip("perfMon", 10000);
perfMon.onEnterFrame = function( ) {
time = (getTimer( ) - lastTime) / 2;
if (time < (FRAME_DUR)) {
// Speed is okay
stars++;
starField( );
} else if (time > (FRAME_DUR + 10)) {
// Running too slowly
_root["star" + stars].removeMovieClip( );
stars--;
}
lastTime = getTimer( );
};
}
function mover( ) {
this._y += this.speed;
this._yscale += this.speed;
if (this._y > 275) {
this._y = 0;
this.speed = Math.ceil(Math.random( ) * 10);
this._yscale = 100;
}
}
function starField( ) {
var star:MovieClip = this.createEmptyMovieClip("star" + stars, stars);
star._rotation = Math.random( )*360;
star._x = 275;
star._y = 200;
var dot:MovieClip = star.createEmptyMovieClip("dot", 0);
dot.speed = Math.ceil(Math.random( ) * 10);
dot.lineStyle(1, 0xFFFFE0, 100);
dot.moveTo(0, 2);
dot.lineTo(0, 5);
dot.onEnterFrame = mover;
}
// Set FRAME_RATE to match the movie's target frame rate
var FRAME_RATE:Number = 24;
var FRAME_DUR:Number = (1 / FRAME_RATE) * 1000;
var time:Number = 0;
var lastTime:Number = 0;
var stars:Number = 0;
performanceMonitor( );
If you run the preceding code, you will see the star field build from
no stars up to a maximum of several hundred stars, as shown in Figure 9-7. Use the Variables tab in the Debugger panel
to view the variables on the main timeline, which displays results
similar to those shown in Figure 9-7.

The number of stars increases until time is close
to FRAME_DUR, at which point the code adds or
deletes stars to maintain equilibrium. If you do anything to change
this equilibrium (such as run other code or resize the SWF), the
change in performance causes a new equilibrium to be reached. Note
that the Flash frame rate tends to change spuriously even when
equilibrium is reached, so the effect will be almost constantly
adding and deleting movie clips. You can build in a buffer if you
prefer. For example, you might delete or add clips only if the actual
time per frame is more than 15% above or below the target frame rate.
Final Thoughts
Graphical effects that work well on your development machine might
bog down on user machines with lesser performance. To make your Flash
content run appropriately on as many machines as possible (including
mobile devices with significantly slower performance
characteristics), you should strive for
scalability.
Your content should adapt to the user's machine.
This hack shows one fairly simple approach: calculate how fast the
user's machine is performing and adjust the
graphical complexity accordingly. Measuring true performance at
runtime is usually easier and more reliable than trying to estimate
performance based on, say, the device type or screen resolution.
The many adjustments you can make at runtime to dynamically change
the movie's complexity include:
Limiting the number of items onscreen (stars in a star field,
monsters in a game, users in an avatar-based chat room). Reducing the number of audio channels in use. Reducing the alpha effects (i.e., turn off transparency on slower
machines). Limiting the amount or frequency of movements (such as using fewer
frames in a walk cycle [Hack #28] ). Digital and 3D artists
unfamiliar with performance requirements usually create too many
in-between positions. Often 3 or 5 will suffice instead of the
typical 15 or 20 positions artists provide. Using smaller screen dimensions or reducing the active area of the
screen. You can make the screen size appear larger by adding static
graphics around your active area, such as a border to achieve a
"picture frame" effect or static
gauges to achieve a "dashboard"
look. Using still bitmaps or videos with color effects [Hack #8].
You will find that some changes affect performance in more than one
way and offer benefits on both low-end and high-end machines. For
example, in a bartending game, you might reduce the number of patrons
in the bar and the frequency with which they talk. This reduces the
number of items to be rendered and the number of sound channels in
use. Or you might reduce the size of the patrons so they take up less
screen real estate. On higher-performance machines, this allows you
to place more patrons at the bar. On lower-end machines, this ensures
that the patrons displayed are rendered more quickly.
Runtime performance of the playback machine isn't
the only reason to adjust your Flash content. You should also take
into consideration the user's personal performance
on interactive exercises. For example, you might display directions
or play audio help if the user does something wrong or is taking too
long to perform the desired operation. Or you might make a video game
or educational game easier on the first level in response to the user
failing repeatedly.
There are many other ways to affect the perceived performance or user
experience. For example, implementing keyboard shortcuts [Hack #96] makes an application feel
more responsive than requiring mouseclicks. Likewise, an animation
that plays repeatedly becomes boring no matter how well it performs.
Shorten the animation if it is played repeatedly, limit the number of
times it is played, or allow the user to turn it off or skip
it.
|