13.2 Avatar: A MovieClip Subclass Example

 < Day Day Up > 

Our example MovieClip subclass, named Avatar , is an on-screen representation of a user in a chat room or a game.

For the purposes of this example, we assume that the relationship between our Avatar class and the MovieClip class is a natural "Is-A" inheritance relationship. In other words, the Avatar class doesn't need to inherit from some other class, and users of our Avatar class need to treat each Avatar instance like a MovieClip instance (drag it, manage its depth, mask it, etc.). These assumptions are, in truth, a bit far- fetched . The Avatar class could quite appropriately be implemented using composition, as we'll show later. For the sake of learning, however, we'll show how to implement the class with inheritance first. (The present difficulty is that any class simple enough to use as an example is, by its very simplicity, probably a better candidate for composition than inheritance.)

13.2.1 The AvatarSymbol Movie Clip

As we learned earlier, every MovieClip subclass has a corresponding movie clip symbol. Our Avatar class's movie clip symbol is called AvatarSymbol . It contains a graphical depiction of the user in three different states: Idle, Sad, and Happy. The three states correspond to three labeled frames in AvatarSymbol 's timeline. To change the state of the avatar, we position AvatarSymbol 's playhead at the appropriate frame in its timeline. Figure 13-1 shows AvatarSymbol 's timeline and the contents of its first frame, the Idle state.

Figure 13-1. The AvatarSymbol timeline
figs/as2e_1301.gif

Here are the steps we follow to create the AvatarSymbol movie clip, which we'll later associate with the Avatar class (see "Linking AvatarSymbol to the Avatar Class"):

  1. Create a new Flash document named AvatarDemo.fla.

  2. Select Insert New Symbol.

  3. For the symbol Name , enter AvatarSymbol . The name is arbitrary, but by convention, you should name the symbol Class Symbol where Class is the name of the class with which you expect to associate the symbol.

  4. For the symbol's Behavior type, select Movie Clip.

  5. Click OK. Flash automatically enters Edit mode for the AvatarSymbol symbol.

Here are the steps we follow to build AvatarSymbol 's timeline and contents:

  1. Double-click Layer 1 and rename it to graphics .

  2. Select frame 30 of the graphics layer.

  3. Choose Insert Timeline Frame (F5).

  4. Timeline Layer, create two new layers , and name them labels and scripts .
  5. With frame 1 of the scripts layer selected, enter the following code into the Actions panel (F9):

     stop( ); 

  6. Using Insert Timeline Blank Keyframe (F7), create blank keyframes on the labels and graphics layers at keyframes 10 and 20.

  7. With frame 1 of the labels layer selected, in the Properties panel, under Frame, change <Frame Label> to IDLE .

  8. With frame 10 of the labels layer selected, in the Properties panel, under Frame, change <Frame Label> to SAD .

  9. With frame 20 of the labels layer selected, in the Properties panel, under Frame, change <Frame Label> to HAPPY .

  10. On the graphics layer, use Flash's drawing tools to draw an idle face on frame 1, a sad face on frame 10, and a happy face on frame 20.

You may have noticed that we added a stop( ) statement on frame 1 but not on frames 10 and 20. We'll see why in a moment. Now that the AvatarSymbol symbol is complete, let's turn our attention to the Avatar class.

13.2.2 The Avatar Class

The source code for the Avatar class is surprisingly simple. To subclass the MovieClip class, we merely use the extends keyword, as we would for any other class. The basic code is:

 class Avatar extends MovieClip { } 

figs/as1note.gif In ActionScript 1.0, MovieClip could be subclassed using an arcane nest of #initclip , Object.registerClass( ) , and new MovieClip( ) calls. In ActionScript 2.0, the Flash authoring tool handles all that dirty work. Syntactically, MovieClip is extended like any other class, with the extends keyword.

The Avatar class defines two instance methods :


init( )

Initializes each Avatar instance


setState( )

Sets the avatar's state using one of three constants (class properties) IDLE , SAD , and HAPPY .

Example 13-1 shows the Avatar class in its entirety.

Example 13-1. Avatar, a MovieClip subclass
 class Avatar extends MovieClip {   public static var HAPPY:Number = 0;   public static var SAD:Number = 1;   public static var IDLE:Number = 2;   public function init ( ):Void {     setState(Avatar.HAPPY);   }   public function setState(newState:Number):Void {     switch (newState) {       case Avatar.HAPPY:         this.gotoAndStop("HAPPY");         break;       case Avatar.SAD:         this.gotoAndStop("SAD");         break;       case Avatar.IDLE:         this.gotoAndStop("IDLE");         break;     }   } } 

Notice that the class has no constructor function. Instances of MovieClip subclasses are not created using the standard constructor call syntax (using the new operator), so there's no need to provide a constructor function. Our init( ) method fulfills the traditional role of the constructor and should be called on each Avatar instance directly after it's created. (You can use constructor functions with MovieClip subclasses but, as we'll learn shortly, doing so can be cumbersome and error prone.)

The Avatar.init( ) method calls setState( ) to set each Avatar instance's initial state to Avatar.HAPPY . In the setState( ) method, we see the real marriage between the Avatar class and the AvatarSymbol movie clip; setState( ) changes the visual state of an Avatar instance by positioning the playhead of the associated movie clip instance. For example, in the expression this.gotoAndStop("HAPPY") , the current object ( this ) is the instance of the AvatarSymbol movie clip. Thus, setState( ) sends the movie clip instance's playhead to the frame labeled HAPPY .

Within MovieClip subclass .as files, you must use this explicitly with MovieClip methods whose names collide with corresponding global functions. For example, you must use this.gotoAndStop( ) not gotoAndStop( ) . The affected MovieClip methods are: duplicateMovieClip( ) , getURL( ) , gotoAndPlay( ) , gotoAndStop( ) , loadMovie( ) , loadVariables( ) , nextFrame( ) , play( ) , prevFrame( ) , removeMovieClip( ) , startDrag( ) , stop( ) , stopDrag( ) , and unloadMovie( ) .

To be safe, you should always use this when invoking any MovieClip method on the current object.


Observant (or perhaps, confused ) readers may notice that there is no instance property that stores the Avatar instance's state. How then is the state stored? In fact, it isn't. Changes to the Avatar instance's state are represented on screen via the associated clip's timeline, but that state is never retrieved, so it is not stored in an instance property. In one sense, the state is "stored" implicitly by the clip's playhead position. At any given time, the frame displayed in the associated clip corresponds with the Avatar instance's state. Although we could, in theory, use the MovieClip._currentframe property to check the playhead's position, thus retrieving the state, we should not do so in practice. If the state of an Avatar instance needs to be retrieved, we should define a state instance property to store the instance's current state.

You may have noticed that the AvatarSymbol movie clip includes a stop( ) command on frame 1 but not on frames 10 and 20. That initial stop( ) command ensures that the playhead stops at frame 1 (the Idle state) when the clip first loads. What about stopping at frame 10 or 20 when the state of the clip changes to the Happy or Sad state? Here is another case in which the implementation for the movie clip symbol depends in part on the code you expect to write in the associated class. Because setState( ) invokes MovieClip.gotoAndStop( ) , we don't need a separate stop( ) command at frames 10 and 20. If, however, setState( ) invoked gotoAndPlay( ) instead of gotoAndStop( ) , we'd need separate stop( ) commands in frames 10 and 20 of our movie clip (or perhaps shortly thereafter if the state change included an animation).

With practice, you'll learn how to design and coordinate a MovieClip subclass and its associated movie clip symbol. The degree and type of integration required depends on the responsibilities you assign to each element. As much as possible, you should keep logic and data in the class and use the symbol for display only.

13.2.3 Linking AvatarSymbol to the Avatar Class

To associate the AvatarSymbol movie clip with the Avatar class, we must set the AvatarSymbol 's so-called "AS 2.0 Class" in the Flash authoring tool, as follows :

  1. Select the AvatarSymbol movie clip in AvatarDemo.fla's Library.

  2. Select the pop-up Options menu in the top-right corner of the Library panel, and choose the Linkage option.

  3. In the Linkage Properties dialog box, for Linkage, select Export for ActionScript.

  4. In the Linkage Properties dialog box, for Identifier, enter AvatarSymbol .

  5. In the Linkage Properties dialog box, for AS 2.0 Class, enter Avatar .

  6. Click OK.

If our Avatar class were part of a packagesay org. moock .chat then in Step 5, we would enter the fully qualified class name, org.moock.chat.Avatar , instead of the unqualified class name, Avatar . Notice that in the Linkage Properties dialog box, we chose to export the AvatarSymbol movie clip in frame 1. If we had wanted to preload the AvatarSymbol movie clip and the Avatar class, we'd have followed the instructions we used to preload a component in Chapter 12.

Now that we've created a movie clip symbol and linked it to a MovieClip subclass, we can create instances of the symbol that combine its own content with the class's behavior. Let's see how.

13.2.4 Creating Avatar Instances

We create an instance of a MovieClip subclass by creating an instance of the associated library symbol using attachMovie( ) (exactly as we create a normal MovieClip instance). Here's the code:

 var av:Avatar = Avatar(someMovieClip.attachMovie("AvatarSymbol",                                                   "avatar", 0)); 

The instance of the library symbol ( AvatarSymbol ) is physically added to the Stage and also reflected back to ActionScript in the form of the object returned by attachMovie( ) . The returned object is an instance of the subclass ( Avatar ), while the on-stage movie clip is an instance of the symbol ( AvatarSymbol ).

Flash's use of the term "instance" to describe the relationship that an on-stage movie clip has to its library symbol is unfortunate in an OOP discussion. An on-stage movie clip "instance" is better described as a "physical incarnation" of its library symbol. That physical incarnation is not an "instance" of the library symbol in the OOP sense. That is, the library symbol is not a class; it is a graphical template. However, a physical incarnation of a movie clip symbol is always represented in ActionScript by a corresponding instance of a class, in the traditional OOP sense. Specifically, the ActionScript representation of the physical incarnation of a movie clip is an instance either of the MovieClip class or of a MovieClip subclass such as Avatar . (Say that ten times fast!)

Bearing in mind the subtle but important distinction between a MovieClip instance and a movie clip symbol's physical incarnation on stage, let's revisit the preceding instance-creation code:

 var av:Avatar = Avatar(someMovieClip.attachMovie("AvatarSymbol",                                                   "avatar", 0)); 

When that code runs, a physical incarnation of the AvatarSymbol symbol is attached (added) to the Stage, inside the existing physical movie clip referenced by the variable someMovieClip . The physical incarnation of AvatarSymbol is named "avatar" and is placed on depth 1 of someMovieClip . The attachMovie( ) method then returns an ActionScript object that we will use to control the physical incarnation we just created. That ActionScript object is an instance of our Avatar class. We store it in a variable named av . So technically there are two "instances" here: the on-stage instance of the AvatarSymbol movie clip symbol and the ActionScript object (instance) whose class is Avatar . However, in a more casual discussion (and elsewhere in this book), you'll often find these two instances treated as one and the same.

Dual-instance issues aside, notice that in our instance-creation code we cast the return value of attachMovie( ) to the Avatar type. The cast is necessary because attachMovie( ) 's return type is MovieClip , but the object returned in this case is actually an Avatar instance. For example, this code generates a type mismatch error:

 var av:Avatar = someMovieClip.attachMovie("AvatarSymbol",                                            "avatar", 0); 

But this code does not cause an error because we're telling the compiler that the return value of attachMovie( ) will, in this case, be an Avatar instance:

 var av:Avatar = Avatar(someMovieClip.attachMovie("AvatarSymbol",                                                   "avatar", 0)); 

Note that the preceding cast is an (unsafe) downcast . If we were being more cautious, we'd use:

 var av:Avatar; var temp_mc:MovieClip = someMovieClip.attachMovie("AvatarSymbol",                                                    "avatar", 0); // Verify that the cast will succeed at runtime before attempting it. if (temp_mc instanceof Avatar) {   av = Avatar(temp_mc); } else {   trace("Warning: could not cast temp_mc to type Avatar.") } 

For full information on casting, see Chapter 3.

The instance-creation code discussed in this section can be used in the AvatarDemo.fla , in any class loaded by AvatarDemo.fla , or, more generally , in any class that has access to both the Avatar class and the AvatarSymbol movie clip symbol. When a MovieClip subclass is reused across many projects, consider compiling it as a component, which includes both the class and the symbol in a single file. For details on creating components , see Help Using Components Creating Components in Flash's online Help.

13.2.5 Initializing Avatar Instances

To initialize our new Avatar instance, we invoke init( ) on it, as follows:

 av.init( ); 

Creating Avatar instances with the attachMovie( ) syntax can be ungainly because we have to specify the AvatarSymbol and the name of that symbol's physical incarnation ("avatar") in the call. Furthermore, requiring developers to call init( ) on each Avatar is sure to lead to errors. To smooth the instance-creation process, we'll create a class method, createAvatar( ) . The createAvatar( ) method creates and initializes a new Avatar instance, wrapping the attachMovie( ) and init( ) calls into a single call for the end developer. Here's the code:

 public static function createAvatar (name:String, target:MovieClip,                                       depth:Number):Avatar {   var av:Avatar = Avatar(target.attachMovie("AvatarSymbol", name, depth));   av.init( );   return av; } 

The createAvatar( ) method conveniently supplies the name of the symbol associated with the Avatar class, so users of the class don't need to know or remember that it's AvatarSymbol . The method also invokes init( ) on the new instance after it's created, so we can be sure init( ) will always be invoked on new Avatar instances. Here's how we'd use the createAvatar( ) method:

 var av:Avatar = Avatar.createAvatar("avatar", someMovieClip, 0); 

The createAvatar( ) method also gives us the flexibility to define parameters for use during instance initialization, exactly like a constructor function. For example, we could easily add parameters to createAvatar( ) that set the new instance's initial position:

 public static function createAvatar (name:String, target:MovieClip,                                       depth:Number, x:Number,                                      y:Number):Avatar {   var av:Avatar = Avatar(target.attachMovie("AvatarSymbol", name, depth));   av.init(x, y);   return av; } public function init (x:Number, y:Number):Void {   setState(Avatar.HAPPY);   this._x = x;   this._y = y; } 

Our avatar-creation code would now look like this:

 var av:Avatar = Avatar.createAvatar("avatar", someMovieClip, 0, 100, 100); 

which is much tidier and easier to understand than:

 var av:Avatar = someMovieClip.attachMovie("AvatarSymbol", "avatar", 0); av.init(100, 100); 

For another example of a MovieClip subclass that uses an instance-creation method, see the UIObject.createClassObject( ) method and the Menu.createMenu( ) method in Flash's online Help, under Help Using Components Components Dictionary UIObject and Help Using Components Components Dictionary Menu (Flash Professional Only).

The MovieClip.attachMovie( ) method supports a parameter, initObj , that can transfer properties to a new movie clip at creation time. For code clarity, I recommend against requiring initObj as part of a class design. Instead, I prefer to use a method such as createAvatar( ) with a helper method, init( ) , to handle instance initialization (as shown in the preceding example).

Here's the code for the completed Avatar class, showing the new createAvatar( ) method and updated init( ) method in context with the rest of the class:

 class Avatar extends MovieClip {   public static var HAPPY:Number = 0;   public static var SAD:Number = 1;   public static var IDLE:Number = 2;   public static function createAvatar (name:String, target:MovieClip,                                         depth:Number, x:Number,                                        y:Number):Avatar {     var av:Avatar = Avatar(target.attachMovie("AvatarSymbol", name, depth));     av.init(x, y);     return av;   }   public function init (x:Number, y:Number):Void {     setState(Avatar.HAPPY);     this._x = x;     this._y = y;   }   public function setState(newState:Number):Void {     switch (newState) {       case Avatar.HAPPY:         this.gotoAndStop("HAPPY");         break;       case Avatar.SAD:         this.gotoAndStop("SAD");         break;       case Avatar.IDLE:         this.gotoAndStop("IDLE");         break;     }   } } 

 < Day Day Up > 


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net