Check out "Do you speak JavaScript?" - my latest video course on advanced JavaScript.
Language APIs, Popular Concepts, Design Patterns, Advanced Techniques In the Browser

Create your own tween manager class in AS3

There are some features in Flash that we can't work without. Tween classes are among the most used ones. They give you ability to animate objects without using the timeline, to change the animation fast and easy. The idea of these classes is very simple. That's why I think that it is a good idea to have your own tween manager that you can modify to fit into your needs.

The result of this article can be found here and the source files are available for download here.The basic structure of our tween manager:

package lib.document {
    import flash.display.MovieClip;
    public class TweenManager extends MovieClip {
      private
      var _objectToModify: Object;
      private
      var _properties: Object;
      public
      function TweenManager(objectToModify: Object, properties: Object) {
        _objectToModify = objectToModify;
        _properties = properties;
      }
    }
  }

We are going to pass the object that we want to modify and the properties that we want to change. The idea is to create a function that calls every frame. All the magic will be done there. As you can see in the code below I created a public function - start, which adds listener for ENTER_FRAME event. So now we have a repeated method, i.e. loop.

package lib.document {
    import flash.display.MovieClip;
    import flash.events.Event;
    public class TweenManager extends MovieClip {
      private
      var _objectToModify: Object;
      private
      var _properties: Object;
      public
      function TweenManager(objectToModify: Object, properties: Object) {
        _objectToModify = objectToModify;
        _properties = properties;
      }
      public
      function start(): void {
        addEventListener(Event.ENTER_FRAME, loop);
      }
      public
      function loop(e: Event): void {}
    }
  }

What are we going to pass as properties parameter? I think it is a good idea to use JSON object, because it's really flexible and we can add/remove properties really fast without changing anything in our class. Here is an example of the creation of an object from our tween class.

package lib.document {
    import flash.display.MovieClip;
    public class App extends MovieClip {
      public
      var clip: MovieClip;
      public
      function App() {
        var properties: Object = {
          x: {
            start: 50,
            end: 390,
            steps: 100
          }
        }
        var t: TweenManager = new TweenManager(clip, properties);
        t.start();
      }
    }
  }

There are two things that you have to notice. The first one is that you should have a movie clip on the stage that you can use for the tween. The second thing is the properties object. As you can see we are going to change the "x" property of the clip from 50 to 390 for 100 steps, i.e. 100 frames. To be able to do that we need the value of "x" for each one of these 100 frames. The function "calculateValues" will do that for us:

package lib.document {
    import flash.display.MovieClip;
    import flash.events.Event;
    public class TweenManager extends MovieClip {
      private
      var _objectToModify: Object;
      private
      var _properties: Object;
      public
      function TweenManager(objectToModify: Object, properties: Object) {
        _objectToModify = objectToModify;
        _properties = properties;
      }
      public
      function start(): void {
        for (var i: * in _properties) {
          _properties[i].values = calculateValues(_properties[i].start, _properties[i].end, _properties[i].steps);
        }
        addEventListener(Event.ENTER_FRAME, loop);
      }
      public
      function loop(e: Event): void {}
      private
      function calculateValues(startPos: Number, endPos: Number, steps: Number): Array {
        var values: Array = [];
        for (var i: int = 0; i < steps + 1; i++) {
          values.push(startPos + (((endPos - startPos) / steps) * i));
        }
        return values;
      }
    }
  }

As you can see we are adding dynamically a new array for every property in our properties object. "values" is an array with all the values of "x" for each of these 100 frames. The last thing that we should make is to pass the values to our clip, i.e. to write the content of the "loop" method.

package lib.document {
    import flash.display.MovieClip;
    import flash.events.Event;
    public class TweenManager extends MovieClip {
      private
      var _objectToModify: Object;
      private
      var _properties: Object;
      public
      function TweenManager(objectToModify: Object, properties: Object) {
        _objectToModify = objectToModify;
        _properties = properties;
      }
      public
      function start(): void {
        for (var i: * in _properties) {
          _properties[i].values = calculateValues(_properties[i].start, _properties[i].end, _properties[i].steps);
          _properties[i].valueIndex = 0;
          _properties[i].isItDone = false;
        }
        addEventListener(Event.ENTER_FRAME, loop);
      }
      public
      function loop(e: Event): void { // looping through the properties			for(var i:* in _properties) {				
        // checking if the properties tween is done			
        if (!_properties[i].isItDone) {
          // checking if the current value is the last one		
          if (_properties[i].valueIndex == _properties[i].steps + 1) {
            // marking the tween as a done		
            _properties[i].isItDone = true;
          } else {
            // applying the value	
            _objectToModify[i] = _properties[i].values[_properties[i].valueIndex];
            // incrementing the values' index		
            _properties[i].valueIndex += 1;
          }
        }
      }
      // checking if all the tweens are done
      var areAllDone: Boolean = true;
      for (i in _properties) {
        if (!_properties[i].isItDone) {
          areAllDone = false;
        }
      }
      // if yes then remove the listener	
      if (areAllDone) {
        removeEventListener(Event.ENTER_FRAME, loop);
      }
    }
    private
    function calculateValues(startPos: Number, endPos: Number, steps: Number): Array {
      var values: Array = [];
      for (var i: int = 0; i < steps + 1; i++) {
        values.push(startPos + (((endPos - startPos) / steps) * i));
      }
      return values;
    }
  }
  }

Please notice lines 18 and 19. We added a flag (isItDone) that indicates when the tween is finished and "valueIndex", which we used to go through all the values. When you run the flash you will see that the circle is moving from x=50 to x=390 for 100 frames. Let's add some other properties and see how it looks:

package lib.document {
    import flash.display.MovieClip;
    public class App extends MovieClip {
      public
      var clip: MovieClip;
      public
      function App() {
        var properties: Object = {
          x: {
            start: 50,
            end: 390,
            steps: 70
          },
          y: {
            start: 50,
            end: 390,
            steps: 70
          },
          rotation: {
            start: 0,
            end: 180,
            steps: 30
          },
          alpha: {
            start: 1,
            end: 0.5,
            steps: 10
          }
        }
        var t: TweenManager = new TweenManager(clip, properties);
        t.start();
      }
    }
  }

We can change as many properties as we want and obviously to set different steps for each one of them.Ok, the Tween class looks good so far but it isn't as cool as we wanted. It's because there is no easing. We used only a linear type of motion. To add other types we're going to use some maths by Robert Penner. We will just change the calculateValues method and it will support different types of motions. Here is the final version of the class:

package lib.document {
    import flash.display.MovieClip;
    import flash.events.Event;
    public class TweenManager extends MovieClip {
      private
      var _objectToModify: Object;
      private
      var _properties: Object;
      public
  
      function TweenManager(objectToModify: Object, properties: Object) {
        _objectToModify = objectToModify;
        _properties = properties;
      }
      public
  
      function start(): void {
        for (var i: * in _properties) {
          _properties[i].values = calculateValues(_properties[i].start, _properties[i].end, _properties[i].steps, _properties[i].method);
          _properties[i].valueIndex = 0;
          _properties[i].isItDone = false;
        }
        addEventListener(Event.ENTER_FRAME, loop);
      }
      public
  
      function loop(e: Event): void {
        // looping through the properties	
        for (var i: * in _properties) {
          // checking if the properies twee it's done		
          if (!_properties[i].isItDone) {
            // checking if the current value is the last one	
            if (_properties[i].valueIndex == _properties[i].steps + 1) {
              // marking the tween as a done			
              _properties[i].isItDone = true;
            } else {
              // applying the value			
              _objectToModify[i] = _properties[i].values[_properties[i].valueIndex];
              // incrementing the values' index	
              _properties[i].valueIndex += 1;
            }
          }
        }
        // checking if all the tweens are done		
        var areAllDone: Boolean = true;
        for (i in _properties) {
          if (!_properties[i].isItDone) {
            areAllDone = false;
          }
        }
        // if yes then remove the listener	
        if (areAllDone) {
          removeEventListener(Event.ENTER_FRAME, loop);
        }
      }
      private
      function calculateValues(startPos: Number, endPos: Number, steps: Number, mtd: String): Array {
        var arr: Array = new Array();
        var calcEndPos: Number;
        for (var i: Number = 0; i <= steps; i++) {
          calcEndPos = (endPos - startPos);
          switch (mtd) {
            case "InBack":
              arr[i] = Ease.InBack(i, startPos, calcEndPos, steps, 1.70158);
              break;
            case "OutBack":
              arr[i] = Ease.OutBack(i, startPos, calcEndPos, steps, 1.70158);
              break;
            case "InOutBack":
              arr[i] = Ease.InOutBack(i, startPos, calcEndPos, steps, 1.70158);
              break;
            case "OutBounce":
              arr[i] = Ease.OutBounce(i, startPos, calcEndPos, steps);
              break;
            case "InBounce":
              arr[i] = Ease.InBounce(i, startPos, calcEndPos, steps);
              break;
            case "InOutBounce":
              arr[i] = Ease.InOutBounce(i, startPos, calcEndPos, steps);
              break;
            case "InCirc":
              arr[i] = Ease.InCirc(i, startPos, calcEndPos, steps);
              break;
            case "OutCirc":
              arr[i] = Ease.OutCirc(i, startPos, calcEndPos, steps);
              break;
            case "InOutCirc":
              arr[i] = Ease.InOutCirc(i, startPos, calcEndPos, steps);
              break;
            case "In":
              arr[i] = Ease.In(i, startPos, calcEndPos, steps);
              break;
            case "Out":
              arr[i] = Ease.Out(i, startPos, calcEndPos, steps);
              break;
            case "InOut":
              arr[i] = Ease.InOut(i, startPos, calcEndPos, steps);
              break;
            case "InElastic":
              arr[i] = Ease.InElastic(i, startPos, calcEndPos, steps, 0, 0);
              break;
            case "OutElastic":
              arr[i] = Ease.OutElastic(i, startPos, calcEndPos, steps, 0, 0);
              break;
            case "InOutElastic":
              arr[i] = Ease.InOutElastic(i, startPos, calcEndPos, steps, 0, 0);
              break;
            case "InExpo":
              arr[i] = Ease.InExpo(i, startPos, calcEndPos, steps);
              break;
            case "OutExpo":
              arr[i] = Ease.OutExpo(i, startPos, calcEndPos, steps);
              break;
            case "InOutExpo":
              arr[i] = Ease.InOutExpo(i, startPos, calcEndPos, steps);
              break;
            case "Linear":
              arr[i] = Ease.Linear(i, startPos, calcEndPos, steps);
              break;
            case "InLinear":
              arr[i] = Ease.InLinear(i, startPos, calcEndPos, steps);
              break;
            case "OutLinear":
              arr[i] = Ease.OutLinear(i, startPos, calcEndPos, steps);
              break;
            case "InOutLinear":
              arr[i] = Ease.InOutLinear(i, startPos, calcEndPos, steps);
              break;
            case "InQuad":
              arr[i] = Ease.InQuad(i, startPos, calcEndPos, steps);
              break;
            case "OutQuad":
              arr[i] = Ease.OutQuad(i, startPos, calcEndPos, steps);
              break;
            case "InOutQuad":
              arr[i] = Ease.InOutQuad(i, startPos, calcEndPos, steps);
              break;
            case "InQuart":
              arr[i] = Ease.InQuart(i, startPos, calcEndPos, steps);
              break;
            case "OutQuart":
              arr[i] = Ease.OutQuart(i, startPos, calcEndPos, steps);
              break;
            case "InOutQuart":
              arr[i] = Ease.InOutQuart(i, startPos, calcEndPos, steps);
              break;
            case "InQuint":
              arr[i] = Ease.InQuint(i, startPos, calcEndPos, steps);
              break;
            case "OutQuint":
              arr[i] = Ease.OutQuint(i, startPos, calcEndPos, steps);
              break;
            case "InOutQuint":
              arr[i] = Ease.InOutQuint(i, startPos, calcEndPos, steps);
              break;
            case "InSine":
              arr[i] = Ease.InQuint(i, startPos, calcEndPos, steps);
              break;
            case "OutSine":
              arr[i] = Ease.OutQuint(i, startPos, calcEndPos, steps);
              break;
            case "InOutSine":
              arr[i] = Ease.InOutQuint(i, startPos, calcEndPos, steps);
              break;
            default:
              arr[i] = Ease.Linear(i, startPos, calcEndPos, steps);
              break;
          }
        }
        return arr;
      }
    }
  }

And the usage:

package lib.document {
    import flash.display.MovieClip;
    public class App extends MovieClip {
      public
      var clip: MovieClip;
      public
      function App() {
        var properties: Object = {
          x: {
            start: 50,
            end: 390,
            steps: 170,
            method: "OutBack"
          },
          y: {
            start: 50,
            end: 390,
            steps: 170,
            method: "OutBounce"
          },
          rotation: {
            start: 0,
            end: 180,
            steps: 300,
            method: "OutElastic"
          },
          alpha: {
            start: 1,
            end: 0.5,
            steps: 100
          }
        }
        var t: TweenManager = new TweenManager(clip, properties);
        t.start();
      }
    }
  }

Together with "start", "end" and "steps" we are passing a new property "method" which is used by the "calculateValues" function. The class "Ease" contains mathematics methods that calculate the values. No need to understand it, just have to know how to use it. The result of all of this could be seen here.Of course the class is not fully functional. I mean there are no checks if some of the properties like "start" or "end" are missing. We can also add an ability to call a callback method when some of the tweens finish. It's just a basic manager that you can develop.In some of the next articles I will show you how to use this class together with Papervision3D to animate object in the 3D space.

If you enjoy this post, share it on Twitter, Facebook or LinkedIn.