Hack 84 Obscure Operators

figs/expert.gif figs/hack84.gif

Undocumented ActionScript isn't the only fertile hunting ground for the curious coder. Discover nonobvious uses for several obscure ActionScript operators.

Although ActionScript is large, typical developers can solve 90% of their problems using 10% of the available features. The remaining ActionScript is used only rarely or for very specific purposes. Some ActionScript is used rarely because developers don't know how to use it or it has a nonobvious use.

Here are a few alternative uses for obscure ActionScript commands and operators.

Modulo: Clamp and Snap

The modulo operator, %, returns the remainder of a division operation assuming no decimal part. For example, the result of 15 modulo 6 is 3, which is the remainder of 15 divided by 6:

trace(15 % 6);  // Displays: 3

What is it good for? Because the result of a%b can never be greater than b, you can use modulo division to limit (clamp or clip) an expression a to the range 0 to b.

The following animation helps visualize the results. If you attach the following code to the first frame of a new movie, you will see that the center point of the ball movie clip never goes beyond the line at x = 300. The clamp( ) function, which acts as the onEnterFrame( ) handler for the ball clip, uses (this._x + speed)%300 to set the position of the ball. This limits ball._x to the range 0 to 300.

function clamp( ):Void {

  this._x = (this._x + speed)%300;

}

// Create ball

var ball:MovieClip = this.createEmptyMovieClip("ball",

                   this.getNextHighestDepth( ));

ball.lineStyle(20, 0x0, 100);

ball.lineTo(0, 1);

ball._x = 0;

ball._y = 200;



// Create line

this.lineStyle(null, 0x0, 100);

this.moveTo(300, 190);

this.lineTo(300, 210);

var speed = 4;



// Set up animation

ball.onEnterFrame = clamp;

Another use of modulo is in snapping a value to a particular multiple of a number. If x = a%b, you can constrain x to whole values that are divisible by b by either adding or subtracting x%b from x. These lines display 300 and 400:

x = 350;

trace(x - x%100); // Next closest lower number divisible by 100

trace(x + x%100); // Next closest higher number divisible by 100

The following code uses snapping in a simple drag-and-drop example. The ball movie clip follows the mouse, snapping to the nearest 30-pixel multiple.

ball.onMouseMove = function( ) {

  this._x = _xmouse - _xmouse%size;

  this._y = _ymouse - _ymouse%size;

  updateAfterEvent( );

};

size = 30;

The dragger.fla file on this book's web site uses the snapping code to create a series of tiles that can be arranged only along a grid. If you run the example and drag and drop the colored squares, you will see that they snap to the nearest whole position, allowing you to stack them edge to edge, as seen on the right side of Figure 10-10.

Figure 10-10. The colored squares snap into position as you drag and drop them
figs/flhk_1010.gif


This sort of feature would be useful in:

  • Flash puzzle games, especially sliding block games and jigsaws

  • Flash tile-based action games (platform games)

  • Flash board games

  • Icon-driven Flash OS-type interfaces (in which you create a site that looks like an operating system desktop, typically driven by point, click, and drag-and-drop icons)

Optimize Range Checking

The Math.abs( ) method returns an expression's absolute value. Math.abs(5) and Math.abs(-5) both return 5. Not the most exciting method, but it is an efficient way to limit a number at both ends of a scale. Simply offset the scale so that the top and bottom ends of the scale are the same absolute number (such as -5 and +5 instead of 0 and 10).

Checking the upper and lower limits for the x and y coordinates of a clip traditionally requires four conditionals. You can hack this down to two conditionals by moving the origin to the center of the Stage. In the case of a default Stage size (550 400), the leftmost and rightmost x positions are -275 and 275 (instead of 0 and 550) and the topmost and bottommost y positions are -200 and 200 (instead of 0 and 400).

This optimization isn't apparent unless you need things to run really fast, as in the following code. It uses setInterval( ) with a one-millisecond interval to force Flash to run the animation as fast as it can. The optimized range-checking functions are shown in bold.

function anim(target:MovieClip):Void {

  if (Math.abs(target._x) > 275) {

    target.xSpeed = -target.xSpeed;

  }

  if (Math.abs(target._y) > 200) {

    target.ySpeed = -target.ySpeed;

  }

  target._x += target.xSpeed;

  target._y += target.ySpeed;

  updateAfterEvent( );

}

// Create balls

for (var i = 0; i < 20; i++) {

  var ball:MovieClip = this.createEmptyMovieClip("ball"+i,

                       this.getNextHighestDepth( ));

  ball.lineStyle(5, 0x0808AF, 100);

  ball.lineTo(0, 1);

  ball.xSpeed = Math.random( ) * 10;

  ball.ySpeed = Math.random( ) * 10;

  this["fastAnim"+i] = setInterval(anim, 1, ball);

}

// Capture screen extents

var temp:String = Stage.scaleMode;

Stage.scaleMode = "exactFit";

var sWidth:Number = Stage.width;

var sHeight:Number = Stage.height;

Stage.scaleMode = temp;

// Move Stage's original origin to center

this._x = sWidth / 2;

this._y = sHeight / 2;

// Mark out Stage

this.lineStyle(undefined, 0x0, 100);

this.beginFill(0xF0FAFF, 100);

this.moveTo(-sWidth, sHeight), this.lineTo(sWidth, sHeight);

this.lineTo(sWidth, -sHeight), this.lineTo(-sWidth, -sHeight);

this.endFill( );

The still image of the output shown in Figure 10-11 doesn't do the effect justice. On a modern computer, the dots fly quickly around the Stage. If you do not have a high enough monitor refresh rate, you will not see many of the dots at all, but rather the trails they leave behind!

Figure 10-11. Flying dots demonstrating optimized range checking
figs/flhk_1011.gif


On my machine, I see around 15% to 20% faster animation using optimized range checking with 5-pixel wide circular movie clips. The speed increase depends heavily on the speed of your computer and the complexity of the movie clip graphics (remembering that screen redraw, not the code, is usually the performance bottleneck).

The following code uses the Math.min( ) and Math.max( ) methods to "clip" a value to a particular range. It skips the conditional expression and simply forces the value into the desired range. (The code assumes you haven't shifted the origin as in the previous example.) It works well when you want to restrict an object's range of motion, say a puck along a slider, but not for objects that need to "reverse course," such as a bouncing ball. Here, the calculation prevents the movie clip from traveling off stage:

function anim(target:MovieClip):Void {

  target._x = Math.max (0, Math.min (target._x + target.xSpeed, 550));

  target._y = Math.max (0, Math.min (target._y + target.ySpeed, 400));

  updateAfterEvent( );

}

Or, more generally:

function clip(input:Number, minVal:Number, maxVal:Number):Number {

  return Math.max (minVal, Math.min (input, maxVal));

}

function anim(target:MovieClip):Void {

  target._x = clip(target._x + target.xSpeed, 0, 550);

  target._y = clip(target._y + target.ySpeed, 0, 400);

  updateAfterEvent( );

}

Optimize Inertial Motion with Bit Shifting

Flash's base unit of measure is a twentieth of a point (a "twip"). Flash allows you to define pixel values with precision down to .05 pixels (smaller increments are ignored), so drawing a pixel at the point (10.5, 10.5) is legal. Flash displays graphics at fractional locations using antialiasing. By blurring the pixel, Flash attempts to give the impression that the pixel is at the fractional position, even though the video card can address pixels only at integer positions, such as (10, 10). The problem is that you can lose detail through the blurring effect, so it is best to position graphics at whole-pixel intervals.

Inertial motion [Hack #39] is one of the most oft-used tricks in Flash. However, the standard inertia algorithm returns fractional pixel values. For example, the following code creates the ubiquitous ball movie clip and animates it with an inertial motion toward the mouse position (a.k.a. the Cheesy Flash Mouse Follower effect):

inertia = function ( ):Void {

  this._x -= (this._x - _xmouse) / 4;

  this._y -= (this._y - _ymouse) / 4;

  trace(this._x)

};

// Create ball

var ball:MovieClip = this.createEmptyMovieClip("ball", 

    this.getNextHighestDepth( ));

ball.lineStyle(20, 0x0, 100);

ball.lineTo(0, 1);

ball.onEnterFrame = inertia;

Almost none of the traced values (this._x) give an integer, which means that the clip can appear blurry because it is positioned on the Stage between physical pixel positions. More typically, if you are adding inertia effects to drop-down menus or scrollbars, it will make any associated text fuzzy and wreak havoc with pixel fonts.

Instead of dividing by 4 in the preceding example (which, incidentally, is an arbitrary value, chosen because it gives a pleasing inertial effect at my chosen frame rate of 18 fps), you can instead bit-shift the value to the right by 2 binary digits via the following:

inertia = function ( ):Void {

  this._x -= (this._x - _xmouse) >> 2;

  this._y -= (this._y - _ymouse) >> 2;

  trace(this._x)

};

The >> 2 operation tells Flash to shift the bits in the answer 2 positions to the right, effectively dividing the result by 22, or 4, using integer division. This is faster than the standard ActionScript division operator, /, which performs floating-point division. Now the trace( ) statement displays integer values only, which keeps your animated content much sharper, assuming that the content is snapped to the nearest pixel (ViewSnappingSnap to Pixels) and all your graphics are rendered in exact pixel sizes (you can ensure this by specifying integer values for Width and Height in the Properties panel).

Final Thoughts

As you can see, there are many ways to use the more obscure features of ActionScript, which are often more optimized or shorter than mainstream solutions. It pays to keep an eye out for alternative ActionScript when performance is at a premium.