Hack 38 Use Acceleration to Simulate Gravity and Friction

figs/expert.gif figs/hack38.gif

Many real-world phenomena, such as gravity and friction, affect the velocity of objects over time. Model such changes by a simple acceleration equation to create realistic motion under simulated physical conditions.

Some physical effects are hard to get right in scripted animation unless you know the underlying math. Acceleration calculations are easier than they might appear. Like all forces acting on masses, gravity and friction cause acceleration (or deceleration). When you are performing iterative calculations (which you are always doing when you are animating sprites over time), the math works out to be surprisingly easy.

When modeling real-world movement, you often have an accelerating force and a decelerating force acting on the same body (such as gravity and air friction acting on a falling body). If the forces are balanced (such as when the thrust of an airplane counteracts the air resistance), the velocity of the object is constant (acceleration is zero). The trick is simply adding the forces together to determine the overall force on the object.

A force such as gravity provides constant acceleration, meaning that the velocity of the object changes over time (just as your car speeds up if you provide enough gas to accelerate). The velocity of an object at any time is equal to its existing velocity, plus the effect of acceleration over the time interval, such as:

newV = oldV + (acceleration * time)

Likewise, the position of an object at any time is equal to its old position, plus the effect of its velocity over the time interval, such as:

newPos = oldPos + (newV * time)

If we recalculate the position and velocity often enough that the speed doesn't change during each iteration, and we use 1 as the time interval so the units drop out, we can simplify the position equation as:

newPos = oldPos + newV

In words, this means, "To calculate the new position, take the old position and add the current velocity." For example, if I drive at 60 miles per hour (mph) for one hour, I've traveled 60 miles from my original position. Therefore, in each frame of the animation, we add a small amount to the position in the direction of the velocity vector.

At the end of an interval, we recalculate the velocity to account for acceleration. The velocity calculation can likewise be simplified as:

newV = oldV + acceleration

In words, this means, "To calculate the current velocity, take the old velocity and add the acceleration." For example, if I'm driving at 60 mph and I accelerate by 10 mph per second for one second, I'm now traveling at 70 mph. Therefore, in each frame of the animation, we add a small amount to the velocity in the direction of the acceleration vector (usually pointing down in the case of gravity).

Acceleration due to Earth's gravity is approximately 32 feet per second squared (or 9.8 meters per second squared), but if you aren't providing an accurate physics calculation, you can use any constant value for the acceleration that makes your animation run at the rate you'd like.

Resting friction (the friction acting on a body at rest) tends to be greater than rolling friction (the friction acting on, say, a rolling ball). Air friction generally increases in proportion to the object's speed, so we use a simple coefficient of friction to approximate the resulting force as a fraction of the current velocity.

The following code generates a number of particles [Hack #33] and animates them falling under the acceleration of gravity and being resisted by air friction:

function fall( ) {

  // Add acceleration due to gravity

  this.speedY += GRAVITY;

  // Reduce the speed due to friction

  this.speedY *= FRICTION;

  // Assume both forces work exclusively in the Y direction

  this._y += this.speedY;

  // Make the clip bounce up when it hits the "floor" (a line)

  if (this._y > 400) {

    this._y = 400;

    this.speedY = -this.speedY * ELASTICITY;

  }

}

function drag( ) {

  // When the user clicks on a clip, make it draggable   

  // and stop animating it via onEnterFrame.

  this.startDrag( );

  delete this.onEnterFrame;

  // We could save a function call by using the following:

  //   this.onMouseMove = updateAfterEvent;

  // because the onMouseMove( ) handler calls only one function.

  // However, we use the following function definition in case

  // you want to add extra features, such as range checking

  // to prevent clips from being dragged off stage.

  this.onMouseMove = function( ) {

    updateAfterEvent( );

  };

}

function drop( ) {

  // Initialize the drop animation and 

  // stop the clip being draggable.

  // The initial y velocity is zero.

  this.speed.y = 0;

  this.stopDrag( );

  this.onEnterFrame = fall;

}

// MAIN CODE

// Create 20 ball clips

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

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

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

  ball.moveTo(0, -3);

  ball.lineTo(1, -3);

  ball._x = Math.random( ) * 550;

  ball._y = Math.random( ) * 200;

  ball.speedY = 0;

  ball.onEnterFrame = fall;

  ball.onPress = drag;

  ball.onRelease = ball.onReleaseOutside = drop;

}

//Initialize physical constants

var GRAVITY:Number = 0.5;

var FRICTION:Number = 0.995;

var ELASTICITY:Number = 0.85;

// Draw the ground line

this.lineStyle(0, 0xDDDDDD, 100);

this.moveTo(0, 400);

this.lineTo(550, 400);

Gravity is simulated by applying the following line every frame. It changes the speed in the Y direction over time:

this.speedY += GRAVITY;

If the ball is falling, gravity increases the velocity and speeds the fall. If the ball is climbing, gravity decreases the velocity and slows the ascent, just as a bouncing ball would act.

The effect of air friction is simulated via the line:

this.speedY *= FRICTION;

If FRICTION is less than 1, it slows the ball down regardless of the direction it is moving, just like real friction. If set equal to 1, there is no frictional effect. If set greater than 1, it creates thrust, like that of a rocket (it speeds up the clip rather than slowing it down).

When the ball hits the floor and bounces, we want to simulate the loss of energy typical in such collisions. The elasticity coefficient is applied via the line:

this.speedY *= ELASTICITY;

If ELASTICITY is less than 1, the ball bounces with less energy each time it hits the floor, just like in the real world. If set equal to 1, the bouncing ball is perfectly elastic (it would bounce forever to the same height if not slowed by friction). If elasticity is set greater than 1, the ball bounces higher with each successive bounce (neat trick!).

Final Thoughts

Because animation works on a per-frame basis, equations involving motion are easier to code up than the equations of motion you learned in physics class. If you consider motion per frame, such motion always results in fundamentally linear equations, making your scripts short and efficient. See if you can simulate motion in the X direction as well as the Y direction (hint: gravity has no effect in the X direction, and you should give your ball an initial horizontal velocity).

You can add all sorts of variations to an animation by setting the gravity to a different value (or even a negative value to make things float). You can set gravity to 0 to achieve a 2D effect in which the camera appears overhead (such as balls on a billiard table, so you better add bumpers all around). Throw sliders on stage [Hack #61] to control the gravity, friction, and elasticity settings, and go wild! You can also add sounds or make the ball squish when it bounces.