Web DevCenter    
 Published on Web DevCenter (http://www.oreillynet.com/javascript/)
 See this if you're having trouble printing code examples


ActionScript Cookbook

An Introduction to OOP in Flash and ActionScript, Part 2

by Joey Lott, author of ActionScript Cookbook
08/19/2003

In Part 1 of this article we looked at the basics of how to work with the built-in ActionScript class. These classes include MovieClip, Array, Date, and many more. Learning how to first work with the built-in classes is a very important step in object-oriented programming. And for many developers, that much may suffice.

However, learning to create your own custom classes in ActionScript can really propel your efficiency to another level. In the second part of this series we'll take a look at how custom classes can be a benefit, and then we'll look at how to actually create your own custom ActionScript classes.

Why Make Custom Classes?

There are a lot of things you can do in this world. So before you launch off into learning something new it is often wise to ask why. In this case, it is important to ask why building a custom ActionScript class is such a great idea. If you're going to invest your time in learning how to accomplish this task, you want to first make sure it's worthwhile.

So what are the benefits of building custom classes? The potential benefits are too numerous to list. But to give a few examples:

Related Reading

Actionscript Cookbook
Solutions and Examples for Flash MX Developers
By Joey Lott

Basically, if you like to do things the hard way, then read no further. But if you want to work smarter, not harder, then welcome to custom classes.

Of Classes and Prototypes

Let's begin our discussion of how to create custom classes by looking at how Flash MX determines inheritance. Inheritance refers to instances of a class automatically getting the methods and properties of the class. Unlike languages with a formal class structure, Flash MX ActionScript uses what is known as prototype-based inheritance. The class is defined by what we call a top-level object. This top-level object has a property-named prototype. All instances of the class automatically inherit all methods and properties assigned to the prototype. Because of the way the built-in classes are defined, they don't all allow you to see the methods and properties of their prototype. Some do, however. The XML class is one such class. You can see this for yourself:

  1. Open a new Flash document.
  2. On the first frame of the default layer of the main timeline add the following code:
    for(var item in XML.prototype) {
      trace(item);
    }
  3. Test the movie.

When you try this you should see a list of the properties and methods of the XML class listed in the Output window. Even without having to know what these properties and methods actually do, you can verify that they correspond to the properties and methods listed in the Actions toolbox for the XML class.

Adding to Existing Prototypes

Next, before we create a new prototype for a totally new class, let's look at how we can add a new property or method to an existing prototype. We'll employ the same techniques to add properties and methods to the new custom prototypes as well, so this is a good starting point.

Let's add one property and one method to the MovieClip prototype. Once we've defined the new property and method we can access them from any movie clip instance. We can add new properties and methods to a prototype just the same way as we added custom properties and methods to objects in Part 1 of this article. Only now, instead of applying the custom property or method to the instance, we apply them to the prototype. The new property we'll add to the MovieClip class is called currentDepth. We'll initialize it to 1.

MovieClip.prototype.currentDepth = 1;

Now, we'll add a method, getNewDepth(). This method will return the value of currentDepth, and then increment currentDepth.

MovieClip.prototype.getNewDepth = function() {
  return this.currentDepth++;
};

Now, from any movie clip instance you can utilize the property and method. The getNewDepth() method can be particularly useful if you programmatically add a lot of movie clips and/or text fields to your movies using duplicateMovieClip(), attachMovie(), createEmptyMovie(), and createTextField(). Here is an example. Notice that getNewDepth() can be invoked from any movie clip.

var newDepth = this.getNewDepth();
this.createEmptyMovieClip("holder_mc", newDepth);
var nestedNewDepth = holder_mc.getNewDepth();
holder_mc.createEmptyMovieClip("nested_mc", nestedNewDepth);

Creating New Prototypes

Adding new properties and methods to existing classes can be moderately useful. But we're more interested in creating new prototypes. So how does one go about such a thing? It's actually quite simple: Define a function. Believe it or not, that's all there is to it. For example:

function NewClass() {
}

Each new function automatically has a prototype property to which you can assign properties and methods. When a function is used as a simple function, of course, we don't work with the prototype at all. But in cases where we want to define a new class we first create the function (we call this the constructor function), and then we assign the properties and methods to the prototype. For example, if the function/class name is NewClass, as in the preceding example, then we can add new properties and methods to the prototype as follows:

NewClass.prototype.someProperty = "someValue";
NewClass.prototype.someMethod = function() {
  trace("someMethod called");
};

You can then create a new instance of the NewClass class using a new statement just as with many of the built-in classes.

var nc = new NewClass();

And you can then utilize the properties and methods of that instance:

trace(nc.someProperty);
nc.someMethod();

Now, of course, the NewClass example is not particularly useful. So instead, let's look at an example of a class that you might actually want to use. Here we create a Drawer class. This class allows you to specify a movie clip, and then you can tell it to draw a circle in the movie clip.

function Drawer(mc) {
  this.mc = mc;
}

Drawer.prototype.mc;

Drawer.prototype.drawCircle = function(radius, x, y, outlineColor, fillColor) {
  this.mc.lineStyle(1, outlineColor, 100);
  if(fillColor != undefined) {
    this.mc.beginFill(fillColor, 100);
  }
  var angleDelta = Math.PI / 4;
  var ctrlDist = radius/Math.cos(angleDelta/2);
  var angle = 0;
  var rx, ry, ax, ay;
  this.mc.moveTo(x + radius, y);
  for (var i = 0; i < 8; i++) {
    angle += angleDelta;
    rx = x + Math.cos(angle-(angleDelta/2))*(ctrlDist);
    ry = y + Math.sin(angle-(angleDelta/2))*(ctrlDist);
    ax = x + Math.cos(angle)*radius;
    ay = y + Math.sin(angle)*radius;
    this.mc.curveTo(rx, ry, ax, ay);
  }
};

Notice that the constructor function for the Drawer class accepts a parameter. This parameter is a reference to the movie clip in which it can draw. The drawCircle() method then draws a circle in that movie clip. Here's an example:

drwr = new Drawer(this);
drwr.drawCircle(100, 0, 0, 0, 0xFDACFD);

The Drawer class is a pretty simple class as it stands, but obviously you could add many more methods to it for drawing different types of shapes.

Extending Classes

A powerful feature of classes is the ability to extend them. This means that a new class can inherit all the functionality of another class. We call the parent class the superclass, and the class that inherits from the superclass is a subclass. Creating subclasses can be very valuable when, for example, you want to create a group of related classes that share common functionality, but that also have their own, unique functionality as well. For example, consider the following classes: Dog, Cat, Bird. Each of these classes might share some of the same functionality. For example, they each might have a walk() and sleep() method. But clearly each class would also have some functionality that the others don't. The Dog class might have a bark() method. The Cat class might have a meow() method. And the Bird class might have a fly() method.

In such a case you could recreate the shared functionality in each of the classes. Or you could create a superclass, Animal, for which each of the three classes is a subclass. The Animal class defines the walk() and sleep() methods and the subclasses inherit these methods without having to have them explicitly defined within them.

In order to create a subclass all you need to do is set the class's prototype equal to a new instance of the superclass. For example, here's how you can define the Dog class to extend Animal:

function Dog() {
}

Dog.prototype = new Animal();

Now that you've had the chance to see the line of code that tells Flash that one class extends another, let's take a look at the full example with the Animal, Dog, Cat, and Bird classes. We'll create this example so that it simply illustrates that the subclassing is working. For that purpose we'll place simple trace() actions within the methods. Obviously, therefore, these classes would not be useful in an actual production environment, and if you intend to follow along you'll need to make sure you use the test player. Also, as a note, should you want to follow along, you can add all this code to the first frame of the default layer of the main timeline of a new Flash document.

First, here's the Animal class that defines the walk() and sleep() methods.

function Animal() {
}

Animal.prototype.walk = function() {
  trace("walking");
};

Animal.prototype.sleep = function() {
  trace("sleeping");
};

Now, here's the Dog, Cat, and Bird classes. Each extends the Animal class, and in addition to the inherited methods, each defines one more additional method.

function Dog() {
}

Dog.prototype = new Animal();

Dog.prototype.bark = function() {
  trace("barking");
};
 function Cat() {
}

Cat.prototype = new Animal();

Cat.prototype.meow = function() {
  trace("meowing");
};

function Bird() {
}

Bird.prototype = new Animal();

Bird.prototype.fly = function() {
  trace("flying");
};

Once you've defined these classes, you can test them by creating some instances and calling the methods. You should be able to invoke both the inherited methods and the method that is defined explicitly for each class.

spot = new Dog();
kitty = new Cat();
wings = new Bird();
spot.walk();  // Displays: walking
spot.sleep(); // Displays: sleeping
spot.bark(); // Displays: barking
kitty.walk();  // Displays: walking
kitty.sleep(); // Displays: sleeping
kitty.meow();  // Displays: meowing
wings.walk();  // Displays: walking
wings.sleep();  // Displays: sleeping
wings.fly();  // Displays: flying

You should notice that even though the walk() and sleep() methods are not explicitly defined within the subclasses, those methods are accessible to instances of the subclasses.

Creating External Class Files

Once you start creating a lot of classes you may find it useful to store the classes in external files. This can have several benefits. First of all, it allows you to use the classes in multiple Flash applications. Secondly, it is a good way of organizing your code. Rather than cluttering your timelines with classes you can simply define the class in an external file and load it when you want to use it in an application.

When you store ActionScript code in external files, the files should be plain text documents saved with the file extension .as. For example, you can save the Dog class in a file named Dog.as. You can save the file in the same directory as the .fla file, or a subdirectory of that directory. That is useful for classes that you plan to use in that application only. For classes that you want to use in multiple applications you will find it helpful to save them to the Flash Include directory. The Include directory can be found in the Configuration directory of the Flash MX installation. Regardless of what directory the .fla file is saved in, Flash will be able to load an .as file from the Include directory. Flash always looks to the local directory first. Therefore, if you have an .as file with the same name saved in both the local and the Include directories, Flash will load the local version.

Tip: Add an extra blank line to the end of your .as files. Sometimes, if you don't add an extra line Flash will generate errors.

Once you have saved your code into an external .as file you need to tell Flash to load the code into the application. You can accomplish this with the #include directive. The #include directive includes the name of the file to load. For example:

#include "Dog.as"

Notice that the name of the file to load is in double quotes. Also, the #include directive should not end in a semi-colon. If you place a semi-colon at the end of a #include directive you will receive an error.

If the .as file is in a subdirectory of either the .fla directory or the Include directory then you need to specify that directory as part of the file name in the #include directive. For example, if the Dog.as file is saved in a subdirectory named classes then the #include directive will look like this:

#include "classes/Dog.as"

Don't forget that if you want to load an external subclass you also need to load the external superclass. In the case of the Dog class you also need to load Animal.

#include "Animal.as"
#include "Dog.as"

Note: The code from an external .as file is loaded and compiled into the .swf when you export the movie. Therefore, it is unnecessary to copy the .as files along with your .swf files when distributing the application. Also, this means that if you make changes to the .as file, you will need to re-export the .swf.

Extending MovieClip

The MovieClip class happens to be one of the key classes in Flash. Even the simplest of Flash applications utilize movie clips. Creating classes that extend MovieClip can be a very powerful and useful resource. The reason that we're discussing MovieClip subclasses apart from the previous discussion of subclasses in general is that extending MovieClip is somewhat specialized.

In order to create a MovieClip subclass you need to create a MovieClip symbol in the library with which to associate the class. You need to set the symbol to export for ActionScript. And you need to associate the class with the symbol's linkage identifier. Let's take a look at how this works.

The first step in creating a MovieClip subclass is to create a new MovieClip symbol. The symbol may or may not contain artwork and/or nested movie clips, buttons, and text fields, depending on its functionality. Therefore, this step can be as simple as pressing F9, typing in a name, and clicking OK. Because MovieClip instances have a graphical representation, subclass instances should have graphical representation as well. That is why when you create a MovieClip subclass you need to have a symbol with which to associate it.

Once you have created the symbol you should next set it to export for ActionScript and assign it a linkage identifier. You can accomplish this by right-clicking/command-clicking on the symbol in the library and choosing the Linkage menu item. Then, in the Linkage Properties dialog you can click the Export for ActionScript checkbox and then type in a linkage identifier. As a best practice, give the symbol a linkage identifier in the following form: SymbolNameSymbol. For example, if the symbol is named Car, then give it a linkage identifier of CarSymbol.

Typically, for the sake of organization, we define MovieClip subclasses within the symbol with which they are associated. Therefore, you can define the class on the first frame of the symbol's timeline. When you define a class inside a symbol, however, you can run into a problem: Because of the loading order within Flash a class defined within a symbol may not get loaded by the time you try to do something with it. Therefore, you need to tell Flash to load the class first, before anything else in the movie. You can do this with the #initclip and #endinitclip directives. These directives should be placed around all the class definition within the symbol. The #initclip directive should be placed at the beginning, and the #endinitclip should be placed at the end. Just like the #include directive, the #initclip and #endinitclip directives should not be followed by a semicolon.

The class itself is defined much like any other class. And in order to extend the MovieClip class you set the class's prototype to a new MovieClip instance using the MovieClip constructor. This is the one time in which the MovieClip constructor is used in ActionScript.

There is, then, one more step. You still need to tell Flash to associate the class with the symbol. In order to do this you can use the Object.registerClass() method. The method takes two parameters -- a string specifying the name of the linkage identifier for the symbol and a reference to the class. This association established by Object.registerClass() causes Flash to automatically call the constructor of the class whenever a new instance of the symbol is created either during authoring time or during runtime. For this reason, it should be noted, you should not define the constructor to expect any parameters. Instead, if you need to initialize the instances with some values, you should create a method that handles that.

So now that you've had a chance to read the theory, let's see it in practice. Let's create a new MovieClip subclass named CarClass. Follow along with these steps:

  1. Open a new Flash document.
  2. Create a new MovieClip symbol named Car.
  3. Set the symbol to export for ActionScript, and give it a linkage identifier of CarSymbol.
  4. Within the symbol draw a car that faces to the right. If you find yourself artistically challenged, a rectangle with two circles at the bottom will suffice.
  5. Add a new layer to the symbol's timeline. Name the new layer Actions.
  6. On the first frame of the Actions layer add the following code:
    #initclip
    
    function CarClass(){}
    
    CarClass.prototype = new MovieClip();
    
    CarClass.prototype.interval;
    
    CarClass.prototype.drive = function() {
      this.interval = setInterval(this, "moveCar", 10);
    };
    
    CarClass.prototype.moveCar = function() {
      this._x++;
      var bounds = this.getBounds();
      if(this._x + bounds.xMin > 550) {
        this._x = -bounds.xMax;
      }
      updateAfterEvent();
    };
    
    CarClass.prototype.stop = function() {
      clearInterval(this.interval);
    };
    
    Object.registerClass("CarSymbol", CarClass);
    
    #endinitclip
  7. Return to the main timeline.
  8. Rename the default layer to Car.
  9. Drag an instance of the Car symbol from the library to the stage. Name the new instance theCar_mc.
  10. Create a new layer named Actions.
  11. On the first frame of the Actions layer add the following code:
    oListener = new Object();
    oListener.car = theCar_mc;
    oListener.onMouseDown = function() {
      this.car.drive();
    };
    oListener.onMouseUp = function() {
      this.car.stop();
    };
    Mouse.addListener(oListener);
  12. Test the movie.

When you press the mouse the car should move to the right. When you release the mouse, the car will stop. Let's review the code briefly:

First, of course, we enclose the class code in #initclip and #endinitclip directives. Then, we define the CarClass constructor.

#initclip

function CarClass(){}

Because we want CarClass to be a subclass of MovieClip, we assign a new MovieClip instance to the prototype for the class.

CarClass.prototype = new MovieClip();

For clarity we declare the interval property for the prototype. This step is not entirely necessary, but it can be helpful for later determining all the properties of a class.

CarClass.prototype.interval;

The drive() method sets a new interval on which the moveCar() method is called.

CarClass.prototype.drive = function() {
  this.interval = setInterval(this, "moveCar", 10);
};

Note: If you are not familiar with the setInterval() action, you can read more at www.person13.com/ascb/excerpts/setInterval.html.

The moveCar() method increments the object's _x property. Notice that instances of the class only have an _x property because they inherit it from the superclass, MovieClip. Then, we also add a little bit of code that checks to see if the car has gone off the stage. If so, we set it back to the right side of the stage. The updateAfterEvent() action makes sure the stage view refreshes often enough so that the animation is smooth.

CarClass.prototype.moveCar = function() {
  this._x++;
  var bounds = this.getBounds();
  if(this._x + bounds.xMin > 550) {
    this._x = -bounds.xMax;
  }
  updateAfterEvent();
};

The stop() method simply clears the interval so that the moveCar() method is no longer called.

CarClass.prototype.stop = function() {
  clearInterval(this.interval);
};

Then, the Object.registerClass() method tells Flash to associate the class with the symbol with the linkage identifier of CarSymbol.

Object.registerClass("CarSymbol", CarClass);

And then, on the main timeline there is a little bit of code as well. We define a new listener object named oListener. Mouse listener objects can listen for several types of events, including events handled by onMouseDown() and onMouseUp(). Therefore, we define these methods such that when the mouse is pressed the Car instance's drive() method is called, and when the mouse is released the Car instance's stop() method is called.

oListener = new Object();
oListener.car = theCar_mc;
oListener.onMouseDown = function() {
  this.car.drive();
};
oListener.onMouseUp = function() {
  this.car.stop();
};
Mouse.addListener(oListener);

Note: You can download the completed Car example from www.person13.com/ascb/.

Conclusion

That wraps up Part 2 of this article. We started out by talking about how to create instances of the built-in ActionScript classes, and we eventually talked about advanced topics such as creating your own custom classes and even creating subclasses! We covered a lot of material. For more information on these topics you may want to refer to the ActionScript Cookbook, ActionScript for Flash MX: the Definitive Guide, and Colin Moock's Website at www.moock.org. Also, if you have questions about anything in this article, I welcome you to email me at .

Joey Lott is a founding partner of The Morphic Group. He has written many books on Flex and Flash-related technologies, including Programming Flex 3, ActionScript 3 Cookbook, Adobe AIR in Action, and Advanced ActionScript 3 with Design Patterns.


O'Reilly & Associates recently released ActionScript Cookbook.


Return to the Web DevCenter.

Copyright © 2009 O'Reilly Media, Inc.