The loops we've looked at so far cause the interpreter to execute blocks of code repeatedly. Most of your loops will be of this "ActionScript-statement" type. Sometimes it's desirable to create a timeline loop by looping Flash's playhead in the timeline. To do so, attach a series of statements to any frame; on the next frame, attach a gotoAndPlay( ) function whose destination is the previous frame. When the movie plays, the playhead will cycle between the two frames, causing the code on the first frame to execute repeatedly.
We can make a simple timeline loop by following these steps:
Create a new Flash movie.
On frame 1, attach the following statement:
trace("Hi there! Welcome to frame 1");
On frame 2, add a blank keyframe and attach the following statements:
trace("This is frame 2"); this.gotoAndPlay(1);
Select Control Test Movie.
When we test our movie, we see an endless stream of the following text:
Hi there! Welcome to frame 1 This is frame 2 Hi there! Welcome to frame 1 This is frame 2
Timeline loops can do two things ordinary loops cannot:
They can execute a block of code an infinite number of times without causing an error.
They can execute a block of code that requires a Stage update between loop iterations.
This second feature of timeline loops requires a little more explanation. When any frame's script is executed, the movie Stage is not updated visually until the end of the script. This means that traditional loop statements cannot be used to perform repetitive visual or audio tasks because the task results aren't rendered between each loop iteration. Repositioning a movie clip, for example, requires a Stage update, so we can't programmatically animate a movie clip using a normal loop statement.
You might assume that the following code would visually slide the ball_mc movie clip horizontally across the Stage:
for (var i = 0; i < 50; i++) { ball_mc._x += 10; }
Conceptually, the loop statement has the right approach�it repetitively updates the position of ball_mc by small amounts, which should give the illusion of movement. However, in practice, ball_mc doesn't move each time its _x position is changed, because the Stage isn't updated between loop iterations. Instead, we'd see ball_mc suddenly jump 500 pixels to the right�10 pixels for each of the 50 loop iterations�after the script completes.
To allow the Stage to update after each execution of the ball_mc._x += 10; statement, we can use a timeline loop like this:
// CODE ON FRAME 1 ball_mc._x += 10; // CODE ON FRAME 2 this.gotoAndPlay(1);
Because Flash updates the Stage between any two frames, the ball will appear to animate. But the timeline loop completely monopolizes the timeline it's on. While it's running, we can't animate any other content on that timeline. A better approach is to put our timeline loop into an empty, two-frame movie clip. We'll get the benefit of a Stage update between loop iterations without freezing a timeline we may need for other animation.
The following steps show how to create an empty-clip timeline loop:
Create a new Flash movie.
Create a movie clip symbol, named ball, that contains a circle shape.
On the main Stage, rename layer Layer 1, ball.
On the ball layer, place an instance of the ball symbol.
Name the instance of the ball clip ball_mc.
Select Insert New Symbol to create a blank movie clip symbol.
Name the clip symbol process.
On frame 1 of the process symbol, attach the following code:
_root.ball_mc._x += 10;
On frame 2 of the process symbol, add a blank keyframe and attach the following code:
this.gotoAndPlay(1);
Return to the main movie timeline and create a layer called scripts.
On the scripts layer, place an instance of the process symbol.
Name the instance processMoveBall_mc.
Select Control Test Movie.
The processMoveBall_mc instance will now move ball_mc without interfering with the playback of the main timeline upon which ball_mc resides.
Note that step 12 isn't mandatory, but it gives us more control over our loop. By giving our process instance a name, we can stop and start our loop by starting and stopping the playback of the instance, like this:
processMoveBall_mc.play( ); processMoveBall_mc.stop( );
Note that in this example, processMoveBall_mc and ball_mc must both exist on the main timeline for as long as the loop is supposed to work. If we want to make the code more portable, we can use a relative reference to our ball_mc clip in the process symbol:
_parent.ball_mc._x += 10;
|
You'll find the sample timeline loop and empty-clip loop .fla files in the online Code Depot.
Timeline loops are effective but not necessarily elegant. We can use an event handler on a movie clip to achieve the same results as a timeline loop but with more flexibility (just try to follow along with this example, or see Chapter 10 for details on movie clip event handlers).
When placed on a movie clip, an onEnterFrame( ) event handler causes a block of code to execute once per tick of the frame rate of a movie. We can use an onEnterFrame( ) event handler on a single-frame empty clip to execute a block of code repetitively, while allowing for a Stage update between each repetition (just as a timeline loop does). Follow these steps to try it out:
Create a ball_mc instance by following steps 1 through 5 from the previous section.
On the main timeline, create a new layer called scripts.
On frame 1 of the scripts layer, attach the following code:
// This code requires Flash Player 6 or later this.createEmptyMovieClip("processMoveBall_mc", 1); processMoveBall_mc.onEnterFrame = function ( ) { _root.ball_mc._x += 10; }
Select Control Test Movie.
The ball_mc instance should animate across the Stage.
The onEnterFrame( ) loop frees us from nesting our code inside a movie clip and doesn't require a two-frame loop, as timeline loops do. All the action of a clip event loop happens in a single event handler. To stop an onEnterFrame( ) loop, simply remove the movie clip that bears it. For example:
processMoveBall_mc.removeMovieClip( );
Or, delete the onEnterFrame( ) handler:
delete processMoveBall_mc.onEnterFrame;
If our application is simple, we may wish to forego our empty clip onEnterFrame( ) loop altogether. In some cases, we can, quite legitimately, attach an event loop directly to the clip being manipulated. In our ball_mc example, we can avoid the need for a separate empty clip by defining our onEnterFrame( ) handler directly on the ball_mc instance:
ball_mc.onEnterFrame = function ( ) { this._x += 10; }
We can even define the onEnterFrame( ) event loop on the main timeline by placing this code on one of its frames:
this.onEnterFrame = function ( ) { this.ball_mc._x += 10; }
Notice that we intentionally avoid referring to the main timeline as _root, which may change meaning if the entire movie is loaded into a movie clip.
In Flash Player 6, each movie clip (including the main timeline) can take only one onEnterFrame( ) event handler. In most applications, a single, main application loop runs once per frame, updating the entire movie. For example:
// CODE ON MAIN TIMELINE function mainLoop ( ) { // Perform application-wide updates here } this.onEnterFrame = mainLoop;
Less frequently, multiple movie clips can update various aspects of the application, each with their own onEnterFrame( ) handler.
Because timeline and onEnterFrame( ) loops iterate once per frame, their execution frequency is tied to the frame rate of a movie. If we're moving an object around the screen with a timeline or an event loop, an increase in frame rate can mean an increase in the speed of our animation.
When we programmed the movement of the ball_mc clip in our earlier examples, we implicitly specified the velocity of the ball in relation to the frame rate. Our code says, "With each frame that passes, move ball_mc ten pixels to the right":
ball_mc._x += 10;
Hence, ball_mc's velocity is dependent on the frame rate. If our movie plays at 12 frames per second, then our ball_mc clip moves 120 pixels per second. If our movie plays at 30 frames per second, our ball_mc clip moves 300 pixels per second!
When timing scripted animations, it's tempting to calculate the distance to move an item in relation to the movie's frame rate. So, if a movie plays 20 frames per second, and we want an item to move 100 pixels per second, we're tempted to set the speed of the object to 5 pixels per frame (5 pixels * 20 frames per second = 100 pixels per second). There are two serious flaws in this approach:
By relying on the frame rate to determine the speed of an item, we make it painful to change the frame rate. If we change the frame rate, we have to recalculate our speed and edit our code accordingly.
The Flash Player does not necessarily play movies back at the frame rate set in the Flash authoring tool; it often plays them slower. If the computer running the movie cannot render frames fast enough to keep up with the designated frame rate, the movie slows down. This slowdown can even vary depending on the system load; if other programs are running or if Flash is performing some processor-intensive task, the frame rate may drop for only a short period and then resume its normal pace.
You can test a movie's actual frame rate yourself using the time-tracker tool available at:
In some cases, an animation that plays back at slightly different speeds can be deemed acceptable. But when visual accuracy matters or when we're concerned with the responsiveness of an action game, it's much more appropriate to calculate the distance to move an object based on elapsed time rather than relative to the frame rate. Example 8-4 shows a quick-and-dirty sample of time-based animation (i.e., the ball_mc speed is independent of the frame rate). The new movie has three frames and two layers, one layer with the ball_mc instance and the other with our scripts.
// CODE ON FRAME 1 var distancePerSecond = 50; // Pixels to move per second var now = getTimer(); // The current time var then = 0; // The time when last frame was rendered var elapsed; // Milliseconds between frame renders var numSeconds; // elapsed expressed in seconds var moveAmount; // Distance to move each frame // CODE ON FRAME 2 then = now; now = getTimer(); elapsed = now - then; numSeconds = elapsed / 1000; moveAmount = distancePerSecond * numSeconds; ball_mc._x += moveAmount; // CODE ON FRAME 3 gotoAndPlay(2);
Note that our time-based movement might appear jerky if the frame rate suddenly changes. We could smooth things out by using an elapsed-time measurement that averages the time between a series of frames instead of just two.