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.
In this tutorial, we’ll learn how to use AS3 to create an RIA that can modify the color properties of an image, such as Brightness, Contrast, Hue and Saturation. Read on!
Final Result Preview
Let’s take a look at the final result we will be working towards:
Step 1: Brief Overview
We will use a native ActionScript Class that will get new values from a Slider Component and then apply them to the target image using the ColorMatrixFilter class.
Step 2: Flash Document Settings
Launch Flash and create a new document. Set the stage size to 557x400px, and the frame rate to 24fps.
Step 3: The Interface
The interface will be very simple; just an image in the stage that will be then modified by the Sliders Components in the Adjust Color Panel.
Step 4: Demo Image
We’ll need an image to test our application, choose it from your personal collection or download one for testing.
Create a panel and four Sliders with instance names as seen in the following image:
You can add bars above the Sliders as shown to improve the appearance.
Step 6: Slider Values
Let’s set the Slider components’ values.
These are obtained from the minimum and maximum valid values of the AdjustColor class, which we will use to adjust each property:
brightSL: -100 to 100
contSL: -100 to 100
hueSL: -180 to 180
satSL: -100 to 100
Step 7: New ActionScript Class
Create a new ActionScript 3.0 Class (Cmd + N) and save it as Main.as in your class folder.
Step 8: Class Structure
Create your basic class structure to begin writing your code.
>
package
{
import flash.display.Sprite;
public class Main extends Sprite
{
public function Main():void
{
// constructor code
}
}
}
Step 9: Required Classes
These are the classes we’ll need to import for our class to work; the import directive makes externally defined classes and packages available to your code.
These are the variables we’ll use; read the comments in the code to learn more about them.
private var color:AdjustColor = new AdjustColor(); //This object will hold the color properties
private var filter:ColorMatrixFilter; //Will store the modified color filter to change the image
Step 11: Constructor
The constructor is a function that runs when an object is created from a class; this code is the first to execute when you make an instance of an object, or in this case it is run when the SWF is loaded, as it is in the document class.
It will perform the neccesary actions to start the application.
public final function Main():void
{
//Code
}
Step 12: Initial Matrix
The color matrix will be generated by the values stored in the AdjustColor properties; we need to set initial values to these properties in order to get a correct matrix. If we don’t do this, an array with null values will be generated.
/* Required to create initial Matrix */
color.brightness = 0;
color.contrast = 0;
color.hue = 0;
color.saturation = 0;
/* This function will add the necessary event listeners */
addListeners();
Step 13: Add Slider Listeners
This function adds listeners to the Slider components in order to call certain functions whenever their values change.
private final function addListeners():void
{
colorPanel.brightSL.addEventListener(SliderEvent.CHANGE, adjustBrightness);
colorPanel.contSL.addEventListener(SliderEvent.CHANGE, adjustContrast);
colorPanel.hueSL.addEventListener(SliderEvent.CHANGE, adjustHue);
colorPanel.satSL.addEventListener(SliderEvent.CHANGE, adjustSaturation);
}
Step 14: Brightness
This function modifies the Brightness value, getting its data from the brightSL slider component.
private final function adjustBrightness(e:SliderEvent):void
{
color.brightness = e.target.value;
update();
}
Step 15: Contrast
This function modifies the Contrast value, getting its data from the contSL slider component.
private final function adjustContrast(e:SliderEvent):void
{
color.contrast = e.target.value;
update();
}
Step 16: Hue
This function modifies the Hue value, getting its data from the hueSL slider component.
private final function adjustHue(e:SliderEvent):void
{
color.hue = e.target.value;
update();
}
When you modify the hue of a color, you move it around the color wheel by the specified number of degrees.
Step 17: Saturation
This function modifies the Saturation value, getting its data from the satSL slider component.
private final function adjustSaturation(e:SliderEvent):void
{
color.saturation = e.target.value;
update();
}
When you modify the saturation of a color, you move it towards or away from the center of the color wheel.
Step 18: Update Function
This function is called in every slider change. It renews the ColorMatrixFilter value and applies it to the image in stage.
private final function update():void
{
filter = new ColorMatrixFilter(color.CalculateFinalFlatArray());
image.filters = [filter];
}
Step 19: Set Main Class
We’ll make use of the Document Class in this tutorial, if you don’t know how to use it or are a bit confused please read this QuickTip.
Step 20: Test
You’re ready to test — press Cmd+Return to export your application and see it working!
Conclusion
You’ve learned an excellent technique of image manipulation, experiment with it!
Thanks for reading this tutorial, I hope you’ve found it useful!
In this tutorial, we’ll learn how to implement native multitouch gestures to use in your applications. Read on!
Final Result Preview
Let’s take a look at the final result we will be working towards:
Note: This example will work only on Multitouch devices (Tablets, Smartphones, Touch Screen Computers and Multitouch trackpads under AIR).
Note for Android users: Multitouch won’t run in the SWF embedded in an HTML page in an Android browser, but the Source download does include an APK you can use to check it out. (Please note that the APK is just for the purpose of a quick demonstration of the gestures themselves, and does not display entirely correctly.)
You can pinch to zoom, spin to rotate, and swipe to change the image. Check out the video demo if you’re unable to test the preview on your device:
Don’t forget to subscribe to Activetuts+ screencasts via iTunes!
Step 1: Brief Overview
We’ll use the native Multitouch support built in to the Flash Player to create a gesture-based image application.
Step 2: Flash Document Settings
Launch Flash and create a new document. Set the stage size to 600x300px, and the frame rate to 24fps.
Step 3: Interface
The interface will be very simple, just an image in the stage that will be then modified by the gestures.
Step 4: Get Some Images
We’ll need some images to test our application, choose them from your personal collection or download a few for testing.
These are the images from the demo, obtained from Flickr, all with a Creative Commons License.
Convert one of the images to a movie clip, and add the rest of the images to that clip in different frames. Name the clip SlideItem and mark the Export for ActionScript box.
Step 6: TweenNano
We’ll use a different tween engine from the default included in Flash, this will increase performance as well as being easier to use.
Create a new (Cmd + N) ActionScript 3.0 Class and save it as Main.as in your class folder.
Step 8: Class Structure
Create your basic class structure to begin writing your code.
package
{
import flash.display.Sprite;
public class Main extends Sprite
{
public function Main():void
{
// constructor code
}
}
}
Step 9: Required Classes
These are the classes we’ll need to import for our class to work, the import directive makes externally defined classes and packages available to your code.
These are the variables we’ll use, read the comments in the code to find out more about them.
private var slideItem:SlideItem; //The object that will be affected by the gestures
private var tempContainer:Sprite; //An empty sprite that will store the slide item
Step 11: Constructor
The constructor is a function that runs when an object is created from a class, this code is the first to execute when you make an instance of an object or runs using the Document Class.
It performs the necessary actions to start the application.
public final function Main():void
{
//Code
}
Step 12: Enable Gestures Input
This line tells the Flash Player to identify the multi-touch mode for touch and gesture event handling.
This means that the listeners() function called in the previous step will add event listeners to the sliding image, to make it listen for zooming, panning, rotating, and swiping gestures.
Step 15: Pinch to Zoom
This function responds to the well known pinch gesture. It handles the image zoom in and zoom out.
private final function onZoom(e:TransformGestureEvent):void
{
/* Use the event data to scale the target image */
e.target.scaleX *= e.scaleX;
e.target.scaleY *= e.scaleY;
}
Step 16: Spin to Rotate
Rotation is handled by this function, two fingers are used to spin the image in the stage.
private final function onRotate(e:TransformGestureEvent):void
{
/* Use the event data to rotate the target image */
e.target.rotation += e.rotation;
}
Step 17: Slide to Pan
The Pan function is used to move the image to see parts that are off-stage.
private final function onPan(e:TransformGestureEvent):void
{
e.target.x += e.offsetX;
e.target.y += e.offsetY;
}
Step 18: Swipe to Swap
This function is a little bigger due to the four swipe directions available. The gesture is similar to that from the previous step, but firmer, and instead of simply moving the image around, it swaps it for a different image.
It first checks if a previous item is on the stage and stores it in a container in order to tween it and be able to remove it later. Then, the offset property is read to determine the direction of the swipe, showing the corresponding animation and image.
When the animation is finished, the item offstage is destroyed to save memory.
private final function removeLast():void
{
listeners('remove', tempContainer.getChildAt(0) as Sprite);
removeChild(tempContainer);
tempContainer = null;
}
Step 20: Set Main Class
We’ll make use of the Document Class in this tutorial, if you don’t know how to use it or are a bit confused please read this Quick Tip.
Conclusion
Gestures add a nice touch and and offer great interaction in your application, use them!
Thanks for reading this tutorial, I hope you’ve found it useful!
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!