Advertisement
ActionScript

AS3 101: The Display List – Basix

by

In this sixth installment of AS3 101, we'll be studying topics related to the "Display List", Flash's rendering system. With a solid understanding of how to program the Display List, a whole world of dynamic possibilities open up. You will no longer be tied to static display structure that you create on the timeline in the IDE, but you can create and destroy MovieClips (and more) with code.

There is much to know, so we'd better get started. Our ultimate goal is a wall of clickable images (go and check out the demo). While simple, it introduces many Display List programming techniques.

Step 1: What is the Display List?

The Display List is the term used in reference to Flash's rendering engine and process. Technically, it's not the engine itself but a hierarchical tree of what gets rendered. Anything that can be seen in a Flash movie is part of the Display List. A corollary to this notion is that if an object is on the Display List, then it is drawn by the renderer. If an object is not on the Display List, it is not drawn.

We will explore a few of the key Display List concepts in the coming steps.

Step 2: The DisplayObject

ActionScript defines something called a "DisplayObject". We're going to be getting ahead of ourselves here; we haven't discussed Object-Oriented Programming techniques so far in AS3 101, and unfortunately this particular topic is best understood with a grasp on inheritance. Don't worry. We'll cover OOP soon enough, and in the meantime I'll try my best to hide those geekier details from you.

So, a DisplayObject is basically anything that can be displayed on the Display List. For example, the good old MovieClip is a type of DisplayObject. However, an Array is not. ActionScript 3 defines quite a few types of displayable objects, and you can define your own, as it happens. The following is a list of DisplayObjects provided by the Flash Player:

  • Shape A shape can display vector graphics. If you draw a shape on the stage and publish your SWF, that shape is represented in ActionScript as a Shape object. You can work with Shapes programatically if you want to create vector objects with code.
  • Bitmap Essentially just an image. If you load in a JPG file and display it, it gets represented as a Bitmap. Curiously, if you import a bitmap image into the Flash library, and drag an instance of it out onto the stage, it does not become a Bitmap instance in ActionScript. Instead, it's a Shape object with a bitmap fill. Weird, I know.
  • Video As you'd expect, something capable of displaying a video through a NetStream or embedded into the SWF.

There are more types, but we'll get to them in time. What's important to note right now is that even though Shapes, Bitmaps, and Videos all display things differently, they actually have quite a bit in common. Most obvious is that all of them display something. They can be added to the Display List. Yes, they specialize in different types of display tasks, but in the end they are all DisplayObjects, and therefore can be rendered by the Flash Player.

In addition, all DisplayObjects can perform certain display-related tasks. This includes, but is not limited to:

  • Setting the position (through the x and y properties, and in Flash Player 10, z)
  • Setting the rotation (and in Flash Player 10, the rotationX, rotationY, and rotationZ)
  • Setting the scale (through the height, width, scaleX and scaleY properties)
  • Setting the alpha
  • Setting the blendMode
  • Setting filters
  • Applying a mask
  • Performing hit tests (through hitTestObject and hitTestPoint)

You can see that this is quite a list. Of course, any displayable object will need to have some basic functionality. Every other DisplayObject can do all of the above, but starts to add more functionality.

So...why not just go with the more functional, more capable types? For one, specialization. Only a Bitmap object can display a loaded image, and only a Video object can display a video. But there's also something called overhead. You can draw vector art into a MovieClip just like you can a Shape, but because a MovieClip does so much more than a Shape, it requires more memory just to exist. If all you need to do is display a vector shape, and not mess around with mouse interactivity or timelines, then you should just use a Shape, and not a MovieClip.

Step 3: The InteractiveObject

ActionScript further defines something called an "InteractiveObject." An InteractiveObject is another type of DisplayObject, so it can do all of that general stuff we talked about in the last step. But InteractiveObjects add a key element: interactivity (you should have seen that one coming). There are two types of InteractiveObjects:

  • TextField You wouldn't expect a TextField to be interactive, but if the TextField is an input field, then it can respond to user events, like changes in the typed-in text. At the same time, you can determine when the user focuses and de-focuses the TextField.
  • SimpleButton A SimpleButton is the ActionScript representation of a Button library symbol. Most ActionScript developers avoid both the library version and the ActionScript version, but it's nice to know that this is here.

Even though TextFields and SimpleButtons are vastly different, they both share common traits, in addition to the common traits shared by all DisplayObjects. InteractiveObjects can:

  • have a contextMenu
  • receive mouse events from the user
  • receive key events from the user
  • be tabbed to with the tab key

Again, these are things that all InteractiveObjects can do. A Shape, however, can't do these things. Conversely, a TextField has things like alpha and filters, just like all DisplayObjects. Deeper into the rabbit hole we go...

Step 4: The DisplayObjectContainer

ActionScript lastly defines something called a "DisplayObjectContainer." A DisplayObjectContainer is a type of DisplayObject, so it can be positioned and filtered and so on. It's also a type of InteractiveObject, so it can received mouse events and so on. But it further adds more functionality.

You're probably familiar with the idea of putting one MovieClip inside of another MovieClip. The fact that a MovieClip can contain other display objects makes it a DisplayObjectContainer. This idea of containment is what DisplayObjectContainer adds to the table. Pretty much everything defined by DisplayObjectContainer has to do with managing the child objects of that container. A container can contain another container, as you might know from Flash files with highly nested MovieClip structures. DisplayObjectContainers can:

  • add children
  • remove children
  • get references to its children
  • manage the depths (stacking order) of its children
  • enable or disable the mouse or tabbing for all of its children at once

As you can see, the DisplayObjectContainer is mostly concerned with containing other DisplayObjects. But remember that it's everything that a DisplayObject is (it can be moved, scaled, rotated, filtered, etc), as well as everything that an InteractiveObject is (it has mouse and key events, etc). There are three DisplayObjectContainers to know about:

  • Sprite You can think of a Sprite as a MovieClip without a timeline. It can be dragged, have sound associated with it, display programmatically drawn vector art, and alter its interactive behavior with things like useHandCursor and hitArea. Most of the UI components that ship with Flash are Sprites (and not MovieClips)
  • MovieClip is the one you know and love. You can think of a Sprite as a MovieClip without a timeline, or you can think of a MovieClip as a Sprite with a timeline. A MovieClip can do everything that a Sprite can do, plus it adds properties and methods for working with its timeline (like gotoAndStop, currentFrame, etc).
  • Loader A Loader can load and subsequently display an external SWF or image file. Working with Loaders is important because they are the only way to load these external assets.

The general consensus is that if you don't need a timeline, a Sprite is preferable to a MovieClip because there is less overhead involved.

Step 5: Working with the Display List Visually

I'm sure you're comfortable with putting things on the Display List, even if you're not familiar with that term. When you create a MovieClip symbol, and drag an instance of it onto the stage, you're creating a MovieClip object and adding it to the Display List. Let's do the following:

Create a new ActionScript 3 document. Draw a shape on the stage, select it, and turn it into a MovieClip symbol. Give it an instance name. I'll be using "manualClip".

Now click in the first frame of the layer, and open the Actions panel. Type the following code:

var len:int = this.numChildren;
for (var i:int = 0; i < len; i++) {
	var display:DisplayObject = this.getChildAt(i);
	trace(display.name);
}

If you run your movie, you should see the following in the Output panel:

Now, what did we do in that script? We started working with some Display List topics.

First was numChildren. This is a property of any DisplayObjectContainer, and it returns the number of immediate children contained within (it only goes one level deep: any children that further contain children are not counted). We're setting this value to another variable (called "len") as per good looping techniques discussed in the last AS3 101 tutorial.

Then inside the loop, we use another DisplayObjectContainer technique, getChildAt. This is one way of obtaining access to individual children inside of the container. In this case, we're getting them by depth.

Then, once we have the DisplayObject, we trace it's name.

It may or may not be surprising to you that the object we placed manually in the IDE shows up through ActionScript. For the most part, there is little difference between working in the IDE or with code when it comes to the object actually displayed. Many times, it's just easier to draw or place assets on the stage visually. Other times, we might need to be more dynamic about it, and create objects with code. It's good to know that either way, we pretty much have full control over the process.

Step 6: Working with the Display List Programatically

We'll take things a step further (hence the increment in the step number...). We will now create and display a DisplayObject with code. Use the FLA you started with in the last step and add the following code at the top (before the code you wrote last time).

var sprite:Sprite = new Sprite();
sprite.graphics.beginFill(0xff9933, 1);
sprite.graphics.drawRect(0, 0, 200, 200);

What does this do? Well, it creates a Sprite. A Sprite can only be created programatically; all symbols in your library are MovieClips. Remember that a Sprite is just a MovieClip without a timeline. Since we can't really create content on the timeline using code, we may as well use Sprites whenever we need to create a DisplayObjectContainer programatically.

After we have our Sprite, we use the graphics property to draw a square with code. There's quite a bit we can do with the drawing API, but in a nutshell it's like a step-by-step set of instructions on how to use a pen to draw shapes. First, we set the fill color, then we use a convenient method to move the pen around in a rectangle, drawing a square.

Unfortunately, we don't have time to delve deeper into the Graphics object, but check out the documentation for a complete list of things you can do to draw programmatically. It's pretty deep and can do so much more than draw rectangles. In fact, the core 3D engine of Flash Player 10 is based off the ability to drawTriangles. It gets crazy...but have fun with it!

At any rate, if you run the movie now, you'll see no change from the last time. Why is that? Because while we have created the Sprite, it's not yet on the Display List. Remember this. It trips of seasoned developers all the time. You need to add the Sprite (or any other DisplayObject you create with code) to the Display List. You do that using the addChild method of the container you wish to use, like this:

this.addChild(sprite);

Now if you run the script, you should not only see a large orange square taking over your screen (and possibly obscuring the other clip you created), you should see an extra line in your Output panel:

Notice the name it was given? This is the default name. Flash keeps count of how many instances get created (during the life of the movie, I believe) and just applies a default name of "instanceN" for all new display objects. This name can be set at any time, though. It's not required, but it can be handy to give your programatically created display objects a unique name so you can easily identify them later, either through traces or when using getChildByName. Let's add the following line (still before the loop, but after the sprite gets created);

sprite.name = "orangeBox";

And that results in:

For reference, here is the whole script as it should stand:

var sprite:Sprite = new Sprite();
sprite.graphics.beginFill(0xff9933, 1);
sprite.graphics.drawRect(0, 0, 200, 200);
sprite.name = "orangeBox";
this.addChild(sprite);

var len:int = this.numChildren;
for (var i:int = 0; i < len; i++) {
	var display:DisplayObject = this.getChildAt(i);
	trace(display.name);
}

Being able to create and add DisplayObject at run time is a powerful technique of Flash, and opens up the door for dynamic displays, as we'll see by the end of the tutorial.

Step 7: Understanding Depth

Understanding depth itself isn't so difficult: all display objects are layered within a "stacking order". An object with a higher depth will "be on top of" and obscure an object with a lower depth.

And it's not difficult to keep in mind that containers have their own set of depths for their children. A MovieClip full of Shapes will stack the Shapes in order. Put that MovieClip on top of another MovieClip full of Shapes, and all of the top MovieClip's Shapes will be on top of the bottom MovieClip's shapes. There is no interlacing of children of one container with the children of another container.

What gets tricky is understanding how ActionScript 3 handles depth. But even that's not so bad, and you may be interested to know that it's way better than how ActionScript 2 handled depth.

First, you should know that the lowest depth of any container is 0. There are no negative depths.

Second, the depths of any given container are always tightly-packed. That means that there are no empty spaces in the depth numbers. If there are 10 children, they occupy depths 0 through 9, no exceptions.

Thirdly, understand that ActionScript manages this tightly-packed numbering system for you. Any time you add a child to a container, that child is inserted into the stack like a single card being put back into the deck. A child that is added at a certain depth pushes all of the children at the depth and height upward in the stacking order. A child that is removed from a certain depth causes all of the children above to slide back down to fill in the gap.

And lastly, you should learn a few of the methods we can use to manipulate the children of a container.

addChild is the simplest way to add a DisplayObject. You simply call it off the container to which you want to add, and pass in the DisplayObject. This automatically puts the child at the top of the stack. We saw this in the last step. We had a MovieClip on stage already, before our code ran, and then we used addChild on our new Sprite. This put the Sprite at depth 1, above the MovieClip.

addChildAt is similar to addChild, only you get to specify the index at which to add the child. If we change the previous step's code to read:

this.addChildAt(sprite, 0);

We'll add that sprite at depth 0. Since the MovieClip was already at depth 0, it gets bumped upward to depth 1, and the Sprite goes in at depth 0. If you run that movie, you'll see the sprite on the bottom, and a change in the output:

removeChild and removeChildAt work much like their "add" counter parts. removeChild specifies the child to remove, just like addChild. removeChildAt is a little different, in that all you need is the depth number. Whatever child is at that depth gets removed. For example, building on our previous example, we could remove the MovieClip we added in the IDE:

this.removeChildAt(1);

or

this.removeChild(manualClip);

Both are useful for different things...sometimes you know exactly which clip to remove, sometimes you just want to remove the child that's on bottom or on top.

getChildAt provides one way to access the children of a container. In this case, we get it by depth. We used this at the start of our current FLA example.

this.getChildByName("manualClip");

will get a reference to the MovieClip we placed with the IDE (unless, of course, we've already removed it)

getChildIndex gives us the depth value of the supplied child object

setChildIndex takes a child object and a new depth index and swaps the depths around to accomodate the request. Again, this has the effect similar to moviing a single card around in a deck of cards. The intermediate children get slid around to make room and fill in the gap.

There are more methods, for manipulating depths and more. Check out the documentation of the DisplayObjectContainer class for more information.

Step 8: Alpha, Visible, and Being on the Display List

There are three ways to not see a given display object: set it's alpha to 0, set its visibility to false, or pass it to removeChild. What's the difference between these? Glad you asked.

Setting the alpha to 0 will make it so that your eyes can't see the object. However, it's still present. An alpha of 0 is like the invisible man: you may not be able to see him but you can still run into him. If you have a MovieClip with mouse events hooked up to it, and set its alpha to 0, you will still see those mouse events trigger. This can be useful for creating a hit area that triggers events elsewhere in the movie.

If your goal is to simply not see the object, then an alpha of 0 is still problematic because the renderer is still drawing the object. The effect is that you can't see it, but Flash is still working to render the transparent pixel. It's better to set the visibility to false.

Setting the visible property to false results in removing the display object from the rendering queue, so if you've faded an object away, once you're done with that you should set visible to false. However, an invisible display object is still a bit of an invisible man, taking up space. Maybe treating it like a gaseous element is more appropriate. It doesn't incur events, but it does take up a place in the depth stack, and its dimensions affect the dimensions of its container. Otherwise, it's fairly well non-existent.

When you remove a child, you can still hang onto it in a variable. It still exists in memory, and you can then call addChild with it at any point to put it back on the Display List. However, if you don't have any other reference to it when you call removeChild, the object becomes eligible for garbage collection and will be deleted altogether if you don't hang onto it.

There is a time and place for each of these techniques, and a good ActionScript developer knows when to use which. There is a good roundup of the three techniques discussed by Colin Moock at InsideRIA.

Step 9: Creating Predefined DisplayObjects from the Library

One of the coolest things we can do is combine the visual expression tools of the Flash IDE and the dynamicism of a code-driven Display List. We can create a symbol in Flash, set it up so that ActionScript has access to it, and then create instances of these symbols with code. Let check it out.

First, draw some artwork and turn it into a symbol. However, when you get the dialog window to create the symbol, make sure your window looks like this:

Instead of this:

If it looks like the smaller window, then click on the "Advanced" button to make it the larger window.

Now, Click on the checkbox that says, "Export for ActionScript". When you do so, the entire "Linkage" section becomes enabled.

The text entered into the "Class:" field will be based off of the name you entered into the Name. If you want to change it, keep in mind that certain rules apply. Basically, it has to be a valid ActionScript variable name: numbers, letters, underscores and dollar signs only, and it can't start with a number.

Leave everything else as is, and click OK. You should get this alert:

When you get this window, it's saying something that you don't need to worry about right now. However, you will want to know about this eventually (we'll talk about it in a future AS3 101 episode). I would advise you to resist the temptation to check the "Don't warn me again" option. For now, you don't need this warning, but there will come a time when you do want this warning, because you will have expected something else to happen. However, you can toggle this warning on and off by going into your preferences, into the "Warnings" section, and checking the "Warn on automatically generating ActionScript classes for timelines" option.

So, what did all of that do? It gave us access to that symbol through ActionScript. The name you entered in the "Class" field is the name you need to use to create a new symbol with code. For example, I entered "Box" into my Class field, so now I can write this script:

var box:Box = new Box();
addChild(box);

Run it, and you'll see at least one instance of your symbol on the screen (there may already be one there from when you created it). The point is that we performed the ActionScript equivalent of dragging that symbol out on to the stage in the IDE.

This is incredibly useful; it combines the convenience of creating a MovieClip using the visual tools of the IDE with the dynamic nature of placing things on stage with code. Imagine if you have a thumbnail clip, which is more than just a loaded image; maybe it has a label, and some extra artwork for a rollover effect. You can set up the clip easily in the IDE and then create as many as you need with ActionScript. In fact, we'll do exactly this in just a few steps.

Step 10: Parents Just Don't Understand

Actually, in ActionScript, parents do understand. Pardon the cutesy title.

The thing about display objects is that if you've added one as a child, whether through the IDE or through code, then that object has a parent. However, unlike your or my parents, you can arbitrarily reassign the parent-child relationship as needed. This includes not only the addChild and removeChild methods we've already looked at, but the idea of re-parenting. This just comes down to the use of addChild on a display object that already has one parent to put it into another parent. Consider the following. It assumes you have the Box symbol in your library.

var parent1:Sprite = new Sprite();
parent1.x = 100;
addChild(parent1);

var parent2:Sprite = new Sprite();
parent2.y = 100;
addChild(parent2);

var box:Box = new Box();
parent1.addChild(box);

If you run this movie, you'll see what you hopefully expected: the Box on the screen. The Box inherits the x position set by parent1, since we added it as a child to that Sprite, so the Box should be 100 pixels over from the left edge.

Now, let's add this, after the script we just wrote:

box.addEventListener(MouseEvent.CLICK, reparentBox);

function reparentBox(me:MouseEvent):void {
	parent2.addChild(box);
}

Now run the movie, and the box is in the same place. But now click on the box, and it will move! Why? because we've re-parented it. We've asked parent2, with an x of 0 and a y of 100, to be the parent of the box now. This implicitly removes the box from being a child to parent1. And as soon as parent2 becomes the box's parent, the box inherits the position (along with all other relevant properties, like alpha or rotation) of parent2, so now it should be along the left edge and 100 pixels down from the top.

This idea is rather powerful, and something that allows you to reuse display objects that you otherwise would have to destroy and recreate, which is far less efficient.

This re-parenting technique is probably most commonly used when using Loaders. A Loader is, itself, a display object. So you can add Loaders to the Display List. The Loader object has, as its child, another display object which is what the actual loaded content is. So, you may find it convenient to use a Loader solely as a loading device and, once the content has loaded, reparent the Loader's content into another container. This would avoid the overhead of having a container (the Loader) that otherwise doesn't add anything to the display. A simple example:

var imageContainer:Sprite = new Sprite();
imageContainer.x = 100;
imageContainer.y = 100;
addChild(imageContainer);

var loader:Loader = new Loader();
loader.load(new URLRequest("someImage.jpg"));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoad);

function onImageLoad(e:Event):void {
	imageContainer.addChild(loader.content);
}

To break this down, first we create a Sprite that will ultimately contain the loaded image.

Then we create the Loader, load an image, and add the event listener for the COMPLETE event (note that Loader events are associated not with the Loader itself, but with the Loader's contentLoaderInfo property. It's weird, but that's the way it is. I have trouble remembering that, too, so don't feel bad when you get it wrong).

The re-parenting happens in the COMPLETE event handler. We can't actually do it before then, so keep that in mind. But all we do is tell the image container Sprite to add the Loader's content object as a child. The content is the Loader's child DisplayObject. But without this line, our script won't show the loaded image because the Loader was never added to the Display List. The Sprite, however, was, and so we can implicitly add the loaded image to the Display List, too, by adding it to the Sprite.

Keep in mind that while this is a useful technique, there is no rule saying you have to do it this way. It's perfectly fine to simply create the Loader and add it to the Display List. But there will be times when you'll have the container already, and you might want to eliminate the extra overhead. Or you might want Sprite's ability to have, say, buttonMode set to true, which Loader cannot do.

Step 11: stage and root

One last topic of discussion before we get to the practical example. All display objects have a property called stage and one called root. If you're used to ActionScript 2, these concepts sound familiar, but have been overhauled in ActionScript 3.

The Stage is the top-most, grand-daddy, baddest-of-them-all DisplayObjectContainer. The Stage basically represents the canvas on which the Flash Player draws. All other MovieClips, images, text, etc, are ultimately added to the Stage. As a result, there is only one Stage. And all visible display objects have as a parent, whether directly or through a lineage of parents, the Stage.

The stage property allows access to this special container. Why does it matter? Because with the stage you can get the mouse position in relation to the Flash Player, as opposed to in relation to the display object in question. You can also add event listeners to the stage, like MOUSE_MOVE or MOUSE_UP. Consider this:

var box:Box = new Box();
addChild(box);
box.addEventListener(MouseEvent.MOUSE_MOVE, boxMouseMove);
stage.addEventListener(MouseEvent.MOUSE_MOVE, stageMouseMove);

function boxMouseMove(me:MouseEvent):void {
	trace("mouse moving on the box")
}

function stageMouseMove(me:MouseEvent):void {
	trace("mouse moving on the stage")
}

Try it out (assuming you've still got the Box in your library). Now move your mouse around...notice where the traces occur. The stage event will happen all the time. But the box event will only occur when the mouse is actually over the box. So if you want to listen for mouse movement in general, and not just when the mouse happens to be in a certain area, you should listen to the stage.

The root is another special container, but not nearly as special as the stage. The root is actually the main timeline of any given SWF. When you create a Flash document, you are always presented with a timeline, which is the top-level timeline. You can always create more MovieClips with more timelines, and nest them in a heirarchy, but there is always one main timeline. This is the root.

However, there may not always only be one root. When you create two SWFs, and load one into the other, root means different things depending on where the code is referenced. That is, each of the two SWFs has its own main timeline, and root in SWF 1 refers to the main timeline of SWF 1, while root in SWF 2 refers to the main timeline of SWF 2.

So, there can only be one stage, but there are as many roots as there are SWFs currently loaded.

Now, before we leave this topic, it's very important to understand one point: A display object's stage and root properties are only set when the display object is on the Display List. If it's not, then these properties are null. For example:

var sprite1:Sprite = new Sprite();
addChild(sprite1);
var sprite2:Sprite = new Sprite();

trace(sprite1.stage);
trace(sprite2.stage);

The first trace will output [object Stage], while the second will be null, because sprite2 was never added to the Display List.

This is a common source of errors in more complicated projects. Sometimes you expect stage to be set, but the object hasn't been added to the Display List yet, so you get null reference errors.

Just something to be aware of.

Step 12: Our Mission

We're going to take on a project that will expose us to as many real-world Display List topics as I could cram into a single tutorial. We will work mostly in code, but we'll also make an image tile with assets in the Flash IDE.

The actual piece will be a simple image "wall" that loads images externally (with Loaders), utilizes re-parenting and depth swapping, and the stage for a mouse move event. It's a lot to cover in about 10 steps, so let's get going.

A starter FLA is provided in the download .zip that provides artwork, and is just waiting for you to code it up. But these next steps will be helpful in grokking the structure of the artwork, so even if you go the download route, read though the next steps to better understand what we're trying to do.

Step 13: Set up the Image Tile

First, create a new AS3 Flash document. Save it in a folder dedicated to this project.

Make the stage size 600 x 430. Add a background if you like.

Now draw a square base about 200 x 200 pixels. Select it and press F8 to turn it into a MovieClip. Place the registration point in the center.

Make sure the "Advanced" options are showing, and check the "Export for ActionScript" option. Name the Class "ImageTile" (my preference to match the Class name to the Symbol name, but it's not a requirement). Additionally, since we won't be using any timelines, change the Base class from "flash.display.MovieClip" to "flash.display.Sprite". Press OK, and dismiss the "definition for this class could not be found" dialog.

You don't need an instance of this symbol on the stage. One handy trick is to place it on the stage for easy editing, but on its own guide layer, so that it doesn't get exported with the SWF

Step 14: Add a Caption to the Image Tile

Enter edit mode of the ImageTile symbol, if you're not there already. Add a new layer above the artwork layer. Add a dynamic text field in the lower portion of the square. Give it the following properties:

  • An instance name of "caption_tf"
  • A small-ish font size
  • Center aligned
  • Character Embedding of "Basic Latin"
  • White color
  • A Glow filter with a black color and high blur

Step 15: Build a Grid of Tiles

Start by getting your script ready. In the main timeline (be sure to exit edit mode of the tile), make a new layer, name it something like "code," lock it, and click on the first frame. Then press F9 to open the Actions panel.

We'll start by creating a container for the tiles:

var wall:Sprite = new Sprite();
addChild(wall);

Then we'll instantiate an Array of image URLs:

var images:Array = [
	"mp3.jpg",
	"tween.jpg",
	"uploader.jpg",
	"dice.png",
	"clock.jpg",
	"flickr.jpg",
	"tagcloud.jpg",
	"biggest.jpg",
	"best.jpg",
	"memory.png"
];

And lastly loop over that Array and instantiate a new ImageTile for each URL:

var iLen:uint = images.length;
var tile:ImageTile;
for (var i:uint = 0; i < iLen; i++) {
	tile = new ImageTile();
	wall.addChild(tile);
	tile.x = i % 5 * 210 + 110;
	tile.y = Math.floor(i / 5) * 210 + 110;
}

Here's a fairly standard loop. This will look familiar if you worked through AS3 101 part 4, on Arrays and AS3 101 part 5, on Loops, so I won't go into great detail. But in short, we are looping over the Array and creating a new ImageTile for each iteration. We're adding the tile to the wall Sprite. And we're positioning the tile with a little math, which was covered in the Array tutorial. Essentially, we can take the index number and get the column index by using modulo, and the row index by dividing and rounding down. Multiply each of those by some spacer amount (210 in our case; our tiles are 200 pixels square, so this gives us a 10 pixel margin in between) and you get a grid layout. We have to offset the x and y by 110 pixels, though, because the x/y is in reference to the registration point, which is in the center of the artwork.

Step 16: Set the Caption

We'll keep things simple (but less practical) by simply making the caption the image's file name. In the loop that sets up the tiles, we'll get a reference to the TextField buried within and set it's text property. Add this at the end of the loop:

var imageName:String = images[i];
var caption:TextField = tile.getChildByName("caption_tf") as TextField;
caption.text = imageName;

First, we get the value out of the Array.

Then comes our Display List topic: we're getting a child out of a container by referencing its name. We gave it an instance name of "caption_tf" so we can use that name. However, getChildByName returns a DisplayObject, which can be any type of actual display object. We specifically want a TextField, because if we tried to set the text property on any other display object, ActionScript wouldn't like that very much.

So we do something that you have to do most of the time you use getChildByName: we have to cast it. This just lets ActionScript know that while the language was defined to return getChildByName as one type, we know it's going to be another type, and lets us treat it as such. The as keyword take the operand on the left and casts it as the operand on the right. So we're getting the child by name and making sure it's a TextField. Note that the as keyword returns null if the object being cast can't be cast into the specified type. So if getChildByName actually returned a Bitmap object, we'd be in trouble.

Lastly, we take that TextField and put the imageName into the text property.

Run the movie now, and you should see that each tile has a unique caption.

Step 17: Load the Images

Now we can load the images. This will take a little bit of set up. First, we will need a Dictionary to keep track of which Loader goes with which ImageTile. Before the loop, create a Dictionary:

var tileMap:Dictionary = new Dictionary();

Also before the loop, declare another variable to reuse each time we create a Loader in the loop:

var loader:Loader;

And in the loop, create the Loader and load the image, after everything else in the loop body:

loader = new Loader();
loader.load(new URLRequest("images/" + imageName));
tileMap[loader] = tile;
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoad);

Note that I'm keeping my images in a subfolder called "images" and so I'm prepending the name coming out of the Array with a relative path to that folder.

I'm also using the Dictionary to track Loader we just created with the ImageTile we also created in this iteration. By using the Loader as a key in the Dictionary, the ImageTile as the value, we can easily find the matching ImageTile when the Loader finishes the load. For a discussion on how this works, please see my AS3 101 tutorial on branching.

Before we can test this, we need to write that onImageLoad function.

Step 18: Display the Loaded Images

Outside of the loop, create a new function to handle the Loader's COMPLETE event:

function onImageLoad(e:Event):void {
	var loader:Loader = e.target.loader;
	var tile:ImageTile = tileMap[loader] as ImageTile;
	var image:Bitmap = loader.content as Bitmap;
	image.x = -100;
	image.y = -100;
	tile.addChild(image);
	tileMap[loader] = null;
}

Here's what's happening: we're getting the event's target, which is the Loader's contentLoaderInfo object, and using its loader property to get the Loader.

We then use that Loader as the key to the tileMap to get an ImageTile back out. Here we're casting again, because values are untyped coming out of Dictionaries (and Arrays and Objects). Not strictly necessary, but nice to do (see my tutorial on variables for this discussion).

Then we get the Loader's content as a Bitmap. This is the Display Object that was loaded, and that we'll place into the ImageTile. We need to set its x and y, though, because 0/0 is at the center of the tile.

Then we ask that ImageTile to be the parent of the image.

At this point, the Loader is an empty shell and not really reusable, so we ditch it setting the Dictionary value to null. This removes the references to the Loader and lets the garbage collector trash it.

If you run the movie now, you'll see we have images loading! But the captions are now being covered up...

Step 19: Display the Lodaed Images In the Right Place

What happened to the captions? Well, remember how depths work, and how addChild works. We already have two children within the tile already: the shape background, and the text field. The shape should be at depth 0, the text field at depth 1. When we use addChild, the display object get's placed at the top of the stack, so the image is now at depth 2, covering up the shape and the text field.

How can we address this? By using addChildAt, of course. This lets us specify the depth at which to add the object. Everything "in the way" will simply slide upward in the stacking order.

The simple solution is to change this:

tile.addChild(image);

To this:

tile.addChildAt(image, 1);

While this works, it's less than ideal. If we ever went back and redesigned things so that there was another shape or text field or image involved, we'd have more items in the stack, and who knows if 1 would be right depth anymore? Instead, we'll get a little more involved and make sure the image is always behind the text field. We'll first get the text field's depth, then insert the image at the depth, pushing the text field up.

Try this instead:

var textField:DisplayObject = tile.getChildByName("caption_tf");
var textFieldDepth:int = tile.getChildIndex(textField);
tile.addChildAt(image, textFieldDepth);

Notice that this time we don't care about the TextField cast; we're just using this variable to pass to getChildIndex, which will work with any DisplayObject. getChildIndex then returns the depth of that child, which we then use in addChildAt. More involved, but theoretically more flexible.

Step 20: Move the Wall

OK, we've gotten pretty far, but we're still missing out on a bunch of images. Here's where we'll set up an "auto-scrolling" wall. The further to the right that you move your mouse, the further to the left that the wall moves. You've probably seen this before. We'll go for a simple scroll. These can get fancier, adding easing motion and stuff, but our focus here is to add an event to the stage, so we'll go meat-and-potatoes.

Since we're moving the wall, it makes sense to start there. However, as mentioned earlier, if we're listening for a MOUSE_MOVE event, we should really listen to it from the stage, otherwise there will be areas where we don't get the event. So, we need to add the event to the stage, and it doesn't really matter how we get to the stage, but since the wall is the Sprite that's interested in the event, let's use that. This line can be added anywhere outside of the loop of the image load function. May as well stick it at the end:

wall.stage.addEventListener(MouseEvent.MOUSE_MOVE, onWallMouseMove);

Now let's write the event handler function:

function onWallMouseMove(me:MouseEvent):void {
	wall.x = ((stage.stageWidth - 20) - wall.width) * (stage.mouseX / stage.stageWidth);
}

That's a lot of math! But here's what's going on: we take the over all stage width and subtract the wall width. Since the wall is wider than the stage, we get a negative number. The number happens to be the x value the wall would need to be to line up its right edge with the stage's right edge. But we subtract 20 from the stage width because we want a bit of marigin. So the number actually puts the wall's right edge 20 pixels to the left of the stage's right edge.

Then we take the x position of the mouse and divide it by the width of the stage. This gives us a percentage value, from 0 to 1, of where the mouse is, from left (0) to right (1).

By multiplying the width difference by this percentage, we end up with a value that places the wall somewhere between 0 and the negative maximum.

In the process, we've used two valuable properties of the stage. We can get the width (and height) of the stage, and the position of the mouse, in relation to the stage. For many types of interactivity, these pieces of information are key.

Step 21: Bring the Focused Images to the Front

As a finishing touch, let's allow each image to grow in size when it is clicked. This is actually a bit involved. It entails not only scaling the clicked image, but also managing its depth so that it's on top of the other tiles.

First, lets add a click event. Somewhere in the loop body, add this:

tile.addEventListener(MouseEvent.CLICK, onTileClick);

Then write the handler:

function onTileClick(me:MouseEvent):void {
	var tile:ImageTile = me. currentTarget as ImageTile;
	tile.scaleX = tile.scaleY = 1.3;
}

Here we're taking advantage of the event system again to map a single function to multiple events. We just need to get the event's target and operate on it. We're just setting the scale to a bit larger than normal.

Test it, and you'll hit two problems. One is that the images are larger, but they're not really in the "front." They'll be behind certain tiles, depending on how the tiles were added to the wall. Let's fix that by managing the depths.

function onTileClick(me:MouseEvent):void {
	var tile:ImageTile = me. currentTarget as ImageTile;
	tile.scaleX = tile.scaleY = 1.3;
	wall.setChildIndex(tile, wall.numChildren - 1);
}

This has the wall do some depth manipulation on its children. We want the tile to be at the top of the stack, and we can get that index by finding out how many children are in wall (wall.numChildren) and subtracting 1 (this is like how an Array with a length of 10 has indices 0 through 9 occupied). The use of setChildIndex reshuffles the stacking order so that the specified tile as at the top.

The other problem is that images stay large once you click on another image. We could keep track of a variable to keep hold of the currently selected image, and use that value to shrink the tile back to normal (see AS3 101, the First Part, for how to do this). But let's exercise our Display List muscles and just get the top most display object and shrink that.

function onTileClick(me:MouseEvent):void {
	var tile:ImageTile = me.currentTarget as ImageTile;
	tile.scaleX = tile.scaleY = 1.3;
	var topMost:DisplayObject = wall.getChildAt(wall.numChildren - 1);
	topMost.scaleX = topMost.scaleY = 1;
	wall.setChildIndex(tile, wall.numChildren - 1);
}

First, we get the top most item by using getChildAt, specifying the highest index just like we did with setChildIndex. Then we set the scale values back to 1. We just need to make sure we get the item to scale to normal before we set the new item to scale up.

Step 22: Smoothing

One little bonus tip: you'll notice the scaling isn't very kind to the images. There's a very easy way to address: turn on smoothing on the bitmaps. In onImageLoad:

image.smoothing = true;

That's it! Bitmap objects have a smoothing property that is false by default. Turn it on, once it's loaded, and you can rotate and scale with greater fidelity than you could otherwise (smoothing does incur a bit of overhead in rendering, but that's a sacrifice that you can choose to make when it's appropriate).

Step 23: Conclusion

Now that you know what you know, you may be seeing how some of our earlier AS3 101 projects might be made simpler by utilizing Display List programming. The Grid Game from AS3 101 part 4 (on Arrays) could have been much easier to build if we had made the tile available to ActionScript, and made use of our iteration to place them on the Display List. Believe me, it was actually painful for me to hold back on that topic. But now you're ready for the whole truth! If you want some exercises, go back through our previous tutorials in the AS3 101 series and see if you can improve on the given code to make use of programmatic Display List manipulation. Most of them can.

Better yet, hopefully you can see how introducing these techniques into your own projects can simplify the life of you, the ActionScript developer. Admit it, that's what you are!

Next up in our series will be working with XML. Combining XML with Array and Loops, along with dynamic display objects, leads to a very flexible set of tools. Stay tuned; the big pay off is coming!

Related Posts