Demo:
Gravity and collision models have significant applications to Flash. They
can be used in game design, movies, or simply just as models.
In this article, I'll explain how to create a simple implementation of gravity
and collision using the trusted bouncing-ball model. We'll "fake" some of the
physics for simplicity of code, and because the resulting visual is little
improved with some of the more complicated nuances of physics.
Before we get into the guts, let's first discuss what we want to accomplish.
First, we'd like to have a simple model of a ball that can
bounce off a ground, a ceiling, and walls. We'll simply use the dimensions
of our movie to represent the wall and floor.
Second, we'd like to represent the model in a quasi-3 dimensional
fashion. Since we are simply using the boundaries of the movie to represent
at least 2 of our dimensions, we can use the size of the ball to represent
our third dimension. So, as the ball gets bigger, it has the appearance of
coming toward you, and as the ball gets smaller...you guessed it. (Note: If
we wanted to get more mathematically precise, we could rotate our coordinate
system and have the view be at a non-perpendicular angle to each of the 3 axes.
But again, the goal here is to create a model as simply as possible yet still
make it look authentic.)
Finally, while we can account for the change in velocity
in the y (up/down) direction due to gravity, we'll need some way of changing
the velocity in the x (left/right) and z (front/back) positions. To do this,
we'll insist that our floor has some friction. Each time our ball hits the
floor, we'll account for this by reducing both x and z velocities.
As a quick note, I developed this demo in MX. If this were done in MX 04,
I'd quickly strip out all the code in the onClipEvent() function into an external
.as class file. You could, then, easily plug your bouncing ball model into
other applications pretty easily this way.
So, the Flash implementation is quite simple. Disregarding the sliders that
come with this demo, there's only a ball movieclip with an "EnterFrame" clip
event defined. So, as the movieclip contines to loop back to itself, it will
run the code defined inside that function. The function will be called at the
rate of the flash movie (in this case, 30 frames per second). There's a crutch
to this with an easy workaround, as I'll explain later.
Now, each time the function is called, we need it to do two main things. First,
we'd like it to figure out what the new velocity of the ball is (This means,
figuring out the velocity in the x, y, and z direction). Second, we'd like
it to figure out what the new coordinate position is of this ball at the given
time. When we know these two things, we're pretty much ready to move on and
do it all over again.
So, let's tackle this problem. In a normal iteration of time (when the ball
is not hitting the ground, or walls, or ceiling), a ball's velocity and position
will change in the following way:
// Initial velocity changes at any given time
velocityY = velocityY + Gravity * time;
velocityX = velocityX;
velocityZ = velocityZ;
// New x,y positions after this iteration
positionY = oldPositionY + (velocityY * time);
positionX = oldPositionX + (velocityX * time);
positionZ = oldPositionZ + (velocityZ * time);
The positions in each coordinate is affected in the same way. The new position
will always be the old position plus the change in position (i.e. the velocity
of the ball in the given direction multiplied by the time frame of the iteration).
For simplicity, we're going to set time = 1, but we leave the variable in here
so that the equation makes more sense and it doesn't look like we are adding
a position with a velocity.
The velocities are simple for the x and z direction. They don't change. In
the y-direction, we need to account for gravity. Remember from the high school
physics class you slept through, gravity is an acceleration force. Just as
velocity represents the change in distance over time, acceleration represents
the change in velocity over time. So, the equation for y-velocity is errily
similar to the equations for distance, since it changes over time.
As for the Gravity constant, in high school physics, you learned that the
gravity of the earth is typically measured at 9.8 m/s2. That's great and all,
but for our Flash purposes, since we are using pixels as our unit of measurement
and frame rate as our unit of time, 9.8 really isn't a valid representation.
We're just going to leave gravity as a variable that we can change. By letting
us vary it, it makes for some nice effects (lowering the gravity to 0 lets
the balls "float" in air).
Also, you might be wondering whether our gravitational constant should be
negative or positive. Well, because the 0 y-coordinate is defined at the top
of the movie, and "positive" values of y actually fall below the 0 line, we
want to define gravity as a positive constant so that its force causes the
ball to fall.
Now, we're almost there. We've accounted for the velocity and position changes
of the ball under normal conditions. Now, we need to implement a series of
boundary cases.
In this first block of code below, we'll set velocities = 0 if they are already
close enough to zero. We do this to avoid endless minute calculations of velocities
that will render no effect visually, which helps with speeding up processing.
// If the y velocity is close enough, just set it to 0
if (Math.abs(velocityY) < .001)
{
velocityY = 0;
}
// If the x velocity is close enough, just set it to 0
if (Math.abs(velocityX) < .001)
{
velocityX = 0;
}
// If the z velocity is close enough, just set it to 0
if (Math.abs(velocityZ) < .001)
{
velocityZ = 0;
}
In this second block of code below, we define what happens each time a ball
hits either a wall (in the x or z direction) or the floor or ceiling (in the
y direction). The upper and lower bounds in the x, y, and z direction are hard-coded
for this demo.
// Check for boundary cases...
if (positionY >= 200)
{
positionY = 200;
velocityY = (-1 * velocityY);
velocityX = velocityX * _root.mFriction;
velocityZ = velocityZ * _root.mFriction;
}
if (positionY <= 5)
{
positionY = 5;
velocityY = (-1 * velocityY);
}
if (positionX >= 445)
{
positionX = 445;
velocityX = -1 * velocityX;
}
if (positionX <= 5)
{
positionX = 5;
velocityX = -1 * velocityX;
}
if (positionZ >= 100)
{
positionZ = 100;
velocityZ = -1 * velocityZ;
}
if (positionZ <= 0)
{
positionZ = 0;
velocityZ = -1 * velocityZ;
}
In this bit of logic, we are examining the new x,y, and z positions from
the calculations earlier and determine if we reached at or past a boundary
point. So, taking the x boundaries as an example, if the new position is greater
than 445 (our upper-bound), then simply set the x-position to be 445. Similarly,
if the new position is less than 5 (our lower-bound), then simply set the x-position
to be 5.
Now, recall how I mentioned the fact that our logic being called at the frame
rate of the movie was a crutch? We see why it is here. In real life, because
time is continuous, we don't have to recalculate the positions of objects in
the next instant of time to see if they have hit other objects, and then re-adjust
the objects' positions. Since we are working with a finite number of "instances" of
time in Flash (in our case, 30 frames per second), we have to think ahead before
making our next move. Else, you might see a ball stick through the wall, or
floor, or ceiling for an instant before bouncing back.
Even so, our logic here is simplified. If the ball has hit or passed a wall,
we simply reset the ball's position to the very point it just makes contact
with the wall. But, in real life, the ball has already hit the wall (it has
hit the wall sometime between this frame and the last frame). So, even our
re-adjustment isn't perfectly correct. Here's another instance where we will
just "fake" physics. At a rate of 30 frames per second, only a neurotic physics
purist with uncanny timing abilities would be able to tell that we are actually
faking it.
The other thing you'll notice is that we change the velocity magnitude of
the x,y, or z positions depending on which boundary we've hit. If we've hit
the ceiling, we'll switch the direction of the y-velocity. If we've hit a left
or right wall, the x-velocity. And, if we've hit a front or back wall, the
z-velocity.
Now, you might be asking how we got the z-direction upper and lower bounds
of 100 and 0 since there really is no z-direction. Well, these are arbitrary,
but we'll use the upper and lower limits to later define how large in scale
the ball should be to appear as if its coming closer or going farther away
from you as we discussed in the beginning.
Finally, you'll see that we account for friction of the floor by readjusting
the x and z velocities every time the ball hits the floor. This coefficient
of friction is a number between 0 and 1 (with 0 as a maximum friction and 1
as a floor with absolutely no friction).
In this third and final block of ball-logic code, we now actually move the
ball into its new position, and set the "old" position values to the current
ones, so we are ready to do this process over again:
// Set the old coordinate values to the new ones...
oldPositionY = positionY;
oldPositionX = positionX;
oldPositionZ = positionZ;
// For the Z-direction, let's set the scaling and alpha
// to fake the 3-d
this._alpha = ((positionZ / 100) * 30) + 70;
this._height = ((positionZ / 100) * 8) + 2;
this._width = ((positionZ / 100) * 8) + 2;
// For x,y, just set the coordinate values to the x,y values
this._y = positionY;
this._x = positionX;
So, now the x and y positions are easy to understand. We just set the _x and
_y properties of our movieclip to the new positions. What's the deal with the
z position, then?
Well, what we are doing here is taking the value of the new z-position (which
I predefined earlier as any value between 0 and 100) and getting a percentage
of the maximum. Then, I multiply by a range, and add the entire value by a
lower bound. So, if you take the _height property, the range of values would
be 2 to 10 (2 + 8).
Also, I've thrown in an equation to change the _alpha of the movieclip, so
that things farther away are slightly less apparent than things closer to you
(the range of alphas is 70 to 100).
So, there it is. We've built our logic base for a bouncing ball. Now, using
trusty duplicateMovieClip(), you can create all sorts of balls that jump around
and do their own thing, within the confines of the rules we've set up for them
here. I've thrown in a few sliders on the root timeline so that you can change
the gravity constant, friction, and amount of balls on stage. Also, the initial
x,y, and z velocities are randomized, and the initial x,y, and z positions
all start at the same spot. I won't go into the implementation of these, since
they don't pertain to this article, but the source code is available if you're
curious.
And, that's it. Hopefully, you've learned a little something about how to
implement a bit of physics into your Flash applications, and learned how and
when to fake some of it.
Download *.fla