Friday, November 14, 2008

AS3 Tween class randomly stops during animation

I gave the new AS3 Tween class a whirl to perform some simple animation entirely via ActionScript, rather than sharing the responsibilities between ActionScript and the Timeline.

It worked great...most of the time. The rest of the time, the Tweens would randomly decide to stop -- mid-animation -- without throwing any error as to why. "Most of the time" just doesn't cut it.

The Problem

Here is a simplified example of this situation:

package
{
   import flash.display.*;
   import fl.transitions.*;
   import fl.transitions.easing.*;

   public class SomeContainer() extends Sprite
   {
      ...
      public function animate(o:DisplayObject, x2:int, duration:uint)
      {
         var tween:Tween = new Tween(
               o,
               "x",
               Regular.easeOut,
               someDisplayObject.x,
               x2),
               duration);

      }
   }
}

When invoking animate(), the tween works most of the time, but randomly it stops.

There is no warning of this problem in the ActionScript 3 reference manual, but this Adobe Devnet article contains some fine print that reads:

Note: Consider variable scope when using the Tween class. If a tween is created in a function, it is important that the variable's scope exists beyond the function itself. If a tween is stored to a variable of local scope, ActionScript garbage collection removes the tween as the function completes, which will likely be before the tween has even begun.

So the problem with this scenario is that the Flash Garbage Collector does not care whether the function-scoped variable is still working asynchronously or not - it destroys objects simply by reference counts and in this case nothing outside of the function references this Tween instance.

The Solution

To fix this, simply scope the Tween instance to a class-level variable (a private attribute) rather than a function-level variable. For example:

package
{
   import flash.display.*;
   import fl.transitions.*;
   import fl.transitions.easing.*;

   public class SomeContainer() extends Sprite
   {
      ...
      public function animate(o:DisplayObject, x2:int, duration:uint)
      {
         tween = new Tween(
               o,
               "x",
               Regular.easeOut,
               someDisplayObject.x,
               x2),
               duration);
      }

      private var tween:Tween;
   }
}

This way, the garbage collector sees that something outside of the scope of the function still references the Tween instance created within the function, so it does not destroy the instance.

17 comments:

Anonymous said...

Thanks! This really helps me alot! :D

Anonymous said...

thanks, your article confirmed my suspicion about the gc cleaning the tweener up

Devin said...

Awesome man... I would have been looking for hours without this tip. Great help. I wish they made this more obvious to people.

eng80327 said...

This is simple great. I have been struggling so hard over this problem! Man... tried so many wierd things to get it working.

Thanks a million!

Paulb said...

Thanks mate, stupid bug I new it'd be a simple anwer , GC tramp

Steven said...

Thanks. This is way simpler than storing in arrays and then cleaning it all up on MOTION_FINISH. Whew.

Marc said...

Love this article. A simple one for a simple answer. (to a hell of an annoying problem!) Thank you.

Jon said...

Thanks for taking the time to post - very helpful!

Jay said...
This post has been removed by a blog administrator.
Jay said...

It's been said before, but to better represent the scope of people you've helped: Thanks, dude.

Ricky said...

Glad this is helping so many people! =) Happy coding everyone!

Anonymous said...

That has just saved me soo much time and stress! Cheers.

pau said...

well, doesn't work for me, i mean i got it the way you said it, but it still randomely stops. I use Loader to load images from xml and in the loop which loads images after image i create new Tween, but var definition is outside the function which contains that loop, still the same thing :|

Ricky said...

@pau if you are creating a new Tween for each image you load, you would need to store those in an array rather than a var.

The array would then show the garbage collector that the Tween instances are still referenced by something.

If you over-wrote the same var, then only the most recently-created Tween would work without being garbage-collected.

Some Guy said...

Dude...thank you thank you thank you...I was really pulling my hair out on this exact issue!

MarcoBoomTing said...

Amazing tip! I actually spent hours on this, kept thinking either i'd coded something wrong, or my installation of flash was messing up! Thanks for the article, and i agree with an above comment that things like this should be made more obvious in the documentation!

Chuck said...

You are the man. This has been a recurring problem for me for a long time!

Post a Comment

Was this post helpful? Do you have questions about it? Do you want to share your own programming blog? I'd love to read your feedback.