Simple Design Patterns in Adobe Flash MX
There are many tutorials on the Internet about coding and building visually pleasing applications in Flash. However, I have not seen a tutorial that focuses on "best practices" for Flash coding, or simple design patterns that can be used repeatedly across in Flash applications. It is entirely possible that the limited support for object oriented (OO) design in the Flash environment has hindered such a discussion.
Fortunately, recent versions of Flash MX support ActionScript 2.0: a programming language that meets a proposed ECMA standard for JavaScript 2 and looks a lot like Java. With this new framework in place, it is entirely possible to talk about effective design patterns for Flash applications.
In this tutorial, I wish to develop a rudimentary, but useful foundation for exploring design patterns particular to ActionScript 2.0. I will start by building the framework for a very simple demo application. After building the visual pieces of the demo, I will start to discuss some intial design possibilities. Only simple patterns of inheritance and composition are covered.
Prerequisites
You will need to have a copy of Flash MX 2004 or higher installed. You can download a demo from Adobe's website. The full version can be purchased there as well. Make sure you reboot after install if prompted so that the newest Flash Player plugin is initialized properly.
A Look Ahead
For reference, here is the application we'll be building. It's very simple, but it gives us the chance to explore various features of ActionScript 2.0. Mouse over the image to see the chasers work. Click and drag the balls to move them around by hand.
1 2 3 4 5 6 7 | <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="400" height="300" id="flash_demo" align="middle"> <param name="allowScriptAccess" value="sameDomain" /> <param name="movie" value="/files/flash_demo.swf" /> <param name="quality" value="high" /> <param name="bgcolor" value="#ffffff" /> <embed src="/files/flash_demo.swf" quality="high" bgcolor="#ffffff" width="400" height="300" name="flash_demo" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /> </object> |
Starting a New Project
To get started, follow these steps to create a new project.
- Run Flash. If you're using the trial, select the professional edition.
- Select File->New and then choose Flash Project. Alternatively, select Flash Project from the initial screen.
- Give the project a filename of Demo.flp and save it.
- Open the Project panel by clicking the arrow next to its name. It should be located on the right side of the screen. If it or any other panel in this tutorial is not visible on the screen, make it visible by choosing it from the Window menu.
Now make the files that we will need to hold the code and the graphics of the project.
- Select File->New and choose Flash Document. Save the new document to the same folder as the project file you created with a filename of Demo.fla.
- Select File->New again and choose ActionScript File. Save the new file as Bouncer.as.
- Now create two more ActionScript files and save them with names Chaser.as and Scalable.as.
Finally, add all the created files to the project workspace.
- Click the little yellow box icon in the top left corner of the Project panel and select the Add File menu item.
- Select all the files we just created and press OK.
Creating graphics
Next we will create the graphic elements needed for the demo. I kept the graphics really simple so I could concentrate on the coding, but you are free to be artistic as long as you follow the simple steps below.
- Select the Demo.fla tab at the top of the work space.
- Click the little arrow in the Timeline panel to minimize it and free up some screen space.
- Open the Library panel by pressing Ctrl-L or selecting it from the Window menu. Feel free to dock the panel on the right side of the screen.
- Click the little paper icon in the bottom left corner of the panel to create a new library object. Name the new symbol bouncer_mc and for behavior choose MovieClip. Press OK when finished.
The main view now has a blank canvas in which we can create the graphic for the MovieClip. The little crosshair on the canvas is called the registration point of the MovieClip. Basically, it's the (0,0) origin point. The only restriction for this graphic is that the registration point must be at the middle of the image. Draw, import, or paste whatever you'd like here.
We will need a second graphic for this demo. Create another MovieClip symbol and name this one chaser_mc. Draw whatever you'd like for this one too, keeping the registration point at the center.
Now that all of the resources are built, we can discuss some aspects of ActionScript coding and design. We will focus on some simple constructs of OO programming to start, namely inheritance, composition, and interfaces.
Inheritance
The object model in ActionScript 2.0 supports single inheritance. A class can only derive from a single parent class. The parent class can be another user defined class, or a native Flash class. For instance, we can write a class that derives from the native MovieClip class and inherits all of its methods, properties, and events. Inheriting from MovieClip is especially powerful, since it gives us the opportunity to write simple code that can control graphic symbols we add to our movie library.
We will construct a class that causes our bouncer graphic to move across the stage and reflect off of the edges. The class will also allow a user to pick up a bouncer and move it anywhere on the stage. Our class inherit from MovieClip so that it can be directly linked to our bouncer_mc symbol.
- Start by selecting the tab at the top of the workspace labeledBouncer.as.
- Re-type or cut and paste the following code into the blank script document and save it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | /* This example shows how a class can inherit from the built-in type MovieClip. Any class that inherits from this type can be attached to a movie clip object in the library. When an instance of such a movie clip is placed on the stage, either programmatically or at runtime, an instance of the class is automatically created and acts as the ActionScript interface to the MovieScript object. This example causes any movie clip in the library to bounce around on the stage at a random speed. The inheritance approach to controlling stage objects with classes is the most common, mainly because it is simple to implement. Just extend MovieClip with new functionality and link it to a library object. However, this approach limits a library object to a single class of functionality that is fixed at design time. */ class Bouncer extends MovieClip { //instance variables private var y_vel:Number; private var x_vel:Number; private var x_min:Number; private var x_max:Number; private var y_min:Number; private var y_max:Number; private var dragging:Boolean = false; //constructor public function Bouncer() { //determine the bounding limits for the registration point var halfWidth = this._width/2; var halfHeight = this._height/2; var clipBounds = _root.getBounds(); this.x_min = clipBounds.xMin + halfWidth; this.y_min = clipBounds.yMin + halfHeight; this.x_max = clipBounds.xMax - halfWidth; this.y_max = clipBounds.yMax - halfHeight; //Select a random direction to start moving this clip in. this.x_vel = Math.floor(Math.random()*21)-10; this.y_vel = Math.floor(Math.random()*21)-10; } //event handler, called once per frame function onEnterFrame(){ //if we're dragging, don't move the object if (this.dragging) return; //compute the next location based on the object's velocity var next_x = this._x + this.x_vel; var next_y = this._y + this.y_vel; //make sure we're not going off the left side if (next_x < this.x_min){ this.x_vel *= -1; next_x = this.x_min + (this.x_min - next_x); } //make sure we're not going off the right side else if (next_x > this.x_max){ this.x_vel *= -1; next_x = this.x_max - (next_x - this.x_max); } //make sure we're not going off the top if (next_y < this.y_min){ this.y_vel *= -1; next_y = this.y_min + (this.y_min - next_y); } //make sure we're not going off the bottom else if (next_y > this.y_max){ this.y_vel *= -1; next_y = this.y_max - (next_y - this.y_max); } //assign the computed next position to the graphics x,y values this._x = next_x; this._y = next_y; } //event handler, called when object is pressed (mouse button down) function onPress() { //begin a drag operation on the movie clip (handled by Flash automatically) this.startDrag(); //store a flag stating we're dragging this.dragging = true; } //event handler, called when object is released (mouse button up) function onRelease() { //stop dragging this.stopDrag(); this.dragging = false; } //event handler, called when object is released (button up outside object) function onReleaseOutside() { //stop dragging if we're currently dragging if(this.dragging) { this.stopDrag(); this.dragging = false; } } |
Once the code is written, we can link the code to a graphic symbol in our library.
- Open the Library panel.
- Right click the bouncer_mc symbol and select Linkage.
- Place a check in the box labeled Export for ActionScript.
- In the AS 2.0 class box type Bouncer.
We have now defined a link between our Bouncer class and our bouncer_mc MovieClip symbol. When an instance of the bouncer graphic is created on the stage, Flash will automatically call the constructor for our class. Our class will then receive events and handle them as described in the code above.
We now need to create some instances of the bouncer symbol in our movie.
- Switch back to the Demo.fla file by selecting its tab.
- Open the Library panel and drag the bouncer_mc movie clip to the stage.
- Drag a few more bouncers. Resize some of them for variety using the Resize tool. Select the tool from the toolbar on the left or by pressing Q.
Now we can execute the program and see what happens. Hopefully our bouncers will starting moving and reflecting on their own.
- Save your work.
- Press Ctrl-Enter to start the movie.
Inheritance makes it easy to attach new behaviors to existing graphic elements like our bouncer graphic. For many tasks that involve assigning behaviors to graphic elements, inheritance works fine.
One drawback to this approach is that the behavior of every instance of a graphic element linked to a Flash class is fixed at design time. Every instance of a bouncer that gets placed on the stage will have its behavior determined by the Bouncer class without exception. If I want a bouncer_mc graphic on the stage that is controlled by a different class or just sits there, I'm out of luck. I either have to make a new MovieClip symbol with the same graphic or change my approach to assigning behavior to graphics to solve this problem.
Composition
Composition is the most powerful technique available in OO programming. One object can hold a reference to another object and encapsulate the details of its public interface with another, more appropriate interface. For instance, instead of deriving a class from MovieClip, we can instead build a class that holds a reference to a MovieClip object. Using this approach, we can still control a graphic element at runtime, but without a fixed link defined at design time.
We will construct a class that causes our chaser graphics to follow the mouse cursor. This time we will favor composition over inheritance. For some additional variety, we will also write code that creates the graphic chasers at runtime, instead of placing them on the stage at design time.
- Start by selecting the tab at the top of the workspace labeled Chaser.as.
- Re-type or cut and paste the following code into the blank script document and save it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | /* This example shows how graphical objects in the library can be controlled by simple classes. This class favors composition over inheritance, and simply accepts a reference to a graphical object in its constructor. It then watches for frame events on the graphic, and causes it move toward the mouse cursor at a certain speed. This approach to controlling stage objects using classes is somewhat better than the often used inheritance approach. For example, it does not force an object in the library to match one and only one class. Any object in the library can be passed to this class and become a mouse chaser just as easily as it can be passed to another class. In addition, this approach support assigning and changing behaviors to stage objects at runtime whereas the inheritance approach is fixed at design time. */ class Chaser { private var speed:Number; private var graphic:MovieClip; //event handler called once per frame private function onEnterFrame() { //move the chaser closer to the mouse cursor this.graphic._x += (this.graphic._parent._xmouse - this.graphic._x)*speed; this.graphic._y += (this.graphic._parent._ymouse - this.graphic._y)*speed; } //constructor function Chaser(s:Number, g:MovieClip) { this.speed = s; this.graphic = g; /*This part is tricky! We can't simply point the event handler on the graphic to the function in this class. If we do that, then the event handler in this class will be called in the scope of the graphic. In other words, 'this' will reference the graphic, and not any of the properties that we need in this class! To 'fool' the compiler, we define an inline function that will call the function in this class. Since an inline function inherits variables in its parent's scope we can tell it to call the onEnterFrame in this class by putting a reference to 'this' class into a variable of a different name: 'self'. When self.onEnterFrame is called, it will be executed in the scope of the class.*/ var self = this; this.graphic.onEnterFrame = function() {self.onEnterFrame()}; } } |
We next need to make the chaser movie clip visible to our ActionScript code.
- Open the Library panel.
- Right click the chaser_mc symbol and select Linkage.
- Place a check in the box labeled Export for ActionScript.
- Do not link to the Chaser class like we did for the Bouncer!
Now we can write code that will instantiate the chaser symbols on the stage at runtime.
- Select the Demo.fla tab at the top of the workspace.
- Open the Actions panel.
- Select the first frame of the movie in the Timeline panel. Alternatively, select Layer 1, Frame 1 from the left side of the Actions panel.
- Re-type or cut and paste the following code into the blank document.
1 2 3 4 5 6 7 8 | //dynamically add mouse chasers to the movie for(var i:Number=1; i < 10; i++) { //create the chaser graphic c = this.attachMovie('chaser_mc', 'chaser'+i, i); //create the chaser class that will control the graphics obj = new Chaser(0.05*i, c); } |
With the code in place, we can execute the program and see what happens. The chaser graphics should follow the mouse cursor, each at a different speed.
- Save your work.
- Press Ctrl-Enter to start the movie.
Composition makes it easy to change the behvior of our graphic elements at runtime. Instead of defining a static link between a class derived from MovieClip and a MovieClip symbol in the library, we simply pass any symbol to our Chaser class at runtime. This also allows us to break the often undesired coupling between a symbol and a behavior. All of the chaser_mc symbols we place on the stage will not behave according to the Chaser class unless we say so. We can reuse the chaser graphic for other purposes.
This simple use of composition has its problems, however. First, if we want to expose some of the native functions of the MovieClip hosted by our class (i.e. x, y, width, height, etc.), we have to create a wrapper function for each method or property we want to expose. This could require quite a bit of work.
Second, the simple example given above limits what truly can be accomplished using composition. Imagine that we'd like to create another class, maybe called Fader, that causes a graphic element to fade in and out. Perhaps we want to create a graphic at runtime, pass it to our Chaser class, and then pass it to our Fader class to give it both behaviors. This will not work because the onEnterFrame event handler setup by the Chaser class will be overridden by the onEnterFrame event handler of the Fader class. The graphic will just fade, but not chase.
This second problem can be resolved, of course, with some extra code that allows an object to support multiple event handlers per event (i.e. the Observer pattern). Many of the new components in Flash MX follow this pattern by implementing the EventHandler interface.
Interfaces
As stated earlier, the ActionScript 2.0 object model does not support multiple inheritance. Instead, it uses the concept of interfaces to provide conceptual sets of functionality from a single class. In simple terms, an interface is like a contract signed by a class. When a class states that it will implement an interface, it is bound by contract to provide the methods found in that interface. The interface itself just states the methods that must be provided by a class that implements it.
We will create a simple interface called Scalable and implement it in our Chaser class. The interface will define methods that a scalabale object must have--namely methods for changing the size of the object.
- Start by selecting the tab at the top of the workspace labeled Scalable.as.
- Re-type or cut and paste the following code into the blank script document and save it.
1 2 3 4 5 6 7 8 9 10 11 | /* An interface defines a schema for functions that must be implemented by any class wishing to implement the interface. This example simply states that a class that wants to implement the Scalable interface must implement a function called Scale. The Chaser class in this demo implements the scalable interface. */ interface Scalable { //scale the width and height of an object by a factor public function Scale(w:Number, h:Number); } |
Now we must implement this interface in the Chaser class and call its Scale function from our code.
- Add code to the Chaser class and the loop in the first frame of the program so that they match the example shown below. The sections to be added are shown in blue. The majority of the Chaser class is ommitted because it remains unchanged.
1 2 3 4 5 6 7 8 9 10 11 | class Chaser implements Scalable { private var speed:Number; private var graphic:MovieClip; public function Scale(w:Number, h:Number) { //scale the width and height of the graphic by a factor this.graphic._width *= w; this.graphic._height *= h; } ... |
1 2 3 4 5 6 7 8 9 | //dynamically add mouse chasers to the movie for(var i:Number=1; i < 10; i++) { //create the chaser graphic c = this.attachMovie('chaser_mc', 'chaser'+i, i); //create the chaser class that will control the graphics obj = new Chaser(0.05*i, c); obj.Scale(1.0/i, 1.0/i) } |
Finally, we can execute the program to see the finised project. The slower chaser graphics should now appear larger than the faster ones.
- Save your work.
- Press Ctrl-Enter to start the movie.