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


O'Reilly Book Excerpts: Flash Hacks

Hacking Flash

Editor's note: Whether you're an expert or a novice, Flash Hacks's collection of tips and tricks for optimizing, creating interesting effects, programming in ActionScript, and more will have you looking like a pro. Today we offer two hacks from the book that show you how to use a Macromedia Director feature to simulate pixel-based fades and wipes, and how to use complex shapes as masks in Flash MX. Next week we'll offer two more hacks: the first on how to make it difficult for others to steal or reverse-engineer your online Flash content; the second on creating a performance budget to understand and adjust where you are expending resources.

ExpertHack #1

Fake Per-Pixel Transitions


Simulate pixel-based fades and wipes, as supported in Macromedia Director

Flash doesn't have built-in support for pixel-based transitions. This hack can be used with other video-friendly hacks to make your static bitmaps more interesting .

Flash Hacks

Related Reading

Flash Hacks
100 Industrial-Strength Tips & Tools
By Sham Bhangal

Flash uses a vector-based rendering engine, which doesn't allow direct access to the individual screen pixels. This hack depends on the fact that pixels are small, and when you make something small, it looks like anything else that is small.

The per-pixel transition effect is shown in Figure 1-1.


Figure 1-1. Simulated per-pixel transition, steps 1 through 4

The transition hides (masks) pixels in the first image over time so that the image disappears a few pixels at a time. Masking off the first image reveals a second image that is positioned below it, thus creating the transition effect from the first image to the second image. The masks used to create the preceding effect are shown in . Note that for black pixels, the mask effect shows the first (topmost) image; for white pixels (no mask), it shows the second (bottom) image.


Figure 1-2. Masks for simulated per-pixel transition, steps 1 through 4

As we shall see, we can make much more complex transitions with little change.

This hack requires the following steps:

  1. Make the fake pixel. In this hack we will create a little 4x4 rectangle.

  2. Find a way to make lots of fake pixels. This is done very easily in Flash using MovieClip.attachMovie( ).

The problem here is that we will have thousands of fake pixels, and we can't use anything as processor-extravagant as thousands of onEnterFrame( ) scripts running every frame for the duration of the effect. Instead, we will use setInterval( ), which reduces our processing overhead significantly by running code only once per fake pixel for the duration of the effect.

Make the Pixels

Making a pixel mask is as simple as creating a rectangle:

  1. Create a new Flash document (File→New→Flash Document).

  2. Use Modify→Document to set the Stage area bigger than 200 200 pixels and specify a white background (any light color will allow you to see the black rectangle in Step 3).

  3. Draw a black rectangle with no stroke (the stroke wouldn't be seen but would still slow down our effect).

  4. Using the Properties panel (Window → Properties), set the rectangle's height and width to 4. Set the X and Y coordinates to 0. You can see the result just next to the registration point in Figure 1-3.


    Figure 1-3. A 4x4 pixel mask with its registration point

    NOTE: ActionScript gurus might want to draw the mask using the Drawing API, but it would take too long for Flash to dynamically draw all the rectangles we would need for this effect.

  5. Convert the rectangle into a movie clip symbol by selecting it (with the Selection tool) and pressing F8 (Modify → Convert to Symbol), which brings up the Symbol Properties dialog box. Name the movie clip symbol dot and make sure you have the export options set up as shown in Figure 1-4 (click the Advanced button if you don't see the Linkage options).

Figure 1-4. The Symbol Properties dialog box

You can delete the movie clip instance from the Stage, since we will use the MovieClip.attachMovie( ) method to dynamically attach the Library symbol to the main timeline at runtime. If you want to avoid creating and then deleting the movie clip from the Stage, you can use Insert→New Symbol (Ctrl-F8 or &command;-F8) to create the movie clip symbol directly in the Library.

The effect uses a large mask consisting of these 4x4 squares. The mask is applied to the first image, and the effect works by masking additional rectangles over time, causing the second image (which is below the first) to show through the gaps.

Make Lots of Pixels

On the main timeline, add a new layer and name it actions .

In the actions layer of the main timeline, select frame 1 and attach the following script using the Actions panel (F9):

function drawGrid (theWidth:Number, theHeight:Number):Void {
  var initDot:Object = new Object( );
  var k:Number = 0;
  for (var i:Number = 0; i < theWidth; i += 4) {
    for (var j:Number = 0; j < theHeight; j += 4) {
      var dotName:String = "dot" + i + "_" + j;
      initDot._x = i;
      initDot._y = j;
      this.attachMovie("dot", dotName, k, initDot);
      k++;
    }
  }
}
drawGrid(200, 200);

The preceding code creates a 200x200-pixel square consisting of our 4 4 movie clips (you can invoke the drawGrid( ) function with different dimensions to create a grid of a different size). Each pixel is placed at position (i, j) and depth k on the Stage and has instance name doti_j. The first instance name (situated at the top left of the square) is dot0_0 and the last one is dot199_199 (bottom right). You can see the movie clips created if you run the code in Debug Movie mode (Control→Debug Movie), but be aware that the debugger will take some time to display all your movie clips (although it may look as if Flash has hung up, give it a few seconds!).

TIP: This effect creates a large number of movie clips; (200/4)2 = 2500. Flash seems to become very sluggish if more than 3000 to 4000 movie clips appear on the screen at the same time (even if you are not moving the clips), so you are advised not to go far beyond 2500. If you need to mask a larger area than the one we are working with (200 pixels square), consider making the constituent rectangles bigger rather than adding more of them.

Control the Pixels

The trick now is to make the dots disappear on demand. The way to do this is via setInterval(object, "method", timer), which invokes the function object.method( ) every timer milliseconds. Add the following code after initDot._y = j; in the preceding script:

initDot.timer = 1000 + Math.ceil(Math.random( )*800);

The preceding line creates a property, timer, which stores an integer between 1000 and 1800 for each dot clip. The 1000 specifies the pause before the effect starts, and the 800 is the duration of the effect. Both values are in milliseconds, the standard measure of time in ActionScript.

This hack is based on a mask effect, but Flash allows only one mask per movie clip. The easy way around this limitation is to create all our dot movie clips inside another one that acts as the mask. We also pass in the name of the clip to be masked as a parameter to the drawGrid( ) function (changes are shown in bold):

function drawGrid(theWidth:Number, theHeight:Number,
                  imageClip:MovieClip):Void {
  var initDot = new Object( );
  var k:Number = 0;
  // Create a mask clip to hold all the dots
  this.createEmptyMovieClip("mask", 1);
  // Assign it as the masking clip
  imageClip.setMask(mask);
  for (var i:Number = 0; i < theWidth; i += 4) {
    for (var j:Number = 0; j < theHeight; j += 4) {
      var dotName:String = "dot" + i + "_" + j;
      initDot._x = i;
      initDot._y = j;
      initDot.timer = 1000 + Math.ceil(Math.random( )*800);
      // Place the masking dots within the container mask clip
      mask.attachMovie("dot", dotName, k, initDot);
      k++;
    }
  }
}
drawGrid(200, 200, image1_mc);

So now we have all our dot clips inside another movie clip named mask, which we use as the mask for a movie clip whose name is passed in as a parameter to the drawGrid( ) function. In this case, we use a clip named image1_mc, which we create later in . First though, let's finish off the dot movie clips.

Create the Timers

We already have a timer property for each dot movie clip. Now let's write the code to make our dots disappear.

Edit the dot movie clip symbol and add a new layer named actions (the first layer of a timeline is traditionally named scripts or actions and used exclusively to hold your timeline-based scripts).

In the first frame of the actions layer, add the following code:

removeMe = function ( ) {
  clearInterval(countDown);
  this.removeMovieClip( );
};
var countDown = setInterval(this, "removeMe", timer);

The last line of the preceding code uses setInterval( ) to create a timer named countdown for each dot. It calls the removeMe( ) function when the timer expires. The removeMe( ) function clears the interval and then removes the current dot clip, which creates our "disappearing pixels" transition effect.

TIP: If setInterval( ) is passed a function reference as the first parameter, such as setInterval(removeMe, timer);, the value of the keyword this would be undefined within the removeMe( ) function. Therefore we use the alternative form setInterval(this, "removeMe", timer) in which we pass an object and a method name as the first two parameters. (In this case, the keyword this is the object passed as the first argument.) When removeMe( ) is invoked, the keyword this is in scope, so we can invoke this.removeMovieClip( ) to remove the clip.

Using the Effect

To use the effect, you need to have the two things you want to transition between on two separate layers, with the first image or video clip on the top layer, as shown in . You should give the first clip the instance name image1_mc using the Properties panel. The second image can be called anything since it is never referred to in the code.


Figure 1-5. Setting up a transition between two layers

You can see the effect in action by downloading pixelMask.fla from this book's web site.

Extend the Effect

By changing the time interval before each dot disappears, you can create different transition effects. For example, changing the timer values based on the position of the dots serves as the basis for many common pixel-based transitions:

// Left-to-right wipe
initDot.timer = 1000 + (Math.random( )*(initDot._x)*10);
// Diagonal wipe
initDot.timer = 1000 + (Math.random( )*(initDot._x + initDot._y)*5);

Final Thoughts

Masking is a very underutilized feature of Flash. It's one of those features that seems to have no real use until you delve deeper. No surprise then that many of the coolest effects seem to use it extensively!

Moderate Hack #20

Use Complex Shapes as Masks

Flash MX was the first version to support the ability to create a scripted mask, which is a mask applied dynamically at runtime with the MovieClip.setMask( ) method. Naturally, developers must be aware of how using scripted masks affects runtime performance.

During the Flash MX beta, Macromedia released a version of the application that allowed any shape, however complex, to act as the mask but later had to withdraw this feature because of performance issues. One of the biggest dissidents over the "you can't use complex masks" limitation was Erik Natzke (http://www.natzke.com). Erik creates loads of head-turning (as well as page-turning) tricks that initially dumbfound everyone . It comes as no surprise that Erik uses a lot of masking in his work.

This hack shows you how to get back the functionality of complex masks without compromising performance. It is loosely based on discussions between Macromedia engineers and beta testers during the Flash MX beta period.

Using Complex Masks

A Flash mask must be a solid shape. If Flash sees a complex shape such as a doughnut used as a mask, it will simplify the shape. You can see the problem by setting up this simple FLA.

In a new movie, change the name of the first layer to background, and add two layers above it called maskLayer and actions, as shown in Figure 3-24.


Figure 3-24. Setting up layers in a masking movie

On the background layer, create a filled rectangle that covers the Stage. Give this rectangle a linear gradient fill, as shown in Figure 3-25. Press F8 to convert it to a movie clip symbol. Give it the symbol name back in the Symbol Properties dialog box. Give it an instance name of backClip in the Properties panel. Lock the layer.


Figure 3-25. A gradient

In the maskLayer layer, create a doughnut shape, as shown in Figure 3-26. Press F8 to convert it to a movie clip symbol. Name the symbol mask, and give the clip an instance name of maskClip.


Figure 3-26. A doughnut shape atop the gradient

Finally, attach the following script to frame 1 of the actions layer:

function dragDrop(mc:MovieClip){
  mc.onPress = function( ) {
    this.startDrag(true);
    this.onMouseMove = function( ) {
      updateAfterEvent( );
    };
  };
  mc.onMouseUp = function ( ) {
    delete this.onMouseMove;
    this.stopDrag( );
  };
}
dragDrop(maskClip)
backClip.setMask(maskClip);

As an aside, smoothly dragging and dropping a clip is such a common task that you might also consider making it a class. Here's an example of a class that performs smooth dragging:

// This ActionScript 2.0 code must go in an external SmoothDrag.as file
class SmoothDrag {
  public function SmoothDrag(targetClip:MovieClip) {
    dragDrop(targetClip);
  }
  private function dragDrop(mc:MovieClip):Void {
    mc.onPress = function( ) {
      mc.startDrag(true);
      mc.onMouseMove = function( ) {
        updateAfterEvent( );
      };
    };
    mc.onMouseUp = function( ) {
      delete mc.onMouseMove;
      mc.stopDrag( );
    };
  }
}

The availability of such a class would reduce our code to only a couple of lines:

var myClipDragger:SmoothDrag = new SmoothDrag(maskClip);
backClip.setMask(maskClip);

We will, however, continue with the original, non-class-based version.

Function dragDrop( ) allows you to click and drag maskClip; it drops the clip when you release the mouse button. By making maskClip the masking clip for backClip, we should see only that portion of backClip that is underneath maskClip.

However, we actually see all portions of backClip that are inside the perimeter of maskClip. Flash treats our complex doughnut mask as a simple circular mask, as shown in Figure 3-27.


Figure 3-27. A doughnut is treated as a circular mask

Flash masks are limited by the need to have a continuous perimeter. By making a small gap in the doughnut, we can make our doughnut have one perimeter. The trouble is that cutting a gap will make our "O"-shaped doughnut look more like a "C." We need to make the gap so small that Flash ignores it when drawing the shape but large enough for Flash to treat the entire shape as if it possesses a single perimeter. The trick is to make the gap a hairline.

Select the maskClip instance and double-click it to edit it in place. Using the Line tool, draw a hairline across the doughnut wall, as shown in Figure 3-28.


Figure 3-28. Adding a hairline fracture to our priceless doughnut mask

Select the entire hairline. Use Modify→Shape→Convert Lines to Fills to turn the hairline into a shape. Notice that the thickness of the shape created is less than 1 pixel (it is 0.3 pixels on my machine). Delete the hairline shape. When Flash removes the shape, it leaves the doughnut with a 0.3-pixel gap. Zoomed in and viewed against the pixel grid, as shown in Figure 3-29, you can see that the gap is still less than 1 pixel.


Figure 3-29. A gap of less than 1 pixel

This small gap means that, although Flash thinks we have now created a "C" with a single continuous perimeter, the vector renderer draws this shape as on "O" with no gap at all. Test the movie using Control→Test Movie.

You will see one of two things. If you are lucky, you will see a doughnut-shaped mask without any gap, as seen in Figure 3-30. If you are not so lucky, you may see the hairline gap in the doughnut, shown on the left side of and greatly magnified on the right side of .


Figure 3-30. Doughnut mask showing hairline gap (left) and close up of hairline (right)

If you encounter the problem depicted in Figure 3-30, use the Subselection tool to make the gap a little smaller. Using Snap to Pixel sometimes helps; move the edge of the gap that is furthest away from a pixel snap line up to the snap. You should see a perfect doughnut mask, as shown in Figure 3-31. Zooming in might show the gap, but the renderer ignores the gap during redraws at normal magnification.


Figure 3-31. The desired doughnut mask

Copyright © 2009 O'Reilly Media, Inc.