by Ka Wai Cheung, May 2004
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:
[as]// 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.
[as]// 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.
[as]// 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:
[as]// 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.