Hack 39 Simulate a Throw

figs/expert.gif figs/hack39.gif

To add a nice touch of realism to your interface, script a throw effect in which an object continues to move in the direction it was released.

As we saw previously, acceleration [Hack #38] due to gravity and friction (including air resistance) are far easier to emulate on a per-frame basis than by using the real-world equations from which they're derived.

This hack looks at a simple way to simulate an object being thrown.

When you throw a ball, you define an imaginary force vector. The direction of this vector defines the initial direction in which the ball moves, and the length of the vector is proportional to how hard the ball is thrown (and how far it will travel).

Although we don't know how hard anything is being thrown in Flash (given that forces and mass don't exist in Flash's virtual world), we can simulate motion in a way that appears realistic. As with all physics simulation in Flash, our math just has to be close enough to pass the motion off as authentic. In practice, we don't generate the precisely correct movement, but something that is proportional to it.

Consider Figure 5-12, which depicts a ball being thrown. The user can click and drag the ball to "throw" it around the Stage (it travels in the direction the mouse is moving when the user releases the mouse button).

Figure 5-12. A ball being thrown
figs/flhk_0512.gif


If we measure the distance our ball moves per frame, the force F with which the ball is thrown is proportional to the distance d between the last two known positions of the ball (assuming we are measuring the ball position at regular intervals, such as at the frame rate). The distance between the two positions is proportional to how fast you are dragging the ball. When you release the mouse button, the ball travels in the direction you "threw" it.

The following code (shown with line numbers for reference) implements the throw effect. Function drawPip( ) draws our ball, and function throwClip( ) defines the event handlers that control the throw initialization. The onMouseMove( ) event handler tracks the difference between the last known and current ball positions and stores it as a vector (dirX, dirY). This vector defines our force in direction and magnitude.

You can see the force vector visually if you uncomment all the commented lines between the two sets of **Diagnostic** comments (lines 24-26 and 50-52). This makes the effect of the code much easier to see.

The animation cycle starts via the onPress event (line 15), when you click on the ball clip, whereupon the ball becomes draggable.

The point from which the ball is thrown is detected by the onRelease event if you click, drag, and release (i.e., "throw") the ball or via the onReleaseOutside event, which occurs if your mouse goes outside the Stage (whereupon the code forces you to drop, rather than throw, the ball). The onRelease( ) and onReleaseOutside( ) event handlers start at line 31 and set up an interval that invokes mover( ) to control the ball animation after it has been thrown.

The mover( ) function uses the dirX and dirY variables to control the motion of the ball. The vertical position is increased in the downward direction (i.e., dirY) over time via the GRAVITY constant, and both dirX and dirY are reduced over time via the FRICTION coefficient. The inertial mass of the ball is indirectly modeled by the MOMENTUM constant. The larger the value for MOMENTUM, the greater the amplitude of the force vector.

The interval is cleared (line 17) within the onPress( ) function (starting at line 15) when the ball is clicked, at which point the throw cycle starts again.

// ActionScript 2.0 code

function drawPip(clip:MovieClip, clipName:String, clipDepth:Number,

                 x:Number, y:Number):MovieClip {

  var pip:MovieClip = clip.createEmptyMovieClip(clipName, clipDepth);

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

  pip.moveTo(0, 0);

  pip.lineTo(1, 0);

  pip._x = x;

  pip._y = y;

  return pip;

}

function throwClip(clip:MovieClip):Void {

  clip.oldX = clip._x;

  clip.oldY = clip._y;

  clip.onPress = function( ) {

    clip.startDrag(true, -265, 190, 265, -200);

    clearInterval(clipMove);

    clip.onMouseMove = function( ) {

      clip.dirX = MOMENTUM * (clip._x - clip.oldX);

      clip.dirY = MOMENTUM * (clip._y - clip.oldY);

      clip.oldX = clip._x;

      clip.oldY = clip._y;

      // **Diagnostic**

      // clip.line = clip.createEmptyMovieClip("line", 0);

      // clip.line.lineStyle(4, 0x0, 100);

      // clip.line.lineTo(5 * clip.dirX, 5 * clip.dirY);

      // **Diagnostic**

      updateAfterEvent( );

    };

  };

  clip.onRelease = clip.onReleaseOutside = function ( ) {

    clip.stopDrag( );

    delete clip.onMouseMove;

    clipMove = setInterval(mover, 1, clip);

  };

}

function mover(clip):Void {

  if (Math.abs(clip._x) > 265) {

    clip.dirX = -clip.dirX;

  }

  if (clip._y > 190) {

    clip.dirY = -clip.dirY;

    clip._y = 190;

  }

  clip.dirX = clip.dirX * FRICTION;

  clip.dirY = (clip.dirY * FRICTION) + GRAVITY;

  clip._x += clip.dirX;

  clip._y += clip.dirY;

  // **Diagnostic** 

  // clip.line = clip.createEmptyMovieClip("line", 0);

  // clip.line.lineStyle(4, 0x0, 100);

  // clip.line.lineTo(5 * clip.dirX, 5 * clip.dirY);

  // **Diagnostic**

  updateAfterEvent( );

}

var MOMENTUM:Number = 0.8;

var GRAVITY:Number = 0.5;

var FRICTION:Number = 0.99;

this._x = 275;

this._y = 200;

this.lineStyle(0, 0x0, 200);

this.moveTo(-275, -200);

this.lineTo(-275, 200);

this.lineTo(275, 200);

this.lineTo(275, -200);

var ball:MovieClip = drawPip(this, "ball", this.getNextDepth( ), 0, 190);

throwClip(ball);

Final Thoughts

Something to note for the Flash MX-style coders is the way that the current object, this, is never used in the event handlers. Instead, the code uses function arguments to pass the target clip name. This is much more efficient, and you can see the difference if you substitute clip with this in the most frequently executed event code (the onMouseMove( ) handler, lines 18-30).

Flash Player 7 is optimized to handle data fastest when it is passed as an argument [Hack #100], which is the preferred coding style for ActionScript 2.0 code. This optimization occurs because the Flash Player saves arguments to several hardware registers rather than looking at the variables that source them.

If you use this in place of movie clip references passed as arguments, the Flash Player does not use the register optimizations and your code runs more slowly.