This user is yet to take control of their account and provide a biography. If you are the author of this article, please contact us via [email:support@actionscript.org]. Do you remember the very first time you animated something in Flash? If you're like me, you probably started with a blank stage, made a ball symbol, added some frames, chose "create motion tween", turned the last frame into a keyframe then moved the ball to the other side of the stage and VOILA! You could export the SWF and watch your ball roll across the stage like magic. All you did was specify the starting point of the ball, the number of frames and the end point of the ball while Flash undertook the mind-numbingly tedious task of moving the ball in all the other "inbetween" frames for you. Very handy and powerful. But not quite handy and powerful enough. Kids today aren't satisfied with something which is "merely convenient". No no no. They need something "super-duper-way-fantastico-convenient" and that's where the scripted tween comes in.
While the process of creating a manual tween described above is fairly straightforward, it isn't very flexible. Once your SWF is published all your settings (starting point, ending point, number of frames) are pretty much carved in stone. Not only that but every frame you add between your starting point and ending point increases the size of your SWF and FLA files. Fortunately ActionScript allows you to overcome these limitations with a few lines of code. But before we get too far ahead let's take a quick Math refresher course.
Rate of speed equals the distance traveled divided by the amount of time traveling. Huh? Say what? All this means is that if you've been driving down the road for 50 hours and you've driven 550 miles then you can determine your average rate of speed with the following formula:
[as]//______________________________________________
//
rateOfSpeed = distanceTraveled / timePassed;
//
// or in this example
//
rateOfSpeed = 550 miles / 50 hours;
rateOfSpeed = 550 / 50;
rateOfSpeed = 11 Miles Per Hour;
//____________________________________________
[/as]
That's well and good for cars, but how does that apply to Flash? For starters rather than driving a car down the road in our imaginations, we'll actually be moving a ball across the stage. All we need to get the ball rolling (so to speak), is to convert our units of measure to ones more relevant to Flash. This means instead of tracking time by counting the number of "hours" to pass, we'll be counting the number of "frames" to pass and instead of measuring distance by counting "miles" we'll be counting "pixels". And finally since we'll be using pixels and frames as units of measure, our rate will be in PPF or (Pixels Per Frame) rather than MPH (Miles Per Hour). In short, it means our previous example would look like this:
[as]//_____________________________________________
//
rateOfSpeed = distanceTraveled / framesPassed;
//
// or in this example
//
rateOfSpeed = 550 pixels / 50 frames;
rateOfSpeed = 550 / 50;
rateOfSpeed = 11 Pixels Per Frame;
//__________________________________________
[/as]
But I can hear some of you saying, "We may have our measurements in units which Flash can handle but how does that get my ball across the stage?" Suppose our goal is to get the ball to move from an _x of 0 to an _x of 550 in a time span of 50 frames. Plugging those variables into the above formula shows that we need to move our ball 11 pixels every time it enters a frame (11 PPF). Once we know the "PPF" all we have to do is multiply our currentFrame times our PPF to determine how far the ball has moved. It's just like in our previous driving example, if you're driving 11 MPH for 50 hours then you can determine how far you've driven by multiplying your rate of speed by the length of time you've been driving (11 MPH * 50 Hours = 550 Miles). So essentially what we're going to do is make a ball, move it 11 pixels every time it enters a frame for 50 frames and it's going to end up at a final _x position of 550.
[as]//____________________________________________
//
// Make a red ball 40 pixels in diameter
createEmptyMovieClip("Ball_mc", 10);
with(Ball_mc){
lineStyle(40, 0xFF0000);
lineTo(.5, 0);
_y = 35;
}
//
// Create a variable to track how many frames have passed
var currentFrame = 0;
//
// Every time the ball enters a frame do the following
Ball_mc.onEnterFrame = function(){
//
// Note that we have entered another frame
// by adding one to our currentFrame variable.
currentFrame++;
//
// Move the ball to the right 11 Pixels Per Frame
this._x = currentFrame * 11;
//
// If 50 frames have passed, stop moving the ball
if (currentFrame>50) delete this.onEnterFrame;
}
//____________________________________________
[/as]
We could also express the key line in the above code:
[as]//____________________________________________
//
this._x = currentFrame * 11;
//____________________________________________
[/as]
as:
[as]//____________________________________________
//
this._x = currentFrame * (550 / 50);
//____________________________________________
[/as]
or as:
[as]//____________________________________________
//
this._x = (currentFrame / 50) * 550;
//____________________________________________
[/as]
The arrangement of the code above is probably the most "convenient" (*see footnote) way to illustrate the "Ratio" which exists between distance and time. A "Ratio"is little more than a fraction or percentage as it relates to another fraction or percentage. Consider that you have a set distance (550) and a set amount of time (50). If you travel half way down one of them you will travel half way down the other one. That is to say, if you view frame 25 out of 50, your ball will be at pixel 225 out of 550 or conversely, if your ball is at pixel 225 out of 550 then you would know you were viewing frame 25 of a 50 frame tween. A fraction of one (1/2 in this example) equals the same fraction of the other so they are said to be "proportional". This is a handy thing to know and it can be expressed mathematically like this:
[as]//____________________________________________
//
(currentDistance/totalDistance) = (currentFrame/totalFrames)
//____________________________________________
[/as]
With this knowledge you can boil a simple tween down to one line of code. Since we want to determine our currentDistance variable (which tracks how far our ball has moved) we need to use a bit of Math to isolate currentDistance on one side of the equation. This can be done by multiplying each side by totalDistance like so:
[as]//____________________________________________
//
totalDistance * (currentDistance/totalDistance) = (currentFrame/totalFrames) * totalDistance
//____________________________________________
[/as]
Then we can cancel out both totalDistance variables on the left side and we're left with the following:
[as]//____________________________________________
//
currentDistance = (currentFrame/totalFrames) * totalDistance;
//____________________________________________
[/as]
Let's apply this knowledge to another tween 50 frames in length which moves our ball from an _x of 0 to an _x of 550:
[as]//____________________________________________
//
var totalFrames = 50; // How many frames our tween will take.
var currentFrame = 0; // How many frames have passed so far.
var totalDistance = 550; // The total distance our ball will travel.
Ball_mc.onEnterFrame = function(){
//
// Note that we have entered another frame
// by adding one to our currentFrame variable.
currentFrame++;
//
// Use our formula to determine how far we need to move the ball.
var currentDistance = (currentFrame/totalFrames) * totalDistance;
//
// Move the ball.
this._x = currentDistance;
//
// If 50 frames have passed, stop moving the ball.
if (currentFrame > totalFrames) delete this.onEnterFrame;
}
//____________________________________________
[/as]
Everything works fine but what if we want to move our ball from an _x of 50 to an _x of 500? How do we make a tween that doesn't start at zero? Take a look at the following code:
[as]//____________________________________________
//
var totalFrames = 50; // How many frames our tween will take.
var currentFrame = 0; // How many frames have passed so far.
var startX = 50; // The starting position of our ball.
var endX = 500; // The ending position of our ball.
var changeInX = endX - startX; // The distance our ball will travel (450 pixels).
Ball_mc.onEnterFrame = function(){
//
// Note that we have entered another frame
// by adding one to our currentFrame variable.
currentFrame++;
//
// Use our formula to determine how far we need to move the ball.
var currentDistance = startX +(currentFrame/totalFrames) * changeInX;
//
// Move the ball.
this._x = currentDistance;
//
// If 10 frames have passed, stop moving the ball.
if (currentFrame>totalFrames) delete this.onEnterFrame;
}
//____________________________________________
[/as]
Essentially all we did was create a tween from 0 to 450 and then add 50 to every value which had the effect of shifting the tween 50 pixels to the right.
When looking at the following line of code...
[as]//____________________________________________
//
//
// This is THE quintessential linear tweening code.
// Learn it. Live it. Love it.
//
currentDistance = startX +(currentFrame/totalFrames) * changeInX;
//____________________________________________
[/as]
...it's important to understand two key concepts:
1) The only value which really changes in our tween is the ratio (currentFrame/totalFrames), the other values (startX and changeInX) are constant. It's this change in value which moves our ball across the screen. After all, if (currentFrame/totalFrames) remained a constant number our ball would simply remain stationary.
2) The value of (currentFrame/totalFrames) will always be a number between 0 and 1. When it's 0 the ball will be at its initial position, when its .5 the ball will be half way to its destination and when its 1 the ball will be at its final position. The reason this is so important is that it's going to allow us to do some pretty cool stuff with "easing" which is our next big step in tweening education.
Of course it should almost go without saying that once you know the formula you can plug all your numbers directly into the code if you'd prefer. This can drastically decrease the amount of typing you'll need to do. For example, our previous code sample could be written like this:
[as]//____________________________________________
//
var currentFrame = 0;
Ball_mc.onEnterFrame = function(){
this._x = 50 + (currentFrame/10) * 450;
if (++currentFrame > 10) delete this.onEnterFrame;
}
//____________________________________________
[/as]
| Functions which return values between 0 and 1 are "convenient" because the values they return can easily be scaled and shifted to represent a wider range of numbers. Consider the "Math.random" function which returns random values between 0 and 1. Once you have the random number between 0 and 1 it's easy to multiply it, add to it, and obtain a different range of numbers. For example a random number between 0 and 1 can be scaled and shifted to become a random number from 8 to 12 like so:
[as]//____________________________________________ In the above example values from 8 to 12 are obtained by multiplying our random number (which is between 0 and 1) by 4 and adding 8. We multiply by 4 because the range of numbers between 8 and 12 is 4 (12-8=4) it therefore follows that any number between 0 and 1 multiplied by 4 will "scale" up to a number between 0 and 4. We then add 8 to our number (which is now between 0 and 4) to ensure we get numbers between 8 and 12 because 8+0=8 and 8+4=12. |
The next portion of this tutorial deals with the concept of "Easing" a tween.
Easing, what is it and why do I care? I'm sure you've noticed that our ball doesn't seem to be affected by the laws of physics. I mean it sort of looks like a ball but it seems to be able to take off at 45 pixels per frame without any hesitation then stops equally fast right on a dime. Such performance might make Ferraris or Porsches envious but it doesn't do much to help convince your viewers that the ball is real and has some weight. Real balls and cars need time to get up to speed as well as slow down and this is where easing comes into play. Easing alone can't make you a super designer or scripter overnight, but it can add a sense of realism to your work often lacking in some lesser animations. You may not notice when a tween is being eased, but you almost always notice when it's not. So I hope you'll adopt the following as your new motto, "Don't be a wiener, be an easing tweener". Catchy no?
Now back to business. In our previous example...
[as]//____________________________________________
//
currentDistance = startX +(currentFrame/totalFrames) * changeInX;
//____________________________________________
[/as]
...(currentFrame/totalFrames) was increased at a constant rate which in turn moved our ball across the screen at an even pace. For example during a 10 frame tween our ratio (currentFrame/totalFrames) would hold the values:
[as]0
.1 // a difference of .1 // Taking medium steps
.2 // a difference of .1
.3 // a difference of .1
.4 // a difference of .1
.5 // a difference of .1
.6 // a difference of .1
.7 // a difference of .1
.8 // a difference of .1
.9 // a difference of .1
1 // a difference of .1 // Taking medium steps
[/as]
But if we're going to have our ball start slow and gradually accelerate then we need to slowly increase the value of our ratio (currentFrame/totalFrames). But how can we do that? Well... what would happen if we squared each of our initial values? We'd end up with something like this:
[as]0
.01 // a difference of .01 // Taking small steps
.04 // a difference of .03
.09 // a difference of .05
.16 // a difference of .07
.25 // a difference of .09
.36 // a difference of .11
.49 // a difference of .13
.64 // a difference of .15
.81 // a difference of .17
1 // a difference of .19 // Taking big steps
[/as]
Notice how the values change at an increasing rate yet still start at 0 and end at 1, this is perfect for our easing purposes. We can now ease our ball with the following code:
[as]//____________________________________________
//
Math.square = function (n){
return n*n;
}
var currentFrame = 0;
Ball_mc.onEnterFrame = function(){
//
this._x = 50 + Math.square(currentFrame/10) * 450;
//
// Which is the same as
//
// newX = startX + Math.square(currentFrame/totalFrames) * changeInX;
//
if (++currentFrame > 10) delete this.onEnterFrame;
}
//____________________________________________
[/as]
What if we were to cube the ratio?
[as]0
0.001 // a difference of 0.001 // Taking tiny steps
0.008 // a difference of 0.007
0.027 // a difference of 0.019
0.064 // a difference of 0.037
0.125 // a difference of 0.061
0.216 // a difference of 0.091
0.343 // a difference of 0.127
0.512 // a difference of 0.169
0.729 // a difference of 0.217
1 // a difference of 0.271 // Taking huge steps
[/as]
[as]//____________________________________________
//
Math.cube = function (n){
return n*n*n;
}
var currentFrame = 0;
Ball_mc.onEnterFrame = function(){
//
this._x = 50 + Math.cube(currentFrame/10) * 450;
//
// Which is the same as
//
// newX = startX + Math.cube(currentFrame/totalFrames) * changeInX;
//
if (++currentFrame > 10) delete this.onEnterFrame;
}
//____________________________________________
[/as]
Did you notice how much more pronounced the easing effect is? All we had to do was switch the function (Math.square to Math.cube) which eases the initial ratio (currentFrame/totalFrames) and BLAMMO! Just like that we switched from quadratic to cubic easing. We've come a long way haven't we? Hopefully you understand the following key points:
Point-1) The relationship between the current frame and the amount of total frames is a "ratio" from 0 to 1 and changing that "ratio" is what moves or "drives" the ball across the stage.
Point-2) To ease the tween all we have to do is provide a function which distorts the values of the "ratio" so the results are weighted towards one side.
Point-3) Just for repetition's sake, it's all about the "ratio". Ratio ratio ratio.
Now that you understand the basics of tweening and easing, it's time to get a little fancy. Consider the following code:
[as]//____________________________________________
//
Math.easeIn = function (ratio, power){
if (power == undefined) power = 2;
return Math.pow(ratio, power);
}
//____________________________________________
//
[/as]
The Math.easeIn function accepts 2 arguments but only the first one is required. The first argument "ratio" is the number you want to multiply times itself and the second argument "power" is the amount of times the first argument will be multiplied by itself (for example, squared(2), cubed(3)... etc.). So now rather than being forced to switch between a Math.square function and a Math.cube function all we have to do is change the second argument of our easeIn function. Here's a little code to help clarify:
[as]//____________________________________________
//
var currentFrame = 0;
Ball_mc.onEnterFrame = function(){
//
//
//
// To square the ratio (quadratic easing)
// we don't need to specify a "power" argument since 2 is the default
this._x = 50 + Math.easeIn(currentFrame/10) * 450;
//
// or
//
// To cube the ratio (cubic easing)
// we need to specify a power of 3
this._x = 50 + Math.easeIn(currentFrame/10, 3) * 450;
//
//
//
// Which is the same as
//
// newX = startX + Math.easeIn(currentFrame/totalFrames, power) * changeInX;
//
if (++currentFrame > 10) delete this.onEnterFrame;
}
//____________________________________________
[/as]
Using our new Math.easeIn function has gained us a good deal of flexibility and control. Not only can we change the power argument on the fly, but we can set the power to non whole numbers (such as 1.5 or 3.8) as well. If the easing effect isn't prominent enough, just increase the Power. If the easing effect is too prominent, decrease the Power. It's almost too "easy" once you understand what's going on. But wait! We're not done yet. Right now all we can do is ease in, what about easing out?
As discussed earlier, these are the results returned by a 10 frame quadratic ease in:
[as]0
.01 // a difference of .01 // Taking small steps
.04 // a difference of .03
.09 // a difference of .05
.16 // a difference of .07
.25 // a difference of .09
.36 // a difference of .11
.49 // a difference of .13
.64 // a difference of .15
.81 // a difference of .17
1 // a difference of .19 // Taking big steps
[/as]
But to ease OUT we need to take big steps in the beginning and small steps in the end, so let's just reverse the order of the lines above. We're not doing anything with math yet, just changing the order for clarity.
[as]1 // a difference of .19 // Taking big steps
.81 // a difference of .17
.64 // a difference of .15
.49 // a difference of .13
.36 // a difference of .11
.25 // a difference of .09
.16 // a difference of .07
.09 // a difference of .05
.04 // a difference of .03
.01 // a difference of .01 // Taking small steps
0
[/as]
That sort of gets us headed in the right direction, at least now we're taking big steps first and little steps last. If you give it a bit of thought, it shouldn't be too hard to see that results above can be obtained by using the following formula:
[as]//____________________________________________
//
Math.easeIn(1-ratio, 2);
//____________________________________________
[/as]
For example, if "ratio" holds a value of ".1", you would get the result (1-ratio)*(1-ratio) or (.9)*(.9) or ".81" and if "ratio" held a value of ".2" then (1-.2)*(1-.2) would produce the result ".64".
But we still have a problem because the above code is returning values from 1 to 0 instead of values from 0 to 1 and that has the unwanted result of rolling our ball backwards. Fortunately we can easily get around this minor setback by changing our code to this:
[as]//____________________________________________
//
1 - Math.easeIn(1-ratio, 2);
//____________________________________________
[/as]
A simple test will show how well this works:
[as]//____________________________________________
//
Trace("EaseIn");
for(var i=0; i<=10; i++){
var ratio = i/10;
trace(Math.easeIn(ratio));
}
trace("\rEaseOut");
for(var i=0; i<=10; i++){
var ratio = i/10;
trace(1-Math.easeIn(1-ratio));
}
/* Traces these results
EaseIn
0
0.01
0.04
0.09
0.16
0.25
0.36
0.49
0.64
0.81
1
EaseOut
0
0.19
0.36
0.51
0.64
0.75
0.84
0.91
0.96
0.99
1
*/
//____________________________________________
Now we're taking big steps in the beginning and returning values from 0 to 1 rather than 1 to 0. Everything works great but all that "1-" stuff is a pain to keep track of so we're going to turn the previous code into an easy to use easeOut function then show it in action.
[as]//____________________________________________
//
Math.easeIn = function (ratio, power){
if (power == undefined) power = 2;
return Math.pow(ratio, power);
}
Math.easeOut = function(ratio, power){
return 1-Math.easeIn(1-ratio, power);
}
var currentFrame = 0;
Ball_mc.onEnterFrame = function(){
//
this._x = 50 + Math.easeOut(currentFrame/10, 3) * 450;
//
// Which is the same as
//
// newX = startX + Math.easeOut(currentFrame/totalFrames, power) * changeInX;
//
currentFrame++;
if (currentFrame>10) delete this.onEnterFrame;
}
//____________________________________________
[/as]
So we have two functions, one to ease in and one to ease out. Now it's time to combine the two and make another function for what's probably the most commonly used type of tween, the good old "easeInOut". As you may have guessed, the "easeInOut" tween starts our ball off slowly, speeds it up as it approaches the middle then slows it down again as it nears the end. This is probably the most commonly used type of easing tween so it would be quite handy to combine our two "easeIn" and "easeOut" functions into one easy to use "easeInOut" function. It's time to put your thinking caps on.
When our initial ratio of completion (currentFrame/totalFrames) is less than .5 we need to easeIn and when the ratio is greater than .5 we need to ease out, but we can't just do this:
[as]//____________________________________________
//
Math.easeInOut = function (ratio, power){
if(ratio<=.5){
return Math.easeIn(ratio, power);
}else{
return Math.easeOut(ratio, power);
}
}
//____________________________________________
[/as]
One problem with the above code is that our "easeInOut" function only provides the "easeIn" function values from 0 to .5. This is bad because when we feed our "easeIn" function the value .5, it will return the value .25 because .5*.5=.25. This means our ball will only be 25% of the way to its destination before it will start to ease out. What's even worse is our "easeInOut" function will then start feeding all numbers greater than .5 to the "easeOut" function so for example when .50001 is fed into the "easeInOut" function, the "easeOut" function will return the value 1-(1-5.0001)*(1-5.001) which equals .7501. That means all the numbers between .25 and .75 will be completely ignored and make our tween look really stupid. Therefore we have to make a few modifications to our easeInOut function if we're going to get the smooth transitions we desire.
The solution is to double every number we feed the easeIn function then cut the number it returns in half. For example, when our initial ratio equals .5 we'll double it (to 1), feed 1 into the easeIn function and then cut the number it returns (1*1=1) in half which will equal .5. So now when we feed the easeInOut function numbers between 0 and .5 it will return numbers eased in from 0 to .5 and that's exactly what we want. After making the necessary modifications, our easeInOut function will look like this:
[as]//____________________________________________
//
Math.easeInOut = function (ratio, power){
if(ratio<.5){
return Math.easeIn(2*ratio, power)/2;
}else{
return Math.easeOut(2*(ratio-.5), power)/2+.5;
}
}
//____________________________________________
[/as]
As you can see, I took the liberty of applying the same principle to the "easeOut" portion of the "easeInOut" function as well as the easeIn portion. The only difference being that we first subtract .5 from our "ratio" to offset the values from (.5 through 1) to (0 through .5) so when we double the ratio the "easeOut" function will receive values ranging from 0 to 1 as expected. Then we halve the result returned by the "easeOut" function just like we did for the "easeIn" portion of the function and add .5 to shift the results back to the right end of the tween.
Now let's take everything we've learned so far and use it to ease our ball from an _x of 50 to an _x of 500 in 50 frames.
[as]//____________________________________________
//
// Declare all our Math functions
Math.easeIn = function (ratio, power){
if (power == undefined) power = 2;
return Math.pow(ratio, power);
}
Math.easeOut = function(ratio, power){
return 1-Math.easeIn(1-ratio, power);
}
Math.easeInOut = function (ratio, power){
if(ratio<.5)return Math.easeIn(2*ratio, power)/2;
return Math.easeOut(2*(ratio-.5), power)/2+.5;
}
//
// Make the ball
createEmptyMovieClip("Ball_mc", 10);
with(Ball_mc){
lineStyle(40, 0xFF0000);
lineTo(.5, 0);
_x = 50;
_y = 200;
}
//
// Declare a variable to track the current frame
var currentFrame = 0;
// Do the actual easing
Ball_mc.onEnterFrame = function(){
this._x = 50 + Math.easeInOut(currentFrame/50) * 450;
if (++currentFrame > 50) delete this.onEnterFrame;
}
//____________________________________________
[/as]
So far all our code is looking pretty tight but there's still a little room for improvement. Currently we're only using the Math.pow function to raise the initial ratio to a higher power yet there are many other ways to distort or skew values between 0 and 1 and many of them will produce noticeably different easing results. Here are a few examples for you to consider:
[as]//____________________________________________
//
Math.ezExpo = function (r, p) {
if(r==0 || r==1) return r;
if(p<2)p = 2;
return Math.pow(p, 10*(r-1));
}
Math.ezCirc = function (r, p) {
r = -(Math.sqrt(1-r*r)-1);
if(p>1) return Math.easeIn(r, --p, Math.ezCirc);
return r;
}
Math.ezSine = function (r, p){
r = 1-Math.cos(r*Math.PI/2);
if(p>1) return Math.easeIn(r, --p, Math.ezSine);
return r;
}
//____________________________________________
[/as]
Explaining how each of the above functions work is a bit beyond the scope of this tutorial but it should be sufficient to say they all have a few things in common, they accept numbers between 0 and 1, the values they return are weighted towards the 0 end of the 0 to 1 spectrum and they all accept a "power" argument which controls the strength of the easing effect. In short, they work just like our Math.pow function but return slightly different results. Incorporating the above functions into our existing easing functions will be one of the last alterations we make:
[as]//____________________________________________
//
Math.easeIn = function (ratio, power, ezFunction){
if (power == undefined) power = 2;
if (ezFunction == undefined) ezfunction = Math.pow;
return ezFunction(ratio, power);
}
Math.easeOut = function(ratio, power, ezFunction){
return 1-Math.easeIn(1-ratio, power, ezFunction);
}
Math.easeInOut = function (ratio, power, ezFunction){
if(ratio<.5)return Math.easeIn(2*ratio, power, ezFunction)/2;
return Math.easeOut(2*(ratio-.5), power, ezFunction)/2+.5;
}
//____________________________________________
[/as]
As you can see, our "easeIn" function has been modified to accept an alternate easing equation (ezFunction) as a third argument. If you don't declare an alternate easing equation (if ezFunction == undefined) then the standard "Math.pow" function will be used by default. Since both our "easeOut" and "easeInOut" functions make use of our "easeIn" function the only modifications they needed was to make sure they pass "ezFunction" along as an argument for the "easeIn" function.
Our easing functions now have all the features they're ever going to have. Since we won't be making any further modifications, we can sacrifice a little readability for a moderate performance gain by using shorter variable names. Let's put them to the test and see how well they perform.
[as]//____________________________________________
//
// Declare all our Math functions
Math.easeIn = function (r, p, f){
if (p == undefined) p = 2;
if (f == undefined) f = Math.pow;
return f (r, p);
}
Math.easeOut = function(r, p, f){
return 1-Math.easeIn(1-r, p, f);
}
Math.easeInOut = function (r, p, f){
if(r<.5)return Math.easeIn(2*r, p, f)/2;
return Math.easeOut(2*(r-.5), p, f)/2+.5;
}
Math.ezExpo = function (r, p) {
if(r==0 || r==1) return r;
if(p<2)p = 2;
return Math.pow(p, 10*(r-1));
}
Math.ezCirc = function (r, p) {
r = -(Math.sqrt(1-r*r)-1);
if(p>1) return Math.easeIn(r, --p, Math.ezCirc);
return r;
}
Math.ezSine = function (r, p){
r = 1-Math.cos(r*Math.PI/2);
if(p>1) return Math.easeIn(r, --p, Math.ezSine);
return r;
}
// Make four balls
createEmptyMovieClip("Ball1_mc", 10);
with (Ball1_mc) {
lineStyle(40, 0xFF0000);
lineTo(.5, 0);
_x = 50;
_y = 50;
}
createEmptyMovieClip("Ball2_mc", 20);
with (Ball2_mc) {
lineStyle(40, 0xFF0000);
lineTo(.5, 0);
_x = 50;
_y = 150;
}
createEmptyMovieClip("Ball3_mc", 30);
with (Ball3_mc) {
lineStyle(40, 0xFF0000);
lineTo(.5, 0);
_x = 50;
_y = 250;
}
createEmptyMovieClip("Ball4_mc", 40);
with (Ball4_mc) {
lineStyle(40, 0xFF0000);
lineTo(.5, 0);
_x = 50;
_y = 350;
}
// Declare a variable to track the current frame
currentFrame = 0;
// Move all balls
Ball1_mc.onEnterFrame = function(){
this._x = 50 + Math.easeInOut(currentFrame/50, 2) * 450;
if(++currentFrame > 50) delete this.onEnterFrame;
}
Ball2_mc.onEnterFrame = function(){
this._x = 50 + Math.easeInOut(currentFrame/50, 1, Math.ezSine) * 450;
if(currentFrame > 50) delete this.onEnterFrame;
}
Ball3_mc.onEnterFrame = function(){
this._x = 50 + Math.easeInOut(currentFrame/50, 1, Math.ezCirc) * 450;
if(currentFrame > 50) delete this.onEnterFrame;
}
Ball4_mc.onEnterFrame = function(){
this._x = 50 + Math.easeInOut(currentFrame/50, 2, Math.ezExpo) * 450;
if(currentFrame > 50) delete this.onEnterFrame;
}
//____________________________________________
[/as]
As you can see, changing the equations used to ease our ball can produce a fairly wide variety of results, ranging from the subtle to the extreme. If you'd like to check out a few more alternate easing equations I suggest you visit Robert Penner's site and examine some of his excellent easing equations.
Now that we have the core easing functions in their final state...
[as]//____________________________________________
//
Math.easeIn = function (r, p, f){
if (p == undefined) p = 2;
if (f == undefined) f = Math.pow;
return f (r, p);
}
Math.easeOut = function(r, p, f){
return 1-Math.easeIn(1-r, p, f);
}
Math.easeInOut = function (r, p, f){
if(r<.5)return Math.easeIn(2*r, p, f)/2;
return Math.easeOut(2*(r-.5), p, f)/2+.5;
}
Math.ezExpo = function (r, p) {
if(r==0 || r==1) return r;
if(p<2)p = 2;
return Math.pow(p, 10*(r-1));
}
Math.ezCirc = function (r, p) {
r = -(Math.sqrt(1-r*r)-1);
if(p>1) return Math.easeIn(r, --p, Math.ezCirc);
return r;
}
Math.ezSine = function (r, p){
r = 1-Math.cos(r*Math.PI/2);
if(p>1) return Math.easeIn(r, --p, Math.ezSine);
return r;
}
//____________________________________________
[/as]
...I'd like to take a little time to elaborate on a few different ways to apply them. All the following code samples will assume you have either pasted or included the above easing functions into your Flash file. With that said, let's move on to our first example.
So far, for the sake of simplicity, all our previous examples have dealt with tweening the "_x" property of a clip yet it shouldn't be too hard to imagine using the easing functions to adjust any other property of a movieclip. For example we can fade-in a clip's alpha like this:
[as]//____________________________________________
//
var currentFrame = 0;
RedBall_mc.onEnterFrame = function () {
currentFrame++;
this._alpha = Math.easeIn(currentFrame/50)*100;
if (currentFrame>50) delete this.onEnterFrame;
}
//____________________________________________
[/as]
You don't necessarily need to ease a clip's property either. For example, you might want to ease the scoring of a game. Imagine you have a game with a bunch of bad guys and each time you press the mouse a bad guy dies. Killing one bad guy is super easy but killing all 20 is very difficult therefore you don't just want to give out a set amount of points for every guy killed. Instead, you can ease the amount of points for each guy killed with code similar to the following:
[as]//____________________________________________
//
var score = 0;
var deadGuys = 0;
var totalBadGuys = 20;
var pointsForLevel = 5000;
onMouseDown = function(){
if (deadGuys < totalBadGuys){
deadGuys++;
score = Math.easeIn(deadGuys/totalBadGuys)*pointsForLevel ;
trace(Math.floor(score));
}
}
//____________________________________________
[/as]
All our previous examples involved the use of a variable ("currentFrame") to track the passage of time using frames as a unit of measure but you can use other units of time if you wish. For example you can use the values returned by the "GetTimer" function to create a delayed tween which will start and stop at a set time regardless of the SWF's frame rate like this:
[as]//____________________________________________
//
var startTime = 3000; // What time the tween will start (3 seconds into the movie)
var endTime = 5000; // What time the tween will end (5 seconds into the movie)
var timeDifference = endTime - startTime; // How much time the tween will take to complete once started (2 seconds)
var startX = 50; // Where the ball will start
var endX = 500; // Where the ball will end
var xDifference = endX - startX; // How far the ball will travel to reach it's final position
//
RedBall_mc.onEnterFrame = function () {
var nowTime = getTimer(); // Determine the current time
if (nowTime > startTime) { // If it's past the time to start the tween
if (nowTime < endTime) { // If the tween is not complete
// Determine the ratio of completion
var ratio = (nowTime-startTime) / timeDifference;
// Do the tween
this._x = startX + Math.easeInOut( ratio ) * xDifference;
}else{ // If it is time to end the tween
this._x = endX; // Put the ball at its final position
delete this.onEnterFrame; // Kill the enterFrame event
}
}
}
//____________________________________________
[/as]
The last topic I'd like to discuss is that of "Asymmetrical Easing". So far whenever we've eased our ball using the "Math.easeInOut" function it has moved in a symmetrical fashion. That is to say the ball takes the same amount of time to ease out as it does to ease in, in effect one side of the tween is a mirror image of the other side. But this isn't always the desired result. For example let's say you'd like to tween a car across the stage, and let's say this car has poor acceleration but excellent brakes. To create a convincing tween the car should start off slowly but come to a stop rather abruptly by comparison. This means we need heavy easing in and light easing out. We can achieve this effect with code similar to the following:
[as]//____________________________________________
//
var startX = 50;
var endX = 500;
var currentFrame = 0;
var totalFrames = 50;
RedBall_mc.onEnterFrame = function () {
currentFrame++;
var initialRatio = currentFrame / totalFrames;
var ratio2 = Math.easeIn(initialRatio, 4);
var ratio3 = Math.easeOut(ratio2, 2);
this._x = startX + ratio3 * (endX-startX);
if(currentFrame >= totalFrames) delete this.onEnterFrame;
}
//____________________________________________
[/as]
The concept of easing a value then easing the returned value again (as shown above) can produce such a wide variety of results I thought I'd use our last code sample to illustrate some of the possibilities.
[as]//____________________________________________
//
// Variables
var totalFrames = 100;
var startX = 70;
var endX = 535;
var changeX = endX-startX;
//
// Create Text Fields
for(var i=0; i<8; i++){
createTextField("TF"+i, i+1, 4, 8+i*50, 65, 50);
this["TF"+i].multiline = true;
}
TF0.text = "InOutSine1";
TF1.text = "InSine1\rOutSine1";
TF2.text = "OutSine1\rInSine1";
TF3.text = "InSine2\rOutSine1";
TF4.text = "InSine1\rOutSine2";
TF5.text = "InSine1\rOutExpo2";
TF6.text = "InExpo2\rOutSine3";
TF7.text = "InCube3\rOutQuad2";
//
// Create Diamonds
for(var i=0; i<8; i++){
var dot = createEmptyMovieClip("Dot"+i, i+10);
with(dot){
beginFill(0, 50);
moveTo(0, -20);
lineTo(5, 0);
lineTo(0, 20);
lineTo(-5, 0);
endFill();
_x = startX;
_y = 25+i*50;
}
}
//
// Duplicate And Move All Diamonds
var Depth = 20;
onMouseDown = function(){
for(var i=20; i<20+depth; i++){
this["Dot"+i].removeMovieClip();
}
currentFrame = 0;
depth = 20;
onEnterFrame = function(){
var ratio = currentFrame/totalFrames;
// 1) InOutSine1
var dot = Dot0.duplicatemovieclip("Dot"+depth, depth++);
dot._x = startX + Math.easeInOut(ratio, 1, Math.ezSine)*changeX;
// 2) InSine1 OutSine1
var dot = Dot1.duplicatemovieclip("Dot"+depth, depth++);
var r = Math.easeIn(ratio, 1, Math.ezSine);
dot._x = startX + Math.easeOut(r, 1, Math.ezSine)*changeX;
// 3) OutSine1 InSine1
var dot = Dot2.duplicatemovieclip("Dot"+depth, depth++);
var r = Math.easeOut(ratio, 1, Math.ezSine);
dot._x = startX + Math.easeIn(r, 1, Math.ezSine)*changeX;
// 4) InSine2 OutSine1
var dot = Dot3.duplicatemovieclip("Dot"+depth, depth++);
var r = Math.easeIn(ratio, 2, Math.ezSine);
dot._x = startX + Math.easeOut(r, 1, Math.ezSine)*changeX;
// 5) InSine1 OutSine2
var dot = Dot4.duplicatemovieclip("Dot"+depth, depth++);
var r = Math.easeIn(ratio, 1, Math.ezSine);
dot._x = startX + Math.easeOut(r, 2, Math.ezSine)*changeX;
// 6) InSine1 OutExpo2
var dot = Dot5.duplicatemovieclip("Dot"+depth, depth++);
var r = Math.easeIn(ratio, 1, Math.ezSine);
dot._x = startX + Math.easeOut(r, 2, Math.ezExpo)*changeX;
// 7) InExpo2 OutSine3
var dot = Dot6.duplicatemovieclip("Dot"+depth, depth++);
var r = Math.easeIn(ratio, 2, Math.ezExpo);
dot._x = startX + Math.easeOut(r, 3, Math.ezSine)*changeX;
// 8) InCube3 OutQuad2
var dot = Dot7.duplicatemovieclip("Dot"+depth, depth++);
var r = Math.easeIn(ratio, 3);
dot._x = startX + Math.easeOut(r)*changeX;
if(currentFrame == totalFrames)delete onEnterFrame;
currentFrame++;
}
}
onMouseDown();
//____________________________________________
[/as]
That's it. This tutorial has finally come to an end. I hope you find the easing functions to be as useful as I have and more importantly, I hope you've gained a good deal of insight regarding the concepts of tweening and easing.
One final thing before I go, I don't have Flash MX2004 yet, but for those of you who do, you might find the following class definitions useful:
[as]//____________________________________________
//
class com.pixelwit.ease{
static function easeIn (r:Number, p:Number, f:function):Number{
if(p == undefined) p = 2;
if(f == undefined)f = Math.pow;
return f(r, p);
}
static function easeOut (r:Number, p:Number, f:function):Number{
return 1-easeIn(1-r, p, f);
}
static function easeInOut (r:Number, p:Number, f:function):Number{
if(r<.5)return easeIn(r*2, p, f)/2;
return .5+easeOut((r-.5)*2, p, f)/2;
}
static function ezExpo (r:Number, p:Number):Number{
if(r==0 || r==1) return r;
if(p<2)p = 2;
return Math.pow(p, 10*(r-1));
}
static function ezCirc (r:Number, p:Number):Number{
r = -(Math.sqrt(1-r*r)-1);
if(p>1) return easeIn(r, --p, ezCirc);
return r;
}
static function ezSine (r:Number, p:Number):Number{
r = 1-Math.cos(r*Math.PI/2);
if(p>1) return easeIn(r, --p, ezSine);
return r;
}
}
//____________________________________________
[/as]
For more information or for general discussion of this topic you can check out this thread.
Cheers,
-Sean O'Shell (a.k.a. PiXELWiT)