Part One - Creating a simple program
Actionscript 3 is an ECMAScript-based programming language. I don't want to get into AS3 deeply, what we need to know it is object-oriented, and a virtual machine is needed to run AS3 programs, it is called AVM2 ( Actionscript Virtual Machine 2 ), and it is bulit in Flash Player 9. AS3 code can be edited with several programs, and can be compiled with the free Flex Development Kit provided by Adobe, but the fastest and easiest ( and most recommended ) development environment is Adobe Flex Builder, or Adobe Flex Builder plug-in for Eclipse. That's why we use Flex Builder in this tutorial.
Let's start with something easy, create a simple orange circle first. Open Flex Builder, File menuitem -> New -> Actionscript Project, type FirstCircle for name, and press Enter.
There should be a Navigator view on the left, showing our project files, and a big editor view on the right, showing FirstCircle.as, and a basic AS3 code :
[as]
package
{
import flash.display.Sprite;
public class FirstCircle extends Sprite
{
public function FirstCircle( )
{
}
}
}
[/as]
This is our main class, the "entering point" of our program. It is in the root package, and it is extended from the Sprite class, because AVM2 needs something visible for main class, we cannot create non-visible AS3 programs.
Let's compile and run this class. Run menuitem -> Run, and your default browser appears with a big blueish grey screen, and nothing happens. That is good, because we didn't write anything in the main class yet.
So, let's draw an orange circle. We need a displayobject with graphical abilities, there are three of this kind: the Shape, the Sprite and the MovieClip. Because we don't need a timeline and child display objects, we use the simplest class, the Shape.
[as]
package
{
import flash.display.Sprite;
import flash.display.Shape;
public class FirstCircle extends Sprite
{
public function FirstCircle()
{
// creating a new shape instance
var circle:Shape = new Shape( );
// starting color filling
circle.graphics.beginFill( 0xff9933 , 1 );
// drawing circle
circle.graphics.drawCircle( 0 , 0 , 40 );
// repositioning shape
circle.x = 40;
circle.y = 40;
// adding displayobject to the display list
addChild( circle );
}
}
}
[/as]
For tips on how to use Shape or DisplayObject's graphics use Flex Builder's built-in language reference, or Adobe's LiveDocs. The other important thing is the last row "addChild". DisplayObjects are no longer depth-organized, as in previous versions of actionscript, we have to attach them to displayobject containers instead to make them visible. They still have a .visible property, but that is a little bit different.
Part Two - Advencing the program
We have a beautiful orange circle, play with its x and y coordinates. If you are bored, we should do something more exciting, let's move the ball.
Create a new class, File menuitem -> New -> Actionscript Class, name it MovingBall, press Enter. MovingCircle.as appeared in Navigator view, and in the source editor also. This class will contain our moving circle-related code.
[as]
package
{
import flash.display.Shape;
import flash.events.Event;
public class MovingCircle extends Shape
{
public var xspeed:Number;
public var yspeed:Number;
public function MovingCircle ( )
{
// graphics is an inherited property from Shape
graphics.beginFill( 0xff9933, 1 );
graphics.drawCircle( 0 , 0 , 40 );
}
// initialization, called after parent addChild
public function init ( ):void
{
// x , y and stage are inherited properties
x = Math.random( ) * ( stage.stageWidth );
y = Math.random( ) * ( stage.stageHeight );
xspeed = Math.random( ) * 10;
yspeed = Math.random( ) * 10;
// start step triggering function based on enterframe event
addEventListener( Event.ENTER_FRAME , step );
}
public function step ( event:Event ):void
{
// bounce ball at stage edges
if ( x + xspeed > stage.stageWidth ) xspeed *= -1;
else if ( x + xspeed < 0 ) xspeed *= -1;
if ( y + yspeed > stage.stageHeight ) yspeed *= -1;
else if ( y + yspeed < 0 ) yspeed *= -1;
// set position
x += xspeed;
y += yspeed;
}
}
}[/as]
We extend it from Shape class, because we want drawing inside us, and we need repositioning. We have two public properties: x and y component of our speed.
The public function MovingCircle is our constructor, this function runs once when we instantiate the class. Constructor functions don't have returning values, because the returning value of an instantiation is the instance itself, we cannot pass back anything else. But every other function should have a returning value, or void, if it doesn't return a value, like init and step function above. Returning values should be defined after the function name and brackets with a duble dot. Variable types also should be defined by the same way.
In the constructor our orange circle is drawn. In function init, we randomize our starting speed and position, and initialize an event listener for triggering our step function, and function step is for bouncing our ball at stage edges.
Save MovingCircle.as and switch back to FirstCircle.as, a few modifications have to be done here.
[as]
package
{
import flash.events.Event;
import flash.display.Sprite;
public class FirstCircle extends Sprite
{
public function FirstCircle()
{
stage.addEventListener( Event.ENTER_FRAME , init );
}
public function init ( event:Event ):void
{
stage.removeEventListener( Event.ENTER_FRAME , init );
var circle:MovingCircle = new MovingCircle( );
addChild( circle );
circle.init( );
}
}
}[/as]
In function init we instantiate one MovingCircle class, add it to the display list, and init it.
Run the program, if everything is ok, you should see an orange bouncing ball.
Testing our program in the browser is a little bit annoying, let's use the stand-alone debug player shipped with flex. Project menuitem - Properties - Actionscript compiler menuitem, under HTML wrapper uncheck generate HTML wrapper file, press OK twice, so flex won't start our browser any more.
Let's examine this program deeper, and a few tricks come to light. Why do we have to use an extra init function in MovingCircle class, when we could generate our speed and position in the constructor also? Well, that's an annoying issue of flash player. Until a displayobject is not attached to a display list, its stage property is null, so we couldn't reach it to generate our position, and we couldn't attach any object to the display list before its instantiation. So, we have to instantiate it first, then attach it to display list, then call init function from its parent object.
And why do we have to wait for the first enterframe event of the stage in the main class? This is the other very-very annoying thing of flash player, until the first enterframe event, the stage's width and height is zero, and we cannot use it to calculate a random position.
The other interesting thing is event ( and error ) handling. As you see in the constructor, we initialize an event handler which listens for enterframe events. AS3 has a quite advanced event and error handling model, events can bubble through objects, hold properties, and so on. Errors are special events, they are generated when a runtime exception appears, for example, when we cannot reach an object's property.
Try it. Move the code from init function to the constructor of MovingCircle class, and encapsulate it in a try - catch - finally statement like this:
[as]
try
{
x = Math.random( ) * ( stage.stageWidth );
y = Math.random( ) * ( stage.stageHeight );
xspeed = Math.random( ) * 10;
yspeed = Math.random( ) * 10;
}
catch ( error:Error )
{
trace( "Cannot randomize properties, Error: " + error.message );
trace( "I rather randomize the values by myself" );
x = Math.random( ) * 400;
y = Math.random( ) * 400;
xspeed = Math.random( ) * 10;
yspeed = Math.random( ) * 10;
}
finally
{
trace( "Well, the properties are defined" );
}
[/as]
And run the program. The movie runs smoothly, the circle is moving, but examine the debug console of flex:
Cannot randomize properties, TypeError: Error #1009: Cannot access a property or method of a null object reference.
I rather randomize the values by myself
Well, the properties are defined
So, it runned into the stage == null problem, but our code caught this error, and randomized the numbers from constants.
You can insert as many catch in a "try - catch - finally" statement as many error types you want to listen to.
Play with the code a little : with a for loop create more instances.
FirstCircle.as:
[as]
package
{
import flash.events.Event;
import flash.display.Sprite;
public class FirstCircle extends Sprite
{
public function FirstCircle()
{
for ( var a:int = 0 ; a < 10 ; a++ )
{
var circle:MovingCircle = new MovingCircle( );
addChild( circle );
circle.init( );
}
}
}
}[/as]
Fantastic, isnt it?
Part Three - Embedding and Interactivity
Okay, our program is nice now, but let's make it more spectacular. We need a bitmap of a soccer ball as our circle, and we want a simple sound playing on every bounce. We also want to drag the ball with the mouse, and throw it to an arbitrary direction.
Download the two attachments of the article, and place them in the root of your Flex project.
MovingCircle.as will look like this:
[as]
package
{
import flash.media.Sound;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.Sprite;
import flash.display.Bitmap;
public class MovingCircle extends Sprite
{
// for realistic movement we use gravity
public static var gravity:Number = 1;
public var xspeed:Number;
public var yspeed:Number;
// we want to "throw" the ball, that's why we need previous positions
// to calculate new speed components
private var oldx:Number
private var oldy:Number;
// gfx will be our "skin"
// sfx will be our sound
private var gfx:Bitmap;
private var sfx:Sound;
// that's how we embed external assets
// we have to define an individual class name for them
[ Embed ( source = "soccerball.gif" ) ] private var soccerGFXClass:Class;
[ Embed ( source = "pop.mp3" ) ] private var soccerSFXClass:Class;
public function MovingCircle ( )
{
// instantiate embedded assets
gfx = new soccerGFXClass( );
sfx = new soccerSFXClass( );
// attaching ball to display list
addChild( gfx );
// skin repositioning to middle to make bouncing more precise
gfx.x = - width / 2;
gfx.y = - height / 2;
}
// initialization, called after parent addChild
public function init ( ):void
{
// x , y and stage are inherited properties
// watching our width and height not to cross "wall"
x = width / 2 + Math.random( ) * ( stage.stageWidth - width / 2 );
y = height / 2 + Math.random( ) * ( stage.stageHeight - height / 2 );
xspeed = 5 + Math.random( ) * 10;
yspeed = 5 + Math.random( ) * 10;
// start step triggering based on enterframe event
addEventListener( Event.ENTER_FRAME , step );
// add mouseevents to make ourself draggable
addEventListener( MouseEvent.MOUSE_DOWN , dragCircle );
addEventListener( MouseEvent.MOUSE_UP , releaseCircle );
}
private function dragCircle ( event:MouseEvent ):void
{
// when mousedown, we starting to listen to mouse movement of the stage
// to catch mouse coordinates
stage.addEventListener( MouseEvent.MOUSE_MOVE , moveCircle );
// we stop stepping during dragging
removeEventListener( Event.ENTER_FRAME , step );
}
private function releaseCircle ( event:MouseEvent ):void
{
// removing stage mousemove listening
stage.removeEventListener( MouseEvent.MOUSE_MOVE , moveCircle );
// start stepping
addEventListener( Event.ENTER_FRAME , step );
}
private function moveCircle ( event:MouseEvent ):void
{
// store previous coordinates
oldx = x;
oldy = y;
x = event.stageX;
y = event.stageY;
// calculate new speed components
xspeed = x - oldx;
yspeed = y - oldy;
}
public function step ( event:Event ):void
{
// sett position
x += xspeed;
y += yspeed;
// adding gravity to y component
yspeed += gravity;
// rotating for realistic movement
rotation += xspeed;
// bounce ball at stage edges
if ( x + xspeed > stage.stageWidth - width / 2 )
{
xspeed *= -1;
sfx.play( ); // playing sound
}
else if ( x + xspeed < width / 2 )
{
xspeed *= -1;
sfx.play( ); // playing sound
}
if ( y + yspeed > stage.stageHeight - height / 2 )
{
yspeed *= -1;
sfx.play( ); // playing sound
}
else if ( y + yspeed < height / 2 )
{
yspeed *= -1;
sfx.play( ); // playind sound
}
}
}
}[/as]
Run and try. Drag the ball, and throw it. Hope you like it.