Summary: I was interested in finding a way to control complex timeline animations with the same amount of options that one has when using coded animations.
For example I wanted to be able to create a model in 3d with all the nice lighting effects, shadows, etc.. then export it as a series of pngs that show it rotating and be able to
control that rotation in actionscript.
Unfortunately flash is unable to read the frame labels in its movie clips so I was at a loss as to how flash would be able to read where this animation was.
As a workaround I used a named text box to mimic frame labels.
In my class when an animation is registered it goes through every frame of the animation and records the frame numbers of these labels. So in the provided example it records
that label "red" is on frame 1, "yellow" is frame 37, etc..
Now we can tell the animation to animate to "yellow" and it knows that we mean frame 37 (This come in particularly handy when we begin editing the animations and the frame numbers all
change).
Next we check to see if the animation has been flagged as "loopable". If it has been we calculate the shortest distance to the desired frame (i.e. in a 40 frame animation if you are on frame 30 and wish to go to frame 1
it is shorter to go forward and loop to frame 1 - 11 frames - rather than reversing from 30 t0 1 - 29 frames).
Lastly I added a simple algorithm for easing into the desired frame and an empty public function that notifies you when the animation has reached its destination.
John Howell
/**
*
* @author: John Howell code - at - jrwh.com
* @version: 1.0.0
Usage: This class is used to control complex timeline animations where coded animations is not an option.
This controller will play the animations backwards and forward to get to a desired frame.
If the animation is loopable the controller will calculate the quickest route to the desired end frame and play accordingly.
Set Up: To utilize this class the timeline animation must be set up with the following parameters:
1. animation must be contained within a movieclip
2. there must be a consistant dynamic text box on its own layer with the instance name "fLabel"
3. for every destination frame (i.e. a stopping point for the animation) there must be a keyframe on the fLabel layer and
the text inside the fLabel text box must be changed to a unique identifier.
Don't worry about hiding or designing the fLabel text boxes as they will be removed via code once the class is initiated
For an example of this set up go to http://jrwh.com/samples/animationController/
*/
import mx.transitions.Tween;
import mx.transitions.easing. *;
import Delegate;
class animationController
{
private var animationID : MovieClip;
private var labelToNumberObj : Object;
private var startFrame : Number;
private var endFrame : Number;
private var totalFrames : Number;
private var loopable : Boolean = false;
private var framesToMove : Number;
private var frameCounter : Number;
private var previousFrame:Number;
private var easeArray:Array ;
private var easeStep:Number;
private var useEase:Boolean = false;
public function animationController (id)
{
labelToNumberObj = new Object();
easeArray = []
animationID = id;
totalFrames = animationID._totalframes;
getLabels ();
}
//getLabels goes through the animation and records the fLabel text box frames
private function getLabels ()
{
for (var i = 1; i <= totalFrames; i ++)//go through every frame of the animation
{
animationID.gotoAndStop (i);//goes to every frame
var currentLabel = animationID.fLabel.text;//retrieve the string inside the fLabel text box
if (currentLabel != frameLabel)//if this is a new label record its position
{
var frameLabel = currentLabel;
labelToNumberObj [currentLabel] = i;
}
}
animationID.gotoAndStop (1);//put the animation back to the beginning
animationID.fLabel._visible = 0;//hide the fLabel text box
}
//labelToNumber returns the numerical frame number of a specified label id created by getLabels
public function labelToNumber (lbl : String) : Number
{
return labelToNumberObj [lbl];
}
//onAnimationComplete is an overwritable function that gets called on arrival at the desired frame
public function onAnimationComplete ()
{
}
//animateTo is what will be called to go to a desired frame
public function animateTo (frameID,ease)
{
useEase = ease;
frameCounter = 0;
startFrame = animationID._currentframe;
if (int (frameID) > 0)//if frameID is a number go to that frame number else find out the frame number of the provided frameID
{
endFrame = frameID;
} else
{
endFrame = labelToNumber (frameID);
}
//calculate the shortest route to the desired frame
var nonLoopFrames : Number = Math.abs (endFrame - startFrame);
var forwardLoopFrames : Number = (totalFrames - startFrame) + endFrame;
var rewindLoopFrames : Number = (totalFrames - endFrame) + startFrame;
var loopFrames : Number = (forwardLoopFrames < rewindLoopFrames) ?forwardLoopFrames : rewindLoopFrames;
var animDirection : String;
if (loopFrames < nonLoopFrames && loopable)
{
if (forwardLoopFrames < rewindLoopFrames)
{
animDirection = "forwardLoop"
framesToMove = forwardLoopFrames;
} else
{
animDirection = "rewindLoop"
framesToMove = rewindLoopFrames;
}
} else
{
if (startFrame < endFrame)
{
animDirection = "forward"
} else
{
animDirection = "rewind"
}
framesToMove = nonLoopFrames;
}
var i:Number = 0;
var step:Number = 0;
//set up a rudimentary array of easing values
easeArray = [];
while( step < framesToMove){
i++
step+=i;
easeArray.push(i);
}
easeArray.reverse();
easeStep = 0;
if(startFrame != endFrame){
//start the animation playing in the shortest direction by using the movePlayHead function
animationID.onEnterFrame = Delegate.create (this, movePlayHead, animDirection);
}
}
//movePlayHead moves the animation in the desired direction using the easeArray values (optional)
private function movePlayHead (dir)
{
if(useEase){
var speed = easeArray[easeStep];
}else{
var speed = 1;
}
if (dir == "forwardLoop")
{
if (animationID._currentframe + speed > totalFrames)
{
animationID.gotoAndStop (1 + (totalFrames - animationID._currentframe + speed));
if (animationID._currentframe > endFrame)
{
animationID.gotoAndStop (endFrame);
}
} else
{
animationID.gotoAndStop (animationID._currentframe + speed);
if (endFrame < animationID._currentframe && endFrame > previousFrame)
{
animationID.gotoAndStop (endFrame);
}
}
}
if (dir == "rewindLoop")
{
if (animationID._currentframe - speed <= 0)
{
animationID.gotoAndStop (totalFrames - animationID._currentframe - speed);
if (animationID._currentframe < endFrame)
{
animationID.gotoAndStop (endFrame);
}
} else
{
animationID.gotoAndStop (animationID._currentframe - speed);
if (endFrame > animationID._currentframe && endFrame < previousFrame)
{
animationID.gotoAndStop (endFrame);
}
}
}
if (dir == "forward")
{
animationID.gotoAndStop (animationID._currentframe + speed);
if (endFrame < animationID._currentframe && endFrame > previousFrame)
{
animationID.gotoAndStop (endFrame);
}
}
if (dir == "rewind")
{
animationID.gotoAndStop (animationID._currentframe - speed);
if (endFrame > animationID._currentframe && endFrame < previousFrame)
{
animationID.gotoAndStop (endFrame);
}
if (animationID._currentframe - speed <= 0)
{
animationID.gotoAndStop (endFrame);
}
}
// on arrival at the desired frame delete the onEnterframe and call public onAnimationComplete function
if (animationID._currentframe == endFrame)
{
delete animationID.onEnterFrame;
onAnimationComplete ();
}
animationID.fLabel._visible = 0;
easeStep++;
previousFrame = animationID._currentframe;
}
//indicate whether animation is loopable
public function set looped (val : Boolean)
{
loopable = val;
}
}