Creating a (Simple) Website with Flash and Actionscript 3

Part I. An Intro and Some Graphics

devon o wolfgang


INTRO:
Although Flash 9 may not (officially) be here, yet, the newest incarnation of its scripting language, Actionscript 3, has definitely made its debut and there is no time like the present to start learning what new features it has to offer.

It's difficult to classify the level of this tutorial. If you've been using AS3 in Flash or Flex already, this may seem ridiculously simple and you might as well skip it. On the other hand, if you're still using AS1 or, worse yet, Flash 4/5 style scripting with clip events "on top" of object instances, this tutorial may seem crazily challenging (but don't let it discourage you). Generally speaking, this is targetted towards those who are comfortable and familiar with Actionscript 2, but haven't yet started fiddling around with Actionscript 3.

Before digging in, let's take a quick sneak peak at what we'll be making. When I say "simple", I mean "simple". The graphics have been kept to a bare minimum (practically non existent) in order to create the entire "site" with script and to keep the focus of this tutorial on Actionscript rather than on design. Still, while it may be basic, it will be enough to introduce a few Actionscript 3 solutions to common scripting problems such as user interactivity and loading and formatting external content.

GETTING STARTED:
Because this site is constructed entirely in script, how you write and compile that script is up to you. Which is to say, you can write and compile your AS inside the Flash 9 Alpha IDE, or you can use third party script editors/compilers such as SEPY and MTASC. Just for the record, I'll compile my .swf file with Flash 9 Alpha and write all my script with FlashDevelop. For the sake of this tutorial, it may be best (and highly recommended), to use the Flash 9 IDE for compiling your .swf (as I'll be speaking of saving a .fla file and pointing out an important new feature in the development environment), but use whatever is most comfortable for you.

A LITTLE ORGANIZATION :
Before getting down and dirty with some code, let's set up a directory structure for our website. Create a directory named "AS3 Site" (or whatever else you may wish to call it, but that's how I'll be referring to it). This will be the "main" directory and will contain our .swf file, .fla file (if you're using Flash 9), our .html file (when we're good and ready for it), and our sub directories, which we'll create right now. Inside the "AS3 Site" directory create a "content" directory (for .xml files), a "sounds" directory (for, well, sound files), and a "classes" directory (for, you guessed it, our .as class files). Inside that "classes" directory create a "graphics" directory and an "iface" directory (we'll get to those later, but "iface" is short for "interface" in case you were wondering. "Interface", however, is a reserved word in Actionscript, so we'll avoid that).

THE DOCUMENT CLASS :
If, as suggested, you're using the Flash 9 Alpha IDE, one of the first changes you probably noticed is that in the properties panel is a box marked "Document class:". The document class is a class file that will define your .swf file and should extend the new Sprite class. Once defined inside the document class box, that class will automatically be compiled in your .swf file. That is there will be no need for any additional actionscript written in the Actionscript panel. You can think of the document class as your Flash movie's "_root" (which, incidentally, no longer exists in Actionscript 3). If you're the type of Flash developer who writes Actionscript in the first frame of your .fla file, you can consider the document class that first frame - but in class form. If you have Flash 9 open (and if you don't, open it), create a new Flash Document and set the movie's size to 800x600 pixels (this is a rather arbitrary size as we'll actually be publishing our site to full size, but it gives us something to work with in any case). Enter "classes.WebSite" into the document class box on the properties panel, save the .fla (with whatever name you'd like) in the "AS3 Site" directory, and test your movie. Absolutely nothing happens. This is because we haven't yet created our classes.WebSite document class yet. Not to worry - we'll do that presently.

THE DOCUMENT CLASS ii:
All right, finally time for a bit of scripting. Create a new .as file named "WebSite.as" inside your classes directory and let's get to work. One new thing with AS3 is that all classes must begin with a "package" definition keyword (whereas in AS2, you would simply pre-append the package to the class name). Because this .as file is in our classes directory (which, since it now contains a class file, can be referred to as a "package"), we'll begin our class with

package classes {

Simple enough. After our package declaration, we'll need to import other classes we may wish to use in our class. Another new feature of AS3, you'll notice very quickly, is that practically every Flash inherent class we wish to use in our own classes will need to be imported. Nearly all of the Flash Player API classes are contained in flash.* packages. Top level classes (based on ECMAscript) are few and far between in this latest version of Actionscript. As I mentioned in passing, the document class should extend the Sprite class. More specifically speaking, the document class should extend a relative of Flash's new DisplayObjectContainer class. The DisplayObjectContainer class is the base class for all classes which are able support a "display list," which is to say it can make things visible. Flash's Sprite class is a basic building block for display items. Sprites, basically put, are like our old friend the MovieClip with no timeline. We'll be extending this class quite a bit in this tutorial. To extend a class in AS3, we must first import the class we wish to extend, which brings me full circle back to writing the import directives in our WebSite class. The Sprite class is found in Flash's flash.display package, so let's go ahead and add to our WebSite class

import flash.display.Sprite;

Now, we can define our class. Another major change in Actionscript 3 (one I way too often forget) is that class definitions and constructor functions must specifically be declared "public" (at least if we want them to be public as we do in this case. Other attributes we could use for class definitions are final, dynamic or internal, while constructor methods, in order to follow ECMAscript specs, may only make use of the public attribute). Let's just quickly wrap this class up with a simple trace test (trace(), thankfully, is still a top level function, so requires no import) just to see if it's working properly. For just a moment, this will be our entire class:

package classes {
	
	import flash.display.Sprite;
	
	public class WebSite extends Sprite {
		
		public function WebSite() {
			init();
		}
		
		private function init():void {
			trace ("WebSite constructed!");
		}
	}
}

Back in the Flash 9 IDE, test your movie. If all went well, you should see our trace statement appear in the output panel. If it doesn't, recheck your directory structure, make sure "classes.WebSite" is in Document class box and double check case (like AS2, AS3 is case sensitive so that "Website" is not the same as "WebSite", for example. Also note that in AS3 the "void" data type is now lower case rather than upper).

Assuming that now everything is hunky-dory, let's try doing something a litte trickier. Let's delete the trace statement from our init() method and write a method that initializes our stage. If you played around with the final product, you may have noticed that the page is full screen Flash that recenters itself when the browser is resized and has no extraneous context menu items. In AS2 we could add such functionality to our .swf files by playing around with the Stage class. We follow a similar route in AS3, but now we are able to manipulate the stage property of DisplayObject classes (such as the Sprite class which we are now extending). So, let's create another private method named initStage() and see how that stage property can be used.

private function initStage():void {
	stage.frameRate = 31;
	stage.showDefaultContextMenu = false;
	stage.scaleMode = StageScaleMode.NO_SCALE;
	stage.align = StageAlign.TOP_LEFT;
	stage.addEventListener(Event.RESIZE, onStageResize);
}

There are a couple things to note here. First of all, check out how the frame rate of the movie can now be deliberately set with Actionscript! That is a wonderful thing. "showDefaultContextMenu", "scaleMode", and "align" should all be fairly self explanatory and aside from setting their values with constant properties of the StageScaleMode and StageAlign classes very little has changed from AS2. The last thing to take note of though is the addEventListener method. This is the "new" way of handling events in AS3 (actually, it is very similar to the way component events were utilized in AS2, it's just that in AS2 there were several ways of event handling which have now been whittled down to a single way in AS3). What this method is saying is that the stage property of our WebSite class (which is really a Sprite incarnation, remember) should listen for the event Event.RESIZE which is triggered any time the .swf file is resized. Note that RESIZE is a constant property of the Event class (sounds like we may need another import statement or two). Finally, when that event occurs, a method named onStageResize is called (which means we will need another method). So, let's add our imports for the stage and event scripts and a quick test for our onStageResize() method. Our WebSite class now looks like

package classes {
	
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	
	public class WebSite extends Sprite {
		
		public function WebSite() {
			init();
		}
		
		private function init():void {
			initStage();
		}
		
		private function initStage():void {
			stage.frameRate = 31;
			stage.showDefaultContextMenu = false;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			stage.addEventListener(Event.RESIZE, onStageResize);
		}
		
		private function onStageResize(e:Event):void {
			trace ("You resized the stage according to " + e.type + " event.");
		}
	}
}

One nifty thing to take note of here is that using the AS3 event model, whenever a triggered event calls a method, it passes along an event object as an argument. Quite often we can make use of the properties of that object as we'll see later. Sometimes however, we'll want to call that function without passing an event object. We'll also check out why and how to do that in a bit, as well. Anyway, recompile your .swf and then resize it. Right click on it while you're at it. Good stuff.

DISPLAY OBJECTS :
Now, let's get to creating something we can actually see. Some graphics that is. So, in your "graphics" directory, create a new .as file that will be our Background class and oddly enough function as our background graphic. We want our background image to have a nice radial fill, so we'll need to import a few classes from the flash.display package aside from the Sprite class (which, once again, we'll be extending). We'll also want this class to have a color property which we might want to change on occasion (just looking towards the future), so we'll add that private property as well. Our Background class, then, will look like this for the time being:

package classes.graphics {
	
	import flash.display.Sprite;
	import flash.display.GradientType;
	import flash.display.SpreadMethod;
	import flash.geom.Matrix;
	
	public class Background extends Sprite {
		
		private var _color:uint;
		
		public function Background() {
			_color = 0x669900;
			
			init();
		}
		
		private function init():void {
			drawBackground();
		}
		
		private function drawBackground():void {
			var m:Matrix = new Matrix();
			m.createGradientBox(800, 600);
			graphics.beginGradientFill(GradientType.RADIAL, [0xE9FEB4, _color], [1,1], [0, 255], m, SpreadMethod.PAD); 
			graphics.drawRect(0, 0, 800, 600);
			graphics.endFill();
		}
	}
}

A few interesting things going on here (sentence fragment aside). Note how DisplayObject classes contain a graphics property that allow you to draw filled shapes (or just lines for that matter). The graphics property is much like the AS2 drawing API and uses all the same methods in similar ways, but it also allows you to easily draw primitive shapes such as rectangles, circles, ellipses, and rounded rectangles with just a single line of script. A wonderful addition to AS, that is. Also see how we typed our _color property as a "uint". That is a new primitive datatype in AS3 that is short for "unsigned integer". An unsigned integer is, by definition, just a positive whole number. This is a perfect datatype for hexidecimal color values and is where you will see it used most often. Now, that we have a background, we'll need to actually add it to our WebSite. So, back in our WebSite class, let's add a new private property named "_bg" (for "background", of course) and a private method addBackground to add our background to the display.

package classes {
	
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import classes.graphics.*;
	
	public class WebSite extends Sprite {
		
		private var _bg:Background;
		
		public function WebSite() {
			init();
		}
		
		private function init():void {
			initStage();
			addBackground();
		}
		
		private function initStage():void {
			stage.frameRate = 31;
			stage.showDefaultContextMenu = false;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			stage.addEventListener(Event.RESIZE, onStageResize);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, test)
		}
		
		private function onStageResize(e:Event):void {
			//trace ("You resized the stage according to " + e.type + " event.");
		}
		
		private function addBackground():void {
			_bg = new Background();
			addChild(_bg);
		}
	}
}

In AS2, we had the createEmptyMovieClip() and attachMovie() methods to get display objects (i.e. MovieClips) on our screen. In AS3, we are able to use the much more intuitive "new" keyword to instantiate classes, then simply add them to our display list with the DisplayObjectContainer.addChild() method (bearing in mind that Sprites inherit this method). While there is no concept of depth in AS3 as there was in AS2, there is an index which is very similar in nature. Whenever a DisplayObject is added to the display list with the addChild() method, it is assigned an index number. Similar to depths, the lower the index number, the lower (i.e. "beneath other objects") the DisplayObject will appear.

One other thing to make note of before testing our movie is that we now imported our own classes.graphics package to make use of our Background class. The asterisk signifies that we are importing all classes within that package. Of course, at the moment, that is only the Background class, but eventually there will be others and we will want them all. Go ahead and test your movie and check out the background. Pretty nifty. Try resizing the .swf though. And whoops. The background just remains a 800x600 static rectangle hanging out in the upper lefthand corner. So let's go ahead and add a resize method to our Background class.

public function resize(w:Number, h:Number):void {
	this.width = w;
	this.height = h;
}

This is just a simple function which will accept two numbers as arguments and set the width and height of our background to those values. Back in our WebSite class we will need to make sure we import the flash.display.Stage class and in our onStageResize method we can now get rid of that annoying trace and instead call our Background.resize() method passing along some properties of that Stage class we just imported. Like so

_bg.resize(stage.stageWidth, stage.stageHeight);

In case it wasn't apparent enough, that will simply resize our _bg property (an instance of our Background class) to the dimensions of the stage (i.e. the same size as the .swf file). Go ahead and test it now. Pretty slick, huh.

ANOTHER DISPLAY OBJECT:
Let's move on to another graphics class - our main display (as we'll call it here), which fittingly enough, we will call "classes.graphics.MainDisplay". So, in our graphics directory create another .as file called MainDisplay.as. Once again we will be extending the Sprite class, so you can go ahead with package declaration, import and constructor function. This class will actually look quite like our Background class. Basically we want a rounded rectangle that will hold a text field and display information. Let's start off slow and just create a rounded rectangle with the Sprite's graphics property (just like we did with our Background). We'll also add a reposition() method which, sort of like our Background's resize() method, will set our MainDisplay's x and y properties by passing in a couple number parameters.

package classes.graphics {
		
	import flash.display.Sprite;
	
	public class MainDisplay extends Sprite {
		
		private var _color:uint;
		
		public function MainDisplay() {
			_color = 0x669900;
			
			init();
		}
		
		private function init():void {
			drawDisplay();
		}
		
		private function drawDisplay():void {
			this.graphics.beginFill(_color);
			this.graphics.drawRoundRect(0, 0, 450, 400, 20, 20);
			this.graphics.endFill();
		}		

		public function reposition(xPos:Number, yPos:Number):void {
			this.x = Math.round(xPos);
			this.y = Math.round(yPos);
		}
	}
}

Notice in the reposition() method I round off the values passed to it. This is just a little foresight. If we later use pixel fonts in our main display, we'll always want it positioned on whole pixels. Always thinking ahead. Also notice that we don't have to import the Math class to make use of it. It's one of the few top level classes still remaining in AS3. Of course, back in our WebSite class we will need to add a _mainDisplay property and a method to add it to our display. Remember we already imported all classes in our classes.graphics package, so there'll be no need to worry about those pesky imports this time - we will need to call our addMainDisplay() method from the init() method though. You know the drill.

private function addMainDisplay():void {
	_mainDisplay = new MainDisplay();
	addChild(_mainDisplay);
}

Test your movie. Well, you have a nice green rounded rectangle now, but it's pretty boring and just sits in the upper right hand corner. Let's call its reposition() method in our onStageResize() method and have it centered on our stage.

_mainDisplay.reposition(stage.stageWidth / 2 - _mainDisplay.width / 2, stage.stageHeight / 2 - _mainDisplay.height / 2);

Well, now when we test our movie, we see our _mainDisplay property (that is our little green rounded rectangle) center when we resize the .swf which is all good and well, but we'd like it centered immediately, that is as soon as the user visits the site - not after the user resizes the browser as they may never do that at all. Well, in AS2 the way this problem was solved was to simply call the Stage.onResize() method (assigned to a listener object) immediately, so let's call our onStageResize() method right away (at the end of our init() method). Go ahead and try it.

And you get a big ugly error: "ArgumentError: Error #1063: Argument count mismatch on classes::WebSite/classes:WebSite::onStageResize(). Expected 1, got 0." What is this telling us? Well, it's actually fairly clear as far as error messages go. We created our onStageResize() to accept an Event object as an argument and Flash 9 is now quite adamant in using the correct number of arguments when calling any given method. As you may remember, though, I hinted earlier that there was a way around this requirement.

So, time for a little rest. Heh. That's a little nerd humor folks. An elipsis (three dots...for you non English grads) is called a "rest" keyword in AS3. When used inside as a method argument it states that the following arguments (which will be passed to the method as an array) are optional. "Huh?" you're probably thinking. Well, let's rewrite our onStageResize() method for a a concrete example.

private function onStageResize(... eventArray:Array):void {
	_bg.resize(stage.stageWidth, stage.stageHeight);
	_mainDisplay.reposition(stage.stageWidth / 2 - _mainDisplay.width / 2, stage.stageHeight / 2 - _mainDisplay.height / 2);
}

In essence we are now saying that any arguments passed this method are optional and any passed arguments will now show up in an array named "eventArray". Test the movie again and there we go. Our _mainDisplay is now immediately centered on the screen. Still pretty dull looking though. Just a flat green square. Let's import a few filters into our WebSite class, and dress up our _mainDisplay with a bit of a bevel and a drop shadow. Filters are handled exactly the same in AS3 as they were in AS2, so I won't go into a great amount of detail about them. They can be applied to Sprite instances (and extensions thereof such as our _mainDisplay) in array form, just as they were applied to MovieClips in AS2. The filters can be added to the _mainDisplay just before we add our _mainDisplay to the display list in our addMainDisplay method like so

private function addMainDisplay():void {
	_mainDisplay = new MainDisplay();
	_mainDisplay.filters = [new BevelFilter(3, 90, 0xFFFFFF, .8, 0x000000, .65, 3, 3, 1, 3), new DropShadowFilter(8, 90, 0, .75, 7, 7, 1, 3)];
	addChild(_mainDisplay);
}

Before moving on, let's add a few more things to our MainDisplay class. We'll create a little overlay with an inner drop shadow just to make it a bit more visually interesting and, while we're at it, let's go ahead and add a TextField over top of our overlay which we will get around to using later. We'll make our overlay an instance of the flash.display.Shape class. The Shape class is very similar to a Sprite, but is not interactive. That just means less overhead in the long run. We'll just create a white rounded rectangle with an alpha value of .4 (that would be 40 in AS2. In AS3, alpha values range from 0 to 1 instead of 0 to 100.) a little smaller than the main display itself and add an inner drop shadow with help of our friend the DropShadow class. This all means we will have to import the Shape and DropShadowFilter classes. TextFields in AS3 are also very similar to their AS2 counterparts. We will, of course, have to import the necessary classes, though. The Flash text classes are, ironically enough, kept in a package named flash.text. For our purposes we will be importing the TextFormat and TextField classes.

The TextField and overlay shape will be added with two appropriately named private methods: addOverlay() and addTextfield.

And that wraps up this first part of this series. In later parts we'll see how to add functional buttons, load sounds, load xml, and even play a bit with E4X. Just for the record, these are our current three classes in their entirety:

The Document Class - "classes.WebSite"

package classes {
	
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filters.BevelFilter;
	import flash.filters.DropShadowFilter;
	
	import classes.graphics.*;
	
	public class WebSite extends Sprite {
		
		private var _bg:Background;
		private var _mainDisplay:MainDisplay;
		
		public function WebSite() {
			
			init();
		}
		
		private function init():void {
			initStage();
			addBackground();
			addMainDisplay();
			onStageResize();
		}
		
		private function initStage():void {
			stage.frameRate = 31;
			stage.showDefaultContextMenu = false;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			stage.addEventListener(Event.RESIZE, onStageResize);
		}
		
		private function onStageResize(... eventArray:Array):void {
			_bg.resize(stage.stageWidth, stage.stageHeight);
			_mainDisplay.reposition(stage.stageWidth / 2 - _mainDisplay.width / 2, stage.stageHeight / 2 - _mainDisplay.height / 2);
		}
		
		private function addBackground():void {
			_bg = new Background();
			addChild(_bg);
		}
		
		private function addMainDisplay():void {
			_mainDisplay = new MainDisplay();
			_mainDisplay.filters = [new BevelFilter(3, 90, 0xFFFFFF, .8, 0x000000, .65, 3, 3, 1, 3), new DropShadowFilter(8, 90, 0, .75, 7, 7, 1, 3)];
			addChild(_mainDisplay);
		}
	}
}

and our two display object graphics classes - "classes.graphics.Background"

package classes.graphics {
	
	import flash.display.Sprite;
	import flash.display.GradientType;
	import flash.display.SpreadMethod;
	import flash.geom.Matrix;
	
	public class Background extends Sprite {
		
		private var _color:uint;
		
		public function Background() {
			_color = 0x669900;
			
			init();
		}
		
		private function init():void {
			drawBackground();
		}
		
		private function drawBackground():void {
			var m:Matrix = new Matrix();
			m.createGradientBox(800, 600);
			graphics.beginGradientFill(GradientType.RADIAL, [0xE9FEB4, _color], [1,1], [0, 255], m, SpreadMethod.PAD); 
			graphics.drawRect(0, 0, 800, 600);
			graphics.endFill();
		}
		
		public function resize(w:Number, h:Number):void {
			this.width = w;
			this.height = h;
		}
	}
}

and "classes.graphics.MainDisplay"

package classes.graphics {
		
	import flash.display.Sprite;
	import flash.display.Shape;
	import flash.filters.DropShadowFilter;
	import flash.text.TextField;
	import flash.text.TextFormat;
	
	public class MainDisplay extends Sprite {
		
		private var _color:uint;
		private var _tf:TextField;
		
		public function MainDisplay() {
			_color = 0x669900;
			_tf = new TextField();
			
			init();
		}
		
		private function init():void {
			drawDisplay();
			addOverlay();
			addTextField();
		}
		
		private function drawDisplay():void {
			this.graphics.beginFill(_color);
			this.graphics.drawRoundRect(0, 0, 450, 400, 20, 20);
			this.graphics.endFill();
		}	
		
		private function addOverlay():void {
			var overlay = new Shape();
			overlay.graphics.beginFill(0xFFFFFF, .4);
			overlay.graphics.drawRoundRect(10, 10, 430, 380, 20, 20);
			overlay.graphics.endFill();
			overlay.filters = [new DropShadowFilter(2, 90, 0x000000, .8, 3, 3, 1, 3, true)];
			addChild(overlay);
		}
		
		private function addTextField():void {
			var fmt:TextFormat = new TextFormat("Tahoma", 12, 0x000000, null, null, null, null, null, null, null, null, null, 10);
			_tf.multiline = true;
			_tf.wordWrap = true;
			_tf.width = 400;
			_tf.height = 360;
			_tf.x = 20;
			_tf.y = 20;
			_tf.defaultTextFormat = fmt;
			addChild(_tf);
		}

		public function reposition(xPos:Number, yPos:Number):void {
			this.x = Math.round(xPos);
			this.y = Math.round(yPos);
		}
	}
}

For more detailed information on the Actionscript 3 language, changes, definitions, package descriptions, etc. make sure you head over to the Adobe LiveDocs page.





Part II.