gotoAndPlay and the queue

来源:互联网 发布:马岛战争知乎 编辑:程序博客网 时间:2024/05/02 05:11

http://ericlin2.tripod.com/goto/gotoAndPlayt.html
by ericlin


 "gotoAndPlay" is the oldest actionscript command in the history of Flash. It moves the playhead to another frame. However, the effect can be very complex because it interrupts the natural sequential order of playing.

Two major side effects occur behind the scene - execution of frame scripts and unloading/loading of the child movieClips. 

Also it is important to know the queue structure that handles frame script.

I would first explain the process of normal playing and then explain this script driving playhead movement. Later I will put several quiz and example to demonstrate the concept.

I was told by DVD vender that to move DVD playhead to-and-fro vigorously might cause damage to the machine. In fact, for Flash, we are not so crazy to move this and that playhead here and there. Most of the time, our gotoAndPlay script is so simple as demonstrated in the quiz. Fully understanding about the details behind the scene is just "academic".


Normal playing

For each interval, Flash does these jobs:

1. For active clips on the stage (those already on the stage),

adjust the playhead according to playmode :move to next frame or stay

put the onEnterFrame function of the clip to the queue

put frame script function of the clip to the queue if playhead has moved

initialize the debut clip if the clip has a child come to stage. 

2. How does it initialize the debut clip

Check for child clips that is new. Put child- frame 1 script into the queue

If Child has Children, do nested initialization by putting their frame 1 script to the queue

3. After all elements in the queue are finished, Flash renders the graph.

When the movie starts, the stage is empty. No active clips on the stage. All clips are debut clips. So, the whole job is to do initialization. The _root and all the clips are initialized by putting the frame 1 script on to the queue. The initialization starts from _root to clips with nest structure. So, the queue would like this:

_level0.frame 1 script
_level0.mc1.frame 1 script
_level0.mc1.subClip.frame 1 script
_level0.mc2.frame 1 script
_level0.mc2.subClip.frame 1.script

After an interval, Flash starts to play. If the playMode of the main timeline is "play", then it should now play frame 2. Lets assume that mc2 is no more present and a new subClip appears when mc1 plays to frame 2, then the queue would be like this:

//mc2 is already unloaded, so onEnterFrame and frame script about mc2 is ignored
_level0.mc1.subClip.onEnterFrame
_level0.mc1.subClip.frame 2 script 
// advanced to the next frame
_level0.mc1.onEnterFrame
_level0.mc1.frame 2 script 
// advanced to the next frame
_level0.mc1.subClip2.frame 1 script 
// debut and initialization
_level0.onEnterFrame
_level0.frame 2 script

The initialization is always start from _parent to child. However, the playing of active clip (onEnterFame function and current frame script) starts from the last clip that are newly added. So, the sequence of onEnterFrame seems to be child to _parent. Usually, the initialization also adds the playing roll from top layer down. But, layering is not necessarily the sequence of adding members if we consider loading and unloading. Anyway, the playing contains paired onEnterFrame/frame script ordered by child-> _parent relationship and some debut initialization frame 1 script ordered by _parent -> child relationship.


The queue

I mentioned several times about "queue" . What is that ?

Most procedures or processes use stack. When a procedure calls another subProcedure, it pushes present process point into a stack and the subProcedure will be executed first till it returns. Then the process pop the process point from the stack to continue unfinished part of the mian procedure. However, gotoAndStop/gotoAndPlay does not follow this rule. The command gotoAndStop/gotoAndPlay moves the playhead which evokes frame script of destine frame. The frame script is put to the end of the queue waiting for execution. The current frame script continues till the end and then pickup the next elements in the queue. Anyway, at last, the evoked frame script in the queue will get its turn.

For example: _root.frame 1 contains a command "_root.gotoAndStop(2) and mc.gotoAndStop(3); Then the initial queue by the natural playing will be:

_level0.frame 1 script
_level0.mc.frame 1script

Before Flash renders the graph, it need to run the queue. When the first element of the queue runs, the _root.gotoAndStop(2) moves the playhead of the _root to frame 2 and evokes the frame script of frame 2. Also, "mc.gotoAndStop(3)" moves the playhead of the mc to frame 3 that evokes the frame script of the mc. Instead of executing those frame script immediately, they are put to the queue. The result is :

_level0.frame 1 script
_level0.mc.frame 1script
_level0.frame 2 script 
// from here down are newly added elements by gotoAndStop
_level0.mc. frame 3 script

The 3rd line and 4th lines will be executed after the 2nd line. This is queue.

It is queue not stack. Sometimes this turns out very awkward and confusion. However, it ensures that each frame script be executed without interruption. It is more safe by avoiding the crash from interrupted calls.


The command "mc.gotoAndStop(2)"

The command moves the playhead of mc to destine frame 2 and set the playMode to "play". If destine frame is different from the original one, it put to the queue the frame script of the destine frame. It unloads some children if they are not there anymore. It initialize new debut children if any,  by putting the frame 1 script of the children to the queue.

It is a script, so it always happens after the initial queue. It wont disturb the initial queue structure which is setup by natural playing. Whatever frame scripts evoked by "gotoAndStop" are put at the tail of the queue.

Well, I think, it is enough for such abstract explanation. I would be better to give several quiz and example to demonstrate the actions.


Quizz 1: _currentframe

A movie contains 4 keyframes. In frame4, put a movieclip "mc4". In keyframe 2, we make a jump by this script:

mc4._xscale=400;
gotoAndStop(4);
trace(_currentframe);
mc4._yscale=400;

As we expect, the movie stops at frame 4. Try to answer questions below.

1. Did you see transient frame 2 shown on the screen before it jumps to frame 4 ?

No, no transient frame 2.  Frame 2 does not get rendered. Frame 4 is shown on the screen immedidately. There is even no "gap" or "interval" to hint us it is a re-direct to frame 4.

For each interval, Flash setup a queue of onEnterFrameand frame script. Only after it finishes the execution of queue will it render the graph.  At that time, the playhead is alreay at frame 4. 

2. What result do you expect about trace(_currentframe), 2 or 4 ?

The frame script is nothing but a function call. Although it is the "frame 2 script" that is running, the playhead is already moved to frame 4 by "gotoAndStop(4)" , one line before the "trace" command. So, the _currentframe is 4, not 2.

_currentframe means the position of the playhead. It has nothing to do with what frame script it is running.

3. There are scripts to change the scales of mc4. What scaling do you expect the mc4 will be ?

It appeas stretched vertically but not horizontally.

In the beginning, the playhead is at frame 2. The clip mc4 does not exist there and is not accessible. So, it has no effect to change the _xscale of the non-existing clip. Later, when we move the playhead to frame 4, the clip mc4 is accessible and change of the _yscale stretches the clip vertically.


Quizz 2: queue not stack

A movie of 4 keyframes. We intend to give each frame a trace-able frameLabel. So, in keyframe 2, we add a script : frameLabel="TWO"; and in keyframe 4, we script :  frameLabel="FOUR";

Again, we jump our playhead from frame 2 to frame 4. Here is the script i of frame 2:

frameLabel="TWO";

gotoAndStop(4);
trace(_currentframe);
trace(frameLabel);

The _currentframe is traced out as 4, how about the "frameLabel" ?

You might have expected that when the playhead moves to frame 4, the frameLabel will be set to "FOUR" by the frame 4 script, so we should get an output of "FOUR";

No ! The frameLabel is still traced out as "TWO" because the execution is still within this function block. The frame script in the destine frame 4 is not yet executed. 

Any block of frame script is executed from beginning to the end without interruption. Although the movement of playhead should be accompanied by other frame script, the execution of this block is continuing without interruption. All the accompanied frame script evoked by gotoAndStop/gotoAndPlay are put in a queue and executed sequentially after this block is finished. So, the flow of script is something like below:

set frameLabel="TWO";
move playhead to frame 4 and set playMode to "stop";
put frame script of the destine frame into queue
trace out _currentframe;
trace out frameLabel;
execute the queue , ie,  the frame 4 script which sets frameLabel="FOUR";

So, we know why the _currenframe gets traced out as 4 but the frameLabel still gets traced as "TWO".


Off the topic, here is a similar quizz.

If we want our movie to stops at frame 2, we might have two approaches:

Approach 1:

onEnterFrame=function(){
        if(frameLabel=="TWO"){stop();}
}

Unfortunately, the movie will stop at frame 3, just one frame later than we desired. That is because when the playHead moves to frame 2, it executes "onEnterFrame" first and then "frame script". So, only when playhead moves to the next frame, will onEnterFrame catch the frameLabel as "TWO";

Approach 2:

onEnterFrame=function(){
        if(_currentframe==2){stop();}
}

Yes, the movie successfully stops at frame 2. Because "onEnterFrame" function is executed after playHead movement.


Quiz 3 : gotoAndPlay - GOTO ? PLAY ?

Now we have discussed about gotoAndStop. What about gotoAndPlay ?

gotoAndPlay(2), means move playhead to frame 2 and set the playMode to "play". The result is to show frame 2 on the screen and since playMode is set to "play", the playhead will move to frame 3 after an interval. This is what "play" means.

gotoAndStop(2) also moves the playhead to frame 2 but set the playMode to "stop". So, even after an interval, it stays still on frame 2.

OK, here is a notorious one and also a must know quiz.

A movie of two keyframes. Each keyframe has a stop() script in it. Now we put a button in another layer. This button has script:

on(release){
    gotoAndPlay(2);
}

As expected, the movie stops at frame 1. Now, what about the gotoAndPlay script in the button ?

Questions:

1. The button want it to play but the frame script has a "stop()". Will it stop there or keep playing ?

The movie goes to frame 2 and stops there.

When the playhead is moved to frame 2, put the frame 2 script to the queue, and set playMode to "play"; But when it arrive to the queue, execute the frame 2 script, the palyMode is set to "stop" again. So, it stops.

The end result is similar to gotoAndStop(2);

2. When it stops at frame 2, will button click "gotoAndPlay" has any effect ?

It will go to frame 1 and stops there.

The playhead does not move by "gotoAndPlay(2)", so the frame script is not evoked. It just set the playMode to "play". The result is simply a "play();"

So, the movie plays to frame 1 and then stops there.

In fact, the button script can be shortened as : on(release){ play();}


Quiz 4: Endless loop 

Becareful, this will crash your Flash.

A movie of 5 frames. In the first frame, script:

gotoAndPlay(4);
gotoAndPlay(2);
gotoAndPlay(3);
gotoAndPlay(1);
gotoAndStop(5);

What frame will it stops at ?

Answer: it is an endless loop which crashes your Flash. Why ?

For tutorial, I make it simpler :

A movie with 2 frames. In frame 1, we script like this:

gotoAndStop(2);
gotoAndPlay(1);

What result do you expect ? Will it play normally ? 

No, it will be stuck in a looping and crash your Flash.

If those script were put in a button not in frame 1, clicking of the button just makes the movie go back to frame 1 and play.

Since it is a frame script, movement of the playhead shall evoke frame script. Again, the frame script moves the playhead. Then movement of the playhead evokes this script.  This results in an endless loop. The movie has no chance to finish the script queue to render the content. So, we get nothing on the screen but a crash.

You can also create a similar movie to crash your Flash. In the frame 1, script "gotoAndStop(2);" and in frame 2, script "gotoAndStop(1)". The effect is similar to the example above. It loops forever before it finishes the script queue to render the content.


Quiz 5: unloading and reloading.

A movie of two key frames. Frame 1 contains a movieclip "mc1" and a "stop()". Frame 2 is a blank keyframe. We have a button with script:

on(release){
    mc._xscale=300;
}

Obviously, clicking of the button will stretch the child clip horizontally. What will happen if we move the palyhead following that command:

on(release){
    mc._xscale=300;
    gotoAndStop(2);
    gotoAndStop(1);
}

Nothing ! On the screen, the child movieclip seems not stretched.

If fact, the child clip does get stretched. But, when we gotoAndStop(2), tha clip is unloaded. When it gotoAndStop(1), a new child movieClip is re-created. It appears as if the movie really played those two frames.

So, script driving playhead movement also unload clips when the playhead moves. The same as we really play those frames sequentially.

"Come on !" you might believe that it is an "of-course". Never mind, this quiz is to confirm this point.


Quiz 6: Backward and reloading/duplicating 

This is also a must known nortorious one.

Create a movie contains 3 frames or more. Put a clip named "mc" on the stage. In the frame 1, script:

for (var i = 1; i<=6; i++) {
    mc._x += 50*i;
    mc.swapDepths(i);
    gotoAndStop(3);
    gotoAndStop(2);
}

When the movie plays, we see 7 instances of "mc" on the stage. If you check debug to list objects, there are 7 movieclips. All with the same name "mc". The effect is similar to 

stop();
for (var i = 1; i<=6; i++) {
    var c = mc.duplicateMovieClip("mc", i);
    c._x += 50*i;
}

The usual explanation is that, if we swap the depths to the range of "programming depths - the depth greater or equal to 0", then Flash will failed to find it when the palyhead moves "backward". Consequently, Flash will recreate a new one. This result in a "duplication" of that clip.

That explanation seems to make sense. But, we still don't know definitely why this happens only when the playhead moves "backward" but not "forward".

You may test it by swap a clip depth to a positive number and let it play. At some frame, modify the position of the clip and make _root go back to any frame. Or let it play till the end when the _root will go back to frame 1.  You will see two instances of the clip.

To avoid such conditionL

1. Do not gotoAndStop to previous frame - Do not back play.

2. Stage clip should not swapDepths to programming depths. 


Quiz 7 : effect of combined gotoAndStop for main and child clips. 

This is the most complex one and this quiz is the last example in this article. You may read it patiently.

A movie with 5 keyframes. Each keyframe contains frame script with "stop()"  in frame 1. Create a symbol with 5 keyframes and each keyframes contains frame script, also with "stop()" at frame 1. Put an instance as "mc2" in frame 2 of main movie. Put an instance as "mc5" at frame 5 of main movie. Now we have a button to do our experiment:

on (release) {
    gotoAndStop(2);
    mc2.gotoAndStop(4);
    mc2.gotoAndStop(1);
    gotoAndStop(5);
    mc5.gotoAndStop(2);
    mc2.gotoAndStop(3);
    gotoAndStop(2);
    mc2.gotoAndStop(2);
}

When I click the button, here is the output:

_level0 frame 2 script
_level0 frame 5 script
_level0 frame 2 script
_level0.mc2 frame 1 script
_level0.mc2 frame 2 script

Can you write down the queue for the on(release) block ? Here we goes:

root frame 2 script // playhead move
mc2 frame 1 script // debut init
mc2 frame 4 script // playhead move
mc2 frame 1 script // playhead move
root frame 5 script // playhead move
mc5 frame 1 script // debut init;
(mc2 not accesible at frame 5, mc2.gotoAndStop(3) is ignored);
root frame 2 script // playhead move
mc2 frame 1 script //debut init
mc2 frame 2 script // playhead move

Now we start to execute the queue. 

Remember that, our playhead of main is at frame 2 now: the first mc2 and mc5 are already unloaded. Those frame script are not accessible. So, what really effective are the same as we see on the output window.

Please do not make a too quick conclusion that child clip gotoAndStop only effects at the end. Lets create another similar movie but extend the existance of mc2 to frame 5, then the execution of the button will output:

_level0 frame 2 script
_level0.mc2 frame 1 script
_level0.mc2 frame 4 script
_level0.mc2 frame 1 script
_level0 frame 5 script
_level0.mc2 frame 3 script
_level0 frame 2 script
_level0.mc2 frame 2 script

Compare this output with previous queue, we pick out the difference. 

1. mc2 does not get unload. So, all frame script about mc2 in the queue are effective.

2. the ignored frame script in parenthesis is now effective. So, mc2 frame 3 script should be active.

3. Since mc2 is not unloaded, it is not reload. The last debut int script should not be there.


Summaries:

1. Pick gotoAndStop/gotoAndPlay correctly. In many circumstances, they are not interchangeable.

2."gotoAndStop" moves the playhead immediately. Loading and unloading processes follows. Some frame script of the child in the queue is aborted if the clip is unloaded. Unless we check the sequence of the frame script in the queue carefully, it is difficult tell what frame script is already executed before it is unload and what frame script is unload before execution.

3.If we script "gotoAndStop(4) in frame 2, it moves the playhead immediately. The stage of the scene is changed. Every thing is happening on stage of frame 4 then. After this line, anything that is referred by script is the one in frame 4. It is very confusing.

4.Because the script of destine frame is put to the queue, the remain script after "gotoAndStop" is executed before destine frame script. In other word, this remained script will be executed before initialization of all children in the destination frame. It is a bit un-natural and difficult to debug from the point of OOP.

5. It is advisable that the command "gotoAndStop" be put at the last line of frame script or button script. Anything we need to do, do it before gotoAndStop. Let gotoAndStop be the last command. Do not script anything after "gotoAndStop".

原创粉丝点击