We’ve tackled drawing curves, and finding their quadratic and cubic roots, as well as handy applications for using quadratic roots within games. Now, as promised, we’ll look at applications for finding cubic roots, as well as curves’ gradients and normals, like making objects bounce off curved surfaces. Let’s go!
Example
Let’s take a look one practical use of this math:
In this demo, the “ship” bounces off the edges of the SWF and the curve. The yellow dot represents the closest point to the ship that lies on the curve. You can adjust the shape of the curve by dragging the red dots, and adjust the movement of the ship using the arrow keys.
Step 1: Shortest Distance to a Curve
Let’s consider the scenario where a point is located near a quadratic curve. How do you calculate the shortest distance between the point and the curve?
You can see that we have substituted \(y_c\) with the quadratic equation. At a glance, we can see the highest power is 4. Thus, we have a quartic equation. All we need to do is to find a minimum point in this equation to give us the shortest distance from a point to a quadratic curve.
But before that, we’ll need to understand gradients on a curve…
Step 2: Gradient of a Curve
Before we look at the problem of minimizing a quartic equation, let’s try to understand gradients of a curve. A straight line has only one gradient. But a quadratic curve’s gradient depends on which point on the curve we refer to. Check out the Flash presentation below:
Drag the red dots around to change the quadratic curve. You can also play with the slider’s handle to change the position of blue dot along x. As the blue dot changes, so will the gradient drawn.
Step 3: Gradient Through Calculus
This is where calculus will come in handy. You may have guessed that differentiating a quadratic equation would give you the gradient of the curve.
\[
f(x) = ax^2+bx+c\\
\frac{df(x)}{dx} = 2ax+b
\]
So \(\frac{df(x)}{dx}\) is the gradient of a quadratic curve, and it’s dependant on the \(x\) coordinate. Well, good thing we’ve got a method to handle this: diff1(x:Number) will return the value after a single differentiation.
To draw the gradient, we’ll need an equation to represent the line, \(y=mx+c\). The coordinate of the blue point \((x_p, y_p)\) will be substituted into the \(x\) and \(y\), and the gradient of the line found through differentiation will go into \(m\). Thus the y-intercept of line, \(c\) can be calculated through some algebra work.
Check out the AS3:
var x:Number = s.value
var y:Number = quadratic_equation.fx_of(s.value)
point.x = x;
point.y = y;
/**
* y = mx + c;
* c = y - mx; <== use this to find c
*/
var m:Number = quadratic_equation.diff1(x);
var c:Number = y - m * x;
graphics.clear();
graphics.lineStyle(1, 0xff0000);
graphics.moveTo(0, c);
graphics.lineTo(stage.stageWidth, m * stage.stageWidth + c);
Step 4: Coordinate Systems
Always bear in mind of the inverted y-axis of Flash coordinate space as shown in the image below. At first glance, the diagram on right may seem like a negative gradient – but due to the inverted y-axis, it’s actually a positive gradient.
The same goes for the minimum point as indicated below. Because of the inverted y-axis, the minimum point in Cartesian coordinate space (at (0,0)) looks like a maximum in Flash coordinate space. But by referring to the location of origin in Flash coordinate space relative to the quadratic curve, it’s actually a minimum point.
Step 5: Rate of Change for Gradient
Now let’s say I’m interested in finding the lowest point on a curve – how do I proceed? Check out the image below (both figures are in the same coordinate space).
In order to get the minimum point, we’ll just equate \(\frac{df(x)}{dx} = 0\), since by definition we’re looking for the point where the gradient is zero. But as shown above, it turns out that the maximum point on a curve also satisfies this condition. So how do we discriminate between these two cases?
Let’s try differentiation of the second degree. It’ll give us the rate of change of the gradient.
I’ll explain with reference to the image below (drawn in Cartesian coordinate space). We can see that, as we increment along the x-axis, the gradient changes from negative to positive. So the rate of change should be a positive value.
We can also see that when \(\frac{df^2(x)}{dx^2}\) is positive, there’s a minimum point on the curve. Conversely if the rate is negative, a maximum point is present.
Step 6: Back to the Problem
Now we are ready to solve the problem presented in Step 1. Recall the quartic equation (where the highest degree is 4) we arrived at:
Remember, we are interested to find the minimum point on this curve, because the corresponding point on the original quadratic curve will be the point that’s at the minimum distance from the red dot.
So, let’s differentiate the quartic function to get to gradient of this curve, and then equate the gradient of this quartic function to zero. You will see that the gradient is actually a cubic function. I’ll refer interested readers to Wolfram’s page; for this tutorial, I’ll just pluck the result of their algebra workings:
Solve for the roots of this (rather messy) cubic function and we’ll arrive at the coordinates of the three blue points as indicated above.
Next, how do we filter our results for the minimum point? Recall from the previous step that a minimum point has a rate of change that’s positive. To get this rate of change, differentiate the cubic function that represents gradient. If the rate of change for the given blue point is positive, it’s one of the minimum points. To get the minimum point, the one that we’re interested in, choose the point with the highest rate of change.
Step 7: Sample of Output
So here’s a sample implementation of the idea explained above. You can drag the red dots around to customise your quadratic curve. The blue dot can also be dragged. As you move the blue dot, the yellow one will be repositioned so that the distance between the blue and yellow dots will be minimum among all points on the curve.
As you interact with the Flash presentation, there may be times where three yellow dots appear all at once. Two of these, faded out, refer to the roots obtained from the calculation but rejected because they are not the closest points on the curve to the blue dot.
Step 8: ActionScript Implementation
So here’s the ActionScript implementation of the above. You can find the full script in Demo2.as.
First of all, we’ll have to draw the quadratic curve. Note that the matrix m2 will be referred to for further calculation.
private function redraw_quadratic_curve():void
{
var cmd:Vector.<int> = new Vector.<int>;
var coord:Vector.<Number> = new Vector.<Number>;
//redraw curve;
m1 = new Matrix3d(
curve_points[0].x * curve_points[0].x, curve_points[0].x, 1, 0,
curve_points[1].x * curve_points[1].x, curve_points[1].x, 1, 0,
curve_points[2].x * curve_points[2].x, curve_points[2].x, 1, 0,
0,0,0,1
);
m2 = new Matrix3d(
curve_points[0].y, 0, 0, 0,
curve_points[1].y, 0, 0, 0,
curve_points[2].y, 0, 0, 0,
0,0,0,1
)
m1.invert();
m2.append(m1);
quadratic_equation.define(m2.n11, m2.n21, m2.n31);
for (var i:int = 0; i < stage.stageWidth; i+=2)
{
if (i == 0) cmd.push(1);
else cmd.push(2);
coord.push(i, quadratic_equation.fx_of(i));
}
graphics.clear();
graphics.lineStyle(1);
graphics.drawPath(cmd, coord);
}
And here’s the one that implements the mathematical concept explained. c1 refers to a point randomly positioned on stage.
private function recalculate_distance():void
{
var a:Number = m2.n11;
var b:Number = m2.n21;
var c:Number = m2.n31;
/*f(x) = Ax^3 + Bx^2 +Cx + D
*/
var A:Number = 2*a*a
var B:Number = 3*b*a
var C:Number = b*b + 2*c*a - 2*a*c1.y +1
var D:Number = c * b - b * c1.y - c1.x
quartic_gradient = new EqCubic();
quartic_gradient.define(A, B, C, D);
quartic_gradient.calcRoots();
roots = quartic_gradient.roots_R;
var chosen:Number = roots[0];
if (!isNaN(roots[1]) && !isNaN(roots[2])) {
//calculate gradient and rate of gradient of all real roots
var quartic_rate:Vector.<Number> = new Vector.<Number>;
for (var i:int = 0; i < roots.length; i++)
{
if (!isNaN(roots[i])) quartic_rate.push(quartic_gradient.diff1(roots[i]));
else roots.splice(i, 1);
}
//select the root that will produce the shortest distance
for (var j:int = 1; j < roots.length; j++)
{
//the rate that corresponds with the root must be the highest positive value
//because that will correspond with the minimum point
if (quartic_rate[j] > quartic_rate[j - 1]) {
chosen = roots[j];
}
}
//position the extra roots in demo
position_extras();
}
else {
//remove the extra roots in demo
kill_extras();
}
intersec_points[0].x = chosen
intersec_points[0].y = quadratic_equation.fx_of(chosen);
}
Step 9: Example: Collision Detection
Let’s make use of this concept to detect the overlap between a circle and a curve.
The idea is simple: if the distance between the the blue dot and the yellow dot is less than blue dot’s radius, we have a collision. Check out the demo below. The interactive items are the red dots (to control the curve) and the blue dot. If the blue dot is colliding with the curve, it will fade out a little.
Step 10: ActionScript Implementation
Well, the code is quite simple. Check out the full source in CollisionDetection.as.
So now that we know when collision will occur, let’s try to program some collision response. How about bouncing off the surface? Check out the Flash presentation below.
You can see the ship (triangle shape), is surrounded by a circle (translucent blue). Once the circle collides with the curve, the ship will bounce off the surface.
Step 12: Controlling the Ship
Here’s the ActionScript to control the ship.
public function CollisionDetection2()
{
/**
* Instantiation of ship & its blue-ish circular area
*/
ship = new Triangle(); addChild(ship);
ship.x = Math.random() * stage.stageWidth;
ship.y = stage.stageHeight * 0.8;
c1 = new Circle(0x0000ff, 15); addChild(c1);
c1.alpha = 0.2;
/**
* Ship's velocity
*/
velo = new Vector2D(0, -1);
updateShip();
stage.addEventListener(KeyboardEvent.KEY_DOWN, handleKey);
stage.addEventListener(KeyboardEvent.KEY_UP, handleKey);
stage.addEventListener(Event.EXIT_FRAME, handleEnterFrame);
/**
* The curve and the calculations
*/
quadratic_equation = new EqQuadratic();
curve_points = new Vector.<Circle>;
populate(curve_points, 0xff0000, 3);
intersec_points = new Vector.<Circle>;
populate(intersec_points, 0xffff00, 3, false);
redraw_quadratic_curve();
}
private function handleKey(e:KeyboardEvent):void
{
if (e.type == "keyDown") {
if (e.keyCode == Keyboard.UP) isUp = true;
else if (e.keyCode == Keyboard.DOWN) isDown = true;
if (e.keyCode == Keyboard.LEFT) isLeft = true;
else if (e.keyCode == Keyboard.RIGHT) isRight = true;
}
if (e.type == "keyUp") {
if (e.keyCode == Keyboard.UP) isUp = false;
else if (e.keyCode == Keyboard.DOWN) isDown = false;
if (e.keyCode == Keyboard.LEFT) isLeft = false;
else if (e.keyCode == Keyboard.RIGHT) isRight = false;
}
}
private function handleEnterFrame(e:Event):void
{
/**
* Control the magnitude
*/
if (isUp) velo.setMagnitude(Math.min(velo.getMagnitude()+0.2, 3));
else if(isDown) velo.setMagnitude(Math.max(velo.getMagnitude()-0.2, 1));
/**
* Control the direction
*/
if (isRight) velo.setAngle(velo.getAngle() + 0.03);
else if (isLeft) velo.setAngle(velo.getAngle() - 0.03);
recalculate_distance();
if (distance < c1.radius) bounce();
updateShip();
}
/**
* Update ship's position, orientation and it's area (the blue-ish circle)
*/
private function updateShip():void {
ship.x += velo.x;
ship.y += velo.y;
ship.rotation = Math2.degreeOf(velo.getAngle());
c1.x = ship.x;
c1.y = ship.y;
if (ship.x > stage.stageWidth || ship.x < 0) velo.x *= -1;
if (ship.y > stage.stageHeight || ship.y < 0) velo.y *= -1;
}
You can see that the keyboard controls are updating flags to indicate whether the left, up, right, or down keys are being pressed. These flags will be captured by the enterframe event handler and update the magnitude and direction of the ship.
Step 13: Calculating the Reflection Vector
I’ve already covered the vector calculation of reflection vector in this post. Here, I shall just cover how to obtain the normal vector from gradient.
So the ActionScript below will implement the mathematical concept explained in the previous step. Check out the highlighted lines:
private function bounce():void
{
var gradient:Number = quadratic_equation.diff1(intersec_points[0].x);
var grad_vec:Vector2D = new Vector2D(1, gradient);
var left_norm:Vector2D = grad_vec.getNormal(false);
var right_norm:Vector2D = grad_vec.getNormal();
var chosen_vec:Vector2D;
if (velo.dotProduct(left_norm) > 0) chosen_vec = left_norm
else chosen_vec = right_norm
var chosen_unit:Vector2D = chosen_vec.normalise();
var proj:Number = velo.dotProduct(chosen_unit);
chosen_unit.scale(-2*proj);
velo = velo.add(chosen_unit);
}
Conclusion
Well, thanks for your time! If you’ve found this useful, or have any questions, do leave some comments.
In the first tutorial of this series, we took a look at drawing curves using equations and AS3. Now, we’re going to tackle solving those equations to find the roots of a curve – that is, the places where the curve crosses a given straight line. We can use this to predict collisions with curved surfaces, and to avoid “tunnelling” in Flash games.
Step 1: Quadratic Roots
First, time for some quick math revision. In this tutorial, we’ll just accept and apply the methods we’ll use, but interested readers can refer to Wikipedia’s page on quadratic equations for information about the mathematical deriviations.
So \(f(x)\) is a quadratic function. If \(f(x)\) is equivalent to 0, \(x\) can be obtained by this formula:
\(b^2 – 4ac\) is called the discriminant of the formula. If the discriminant is negative, the square root of the discriminant will produce imaginary roots, which we can’t plot. Conversely, if the discriminat is positive, you will have real number roots and you’ll be able to plot them onto the screen.
Step 2: Visualising Quadratic Roots
So what are roots? Well in our context they are nothing more than intersection points between the quadratic curve and a line. For example, suppose we are interested to find the intersection point(s) of the following set of equations:
\(
f(x)\ = \ ax^2+bx+c \\
g(x)\ = \ 0
\)
This is a typical scenario of looking for the intersection point(s) between a quadratic curve and the x-axis (because the x-axis is the line where y==0). Since by definition the intersection point(s) are shared by \(f(x)\) and \(g(x)\), we can conclude that \(f(x) = g(x)\) for the values of x that we are looking for.
It’s then a trivial operation where you just substitute the functions and then apply the formula from Step 1 to obtain the roots. Now there are several possibilities we can anticipate as shown below.
(As you can see, “imaginary roots” means, for our purposes, that the curve doesn’t ever cross the x-axis.)
Now let’s consider the case where \(g(x)\) is more than just a mundane horizontal line. Let’s say it’s a slanted line, \(g(x)\ =\ mx\ +\ d\). Now when we equate both functions, we’ll need to do a little precalculation before the formula can be effectively applied.
I’ve included an interactive Flash presentation below so feel free to drag the red and blue dots. Yellow dots indicate the intersection points. You may need to position the curve and line to intersect each other in order for the yellow dots to appear.
Step 3: Plotting This With ActionScript
The full script can be found in Demo1.as; here I’ll just explain a crucial extract of the code. Let’s look at the AS3 for drawing the curve and line:
private function redraw():void
{
var cmd:Vector.<int> = new Vector.<int>;
var coord:Vector.<Number> = new Vector.<Number>;
//redraw curve;
m1 = new Matrix3d(
curve_points[0].x * curve_points[0].x, curve_points[0].x, 1, 0,
curve_points[1].x * curve_points[1].x, curve_points[1].x, 1, 0,
curve_points[2].x * curve_points[2].x, curve_points[2].x, 1, 0,
0,0,0,1
);
m2 = new Matrix3d(
curve_points[0].y, 0, 0, 0,
curve_points[1].y, 0, 0, 0,
curve_points[2].y, 0, 0, 0,
0,0,0,1
)
m1.invert();
m2.append(m1);
quadratic_equation.define(m2.n11, m2.n21, m2.n31);
for (var i:int = 0; i < stage.stageWidth; i+=2)
{
if (i == 0) cmd.push(1);
else cmd.push(2);
coord.push(i, quadratic_equation.fx_of(i));
}
//draw line
n1 = new Matrix();
n1.a = line_points[0].x; n1.c = 1;
n1.b = line_points[1].x; n1.d = 1;
n2 = new Matrix();
n2.a = line_points[0].y; n2.c = 0;
n2.b = line_points[1].y; n2.d = 0;
n1.invert();
n2.concat(n1);
var x:Number = stage.stageWidth //y = mx + c
cmd.push(1); coord.push(0, n2.a * 0 + n2.b);
cmd.push(2); coord.push(x, n2.a * x + n2.b);
graphics.clear();
graphics.lineStyle(1);
graphics.drawPath(cmd, coord);
}
The bulk of ActionScript for drawing the curve from line 80~104 is largely borrowed from the previous tutorial, so I shall just explain a little about the code for drawing a line.
In the Flash presentation above, there are two interactive blue dots. Each of these has coordinates, and with both dots, a line is formed. Since both dots lie on the same line, they share a common slope and y-intercept to form one general line equation:
We can use a little algebra to solve for the two unknowns, \(m\) and \(d\). Given the coordinates of the two blue dots as \((x_1,\ y_1)\) and \((x_2,\ y_2)\):
(Note that a matrix with superscripted -1 refers to inverse of that matrix.)
So using this, \(m\) and \(d\) are calculated. We can can now draw the line by joining the coordinates \((0, y_3)\) and \((stage.stageWidth, y_4)\). How do you find \(y_3\) and \(y_4\)? Well now that \(m\), \(x\) and \(d\) are known, we can simply put all these values into the line general equation,
\(y\ =\ mx\ +\ d\)
..to get those \(y\)s.
Step 4: Calculate the Quadratic Roots
To calculate the position of the intersecting points, we shall use the formula from Step 1. This is done in EqQuadratic.as as the functions shown below:
/**Read-only
* Discriminant of equation
*/
public function get discriminant():Number {
//B*B-4*A*C
return _B * _B - 4 * _A * _C;
}
/**
* Performs calculation to obtain roots
*/
public function calcRoots():void {
var disc:Number = this.discriminant
//handle imaginary roots
if (disc < 0) {
disc *= -1;
var component_real:Number = -_B / (2 * _A);
var component_imaginary:Number = Math.sqrt(disc) / (2 * _A);
_root_i[0] = (component_real + "+ i" + component_imaginary).toString();
_root_i[1] = (component_real + "- i" + component_imaginary).toString();
}
//handle real roots
else {
var sqrt:Number = Math.sqrt(disc);
_root_R[0] = ( -_B + sqrt) / (2 * _A);
_root_R[1] = ( -_B - sqrt) / (2 * _A);
}
}
Further details of EqQuadratic.as:
Function
Type
Input Parameter
Functionality
EqQuadratic
Method
Nil
Class constructor
define
Method
Coefficients a, b and c of quadratic equation
Instantiate the coefficient values
fx_of
Method
Value of x
Returns \(f(x)\) of given \(x\) input.
calcRoots
Method
Nil
Performs calculation to obtain quadratic root
diff1
Method
\(x\) coordinate for the first degree differentiation
Differentiated \(f(x)\) of given \(x\) at the first degree.
diff2
Method
\(x\) coordinate for the second degree differentiation
Differentiated \(f(x)\) of given \(x\) at the second degree.
discriminat
Property, read only
Nil
Returns the value of discriminant, \(b^2 – 4ac\)
roots_R
Property, read only
Nil
Returns a Number vector for roots of real number. Elements are NaN if no real roots exist.
roots_i
Property, read only
Nil
Returns a String vector for roots of imaginary number. Elements are null if no imaginary roots exist.
Step 5: Plotting This With ActionScript
An example of utilising this EqQuadratic.as is in Demo1.as. After the initiation of EqQuadratic, we shall use it to calculate the roots. Then, after validating the presence of real roots, we’ll use them to plot the yellow dots.
Now the roots refer to only the \(x\) component of the coordinates. To obtain the \(y\)s, guess what? Again, we put the values of \(m\), \(d\) (calculated earlier in Step 3) and \(x\) (from the roots) into the line general equation, \(y\ =\ mx\ +\ d\). Check out the corresponding code in line 135 and 136.
Cubic roots, not surprisingly, are the intersection points between a cubic curve and a line. But a cubic curve is a little different to a quadratic curve, and in this respect the possibilities for where intersections could be located are different.
The image below shows a cubic curve intersecting with the x-axis:
Again, here’s a little Flash presentation for you to experiment with. Red and blue dots can be dragged while the yellow ones just indicate the intersection points.
Step 7: General Formula for Cubic Roots
The general formula to find a cubic curve was discovered by Cardano. Although I’m enticed to elaborate on the details, I’ll just point interested readers to the following links:
Anyway, the EqCubic.as class implements this formula to resolve roots of cubic functions along with other mathematical utility functions. Generally all the attributes and methods for EqCubic.as follow the desciption as tabled in Step 4, because both classes EqQuadratic.as and EqCubic.as implement one common interface, IEquation.as, except for the details listed below.
Function
Difference
define
A total of four coefficients (a, b, c, d) to input for cubic equation; just three for quadratic equation.
roots_R, root_i
The total of real and imaginary roots is three for a cubic equation, but two for a quadratic equation.
Step 8: Plotting This With ActionScript
Here’s the Actionscript implementation for the Flash presentation from Step 5. The full code is in Demo3.as.
Again, the ActionScript commands to draw a cubic curve are exactly the same as explained in my previous article, whereas Actionscript commands to draw the line are already explained in Step 3 of this one.
Now let’s move on to calculating and positioning the cubic roots:
After instantiating cubic_equation in the constructor we proceed on to define its coefficients, calculate the roots, and store the roots in a variable.
One little note on the roots: there are a maximum of three real roots for a cubic equation, but not all real roots are present in all situation as some roots may be imaginary. So what happens when there’s only one real root, for example? Well, one of the array of roots called from cubic_equation.roots_R will be a real number, while all the others will be Not a Number (NaN). Check out the highlighted ActionScript for this.
Step 9: Predicting Where an Object Will Collide With Curved Surface
A great application of calculating roots is projecting a collision point onto curved surface, as show below. Use the left and right arrow keys to steer the moving ship, and press up to accelerate. You will notice that collision points which would have happened in the past are slightly dimmed.
Step 10: Implementation
The idea is similar to that in my tutorial about predicting collision points. However, instead of colliding with a straight line, we’re now using a curved line. Let’s check out the code.
The snippet below is called every frame:
private function update(e:Event):void
{
//Steering left and right
if (control == 1) velo = velo.rotate(Math2.radianOf(-5));
else if (control == 2) velo = velo.rotate(Math2.radianOf(5));
//manipulating velocity
var currVelo:Number = velo.getMagnitude();
if (increase == 0) {
currVelo -= 0.5; currVelo = Math.max(currVelo, 1); //lower bound for velocity
}
else if (increase == 1) {
currVelo += 0.5; currVelo = Math.min(currVelo, 5); //upper bound for velocity
}
velo.setMagnitude(currVelo);
//update velocity
ship.x += velo.x; ship.y += velo.y; ship.rotation = Math2.degreeOf(velo.getAngle());
//reflect when ship is out of stage
if (ship.x <0 || ship.x > stage.stageWidth) velo.x *= -1;
if (ship.y <0 || ship.y > stage.stageHeight) velo.y *= -1;
redraw();
recalculate();
}
The core code lies in redraw and recalculate. Let’s first see what’s in redraw. It’s the same one we had been using in previous demos. One little note on drawing the line. We saw in previous demos that two dots are needed to draw get the equation. Well, here we only have one ship. So to get the second point, just add the ship’s velocity to its current position. I’ve highlighted the code for convenience.
private function redraw():void
{
var cmd:Vector.<int> = new Vector.<int>;
var coord:Vector.<Number> = new Vector.<Number>;
//redraw curve;
m1 = new Matrix3d(
w1.x * w1.x, w1.x, 1, 0,
w2.x * w2.x, w2.x, 1, 0,
w3.x * w3.x, w3.x, 1, 0,
0,0,0,1
);
m2 = new Matrix3d(
w1.y, 0, 0, 0,
w2.y, 0, 0, 0,
w3.y, 0, 0, 0,
0,0,0,1
)
m1.invert();
m2.append(m1);
quadratic_equation.define(m2.n11, m2.n21, m2.n31);
minX = Math.min(w1.x, w2.x, w3.x);
maxX = Math.max(w1.x, w2.x, w3.x);
for (var i:int = minX; i < maxX; i+=2)
{
if (i == minX) cmd.push(1);
else cmd.push(2);
coord.push(i, quadratic_equation.fx_of(i));
}
n1 = new Matrix();
n1.a = ship.x; n1.c = 1;
n1.b = ship.x + velo.x; n1.d = 1;
n2 = new Matrix();
n2.a = ship.y; n2.c = 0;
n2.b = ship.y + velo.y; n2.d = 0;
n1.invert();
n2.concat(n1);
var x:Number = stage.stageWidth //y = mx + c
cmd.push(1); coord.push(0, n2.a * 0 + n2.b);
cmd.push(2); coord.push(x, n2.a * x + n2.b);
graphics.clear();
graphics.lineStyle(1);
graphics.drawPath(cmd, coord);
}
Now for recalculate, I’ve done a little vector calculation to check whether the point is behind or in front of the ship. Check out the highlighted code:
private function recalculate():void
{
quadratic_equation.define(m2.n11, m2.n21 - n2.a, m2.n31 - n2.b);
quadratic_equation.calcRoots();
var roots:Vector.<Number> = quadratic_equation.roots_R;
for (var i:int = 0; i < roots.length; i++)
{
var reposition:Sprite = getChildByName("c" + i) as Sprite
//conditions:
//real root, value of x within the range
if (!isNaN(roots[i]) && roots[i] > minX && roots[i] < maxX) {
reposition.x = roots[i];
reposition.y = n2.a * roots[i] + n2.b;
//discriminating between future and already happened collision point
var vec:Vector2D = new Vector2D(reposition.x - ship.x, reposition.y - ship.y);
if (velo.dotProduct(vec) < 0) reposition.alpha = 0.4;
else reposition.alpha = 1
}
else {
reposition.x = -100; reposition.y = -100;
}
}
}
Step 11: Time-Based Collision Detection
Another great application is not quite as obvious as the first. To perform a more accurate collision detection, instead of basing our conclusion on the distance between two objects, we’ll uae their time to impact. Why? Because “tunneling” can happen if we use distance based collision detection:
Consider a collision detection algorithm that’s based on distance for two circles. Of the four frames shown, only frame 2.15 successfully detected collision between two circles. Why? Because the current distance between the gray and the red circles’ centers is less than the sum of both circles’ radii.
(Readers interested on more details on this topic can refer to this article.)
The problem is caused by how Flash proceeds by one discrete frame at a time, which means that only frames 1, 2, and 3 will be successfully captured, and not the moments in between thos snapshots of time. Now the gray and red circles did not collide in these frames according to a distance-based calculation, so the red circle tunnels right through the gray one!
To fix this, we need a way to see the collision that occurred between frames 2 and 3. We need to calculate the time to impact between two circles. For example, once we check that time to impact is less than 1 frame at frame 2, this means that once Flash proceeds 1 frame forward collision or even tunneling will have definitely had taken place.
Given the scenario above, the two gray and red circles are currently located at \((x_{gray},\ y_{gray})\) and \((x_{red},\ y_{red})\). They are moving at \(v_{gray}\) and \(v_{red}\) respectively, and set on a collision path. We are interested to calculate the time taken, \(t\), for them to reach positions \((x’_{gray},\ y’_{gray})\) and \((x’_{red},\ y’_{red})\), indicated by the translucent gray and red circles, where the collision occurred.
Note that I’ve derived the horizontal and vertical components of \(v_{gray}\) into \(v_{gray_x}\) and \(v_{gray_y}\). Same goes with velocity of the red circle; check out this Quick Tip to get to know how these components are derived.
We still lack one relationship to bind all these equations together. Let’s see the image below.
The other relationship goes back to Pythagoras. When both circles meet, the distance between both centers is exactly \(rad_{gray}\) plus \(rad_{red}\).
This is where you substitute equations 1~4 into equation 5. I understand its quite daunting mathematically, so I’ve separated it out into Step 13. Feel free to skip it to arrive at the result at Step 14.
Now at a glance, we can easily see the highest power of \(t\) is 2. So we have ourselves a quadratic equation. Let’s collect all the coefficients contributed by these three terms according to their powers.
Note that this only caters for one term in \(eq.\ 5\). We’ll need to perform the same process for another term \((y’_{gray}-y’_{red})^2\). Since they have the same algebraic form, the result should also be the same.
\[
(y'_{gray}-y'_{red})^2\\
=(v_{gray_y}-v_{red_y})^2*t^2+2(v_{gray_y}-v_{red_y})(y_{gray}-y_{red})*t +(y_{gray}-y_{red})^2
\]
Thus after rearrangement in terms of \(t\), \(eq.\ 5\) should be as follow.
Now this is one huge quadratic formula. We’ll try to group the coefficients into that accepted by EqQuadratic. Compare the two forms:
\[
ax^2+bx+c = 0\\
(p^2+r^2)*t^2+2(pq+rs)*t+(q^2+s^2-(rad_{gray}+rad_{red})^2) = 0\\
a = p^2+r^2)\\
b = 2(pq+rs)\\
c = (q^2+s^2-(rad_{gray}+rad_{red})^2)
\]
Step 15: Sample Implementation
So here’s a Flash presentation to demonstrate the idea. You will see two particles on the stage, one gray and the other red. Both are connected to an arrow, indicating the magnitude and direction of velocity.
Click the "Next" button to progress one frame in time.
Click the "Back" button to revert one frame in time.
To alter the velocity of the particles, press:
“Up” and “down” to increase and decrease the velocity’s magnitude, respectively.
“Left” and “right” to rotate the velocity.
"N" to switch which circle you control.
Finally, to toggle visibility of the arrows, press "V"
Step 16: A Note on the Quadratic Roots
There are two roots to the quadratic equation. In this context, we are interested in the real roots. However if the two particles are not set on collision path (both paths are parallel to each other), then imaginary roots will be produced instead of real roots. In this case, both real roots will remain NaN.
If both particles are set on a collision path, we will get two real roots. But what do these two roots represent?
Recall in Step 12 that we made used of Pythagoras’s Theorem to tie \((x’_{gray},\ y’_{gray})\) and \((x’_{red},\ y’_{red})\) together into an equation. Well, there are two situations where the distance between two circles’ centers are exactly the sum of both radii: one before collision and one after collision. Take a look at this image:
So which one do we choose? Obviously the first because we’re not interested in the instance after collision. So we should always choose the lesser value of both roots and evaluate it. If the value is positive and less than 1, a collision will happen during the next frame. If the value is negative, the collision happened in the past.
Step 17: The ActionScript Explained
Let’s look at the Actionscript implemented for this example. First, the variables.
//c1 is the gray circle
//c2 is the red circle
private var c1:Circle, c2:Circle;
//v1 is the gray circle's velocity
//v2 is the red circle's velocity
private var v1:Vector2D, v2:Vector2D, toggle:Boolean = true, usingV1:Boolean = true;
//tri1 will form the arrowhead of the v1
//tri2 will form the arrowhead of the v2
private var tri1:Triangle, tri2:Triangle;
private var container:Sprite;
private var eq:EqQuadratic;
Then the calculation of roots. You may want to cross check the following ActionScript with the variables above.
var p:Number = v1.x - v2.x;
var q:Number = c1.x - c2.x;
var r:Number = v1.y - v2.y;
var s:Number = c1.y - c2.y;
var a:Number = p * p + r * r;
var b:Number = 2 * (p * q + r * s);
var c:Number = q * q + s * s - (c1.radius + c2.radius) * (c1.radius + c2.radius);
eq.define(a, b, c);
eq.calcRoots();
var roots:Vector.<Number> = eq.roots_R;
Here’s how you should interpret the roots:
//if no real roots are available, then they are on not on collision path
if (isNaN(roots[0]) && isNaN(roots[1])) {
t.text = "Particles not on collision path."
}
else {
var time:Number = Math.min(roots[0], roots[1])
var int_time:int = time * 1000;
time = int_time / 1000;
t.text = "Frames to impact: "+time.toString() + "\n";
if(time>1) t.appendText("Particles are closing...")
else if (time > 0 && time < 1) t.appendText("Particles WILL collide next frame!")
else if (time < 0) t.appendText("Collision had already happened.");
}
Step 17: Application of Cubic Roots
So far we’ve studied quadratic and cubic roots in ActionScript, as well as taking a close look at two examples of quadratic root application.
Conclusion
Thanks for taking the time to read this tutorial. Do leave a comment if you see other applications of quadratic roots (or any errors!)
We see lines used in a lot of scenarios; curves are also used but perhaps not as frequently – but that doesn’t undermine their importance! In this tutorial we shall take a closer look at curves, particularly the quadratic and cubic curve, along with some of their commonly used mathematical features.
Final Result Preview
Let’s take a look at the final result we will be working towards. Drag the red dots and see the gradients change in position.
And here’s another demo, using cubic curves, without the gradients:
Step 1: Curves
Quadratic and cubic will be featured in each of these sections. So let’s first look at the equation of curves. These equations are written in polynomial form, starting with the term of highest degree. The first one is quadratic equation (highest degree is 2); the second is cubic equation (highest degree is 3).
\[f(x) = Ax^2 + Bx + C\ ... (eq\ 1)\]
\[g(x) = Ax^3 + Bx^2 + Cx + D\ ... (eq\ 2)\]
Note that A, B, C and D are real numbers. So now that we are aquainted with it, let’s try to visualise it. Graphing curves will be our next attempt.
Step 2: Graphing Curves
First, let’s graph a quadratic curve. I’m sure all readers have graphed quadratic curve in high school math class, but just to refresh your memory, I present graphs below. They are placed side by side to ease comparison.
Left graph is using Cartesian coordinate space
Right graph is using Flash coordinate space
The obvious difference is the inverted y-axis on Flash coordinate space. They look simple overall, right? Okay, now we’re ready to plot onto Flash coordinate space.
Step 3: Quadratic Coefficients
To position quadratic curves at the right spot, we need to understand their corresponding equations. The curve drawn is really dependant on the equation’s coefficients (for the case of quadratic, those are A, B and C).
I’ve included a Flash presentation below so you can easily tweak these coefficients and get immediate feedback.
To study the effects of individual coefficients on the overall curve, I suggest following the steps below to experiment with the Flash presentation above.
While setting A and B to 0, tweak the value of C to both positive and negative values. You’ll see the line’s height change.
Now tweak the value of B between positive and negative values. Observe what happens to gradient of line.
Now tweak the value of A between positive and negative values, and compare the results.
Then tweak B between being positive and negative again. Observe the curve’s always cutting through the origin.
Finally tweak C. Observe the whole curve shift along the y-axis.
Another interesting observation is that throughout the second and third steps of the above, the point of inflection (i.e. the turning point) stays at the same point on the y-axis.
Step 4: Alternative Equation One
You quickly see that positioning a curve is somewhat difficult. The equation used is impractical if we want to, say, locate the coordinates of the lowest point on a curve.
Solution? We’ll rewrite the equation into a desired form. Check out the following equation:
\[f(x) = P(x+Q)^2+R\]
It’s still a quadratic equation, but it’s taken another form. Now we can easily control the minimum and maximum points on the curve. In the previous Flash presentation, click on button “Approach 1″ on the top right and play with the new values.
Here’s a brief explanation of the coefficients’ roles:
Coefficient
Role
P
Control the curve’s steepness.
Q
Control displacement of curve’s turning point along x-axis.
R
Control displacement of curve’s turning point along y-axis.
Nonetheless, it’s still a difficult task to make the curve pass through a given set of points. We’d have to rigorously pre-calculate on paper before translating it to code.
Fortunately, there is a better solution. But before going through it, let’s have a look at the ActionScript implementation as of now.
Step 5: ActionScript Implementation
Here are the equations written as ActionScript functions (check Graphing.as in the source download).
private function quadratic1(x:Number, A:Number, B:Number, C:Number):Number {
//y = A(x^2) + B(x) + C
return A*x*x+ B*x + C
}
private function quadratic2(x:Number, P:Number, Q:Number, R:Number):Number {
// y = P * (x + Q)^2 + R
return P*(x+Q)*(x+Q) + R
}
And here’s an implementation of the drawing method using Graphics.drawPath(). Just a note that all curves in this article are drawn in similar fashion.
First the variables…
private var points:Vector.<Number> = new Vector.<Number>;
private var drawCommand:Vector.<int> = new Vector.<int>;
Now the y-positions, calculated based on the x-positions and the given coefficients.
private function redraw(A:Number, B:Number, C:Number):void {
for (var i:int = 0; i < 400; i++) {
var x:Number = i - 200;
points[i * 2] = x * 10 + stage.stageWidth >> 1;
if (isApproach1) {
points[i * 2 + 1] = quadratic1(x, A, B, C) + stage.stageHeight >> 1
}
else {
points[i * 2 + 1] = quadratic2(x, A, B, C) + stage.stageHeight >> 1
}
if (i == 0) drawCommand[i] = 1;
else drawCommand[i] = 2;
}
graphics.clear();
graphics.lineStyle(1);
graphics.drawPath(drawCommand, points);
}
(Confused about the >> operator? Take a look at this tutorial.)
Step 6: Alternative Equation Two
Suppose we’re given three points that the quadratic curve must cross through; how do we form the corresponding equation? More specifically, how can we determine the coefficient values of the equation? Linear algebra comes to the rescue. Let’s analyse this problem.
We know that quadratic equations always take form as written in eq. 1 in Step 1.
\[f(x) = Ax^2 + Bx + C\ ... (eq\ 1)\]
Since all three coordinates given are lying on the same curve, they must each satisfy this equation, with the same coefficients as the equation of the curve that we are looking for. Let’s write this down in equation form.
Given three coodinates:
\(S\ \left(S_x,\ S_y\right)\)
\(T\ \left(T_x,\ T_y\right)\)
\(U\ \left(U_x,\ U_y\right)\)
Substitute these values into (eq 1). Note that A, B, C are unknowns at the moment.
Of course we can use simultaneous equations instead, but I prefer using matrices because it’s simpler. (Editor’s note: as long as you understand matrices, that is!)
We’ll get the inverse of K and multiply by the J matrix to get L. After we have successfully solved for A, B, C, we’ll just substitute into the quadratic equation. Thus, we’ll have a quadratic curve that passes through all three points.
Step 7: Importing Coral
As mentioned in the previous step, we need to perform a 3×3 matrix inversion and multiplication. ActionScript’s flash.geom.matrix class won’t be able to help in this. Of course, we have a choice to utilise flash.geom.Matrix3D, class but I prefer the Coral library because I can pry into these custom classes and examine what’s happening under the hood. I personally find this very useful whenever at doubt on proper use of commands even after reading the API documentation.
So download and place the unzipped Coral files into your project source folder.
Step 8: ActionScript Implementation
Here’s a sample of the result. Try to reposition the red dots and see the quadratic curve redrawn to cross through all three points.
Step 9: Implementation Explained
You can find the full script in Draw_curve.as. The following ActionScript is just to enable mouse controls on the little dots.
public function Draw_Curve()
{
//setting up controls
c1 = new Circle(0xFF0000); addChild(c1); c1.x = stage.stageWidth * 0.2; c1.y = stage.stageHeight >> 1;
c2 = new Circle(0xFF0000); addChild(c2); c2.x = stage.stageWidth * 0.5; c2.y = stage.stageHeight >> 1;
c3 = new Circle(0xFF0000); addChild(c3); c3.x = stage.stageWidth * 0.8; c3.y = stage.stageHeight >> 1;
c1.addEventListener(MouseEvent.MOUSE_DOWN, move);
c1.addEventListener(MouseEvent.MOUSE_UP, move);
c2.addEventListener(MouseEvent.MOUSE_DOWN, move);
c2.addEventListener(MouseEvent.MOUSE_UP, move);
c3.addEventListener(MouseEvent.MOUSE_DOWN, move);
c3.addEventListener(MouseEvent.MOUSE_UP, move);
redraw()
}
private function move(e:MouseEvent):void {
if (e.type == "mouseDown") {
e.target.startDrag()
e.target.addEventListener(MouseEvent.MOUSE_MOVE, update);
}
else if (e.type == "mouseUp") {
e.target.stopDrag();
e.target.removeEventListener(MouseEvent.MOUSE_MOVE, update);
}
}
private function update(e:MouseEvent):void {
redraw();
}
The core lies in the redraw function. I’ve highlighted the matrix operations and the quadratic function for the redraw process.
private function redraw():void
{
K = new Matrix3d( c1.x * c1.x, c1.x, 1, 0,
c2.x * c2.x, c2.x, 1, 0,
c3.x * c3.x, c3.x, 1, 0,
0, 0, 0, 1);
K.invert()
L = new Matrix3d( c1.y, 0, 0, 0,
c2.y, 0, 0, 0,
c3.y, 0, 0, 0,
0, 0, 0, 0);
L.append(K);
graphics.clear();
var points:Vector.<Number> = new Vector.<Number>;
var cmd:Vector.<int> = new Vector.<int>;
for (var i:int = 0; i < 200; i++) {
//current x
var x:Number = i * 2;
//f(x) = A (x^2) + B (x) + C
var y:Number = L.n11* x* x + L.n21 * x + L.n31 ;
points.push(x, y);
if (i == 0) cmd.push(1);
else cmd.push(2);
}
graphics.lineStyle(1);
graphics.drawPath(cmd, points);
}
So you can see that the matrix K was initialised and inverted before being appended onto matrix J.
The append() function multiplies the current matrix, J, with the input matrix, K, placed to its left. Another noteworthy detail is that we don’t utilise all the rows and columns in K and J matrices. However since matrix inversion can only happen with a square matrix, we need to fill in the 4th row, 4th column element of K with 1. (There’s no need to do this for J because we don’t need its inversion in our calculation.) Thus, you can see all the other elements are 0 except for the first column.
Step 10: Graphing Cubic Curve
So that’s all for drawing quadratic curves. Let’s move on to cubic curves.
Again, we’ll have a little revision of graphing these curves. Check out the following image:
When you compare this curve to that of quadratic, you will notice that it is steeper, and that a portion of the curve is below the x-axis. One half is mirrored vertically, compared to a quadratic.
Step 11: Cubic Coefficients
I’ve included the following Flash presentation to let you experiment with the coefficients of a cubic equation. Try tweaking the value of A from positive to negative and observe the difference in the curve produced.
Step 12: ActionScript Implementation
Here’s the important section of the implementation of the graphing above:
private function redraw(A:Number, B:Number, C:Number, D:Number):void {
for (var i:int = 0; i < 400; i++) {
var x:Number = i - 200;
points[i * 2] = x * 10 + stage.stageWidth >> 1;
points[i * 2 + 1] = cubic1(x, A, B, C, D) + stage.stageHeight >> 1
if (i == 0) drawCommand[i] = 1;
else drawCommand[i] = 2;
}
graphics.clear();
graphics.lineStyle(1);
graphics.drawPath(drawCommand, points);
}
private function cubic1(x:Number, A:Number, B:Number, C:Number, D:Number):Number {
//y = A(x^3) + B(x^2) + C(x) + D
return A*x*x*x+ B*x*x + C*x +D
}
Again, it’s difficult to position the cubic curve according to a set of points it crosses through. Once again, we refer to linear algebra for an alternative.
Step 13: Alternative Method
We know from Step 6 that the coefficients of a quadratic equation can be calculated based on three given points, and the curve drawn from it will cross through those points. A similar approach can be performed with any four given points to obtain a cubic equation:
\(S\ \left(S_x,\ S_y\right)\)
\(T\ \left(T_x,\ T_y\right)\)
\(U\ \left(U_x,\ U_y\right)\)
\(V\ \left(V_x,\ V_y\right)\)
Substitute these coordinates into (eq 2). Note that A, B, C, D are unknowns.
Now we will utilise all elements in the 4×4 matrix for Q and the whole first column for P. Then Q is inversed and applied to P.
Step 14: ActionScript Implementation
Again, we set up the mouse controls to allow dragging of those points. When any of those points are being dragged, recalculation and redrawing of the curve constantly happen.
redraw is the crucial function where everything happened.
private function redraw():void
{
var left:Matrix3d = new Matrix3d(c1.x * c1.x* c1.x, c1.x* c1.x, c1.x , 1,
c2.x * c2.x * c2.x, c2.x* c2.x, c2.x , 1,
c3.x * c3.x * c3.x, c3.x* c3.x, c3.x , 1,
c4.x * c4.x * c4.x, c4.x* c4.x, c4.x , 1);
left.invert()
var right:Matrix3d = new Matrix3d(c1.y, 0, 0, 0,
c2.y, 0, 0, 0,
c3.y, 0, 0, 0,
c4.y, 0, 0, 0);
right.append(left);
//f(x) = A(x^3) + B (x^2) +C (x) + D
graphics.clear();
var points:Vector.<Number> = new Vector.<Number>;
var cmd:Vector.<int> = new Vector.<int>;
for (var i:int = 0; i < 200; i++) {
var x:Number = i * 2;
var y:Number = right.n11 * x * x * x+
right.n21 * x * x+
right.n31 * x +
right.n41;
points.push(x, y);
if (i == 0) cmd.push(1);
else cmd.push(2);
}
graphics.lineStyle(1);
graphics.drawPath(cmd, points);
}
Finally, let’s look at the product. Click and move the red dots to see cubic curve drawn to pass through all those points.
Step 15: Polynomials of Higher Degree
We just gone through drawing polynomials of degree 2 and 3 (quadratic and cubic). From our experience, we can predict that calculation for polynomial of degree 4 (quintic) will require five points, which will require 5×5 matrix, and so on for polynomials of even higher degrees.
Unfortunately, Coral and flash.geom.Matrix3D only allow for 4×4 matrices, so you’ll have write your own class if the need does come. It’s seldom required in games, though.
Step 16: Dividing Regions
Let’s try to apply our knowledge to divide regions on our stage. This requires some revision of equation inequalities. Check out the image below.
This image above shows a curve dividing the regions into two:
Blue region on top, where for each point y is greater than the equation of the curve.
Red region at bottom, where for each point y is less than the equation of the curve.
It’s not hard to grasp this concept. In fact, you have already experimented on this in Step 11 as you tweaked the coefficients of the cubic formula. Imagine, in the coordinate system, that there is an infinite number of curves, all differentiated by just a slight change in D:
Step 17: ActionScript Implementation
So here’s the sample of output for quadratic curve. You can try to move the red dot around and see the regions coloured.
Here’s the important ActionScript snippet. Check out the full script in Region_Curve.as
private function redraw():void {
var left:Matrix3d = new Matrix3d(c1.x * c1.x, c1.x, 1, 0,
c2.x * c2.x, c2.x, 1, 0,
c3.x * c3.x, c3.x, 1, 0,
0, 0, 0, 1);
left.invert()
var right:Matrix3d = new Matrix3d(c1.y, 0, 0, 0,
c2.y, 0, 0, 0,
c3.y, 0, 0, 0,
0, 0, 0, 0);
right.append(left);
//D = A (x^2)+ B (x) +C
for each (var item: Circle in background) {
var D:Number = right.n11* item.x * item.x + right.n21 * item.x + right.n31 ;
//trace(background[i].y);
if (item.y > D) item.color = 0;
else item.color = 0xAAAAAA;
}
}
Here’s the sample with regard to cubic curve.
And the implementation that comes with it. Again, full script’s in Region_Curve2.as
//D = A + B (x) +C (x^2)
for each (var item: Circle in background) {
var D:Number = right.n11 * item.x * item.x * item.x;+
right.n21 * item.x * item.x +
right.n31 * item.x +
right.n41
//trace(background[i].y);
if (item.y > D) item.color = 0;
else item.color = 0xAAAAAA;
}
Step 18: Variations
How about some tweaks to change the color across different curves? Again, mouse click on the red dots and see the gradient changes across the screen.
Step 19: ActionScript Implementation
Here’s the important ActionScript snippet extracted from Region_Curve3.as. First of all we’ll want to find out the maximum and minimum offset from the original curve.
var max:Number = 0;
var min:Number = 0;
var Ds:Vector.<Number> = new Vector.<Number>;
//D = A(x^2) + B (x) +C
for each (var item: Circle in background) {
var D:Number = right.n11 * item.x * item.x + right.n21 * item.x + right.n31;
var offset:Number = item.y - D;
Ds.push(offset);
if (item.y > D && offset > max) max = offset;
else if (item.y < D && offset < min) min = offset;
}
Once done, we’ll apply it to colouring the individual dots.
//color variations based on the offset
var color:Number
for (var i:int = 0; i < background.length; i++) {
if (Ds[i] > 0) {
color = Ds[i] / max * 255 //calculating color to slot in
background[i].color = color<<16 | color<<8 | color; //define a grayscale
}
else if (Ds[i] < 0) {
color = Ds[i] / min * 255;
background[i].color = color<<16; //define a gradient of red
}
}
Conclusion
So that all for the drawing of curves. Next up, finding roots of a quadratic and cubic curve. Thanks for reading. Do share if you see some real life applications that takes advantage of this tutorial.
Code optimization aims to maximize the performance of your Flash assets, while using as little of the system’s resources – RAM and CPU – as possible. In this tutorial, starting off with a working but resource-hogging Flash app, we will gradually apply many optimization tweaks to its source code, finally ending up with a faster, leaner SWF.
Final Result Preview
Let’s take a look at the final result we will be working towards:
Note that the “Memory Used” and “CPU Load” stats are based on all the SWFs you have open across all browser windows, including Flash banner ads and the like. This may make the SWF appear more resource intensive than it actually is.
Step 1: Understanding the Flash Movie
The Flash movie has two main elements: a particle simulation of fire, and a graph showing the animation’s resource consumption over time. The graph’s pink line tracks the total memory consumed by the movie in megabytes, and the green line plots CPU load as a percentage.
ActionScript objects take up most of the memory allocated to the Flash Player, and the more ActionScript objects a movie contains, the higher its memory consumption. In order to keep a program’s memory consumption low, the Flash Player regularly does some garbage collection by sweeping through all ActionScript objects and releasing from memory those no longer in use.
A memory consumption graph normally reveals a hilly up-down pattern, dipping each time garbage collection is performed, then slowly rising as new objects are created. A graph line that’s only going up points to a problem with garbage collection, as it means new objects are being added to memory, while none are being removed. If such a trend continues, the Flash player may eventually crash as it runs out of memory.
The CPU load is calculated by tracking the movie’s frame rate. A Flash movie’s frame rate is much like its heartbeat. With each beat, the Flash Player updates and renders all on-screen elements and also runs any required ActionScript tasks.
It is the frame rate that determines how much time Flash Player should spend on each beat, so a frame rate of 10 frames per second (fps) means at least 100 milliseconds per beat. If all the required tasks are performed within that time, then Flash Player will wait for the remaining time to pass before moving on to the next beat. On the other hand, if the required tasks in a particular beat are too CPU intensive to be completed within the given time frame, then the frame rate automatically slows down to allow for some extra time. Once the load lightens, the frame rate speeds up again, back to the set rate.
(The frame rate may also be automatically throttled down to 4fps by the Flash Player when the program’s parent window looses focus or goes offscreen. This is done to conserve system resources whenever the user’s attention is focused elsewhere.)
What this all means is that there are actually two kinds of frame rates: the one you originally set and hope your movie always runs at, and the one it actually runs at. We’ll call the one set by you the target frame rate, and the one it actually runs at the actual frame rate.
The graph’s CPU load is calculated as a ratio of actual to target frame rate. The formula used to calculate this is:
CPU load = ( target frame rate - actual frame rate ) / actual frame rate * 100
For example, if the target frame rate is set to 50fps but the movie actually runs at 25fps, the CPU load will be 50% – that is, ( 50 - 25 )/ 50 * 100.
Please note that this is not the actual percentage of system CPU resources used by the running movie, but rather a rough estimate of the actual value. For the optimization process outlined here, this estimate is a good enough metric for the task at hand. To get the actual CPU usage, use the tools provided by your operating system, e.g. the Task Manager in Windows. Looking at mine it right now, it shows the unoptimized movie is using 53% of CPU resources, while the movie’s graph shows a CPU load of 41.7%.
PLEASE NOTE: All the movie screenshots in this tutorial were taken from the standalone version of Flash Player. The graph will most likely show different numbers on your system, depending on your operating system, browser, and Flash Player version. Having any other currently running Flash apps in different browser windows or flash players may also affect the memory use reported by some systems. When analyzing the perfomance of your program, always ensure that no other Flash programs are running as they may corrupt your metrics.
With the CPU load, expect it to shoot up to over 90% whenever the movie goes off screen – for example if you switch to another browser tab or scroll down the page. The lower frame rate that causes this will not be caused by CPU intensive tasks, but by Flash throttling down the frame rate whenever you look elsewhere. Whenever this happens, wait a few seconds for the CPU load graph to settle to its proper CPU load values after the normal frame rate kicks in.
Step 2: Does This Code Make My Flash Look Fat?
The movie’s source code is shown below and contains just one class, named Flames, which is also the document class. The class contains a set of properties to keep track of the movie’s memory and CPU load history, which is then used to draw a graph. The memory and CPU load statistics are calculated and updated in the Flames.getStats() method, and the graph is drawn by calling Flames.drawGraph() on each frame. To create the fire effect, the Flames.createParticles() method first generates hundreds of particles each second, which are stored in the fireParticles array. This array is then looped through by Flames.drawParticles() , which uses each particle’s properties to create the effect.
Take some time to study the Flames class. Can you already spot any quick changes that will go a long way in optimizing the program?
package com.pjtops{
import flash.display.MovieClip;
import flash.events.Event;
import fl.motion.Color;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.system.System;
import flash.utils.getTimer;
public class Flames extends MovieClip{
private var memoryLog = new Array(); // stores System.totalMemory values for display in the graph
private var memoryMax = 0; // the highest value of System.totalMemory recorded so far
private var memoryMin = 0; // the lowest value of System.totalMemory recorded so far
private var memoryColor; // the color used by text displaying memory info
private var ticks = 0; // counts the number of times getStats() is called before the next frame rate value is set
private var frameRate = 0; //the original frame rate value as set in Adobe Flash
private var cpuLog = new Array(); // stores cpu values for display in the graph
private var cpuMax = 0; // the highest cpu value recorded so far
private var cpuMin = 0; // the lowest cpu value recorded so far
private var cpuColor; // the color used by text displaying cpu
private var cpu; // the current calculated cpu use
private var lastUpdate = 0; // the last time the framerate was calculated
private var sampleSize = 30; // the length of memoryLog & cpuLog
private var graphHeight;
private var graphWidth;
private var fireParticles = new Array(); // stores all active flame particles
private var fireMC = new MovieClip(); // the canvas for drawing the flames
private var palette = new Array(); // stores all available colors for the flame particles
private var anchors = new Array(); // stores horizontal points along fireMC which act like magnets to the particles
private var frame; // the movieclips bounding box
// class constructor. Set up all the events, timers and objects
public function Flames() {
addChildAt( fireMC, 1 );
frame = new Rectangle( 2, 2, stage.stageWidth - 2, stage.stageHeight - 2 );
var colWidth = Math.floor( frame.width / 10 );
for( var i = 0; i < 10; i++ ){
anchors[i] = Math.floor( i * colWidth );
}
setPalette();
memoryColor = memoryTF.textColor;
cpuColor = cpuTF.textColor;
graphHeight = graphMC.height;
graphWidth = graphMC.width;
frameRate = stage.frameRate;
addEventListener( Event.ENTER_FRAME, drawParticles );
addEventListener( Event.ENTER_FRAME, getStats );
addEventListener( Event.ENTER_FRAME, drawGraph );
}
//creates a collection of colors for the flame particles, and stores them in palette
private function setPalette(){
var black = 0x000000;
var blue = 0x0000FF;
var red = 0xFF0000;
var orange = 0xFF7F00;
var yellow = 0xFFFF00;
var white = 0xFFFFFF;
palette = palette.concat( getColorRange( black, blue, 10 ) );
palette = palette.concat( getColorRange( blue, red, 30 ) );
palette = palette.concat( getColorRange( red, orange, 20 ) );
palette = palette.concat( getColorRange( orange, yellow, 20 ) );
palette = palette.concat( getColorRange( yellow, white, 20 ) );
}
//returns a collection of colors, made from different mixes of color1 and color2
private function getColorRange( color1, color2, steps){
var output = new Array();
for( var i = 0; i < steps; i++ ){
var progress = i / steps;
var color = Color.interpolateColor( color1, color2, progress );
output.push( color );
}
return output;
}
// calculates statistics for the current state of the application, in terms of memory used and the cpu %
private function getStats( event ){
ticks++;
var now = getTimer();
if( now - lastUpdate < 1000 ){
return;
}else {
lastUpdate = now;
}
cpu = 100 - ticks / frameRate * 100;
cpuLog.push( cpu );
ticks = 0;
cpuTF.text = cpu.toFixed(1) + '%';
if( cpu > cpuMax ){
cpuMax = cpu;
cpuMaxTF.text = cpuTF.text;
}
if( cpu < cpuMin || cpuMin == 0 ){
cpuMin = cpu;
cpuMinTF.text = cpuTF.text;
}
var memory = System.totalMemory / 1000000;
memoryLog.push( memory );
memoryTF.text = String( memory.toFixed(1) ) + 'mb';
if( memory > memoryMax ){
memoryMax = memory;
memoryMaxTF.text = memoryTF.text;
}
if( memory < memoryMin || memoryMin == 0 ){
memoryMin = memory;
memoryMinTF.text = memoryTF.text;
}
}
//render's a graph on screen, that shows trends in the applications frame rate and memory consumption
private function drawGraph( event ){
graphMC.graphics.clear();
var ypoint, xpoint;
var logSize = memoryLog.length;
if( logSize > sampleSize ){
memoryLog.shift();
cpuLog.shift();
logSize = sampleSize;
}
var widthRatio = graphMC.width / logSize;
graphMC.graphics.lineStyle( 3, memoryColor, 0.9 );
var memoryRange = memoryMax - memoryMin;
for( var i = 0; i < memoryLog.length; i++ ){
ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
xpoint = (i / sampleSize) * graphWidth;
if( i == 0 ){
graphMC.graphics.moveTo( xpoint, -ypoint );
continue;
}
graphMC.graphics.lineTo( xpoint, -ypoint );
}
graphMC.graphics.lineStyle( 3, cpuColor, 0.9 );
for( var j = 0; j < cpuLog.length; j++ ){
ypoint = cpuLog[j] / 100 * graphHeight;
xpoint = ( j / sampleSize ) * graphWidth;
if( j == 0 ){
graphMC.graphics.moveTo( xpoint, -ypoint );
continue;
}
graphMC.graphics.lineTo( xpoint, -ypoint );
}
}
//renders each flame particle and updates it's values
private function drawParticles( event ) {
createParticles( 20 );
fireMC.graphics.clear();
for ( var i in fireParticles ) {
var particle = fireParticles[i];
if (particle.life == 0 ) {
delete( fireParticles[i] );
continue;
}
var size = Math.floor( particle.size * particle.life/100 );
var color = palette[ particle.life ];
var transperency = 0.3;
if( size < 3 ){
size *= 3;
color = 0x333333;
particle.x += Math.random() * 8 - 4;
particle.y -= 2;
transperency = 0.2;
}else {
particle.y = frame.bottom - ( 100 - particle.life );
if( particle.life > 90 ){
size *= 1.5;
}else if( particle.life > 45){
particle.x += Math.floor( Math.random() * 6 - 3 );
size *= 1.2;
}else {
transperency = 0.1;
size *= 0.3;
particle.x += Math.floor( Math.random() * 4 - 2 );
}
}
fireMC.graphics.lineStyle( 5, color, 0.1 );
fireMC.graphics.beginFill( color, transperency );
fireMC.graphics.drawCircle( particle.x, particle.y, size );
fireMC.graphics.endFill();
particle.life--;
}
}
//generates flame particle objects
private function createParticles( count ){
var anchorPoint = 0;
for(var i = 0; i < count; i++){
var particle = new Object();
particle.x = Math.floor( Math.random() * frame.width / 10 ) + anchors[anchorPoint];
particle.y = frame.bottom;
particle.life = 70 + Math.floor( Math.random() * 30 );
particle.size = 5 + Math.floor( Math.random() * 10 );
fireParticles.push( particle );
if(particle.size > 12){
particle.size = 10;
}
particle.anchor = anchors[anchorPoint] + Math.floor( Math.random() * 5 );
anchorPoint = (anchorPoint == 9)? 0 : anchorPoint + 1;
}
}
}
}
It’s a lot to take in, so don’t worry – we’ll go through the various improvements in the rest of this tutorial.
Step 3: Use Strong Typing by Assigning Data Types to All Variables
The first change we’ll make to the class is to specify the data type of all declared variables, method parameters, and method return values.
For example, changing this
protected var memoryLog = new Array();
protected var memoryMax = 0; // yes, but what exactly are you?
to this.
protected var memoryLog:Array = new Array();
protected var memoryMax:Number = 0; // memoryMax the Number, much better!
Whenever declaring variables, always specify the data type, as this allows the Flash compiler to perform some extra optimizations when generating the SWF file. This alone can lead to big performance improvements, as we’ll soon see with our example. Another added benefit of strong typing is that the compiler will catch and alert you of any data-type related bugs.
Step 4: Examine Results
This screen shot shows the new Flash movie, after applying strong typing. We can see that while it’s had no effect on the current or maximum CPU load, the minimum value has dropped from 8.3% to 4.2%. The maximum memory consumed has gone down from 9MB to 8.7MB.
The slope of the graph’s memory line has also changed, compared to the one shown in Step 2. It still has the same jagged pattern, but now drops and rises at a slower rate. This is a good thing, if you consider that the sudden drops in memory consumption are caused by Flash Player’s garbage collection, which is usually triggered when allocated memory is about to run out. This garbage collection can be an expensive operation, since Flash Player has to traverse through all the objects, looking for those that are no longer needed but still taking up memory. The less often it has to do this, the better.
Step 5: Efficiently Store Numeric Data
Actionscript provides three numeric data types: Number , uint and int . Of the three types, Number consumes the most memory as it can store larger numeric values than the other two. It is also the only type able to store numbers with decimal fractions.
The Flames class has many numeric properties, all of which use the Number data type. As int and uint are more compact data types, we can save some memory by using them instead of Numbers in all situations where we don’t need decimal fractions.
A good example is in loops and Array indexes, so for example we are going to change
for( var i:Number = 0; i < 10; i++ ){
anchors[i] = Math.floor( i * colWidth );
}
into
for( var i:int = 0; i < 10; i++ ){
anchors[i] = Math.floor( i * colWidth );
}
The properties cpu , cpuMax and memoryMax will remain Numbers, as they will most likely store fractional data, while memoryColor , cpuColor and ticks can be changed to uints, as they will always store positive, whole numbers.
Step 6: Minimize Method Calls
Method calls are expensive, especially calling a method from a different class. It gets worse if that class belongs to a different package, or is a static method. The best example here is the Math.floor() method, used throughout the Flames class to roundoff fractional numbers. This method call can be avoided by using uints instead of Numbers to store whole numbers.
// So instead of having:
anchors[i] = Math.floor( i * colWidth );
// we instead cast the value to a uint
anchors[i] = uint( i * colWidth );
// The same optimization can be performed by simply assigning the uint data type, for example changing
var size:uint = Math.floor( particle.size * particle.life/100 );
// into
var size:uint = particle.size * particle.life/100;
In the example above, the call to Math.floor() is unnecessary, since Flash will automatically round off any fractional number value assigned to a uint.
Step 7: Multiplication Is Faster Than Division
Flash Player apparently finds multiplication easier than division, so we’ll go through the Flames class and convert any division math into the equivalent multiplication math. The conversion formula involves getting the reciprocal of the number on the right side of the operation, and multiplying it with the number on the left. The reciprocal of a number is calculated by dividing 1 by that number.
var colWidth:uint = frame.width / 10; //division by ten
var colWidth:uint = frame.width * 0.1; //produces the same result as multiplication by 0.1
Lets take a quick look at the results of our recent optimization efforts. The CPU load has finally improved by dropping from 41.7% to 37.5%, but the memory consumption tells a different story. Maximum memory has risen to 9.4MB, the highest level yet, and the graph’s sharp, saw-tooth edges shows that garbage collection is being run more often again. Some optimization techniques will have this inverse effect on memory and CPU load, improving one at the expense of the other. With memory consumption almost back to square one, a lot more work still needs to be done.
Step 8: Recycling Is Good for the Environment
You too can play your part in saving the environment. Recycle your objects when writing your AS3 code reduce the amount of energy consumed by your programs. Both the creation and destruction of new objects are expensive operations. If your program is constantly creating and destroying objects of the same type, big performance gains can be achieved by recycling those objects instead. Looking at the Flames class, we can see that a lot of particle objects are being created and destroyed every second:
private function drawParticles( event ):void {
createParticles( 20 );
fireMC.graphics.clear();
for ( var i:* in fireParticles ) {
var particle:Object = fireParticles[i];
if (particle.life == 0 ) {
delete( fireParticles[i] );
continue;
}
There are many ways to recycle objects, most involve creating a second variable to store unneeded objects instead of deleting them. Then when a new object of the same type is required, it is retrieved from the store instead of creating a new one. New objects are only created when the store is empty. We are going to do something similar with the particle objects of the Flames class.
First, we create a new array called inactiveFireParticles[] , which stores references to particles whose life property is zero (dead particles). In the drawParticles() method, instead of deleting a dead particle, we add it to the inactiveFireParticles[] array.
private function drawParticles( event ):void {
createParticles( 20 );
fireMC.graphics.clear();
for ( var i:* in fireParticles ) {
var particle:Object = fireParticles[i];
if( particle.life <= 0 ) {
if( particle.life == 0 ){
particle.life = -1;
inactiveFireParticles.push( particle );
}
continue;
}
Next, we modify the createParticles() method to first check for any stored particles in the inactiveFireParticles[] array, and use them all before creating any new particles.
Step 9: Use Object and Array Literals Whenever Possible
When creating new objects or arrays, using the literal syntax is faster than using the new operator.
private var memoryLog:Array = new Array(); // array created using the new operator
private var memoryLog:Array = []; // array created using the faster array literal
particle = new Object(); // object created using the new operator
particle = {}; // object created using the faster object literal
Step 10: Avoid Using Dynamic Classes
Classes in ActionScript can either be sealed or dynamic. They’re sealed by default, meaning the only properties and methods an object derived from it can have must have been defined in the class. With dynamic classes, new properties and methods can be added at runtime. Sealed classes are more efficient than dynamic classes, because some Flash Player performance optimizations can be done when all the possible functionality that a class can ever have are known beforehand.
Within the Flames class, the thousands of particles extend the built-in Object class, which is dynamic. Since no new properties need to be added to a particle at runtime, we’ll save up more resources by creating a custom sealed class for the particles.
Here is the new Particle, which has been added to the same Flames.as file.
class Particle{
public var x:uint;
public var y:uint;
public var life:int;
public var size:Number;
public var anchor:uint;
}
The createParticles () method will also be adjusted, changing the line
var particle:Object;
particle = {};
to instead read:
var particle:Particle;
particle = new Particle();
Step 11: Use Sprites When You Don’t Need the Timeline
Like the Object class, MovieClips are dynamic classes. The MovieClip class inherits from the Sprite class, and the main difference between the two is that MovieClip has a timeline. Since Sprites have all the functionality of MovieClips minus the timeline, use them whenever you need a DisplayObject that does not need the timeline. The Flames class extends the MovieClip but it does not use the timeline, as all its animation is controlled through ActionScript. The fire particles are drawn on fireMC , which is also a MovieClip that does not make use of its timeline.
We change both Flames and fireMC to extend Sprite instead, replacing:
import flash.display.MovieClip;
private var fireMC:MovieClip = new MovieClip();
public class Flames extends MovieClip{
with
import flash.display.Sprite;
private var fireMC:Sprite = new Sprite();
public class Flames extends Sprite{
Step 12: Use Shapes Instead of Sprites When You Don’t Need Child Display Objects or Mouse Input
The Shape class is even lighter than the Sprite class, but it cannot support mouse events or contain child display objects. As the fireMC requires none of this functionality, we can safely turn it into a Shape.
import flash.display.Shape;
private var fireMC:Shape = new Shape();
The graph shows big improvements in memory consumption, with it dropping and remaining stable at 4.8MB. The saw-tooth edges have been replaced by an almost straight horizontal line, meaning garbage collection is now rarely run. But the CPU load has mostly gone back again to its original high level of 41.7%.
Step 13: Avoid Complex Calculations Inside Loops
They say over 50% of a program’s time is spent running 10% of its code, and most of that 10% is most likely to be taken up by loops. Many loop optimization techniques involve placing as much of the CPU intensive operations outside the body of a loop. These operations include object creation, variable lookups and calculations.
for( var i = 0; i < memoryLog.length; i++ ){
// loop body
}
The first loop in the drawGraph() method is shown above. The loop runs through every item of the memoryLog array, using each value to plot points on the graph. At the start of each run, it looks up the length of the memoryLog array and compares it with the loop counter. If the memoryLog array has 200 items, the loop runs 200 times, and performs this same lookup 200 times. Since the length of memoryLog does not change, the repeated lookups are wasteful and unnecessary. It’s better to look up the value of memoryLog.length just once before the lookup begins and store it in a local variable, since accessing a local variable will be faster than accessing an object’s property.
var memoryLogLength:uint = memoryLog.length;
for( var i = 0; i < memoryLogLength; i++ ){
// loop body
}
In the Flames class, we adjust the two loops in the drawGraph() method as shown above.
Step 14: Place Conditional Statements Most Likely to Be True First
Consider the block of if..else conditionals below, derived from the drawParticles () method:
if( particle.life > 90 ){ // a range of 10 values, between 91 - 100
size *= 1.5;
}else if( particle.life > 45){ // a range of 45 values, between 46 - 90
particle.x += Math.random() * 6 - 3;
size *= 1.2;
}else { // a range of 45 values, values between 0 - 45
transperency = 0.1;
size *= 0.3;
particle.x += Math.random() * 4 - 2;
}
A particle’s life value can be any number between 0 and 100. The if clause tests whether the current particle’s life is between 91 to 100, and if so it executes the code within that block. The else-if clause tests for a value between 46 and 90, while the else clause takes the remaining values, those between 0 and 45. Considering the first check is also the least likely to succeed as it has the smallest range of numbers, it should be the last condition tested. The block is rewritten as shown below, so that the most likely conditions are evaluated first, making the evaluations more efficient.
Step 15: Add Elements to the End of an Array Without Pushing
The method Array.push() is used quite a lot in the Flames class. It will be replaced by a faster technique that uses the array’s length property.
cpuLog.push( cpu ); // slow and pretty
cpuLog[ cpuLog.length ] = cpu; // fast and ugly
When we know the length of the array, we can replace Array.push() with an even faster technique, as shown below.
var output:Array = []; //output is a new, empty array. Its length is 0
for( var i:uint = 0; i < steps; i++ ){ // the value of i also starts at zero. Each loop cycle increases both i and output.length by one
var progress:Number = i / steps;
var color:uint = Color.interpolateColor( color1, color2, progress );
output[i] = color; // faster than cpuLog[ cpuLog.length ] = cpu;
}
Step 16: Replace Arrays With Vectors
The Array and Vector classes are very similar, except for two major differences: Vectors can only store objects of the same type, and they’re more efficient and faster than arrays. Since all the arrays in the Flames class either store variables of only one type – ints, uints or Particles, as required – we shall convert them all to Vectors.
These arrays:
private var memoryLog:Array = [];
private var cpuLog:Array = [];
private var fireParticles:Array = [];
private var palette:Array = [];
private var anchors:Array = [];
private var inactiveFireParticles:Array = [];
…are replaced with their Vector equivalents:
private var memoryLog:Vector.<Number> = new Vector.<Number>();
private var cpuLog:Vector.<Number> = new Vector.<Number>();
private var fireParticles:Vector.<Particle> = new Vector.<Particle>();
private var palette:Vector.<uint> = new Vector.<uint>();
private var anchors:Vector.<uint> = new Vector.<uint>();
private var inactiveFireParticles:Vector.<Particle> = new Vector.<Particle>();
Then we modify the getColorRange() method to work with Vectors rather than arrays.
private function getColorRange( color1, color2, steps):Vector.<uint>{
var output:Vector.<uint> = new Vector.<uint>();
for( var i:uint = 0; i < steps; i++ ){
var progress:Number = i / steps;
var color:uint = Color.interpolateColor( color1, color2, progress );
output[i] = color;
}
return output;
}
Step 17: Use the Event Model Sparingly
While very convenient and handy, the AS3 Event Model is built on top of an elaborate setup of event listeners, dispatchers and objects; then there is event propagation and bubbling and much more, all of which a book can be written about. Whenever possible, always call a method directly rather than through the event model.
The Flames class has three event listeners calling three different methods, and all bound to the ENTER_FRAME event. In this case, we can keep the first event listener and get rid of the other two, then have the drawParticles () method call getStats() , which in turn calls drawGraph() . Alternatively, we can simply create a new method that calls the getStats(), drawGraph() and drawParticles () for us directly, then have just one event listener that’s bound to the new method. The second option is more expensive however, so we’ll stick with the first.
// this line is added before the end of the <code> drawParticles </code>() method
getStats();
// this line is added before the end of the <code> getStats() </code> method
drawGraph();
We also remove the event parameter (which holds the Event object) from both the drawGraph() and getStats() , as they are no longer needed.
Step 18: Disable All Mouse Events for Display Objects That Do Not Need It
Since this Flash animation does not require any user interaction, we can free its display object from dispatching unnecessary mouse events. In the Flames class, we do that by setting its mouseEnabled property to false. We also do the same for all its children by setting the mouseChildren property to false. The following lines are added to the Flames constructor:
mouseEnabled = false;
mouseChildren = false;
Step 19: Use the Graphics.drawPath() Method to Draw Complex Shapes
The Graphics.drawPath() is optimized for performance when drawing complex paths with many lines or curves. In the Flames.drawGraph() method, the CPU load and memory consumption graph lines are both drawn using a combination of Graphics.moveTo() and Graphics.lineTo() methods.
for( var i = 0; i < memoryLogLength; i++ ){
ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
xpoint = (i / sampleSize) * graphWidth;
if( i == 0 ){
graphMC.graphics.moveTo( xpoint, -ypoint );
continue;
}
graphMC.graphics.lineTo( xpoint, -ypoint );
}
We replace the original drawing methods with calls to Graphics.drawPath(). An added advantage of the revised code below is that we also get to remove the drawing commands from the loops.
var commands:Vector.<int> = new Vector.<int>();
var data:Vector.<Number> = new Vector.<Number>();
for( var i = 0; i < memoryLogLength; i++ ){
ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
xpoint = (i / sampleSize) * graphWidth;
if( i == 0 ){
data[ data.length ] = xpoint;
data[ data.length ] = -ypoint;
commands[ commands.length ] = 1;
}
data[ data.length ] = xpoint;
data[ data.length ] = -ypoint;
commands[ commands.length ] = 2;
}
graphMC.graphics.drawPath( commands, data );
Step 20: Make the Classes Final
The final attribute specifies that a method cannot be overridden or that a class cannot be extended. It can also make a class run faster, so we’ll make both the Flames and Particle classes final.
Edit: Reader Moko pointed us to this great article by Jackson Dunstan, which remarks that the final keyword does not actually have any effect on performance.
The CPU load is now 33.3%, while the total memory used stays between 4.8 and 5MB. We’ve come a long way from the CPU load of 41.7% and peak memory size of 9MB!
Which brings us to one of the most important decisions to be made in an optimization process: knowing when to stop. If you stop too early, your game or application may perform poorly on low end systems, and if you go too far, your code may get more obfuscated and harder to maintain. With this particular application, the animation looks smooth and fluid while CPU and memory usage are under control, so we’ll stop here.
Summary
We have just looked at the process of optimization, using the Flames class as an example. While the many optimization tips were presented in a step by step fashion, the order doesn’t really matter. What’s important is being aware of the many issues that can slow down our program, and taking measures to correct them.
But remember to watch out for premature optimization; focus first on building your program and making it work, then start tweaking performance.
Grab a coffee and have a go at these 10 questions on ActionScript 3. If you ace them and feel like telling the world, share your score on Twitter! Feel free to let us know if there are any specific quiz topics you’d like to see.
How The..?
This quiz was built with the jQuizzy Quiz Engine by Siddharth, ace reviewer for Envato. jQuizzy is available for purchase over on Codecanyon
Thanks also to Orman Clark and MediaLoot for their graphical contributions to the Activetuts+ Coffee Break Quizzes.
Hey there and welcome to "Flash Video Training Source", a resource for anybody interested in learning more about Adobe's great tool. We feature educational videos, which will help you master Adobe Flash and help you get to know all of its features. We at "Flash Video Training Source" believe that video training and video... more
Why don't you follow us on Twitter and get the latest video tutorials twitted to your account. Just click on the floating twitter bar to your right!