The new display architecture is one of the two biggest ActionScript changes in version 3.0. (The other biggest change comes in the form of the new event model, which the next chapter covers.) In this chapter, you’ll learn the basics of displaying content, which have been simplified over prior versions of ActionScript in a few ways.
To begin with, presenting visual assets to the user has become much
more consistent. Previous ActionScript versions gave you a handful of
different ways to display a movie clip, for example, and many more when
factoring in the display of all types of visible assets. In ActionScript
3.0, however, all visual assets are displayed in the same general manner:
create the asset (if necessary) using the new
keyword, and then add it to the
display list.
The display list is a type of linear array (although with its own access methods) of objects that the user can see. In simple terms, an empty SWF file has an empty display list, a SWF file with a single movie clip has a display list that contains one item, and so on. (More accurately, each SWF file includes the stage and main timeline at the root of the display list, but you can’t remove these, and so they’re not included as list indices.)
Although the display list can contain only objects with visual data, you can create such a display object without adding it to the display list. That is, a sound can’t be a display object because you can’t see it, but you can create a movie clip (which can be visible) without adding it to the display list.
You can hide a display object while it’s in the display list (by
manipulating its visible
or alpha
properties, or even moving it offstage or
covering it up with another display object). You can also remove an item from the display list without
removing it from memory, thereby making it invisible to the user.
Remember, however, that if an object has a visual component, it’s a
display object and therefore can be included in the display list and,
conversely, objects that are incapable of being seen by the user can’t be
display objects and therefore can’t be included in the display
list.
Review the display object classes to determine which has the most useful set of properties, methods and/or events to satisfy your needs.
Understanding the display list begins with looking at the classes
that make up the collection of all objects that can be part of the
display list, as seen in Figure 13-1. They originate with the
DisplayObject
class, which defines
the basic properties, methods, and events all display objects
share.
This chapter focuses on the most common display objects, but an overview of all the display list classes helps clarify their purposes. Begin on the left of Figure 13-1, with three eponymous classes.
Shape
is a lightweight class
that requires very little memory and performance overhead
because it has no timeline or mechanism for reacting to mouse events.
The result of creating and drawing into a shape with ActionScript is the
same as the element created when you draw a shape by hand in the Flash
authoring environment. Accomplishing this with code is a new feature in
ActionScript 3.0.
Bitmap
and Video
classes are used, as you might expect,
to display bitmaps and videos spawned from ActionScript. Skipping for a
moment to the right half of Figure 13-1, you find three classes
that you’re less likely to use directly. AVM1Movie
references loaded SWF files created using ActionScript 1.0
or 2.0, MorphShape
refers to timeline-based shape tweens, and StaticText
refers to non-interactive text elements created in the
Flash IDE. You can’t create instances of these classes, but they’re used
as parent classes for other display objects.
Moving back to the center of the figure, InteractiveObject
can’t be instantiated
directly, but is a parent class for all interactive elements in the
display list. The third row of the figure contains SimpleButton
, TextField
(so, in ActionScript 3.0, you can
create true buttons on the fly), and another parent class that can’t be
instantiated, DisplayObjectContainer
.
This latter class adds the ability for a display object to have
children. While a bitmap couldn’t contain a nested movie clip, for
example, any child of the DisplayObjectContainer
class can.
The Stage
class can’t be
instantiated but does give you access to stage properties from any
display object in the display list. Loader
is used to load external display assets
such as images and other SWF files, and Sprite
is simply a movie clip without a
timeline, provided for memory and performance optimization. Finally,
MovieClip
adds a timeline to Sprite
, and the collection of display list
classes is complete.
Choosing an appropriate display object depends largely on the task
at hand. Some choices are clear, such as when dynamic bitmaps, videos,
buttons, or text fields must be created, using their respective classes,
or when external assets must be loaded into a Loader
instance.
Others aren’t as obvious, but will soon become second nature.
Shape
instances serve primarily as
canvases for dynamically drawing assets using the Graphics
class, as discussed in Chapter 12. Because shapes have no
timeline or user event capabilities, they’re best suited for drawing
backgrounds and other objects that aren’t interactive. Movie clips are
useful for frame-based animations, just as in the Flash IDE, and sprites
are optimal for ActionScript-based animations when you don’t need a
timeline, but you do need user events.
You want to create an asset, such as a movie clip or sprite, which
is derived from one of the DisplayObject
classes.
Creating any new display object is consistent throughout
ActionScript 3.0, using the new
keyword to spawn a new instance of the display object’s class.
Typically, you want to store that instance in a variable that’s also
typed to the display object’s class, or a relevant parent class. The
following examples show the creation of four different display objects,
and their corresponding variable references with data typing.
var mc:MovieClip = new MovieClip(); var sp:Sprite = new Sprite(); var tf:TextField = new TextField(); var sh:Shape = new Shape();
Building on that consistency, you can also create an instance of a Library-based display object using the same syntax. The only difference is that instead of using one of Flash’s built-in classes, you should use the name of the Library element’s linkage class.
For more information about using the Library and linkage classes, see Chapter 7.
In this next example, a Library movie clip has been given a
linkage class of Ball
. The syntax
still uses the new
keyword to create
an instance of the class, and stores the instance in a variable typed
with the same class.
var ballObj:Ball = new Ball();
If desired, you can also type the variable with a relevant parent
display class. For example, the Library element may have originated as a
movie clip and, when given the linkage class of Ball
, extended MovieClip
as its base class. You could type
the new instance of the Ball
class as
MovieClip
, Sprite
, or DisplayObject
. You could use any one
of the following three possible lines. This example relates to casting
display objects to different data types, which will be covered later in
the chapter.
var ballObj:MovieClip = new Ball(); var ballObj:Sprite = new Ball(); var ballObj:DisplayObject = new Ball();
While you can work with display objects immediately after creating them, you can’t see them until they’re added to the display list. This topic is covered in the next recipe.
13.3 Adding a Display Object to the Display List for adding a display object to the display list.
Use the addChild()
method of
the display object container’s class to add the object to the display
list.
A display object must be in the display list if it’s ever to be
visible at runtime or be responsible for providing access to other
display properties (such as a stage reference). This quality is
independent of the visible
or
alpha
property values of a display
object, or other techniques for showing or hiding a visual asset.
The following script creates a new sprite and positions it at point (30, 30).
var sp:Sprite = new Sprite(); sp.x = sp.y = 30;
However, even though the sprite has x and y coordinates that are within the stage, you can’t see it until it’s added to the display list. The following method adds the specified display object to the top of the display list, regardless of the number of items already in the list.
addChild(sp);
The display list isn’t just a linear array of display objects;
it’s also contiguous. That is, it can’t have gaps between display
objects. This is a device for automating and optimizing depth
management, and eliminates the need for the getNextHighestDepth()
method in prior versions
of ActionScript. Rather than specifying an arbitrarily high depth number
to be sure a new visual asset is on top of another, using addChild()
automatically appends the display
object to the end (top) of the display list.
Further, the list automatically adjusts itself to eliminate any gaps. If you remove an object from the display list (discussed later in this chapter), then all objects higher in the list automatically drop down. If you insert an object in a position lower than other objects in the list (also discussed later in this chapter), then the higher objects all bump up a position.
So far, the sprite has no content, but you can add visual data through the use of another display object. The following script segment creates a shape and draws into it a 40-pixel by 40-pixel yellow square.
var sh:Shape = new Shape(); var g:Graphics = sh.graphics; g.lineStyle(1, 0x000000); g.beginFill(0xFFFF00, 1); g.drawRect(0, 0, 40, 40); g.endFill();
Previously, the sprite was added to the main timeline but, in this case, the shape will be added to the sprite. Display objects can be added to other display objects, and the former become children of the latter.
sp.addChild(sh);
You can certainly draw directly into a sprite. However, in addition to demonstrating hierarchical, or nested, display objects, this emphasizes that not all display objects behave the same way. On its own, the shape couldn’t function as an interactive element because it can’t process mouse clicks, among other events. However, by adding the shape to a sprite, the shape can then, by extension, be clickable.
13.1 Choosing Which Type of Display Object to Use for choosing a display object type, 13.4 Specifying the Depth of a Display Object for setting the depth of an object, 13.6 Removing a Display Object from the Display List, for removing an object from a display list, and 13.9 Casting a Display Object from One Type to Another for recasting a display object type.
You want to place a display object at a particular visual stacking depth, or swap the visual depths of two display objects.
Based on need, use the addChild()
, addChildAt()
, setChildIndex()
, swapChildren()
, or swapChildrenAt()
methods of the display object
container’s class.
You’ll often find it helpful to alter the depths at which display objects reside. Consider, for example, an interactive jigsaw puzzle. To simulate a real-world puzzle experience, it helps to move the clicked piece to the top of the pile while dragging it to the puzzle board.
You have several ways to change the stacking order of display objects. The first is simply to add the same object to the display again. In the following code, a sprite (red, 0xFF0000) is added to the display list first, followed by a movie clip (blue, 0x0000FF). However, the first sprite is added to the display list again. Because it’s the same object, it’s automatically removed from its prior position and placed in its new position. As a result, the sprite moves to the top of the stacking order.
var sp:Sprite = new Sprite(); drawSquare(sp, 0xFF0000); sp.x = sp.y = 20; addChild(sp); var mc:MovieClip = new MovieClip(); drawSquare(mc, 0x0000FF); mc.x = mc.y = 40; addChild(mc); addChild(sp); function drawSquare(obj:Object, col:uint):void { var g:Graphics = obj.graphics; g.beginFill(col, 1); g.drawRect(0, 0, 40, 40); g.endFill(); }
You also have more direct ways of placing a display object at a
specific level. The addChildAt()
method
is a companion to addChild()
but lets
you dictate a destination depth for the display object. The method has
two parameters: the object being placed, and the target depth. As with
addChild()
, the object on which this
method is called dictates scope—the display list to which this child
will be added. Omitting an object reference before the method adds the
child to the current scope—the display list in which the omitted object
resides.
For example, the following adds a library symbol, stored in the
variable boxSp
, to the bottom of the
main timeline (the current scope) by specifying a depth of zero.
var boxSp:Sprite = new Box(); addChildAt(boxSp, 0);
For example, the following adds a display object to the main
timeline (the current scope) because no target for addChild()
is specified. In this case, it adds
a library symbol, stored in the variable boxSp
, to the bottom of the main timeline by
specifying a depth of zero.
var boxSp:Sprite = new Box(); addChildAt(boxSp, 0);
If you wanted to add boxSp
to
the bottom of the child list of an existing display object container,
such as the previously created movie clip mc
, the syntax would follow the examples for
addChild()
and look like this:
mc.addChildAt(boxSp, 0);
13.3 Adding a Display Object to the Display List for adding a display object to the display list.
You need to create a reference to a display object, but you have access to only its name or position in the display list.
People often want to access a display object without already having a reference to the object in question. For example, you may want to generate an object’s name by combining strings or accepting user input, or you may want to loop through all children of a container addressing each item by child index.
The first line of the following ActionScript block retrieves a reference to an object by using its name. The second shows how to reference the bottom-most display object (because it gets the display list child at depth zero).
var dispObj:DisplayObject = getChildByName("claire"); var dispObj2:DisplayObject = getChildAt(0);
Both these methods are really useful. The latter makes it possible to work with an object at any depth, even without knowing which object currently occupies that position in the display list. You virtually require the former when creating display objects with ActionScript because of a small change in the new version of the language.
In ActionScript 2.0, you could use a name that was added to a symbol instance with code to reference its properties or methods. That is, if you created an empty movie clip, you could assign it a name with ActionScript, and then use that name as a proper instance name as if it were applied in Flash’s Property inspector.
In ActionScript 3.0, you can’t do this any more. The string used for the instance’s name property remains a string—a property value—and can’t be evaluated as an object. The following code example illustrates this point. The first code segment creates, names, and positions a display object, and then adds it to the display list. The second segment traces the x coordinate of the display object, using its programmatically assigned name as an instance reference. The result is an error because the value of the name property isn’t understood to be a proper object.
var sp:Sprite = new Sprite(); sp.name = "claire"; sp.x = 100; addChild(sp); trace(claire.x); //yields error: //1120: Access of undefined property claire.
The simple solution is to use the getChildByName()
method to return a fully
qualified object reference, as seen here:
var dispObj:DisplayObject = getChildByName("claire"); trace(dispObj.x); //yields 100
13.4 Specifying the Depth of a Display Object, for setting the depth of the display object.
You want a display object to be hidden from view, unable to receive events, and/or disassociated from familial relationship with other display objects.
Use the removeChild()
or
removeChildAt()
methods of the
display object container’s class to remove the object from the display
list.
You have a few ways to hide a display object, and more than one way to block events from reaching a display object. These tasks required a little thinking in prior versions of ActionScript, but are incredibly easy when using the display list. You just have to remove the display object in question from the display list.
The following line removes a display object sp
from the scope in which the display object
resides. Continuing the scenario shared by other recipes in this
chapter, consider the sprite, sp
, in
the main timeline.
removeChild(sp);
As with addChild()
, you can
also invoke this method in another scope. The following, for example,
removes a hypothetical nested movie clip mc
from its parent sp
.
sp.removeChild(mc);
Finally, just as you could add a child to a specific depth with
addChildAt()
, you can also remove a
child from a specific depth. The following example removes the first
child, or child at the lowest depth, of all nested display objects
within sp
.
sp.removeChildAt(0);
Note that none of these methods removes a child from memory. They
all remove only the object in question from the display list. So you can
temporarily remove a child from the display list and restore it later.
If you want to remove a display object from memory as well, you can set
it to null
.
The following repeats the earlier example of removing sprite
sp
from the display list, but then
also nullifies the sprite so it can be marked for removal from memory by
the garbage collector.
removeChild(sp); sp = null;
13.3 Adding a Display Object to the Display List for adding a display object to the display list and 13.4 Specifying the Depth of a Display Object for setting the depth of a display object.
You want to determine if a display object has children or a particular child, or you want to disable the mouse events of all children.
Check to see if a display object is of type DisplayObjectContainer
to see if it can have
children. Use the numChildren
property to determine if a display object container has children, or the
contains()
method to determine if a
particular child of the container exists. Use the mouseChildren
property to enable or disable
mouse events for all children of a container.
Every visible element is a display object but not all display objects are display object containers as shown in Figure 13-1. Neither shapes, bitmaps, nor videos, for example, are display object containers and, therefore, can’t have children.
When trying to work with children of an object, it helps to avoid
errors by first making sure that the object can have children. You do
this by checking to see if the instance in question qualifies as a
DisplayObjectContainer
data type,
using the is
operator. This
conditional traces a result if mc
is
a movie clip, because a movie clip is a display object container:
var mc:MovieClip = new MovieClip(); if (mc is DisplayObjectContainer) { trace("container"); }
You can use this technique to make sure that any properties or
methods of a container are used without error. For example, the
following code replaces the string in the previous trace
statement with a use of the numChildren
property to see how many children
are nested within the movie clip:
if (mc is DisplayObjectContainer) { trace(mc.numChildren); }
If you want to see if a particular child exists, you can use the
contains()
method. The following
example traces a result only if a child reference sp
is found inside the container mc
.
var mc:MovieClip = new MovieClip(); var sp:Sprite = new Sprite(); mc.addChild(sp); addChild(mc); if (mc is DisplayObjectContainer) { if (mc.contains(sp)) { trace("sp found"); } }
Finally, it’s handy to know how to prevent children of a display object from receiving mouse events. This will become more apparent when events are discussed in the next chapter, but consider this example. If you dynamically create a display object to serve as a button, and add to that object a text field child to serve as the button’s label, then the text field reacts to the cursor and possibly trap mouse events when not wanted.
For example, you may not see the hand cursor responsible for indicating the presence of an interactive element and, depending on how you set up your file, the text field may prevent the button from reacting to a mouse click.
While you certainly want a text field to be able to react to the
mouse at other times, the button label in this example shouldn’t
interfere with standard user interface design. In this case, it’s handy
to use the mouseChildren
property of
the button object to disable mouse events for all children.
To demonstrate this change, you need a little code for handling text and events before covering that material in greater detail in later chapters. For now, the ActionScript has been broken into four segments. The last two blocks set up the text and mouse event, respectively.
The first block creates a new sprite, draws a yellow rectangle
inside, and adds the sprite to the display list. The second block
contains two properties that illustrate the usefulness of mouseChildren
. The buttonMode
property tells Flash Player to treat the sprite as if it were a
button, and display the desired hand cursor when rolling over the
button. The commented line uses the mouseChildren
property, and will be
explained following the script.
var sp:Sprite = new Sprite(); var g:Graphics = sp.graphics; g.beginFill(0xFFFF00, 1); g.drawRect(0, 0, 100, 30); g.endFill(); addChild(sp); sp.buttonMode = true; //sp.mouseChildren = false; addLabel(sp); function addLabel(obj:Sprite):void { var txtFrmt:TextFormat = new TextFormat(); txtFrmt.size = 24; txtFrmt.align = TextFormatAlign.CENTER; var txtFld:TextField = new TextField(); txtFld.width = 100; txtFld.height = 30; txtFld.text = "Label"; txtFld.setTextFormat(txtFrmt); obj.addChild(txtFld); } sp.addEventListener(MouseEvent.CLICK, onClick); function onClick(evt:MouseEvent):void { trace("button clicked"); }
By default, mouseChildren
is
true
, letting children of an object
receive events. In this state, you’ll notice that the cursor doesn’t
change from pointer to hand when rolling over the button. This is
because the text field is the same size of the button, and is
intercepting the cursor before it reaches the button. By un-commenting
the mouseChildren
line, thereby
setting the property to false
, no
children of the sprite receive mouse events, and the button behaves
normally.
You’ll often need to reference a parent or ancestor—a parent’s
parent, or (informally) grandparent—of a display object. Accessing the
parent of a display object is similar to working your way through a
directory structure on your computer. Any parent of a display object
also qualifies as a display object container by the mere fact that it
has children. Moving up one level, so to speak, from child to container
requires only the use of the parent
property.
The following example revisits the red and blue squares from earlier recipes. The parent, or blue movie clip, is positioned at point (20, 20) in the main timeline. The child, or red sprite, is offset another 30 pixels in both the x and y directions. Since a display object’s location is relative to its parent, querying the x location of the child returns a value of 30, but the x location of its parent is 20.
To check these results, the third block of this script traces the x coordinate of the sprite and its parent.
var mc:MovieClip = new MovieClip(); drawSquare(mc, 0x0000FF); mc.x = mc.y = 20; addChild(mc); var sp:Sprite = new Sprite(); drawSquare(sp, 0xFF0000); sp.x = sp.y = 30; mc.addChild(sp); trace(sp.x); trace(sp.parent.x); function drawSquare(obj:Object, col:uint):void { var g:Graphics = obj.graphics; g.beginFill(col, 1); g.drawRect(0, 0, 40, 40); g.endFill(); }
This works without further qualification because the x
property is available to both display
objects and display object containers. When you attempt to use a
property or method that isn’t universally available, the ActionScript
3.0 compiler may object and require additional information to handle
the request. You can see this in the next recipe.
13.9 Casting a Display Object from One Type to Another for casting a display object from one type to another.
You want to explicitly convert a display object from one type to
another. Alternately, you receive a compiler error telling you that a
property or method you’re trying to manipulate may be undefined due to a
static reference to DisplayObject
or
DisplayObjectContainer
.
Cast the display object to the appropriate type, officially declaring its type for the compiler.
Casting is the process of explicitly changing an object from one
data type to another. For example, you can change a string to an integer
by casting it with int()
. The
following casts two strings, and traces the outcome of adding the
resulting integers.
trace(int("1") + int("2")); // 3
If the strings had not been cast to integers prior to using the plus (+) operator, the result would have been an execution that concatenated the strings “1” and “2” to get “12” rather than adding the two numbers 1 and 2 to get 3. You can also cast from one related display object type to another, and the following scenario is a good example of when this is required.
In the previous recipe, the parent
property was used to query the x
location of a parent container. This was possible because both the
parent and child had an x
property.
However, quite often the ActionScript compiler is unsure whether or not
a targeted parent has the requested property or method in its
class.
For instance, a movie clip could be a child of a sprite, or a sprite could be a child of a movie clip. If you were to query a property that applied only to a movie clip, using nothing more than a passing reference to a display object’s parent, the compiler wouldn’t know the data type of the parent and, therefore, couldn’t know if the property existed. Here’s some example code:
var mc:MovieClip = new MovieClip(); drawSquare(mc, 0x0000FF); mc.x = mc.y = 20; addChild(mc); var sp:Sprite = new Sprite(); drawSquare(sp, 0xFF0000); sp.x = sp.y = 20; mc.addChild(sp); var totFr:int = sp.parent.totalFrames; trace(totFr); function drawSquare(obj:Object, col:uint):void { var g:Graphics = obj.graphics; g.beginFill(col, 1); g.drawRect(0, 0, 40, 40); g.endFill(); }
In this situation, you get an error that says something like this:
//1119: Access of possibly undefined property totalFrames through a reference with static type flash.display:DisplayObjectContainer.
Briefly, totalFrames
is a
property of a movie clip but is missing from other types of display
object containers (such as sprite). Without telling the compiler the
parent is a movie clip, it throws an error.
The solution is to cast the parent as type MovieClip
, just as the previous example cast
from string to integer using int()
.
var totFr:int = MovieClip(sp.parent).totalFrames; trace(totFr);
Once the compiler explicitly knows this, it doesn’t generate an error, and the script functions as intended.
13.8 Working with Parents of a Display Object for working with the parents of display objects.
Use the addChild()
method (or
equivalent) and ActionScript 3.0’s automatic display list management to
re-parent the child on the fly.
One of the display list’s greatest qualities is the ability to
change a display object’s parent with very little effort. In the
following setup, the sprite sp
is a
child of the movie clip mc
. As a
result, the sprite displays at point (50, 50), which is the location of
mc
.
var mc:MovieClip = new MovieClip(); mc.x = mc.y = 50; addChild(mc); var sp:Sprite = new Sprite(); var g:Graphics = sp.graphics; g.beginFill(0xFF00FF, 1); g.drawRect(0, 0, 40, 40); g.endFill(); mc.addChild(sp); var mc2:MovieClip = new MovieClip(); mc2.x = mc2.y = 150; addChild(mc2); //mc2.addChild(sp);
However, if you uncomment the last line of the script, sp
is again added to the display list, but
this time, as a child of mc2
. Because
the same display object reference is being added to the display list
twice, the first occurrence is automatically removed from the list, and
the object moves to become a child of mc2
. Consequently, the sprite now displays at
point (150, 150), the location of mc2
.
Re-parenting on the fly has many benefits. Consider, for example, a drag-and-drop scenario where every time you drop an item onto a new parent, the item becomes a child of the drop target. You could easily use this setup to group objects, manipulate their properties as a single item, and even remove an entire group of objects from the display list, simply by altering one parent.
You want to work with properties, methods, or events related to the stage, using a display object as a point of reference.
ActionScript 3.0 no longer has a ubiquitous reference to the
stage. Instead, you typically access the stage through the stage
property of a display object. For this
to be possible, however, the display object must be a child of a display
list.
The following attempt to query the stage frame rate generates an
error because the stage
property of
the display object is null. You can see this directly by trying to trace
the stage
property itself, in the
last line of this script block.
var sp:Sprite = new Sprite(); trace(sp.stage); //null trace(sp.stage.frameRate); //TypeError: Error #1009: Cannot access a property or method of a null object reference at main_fla::MainTimeline/frame1()
Once the display object is added to the display list, however, the
stage
property is valid, and no error
occurs. The stage
property also then
correctly traces a reference to the Stage
instance.
addChild(sp); trace(sp.stage); //[object Stage] trace(sp.stage.frameRate); //12
You most often encounter this issue when using classes, simply because it’s easier to get a reference to the stage within a Flash timeline frame script. This is because the main timeline itself is automatically a part of the display list, and can reference the stage. However, this timeline-based example still illustrates the problem and, ideally, this will help you avoid null object reference errors when the stage is involved.