Accessing displayObjects on the timeline after a gotoAndStop or gotoAndPlay

I have been meaning to add this to my blog for quite some time now. I have read a few postings related to this topic but I am surprised there aren’t a lot more people complaining about this. Personally I feel Adobe really dropped the ball with this bug. I guess I should explain what the bug is before I go on.

Lets pretend we’re creating a button, now I know most of my readers are developers and for the most part we would never create a simple button like this using multiple frames on the timeline. But we all know that designers love to use the timeline and create button states using frames and frame labels. I actually think this posting will help designers as much as it will help the developers who are working with said designers.

Now lets pretend our button has 3 states, up, over, and down and each state is represented on the timeline, 5 frames apart and the frame is labeled the same as each state. So the first frames label is “up”, the 5th frames label is “over”, and the 10th frames label is “down”. On each frame there is a MovieClip named button and each key frame has the same graphic just tinted differently to differentiate the states. Then on our code layer we set up MouseEvents for each state MOUSE_OVER, MOUSE_OUT, and MOUSE_DOWN, in each of the event handlers we move the playhead to each frame using the gotoAndStop method while passing in the frame label that we want the playhead to move to. Ok, so this is all straight forward. Very flash 4. The interesting part of this is when you try to access sibling displayObjects on the same frame right after we call gotoAndStop.

To illustrate this bug lets add a layer below the layer that has our button on it. On this layer add a keyframe at frame 5 and add a MovieClip, whatever you want, import a picture draw a shape, it doesn’t matter. Just as long as you give it an instance name of overClip. Right after we call gotoAndStop(‘over’) try and trace (overClip). If you are not completely following me right now here is a picture of what my timeline looks like:

And our code will look something like this (excuse the timeline code, but it is the easiest way to demonstrate this bug).

button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
button.addEventListener(MouseEvent.MOUSE_OUT, rollout);

function rollover(e:MouseEvent):void {
     gotoAndStop('over');
     trace('the instance name of the overclip is: ' + this.overClip.name);
}

function rollout(e:MouseEvent):void {
     gotoAndStop('up');
}

In the rollover function you will notice I added a trace that traces out the name property of the overClip that appears on the out frame (or frame 5 for those of you who like numbers). If you were to run this code you will notice that the rollover works, the playhead moves to frame 5 and visually you see the change, however, the trace statement throws the following error “TypeError: Error #1009: Cannot access a property or method of a null object reference.” Null?? How can that be, the playhead is at frame 5, and the overClip is on frame 5, why is overClip null? Good question! The other odd thing is if you trace out numChildren right after the gotoAndStop(‘over’) call it will return the correct number of child displayObjects. How can the player know how many children there is but still throw an error saying we’re trying to access a null object reference?

This was a very common practice in AS2, especially in highly creative sites with both Flash Designers and Flash Developers. And as more creative shops start embracing Flash 9 I think this issue will become more common.

Well, the first thing I thought to do was to call stage.invalidate() before I called gotoAndStop(‘over’), in theory this should force a redraw and fire the render event. Well it did fire the render event but I still couldn’t trace out the clip on frame 5. Arghhh!

The one thing I did notice is the error is only thrown the first time rollover is called. Everytime after that the reference to overClip traces out fine. So this got me thinking. It looks like displayObjects do not register until the current frame cycle is complete. For those who do not know, a frame cycle is two parts, first the code executes, once the code on that particular frame executes any display updates are executed (adding overClip to the timeline is a display update).

The first approach to get around this bug I thought up was to kick off a EnterFrame event in the rollever function, after the EnterFrame event is fired, it still didn’t trace out overClip. Then I thought maybe I need a second frame cycle to register everything because technically the EnterFrame event fires at the exact moment the playhead hits the frame which is exactly the same problem we already have. Ok, so lets allow for two EnterFrame events to fire, kill the event, and trace out overClip. Guess what, that works. Here is the code for this approach.

var count = 0;

button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
button.addEventListener(MouseEvent.MOUSE_OUT, rollout);

function rollover(e:MouseEvent):void {
     count = 0;
     gotoAndStop('over');
     addEventListener(Event.ENTER_FRAME, rolloverDelay);
}

function rolloverDelay(e:Event):void {
     count++
     if (count == 2) {
          removeEventListener(Event.ENTER_FRAME, rolloverDelay);
          trace('the instance name of the overclip is: ' + this.overClip.name);
     }
}

function rollout(e:MouseEvent):void {
     gotoAndStop('up');
}

Yes this works, but imagine how complex this mess of code would get if we had a complex 12 state button with animations on each state. Not very practical.

The only way I could come up with to get around this issue was to create an EnterFrame that fires right off the bat, moves the playhead to the last frame of the timeline, and then returns it to the first frame. This ensures that everything that exists on the last frame will be registered and you will be able to access it immediately following the gotoAndStop(‘over’) call. The key to this approach is only items that are still in the display list on the last frame will register. If you have a blank keyframe after overClip it will not register if the last frame of the timeline is beyond that. Here is the code for this approach.

button.buttonMode = true;
button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
this.addEventListener(Event.ENTER_FRAME, frameentered);
this.addEventListener(Event.RENDER, rendered);

function frameentered(e:Event) {
     this.removeEventListener(Event.ENTER_FRAME, frameentered);
     if (overClip == null) {
          stage.invalidate();
          this.gotoAndStop(totalFrames);
     }
}

function rendered(e:Event):void {
     if (this.currentFrame == this.totalFrames) {
          this.gotoAndStop(1);
     }
}

function rollover(e:MouseEvent):void {
     trace('the instance name of the overclip is: ' + this.overClip.
}

Excellent, we’re making progress. But we are working with a very simple example, a two state button (up and over). What if we had a more complex button, say 4 states (up, over, down, and disabled), and at each state different assets were added to the timeline like so.

I know, I said complex, and this timeline is far from complex, where are all the tweens, and hundreds of layers, guides, and masks. Well, lets just pretend all that fun stuff is there for now.

If we were to try the last approach with this more complex timeline we still wouldn’t have access to the clips on the over and down frames. This is because we moved the playhead from the first frame to the last frame and back to the first frame again. The playhead never touched anything in between so the clips on the over and down frames would not have been registered yet.

The first approach I came up with to register the clips on all the frames was quite innovative, so I thought. Basically I used the currentLabels array property of MovieClip. For those who don’t know, the MovieClip class now contains a currentLabel and a currentLabels property. The currentLabel returns a FrameLabel object if one exists for the current frame that the playhead is currently on. A FrameLabel Object contains two properties, name, and frame. The name property is the frame label name and the frame property is the frame number (int) of the current frame the playhead is on. The currentLabels property of MovieClip returns an array of FrameLabel Objects containing all the FrameLabels for the targeted MovieClip. Using the currentLabels array I thought I could loop through each FrameLabel in the array and move the playhead to each frame label and once it is complete return the playhead back to the first frame. However, you cannot use a traditional for loop because the frame cycle will not occur until the for loop is complete. Instead I set up a counter variable and use an EnterFrame to move through each FrameLabel Object for targeted MovieClip. This approach is very similar to our previous approach with more stops in between. Lets call this approach the milk run. Here is what my code looked like for this approach.

button.buttonMode = true;
var count:int;
if (count == 0) {
     this.visible = false;
     addEventListener(Event.ENTER_FRAME, proxyframes);
     addEventListener(Event.RENDER, rendered);
}

function proxyframes(e:Event) {
     stage.invalidate();
     gotoAndStop(this.currentLabels[count].name);
     trace(this.currentFrame);
}

function rendered(e:Event):void {
     count++
     trace('render called' + this.currentFrame)

     if (count == this.currentLabels.length) {
          init();
          gotoAndStop(1);
          removeEventListener(Event.ENTER_FRAME, proxyframes);
          removeEventListener(Event.RENDER, rendered);
     }
}

function init() {
     this.visible = true;
     button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
     button.addEventListener(MouseEvent.MOUSE_OUT, rollout);
     button.addEventListener(MouseEvent.MOUSE_DOWN, press);
     button.addEventListener(MouseEvent.MOUSE_UP, release);
}

function rollover(e:MouseEvent):void {
     gotoAndStop('over');
     trace(overClip.name);
}

function rollout(e:MouseEvent):void {
     gotoAndStop('up');
}

function press(e:MouseEvent):void {
     gotoAndStop('down');
     trace(downClip.name);
}

function release(e:MouseEvent):void {
     gotoAndStop('disabled');
     trace(disabledClip.name);

     //disable buttons if there was a parent clip parent.mouseChildren = false would also work.
     button.buttonMode = false;
     button.removeEventListener(MouseEvent.MOUSE_OVER, rollover);
     button.removeEventListener(MouseEvent.MOUSE_OUT, rollout);
     button.removeEventListener(MouseEvent.MOUSE_DOWN, press);
     button.removeEventListener(MouseEvent.MOUSE_UP, release);
}

So why doesn’t this work. Well if you remember what I said earlier, moving the playhead only registers the last frame that it was on. If you run this code you will notice the rollover and rollout methods return null but the release method traces out the correct value, this is because that was the last stop on our playhead milk run.

In order for this approach to work I would have to layout my timeline appropriately. Each state clip should be on its own layer with nothing after it (including blank keyframes) on the timeline, like so:

Now you will notice that the above code works, we are now able to trace out the extra clips that appear on each frame with a label. However, now that everything exists on the last frame we really don’t need to loop through each FrameLabel to register the clips, we can just use our first trick and move the playhead to the last frame, once it renders move back to the first frame. Even though the looping through the FrameLabels code is overkill for this example I thought I would keep it in so people know that capability exists.

So we are able to access sibling displayObjects now but there still is one functional issue. Since the frames are cascading on the timeline when the playhead is on the down frame both downClip and overClip are visible. Likewise, when the playhead is on the disabled FrameLabel upClip, downClip, and disabledClip are visible. That little bug is not going to slip by the creative department. Normally we would get around this issue by adding blank keyframes on the frame following where the extra clip was displayed. If we were to do this in our example the clips wouldn’t register because they do not exist on the last frame and we would be right back where we started. There are two ways around this, one involves more code (the developer approach), the other involves more keyframes (the designer approach). Both are valid.

The timeline approach basically consists of adding keyframes (not blank keyframes) on the frame after the frame where the extra clip appears and setting the alpha of the clip on the second frame to 0. This ensures that the clip is still on the display list in the last frame and allows it to register. With this approach your timeline would now look like so:

Depending on what else is happening in your application this approach may not work. This is when you have to resort to code (yay). Basically with code we do the same thing, however, instead of simply setting the alpha to 0 we’ll set the visibility to false. This only requires three extra lines of code which are included below.

stop();

button.buttonMode = true;
var count:int;
if (count == 0) {
     this.visible = false;
     addEventListener(Event.ENTER_FRAME, proxyframes);
     addEventListener(Event.RENDER, rendered);
}

function proxyframes(e:Event) {
     stage.invalidate();
     gotoAndStop(this.currentLabels[count].name);
     //gotoAndStop(this.totalFrames);
     trace(this.currentFrame);
}

function rendered(e:Event):void {
     count++
     trace('render called' + this.currentFrame)

     if (count == this.currentLabels.length) {
          init();
          gotoAndStop(1);
          removeEventListener(Event.ENTER_FRAME, proxyframes);
          removeEventListener(Event.RENDER, rendered);
     }
}

function init() {
     this.visible = true;
     button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
     button.addEventListener(MouseEvent.MOUSE_OUT, rollout);
     button.addEventListener(MouseEvent.MOUSE_DOWN, press);
     button.addEventListener(MouseEvent.MOUSE_UP, release);
}

function rollover(e:MouseEvent):void {
     gotoAndStop('over');
     trace(overClip.name);
}

function rollout(e:MouseEvent):void {
     gotoAndStop('up');
}

function press(e:MouseEvent):void {
     overClip.visible = false;
     gotoAndStop('down');
     trace(downClip.name);
}

function release(e:MouseEvent):void {
     gotoAndStop('disabled');
     trace(disabledClip.name);
     overClip.visible = false;
     downClip.visible = false;
     button.buttonMode = false;
     button.removeEventListener(MouseEvent.MOUSE_OVER, rollover);
     button.removeEventListener(MouseEvent.MOUSE_OUT, rollout);
     button.removeEventListener(MouseEvent.MOUSE_DOWN, press);
     button.removeEventListener(MouseEvent.MOUSE_UP, release);
}

That’s it. Adobe, if you are reading this, please, please, please fix this bug. It is very annoying. It is obvious that Flash Player 9 was built with Flex in mind (aka no timeline) since that is all that was available when Flash Player 9 was released. I remember reading someone from Adobe admitting that on their personal blog, if I can find the link again I will post it in the comments below. I covered a lot here, but this bug goes even further. Sometimes Event.RENDER doesn’t even fire. In another related bug stop events on the first frame of nested MovieClip do not always work. Supposidly Adobe fixed this in version 9.0.45 of the player but as you can see by the comments on Emmy’s blog posting about the 9.0.45 release it was never really fixed. In fact we ran into this issue at work recently in the latest 9.0.115 version of the player.

I actually met with the Adobe Flash Player team last week and we discussed a lot of these issues, hopefully they will take these into consideration for the next release. To a developer they may seem like minor issues but designers and animators are going to have a difficult time getting around a lot of these bugs. If anyone else has any workarounds please feel free to explain them in the comments below.

P.S. this is my longest post ever, yay me.

17 Comments

  1. Very nice explaination! You are in my Favorites now.
    Thank you.
    I am trying to rebuild this and I am interest about the used graphics/clips?

  2. name

    This approach has been around since AS 2.0. It allows to make button with “normal_up”, “normal_over”, “normal_down”, “selected_up”, “selected_over”, “selected_down”, “normal_disabled”, “selected_disabled” states. Also it warps nicely into a package with button class and button-event class. It works more stable when you setup private vars that store button states plus there are some bullet-proof tricks to handle disabled states and restoring events with only making hotspot timeline shorter.

    Best regards.

  3. @name Yes the timeline approach has been around since AS2, even AS1 for that matter. The point of this article is to show the key difference and known bug in AS3 that you can’t reference descendant displayObjects when you move the timeline to one of the states along the timeline. The fact that the numChildren property is correct and you can’t access the displayObjects proves that there is something wrong. You shouldn’t have to count ADDED_TO_STAGE events to make sure all sibling clips exist before accessing them. Thanks for your comment though.

  4. name

    Sure, no problem. I am glad to share. Also, I am absolutely in agreement that AS 3.0 has some really annoying bugs (we all expect that things simply should work when you buy them, don’t they?). BTW I really enjoy reading your articles. This is a great help for development community. Thank you.

  5. Yes, this is s terrible bug i have been fighting a lot. And I really think it must be fix.
    I have found one solution to easy problems like the example of the button, where you have only one external timeline.
    if you put the trace(‘the instance name of the overclip is: ‘ + this.overClip.name) in the “over” label, it will work fine, but of course you dont wont to put any code in frames.
    So you can add code to frames dinamically.

    this.addFrameScript(numFrame-1, codeFrameTrace);

    public function codeFrameTrace():void{
    trace(‘the instance name of the overclip is: ‘ + this.overClip.name)
    }

    Now this code will be execute after the mc is registered, and the problem is solved.
    I think this is not the best think to do, but its the best and easy solution i have found.
    Thanks for your comments.

  6. Thanks for this extensive research, would be great if Adobe picks it up. We’re in the same (deep) shit as we’re having a tight workflow back and forth between designers and developers, and it didn’t take more than a few days experimenting with CS3 & AS3 to run into this bug.
    And it actually doesn’t end there. If you link a class to any of the siblings on stage (via the library), the behaviour of the player changes. Usually it makes them known earlier, so instead of having to wait one or two enterFrames, they can be known at once. There’s also a huge difference with code inside a class linked to an object on stage: if you do the same there (gotoAndStop() etc.) it’s a lot more accurate and predictable. So it looks to me that the timeline code approach has gotten considerably less attention in the overhaul to AS3.
    I tend to praise Adobe for the fact that they manage to produce a tool usable for both fully code-based developers and timeline developers (aka scripters :D), but I guess this shows how difficult it has become to please both worlds. And ieven though I’m a fully code-based developer myself, and not very fond of timeline code, I really hope they fix this!

  7. I have used your described technique in AS2 for a few years and it became a basic tool for me. Switching to AS3, I started using the BaseButton class for interactive objects and bound movieclips to the button states for example:

    myButton.setStyle(“upSkin”, scrubButton_upSkin);
    myButton.setStyle(“overSkin”, scrubButton_overSkin);
    myButton.setStyle(“downSkin”, scrubButton_downSkin);
    myButton.setStyle(“disabledSkin”, scrubButton_disabledSkin);

    This method is definitely less designer friendly, however. The timeline is useful! Plus it seems to me that there is a fair burden of code compared to the timeline technique. I can’t recall the exact numbers but it seemed that using BaseButton right off the bat causes 25K to be added. I meant to port my old method over to compare but have not had a chance.

    What it seems to me scott is that there is now some loss of synchronicity here. What if you commanded “gotoandplay(“over”)” and then on the second frame in after the over frame add a callback or dispatch an event. Oh the [email protected]! That’s just ugly thinking about it.

    – Randy

  8. Hi there ,

    I have been coding in an environment where most movieclip buttons are created by designers, and have found even in as2 flash can bug out when executing code to manipulate frame actions , my final best practice I follow is to create all or move all instaces to frame one and hide with code until needed. I know its not the most fantastic idea but it eliminates many possible scripting/frameaction issues.

    Greetings from SouthAfrica

  9. Pete

    The simplest catch solution!
    Matías Ini, you are my hero.

    Here is my implementation of your idea:

    My sample class:
    ————————-
    public class TestClass
    {
    private var mc1:MovieClip;
    private var mc2:MovieClip;

    public function TestClass()
    {
    // stop playhead at frame 2
    gotoAndStop(2);

    workAroundFlashBug();

    // CONSTRUCTOR CONTINUES AT INIT() due to workaround

    // clipCCW = ccw;
    // clipCW = cw;

    // selectRotationAnimation();
    }

    private function init()
    {
    // FINISHES CONSTRUCTOR

    // these stages are present on the current frame
    // we save them to local variables
    mc1 = stageInstance1;
    mc2 = stageInstance2;

    // do whatever other constructor actions
    }

    private function workAroundFlashBug()
    {
    this.addFrameScript( currentFrame-1, init );
    }
    }
    ————————-

    Original post:

    Matías Ini wrote:
    ————————-
    this.addFrameScript(numFrame-1, codeFrameTrace);

    public function codeFrameTrace():void{
    trace(’the instance name of the overclip is: ‘ + this.overClip.name)
    }
    ————————-

  10. Pete

    Sorry, forgot to remove the frameScript. Do this in the init() function.

    Cheers,
    Pete

    ————————-
    private function init()
    {
    // FINISHES CONSTRUCTOR

    // remove dirty frame script
    this.addFrameScript( currentFrame – 1, null );

    // these stages are present on the current frame
    // we save them to local variables
    mc1 = stageInstance1;
    mc2 = stageInstance2;

    // do whatever other constructor actions
    }

    private function workAroundFlashBug()
    {
    this.addFrameScript( currentFrame-1, init );
    }
    }
    ————————-

  11. Pete

    Edit:

    // these stages are present on the current frame

    I mean, of course:
    // these stage instances are present on the current frame

    A little messy today :)

    Pete

  12. Scott

    Can anyone help me? I am very green at this and am just trying to do a simple, simple, simple flash site but I cannot get around this 1009 bug. It’s awful and stopping me from getting anywhere.

    Basically how do you get the program to recognize the button as not null. I guess I mean how do you get it to read the button then apply the Action script in the proper order. I can’t believe Adobe designed this flaw into the program. Horrible! And when i say simple I mean I don’t understand hardly any of the code above. I’m really depressed over this- ruining my efforts t get a site up- i’d made such good progress til now.
    The code I am using is below:

    stop();

    FsAs.addEventListener(MouseEvent.CLICK, FestsAwards);
    function FestsAwards(event:MouseEvent):void {
    gotoAndPlay(4)
    }

    CsBs.addEventListener(MouseEvent.CLICK, ContsBios);
    function ContsBios(event:MouseEvent):void {
    gotoAndPlay(35)
    }

    Imgs.addEventListener(MouseEvent.CLICK, Images);
    function Images(event:MouseEvent):void {
    gotoAndPlay(61)
    }

    CstCrw.addEventListener(MouseEvent.CLICK, CastCrew);
    function CastCrew(event:MouseEvent):void {
    gotoAndPlay(111)
    }

    BlgNws.addEventListener(MouseEvent.CLICK, BlogNews);
    function BlogNews(event:MouseEvent):void {
    navigateToURL(new URLRequest(“http://www.shores.blogspot.com/”));
    }

  13. hi
    in AS2 there is a similar bug let me explain it :
    if i manually place an mc in frame 1 and that mc has a function inside lets call it “myFunction” and in the frame 1 i use this :

    trace(mc.myFunction)

    it will return “undefined” , sometime i used the code you described to wait for the second onEnterFrame to occours, but then i started to use a similar approach where i create a MovieClip.prototype.onload and inside this prototype i create the code to wait for that secondframe and fire a custom event so instead of calling mc.myFunction first i declare the listener something like this :

    mc.loadedDone=function(){
    //this function is called by MovieClip.prototype.onload
    trace(mc.myFunction)
    }

    so far the designers have no problems using this code :)

  14. Was this ever fixed? Do flash player 10 and the CS4 IDE address this bug?

  15. sc2071

    I found that while Flash doesn’t receive the instance names, it does know that it has X number of children. Based on that, I created a function that uses the Event.ADDED listener to check that all children are not null before proceeding.

    For the most part it seems to work for me, but I’m always looking for better and more efficient code.

    Details and code are here:

    http://www.actionscript.org/forums/showthread.php3?t=166129

  16. Hello-
    I am a designer(not a developer) and I am having a similar issue.
    I am using a button to navigate the timeline, but the first time I click on it, it goes to the wrong frame. But if I go to some other areas of the timeline then back to that button it goes to the correct frame. Any suggestions?

    It’s very basic code, just your generic:
    on (press, release) {
    gotoAndPlay(“metro”);
    }

    Thanks!

  17. Hey Scott,
    I’ve created a class that you can use in place of the standard flash.display.MovieClip object. GotoClip stores commands you place on a MovieClip and then applies them to the MovieClip when the Flash Player has gotten around to rendering the MovieClip. Check it out: http://code.google.com/p/as3-display-list-library/
    Now includes an archive with all source and example FLA.

Trackbacks/Pingbacks

  1. Tutorial | Accessing displayObjects on the timeline after a gotoAndStop or gotoAndPlay « Flash Enabled Blog - [...] Read Tutorial No Comments Leave a Commenttrackback addressThere was an error with your…

Leave a Comment

Your email address will not be published. Required fields are marked *