Sunday, April 10, 2011

Gravity tables for side scroller physics

As I mentioned earlier, I am working on a game in ActionScript (in other words, a Flash game but one that is programmed as opposed to developed primarily on the timeline). It is a two dimensional side scrolling game and as expected, one of the main movements of the player (besides going right and left) is jumping (and by extension, falling).

In order to implement jumping and falling, not just for the player but for anything, I needed to build a rudimentary physics engine to handle gravity. Now, since this is just a two dimensional arcade style game the physics do not have to move beyond a high school level but they need to be implemented well.

The way I chose to implement gravity was by using pre-calculated lookup tables that describe the Y offset for each frame of jumping or falling. If a player or object is in a jump or a fall, they simply just have to iterate through a list of values of in a table that will tell them how much to adjust their Y axis by (positive numbers bring them up more for a jump and negatives numbers bring them down for falling). This way, all the calculations are done before hand.

This does not mean there is a hardcoded table that has to be updated painstakingly by hand, nor does it mean every object has to use the same table. I wrote an ActionScript class that builds these tables based on parameters given so that you can easily build custom tables on the fly just using one line of code. The benefit is that the calculations are only done once and all of the live processing of gravity is handled through lookups.

If you are wondering how a vertical-only table can account for left and right movement as well, remember that left and right movement can be handled separately but simultaneously with vertical movement. Jumping straight up and jumping up and to the left are the same as far as the forces of gravity are concerned. If you drop a bullet and shoot a bullet, they will both hit the ground at the same time. Okay, no they won't due to the fact the bullet will act like a plane and glide and you can get into a whole aerospace engineering debate but for the purposes of what we are doing, horizontal motion has no effect on vertical motion.

The best part about the GravityTable class I created is that it is short and sweet. Here is the constructor, which is the meat of the code. This constructor creates a GravityTable which can be used indefinitely for any object that uses the parameters given.

public static const TILE_HEIGHT:uint = 25;
public static const MILLISECONDS_PER_FRAME:Number = 1000 / 25;
private var gravitationalDisplacementPixels:Vector.<Number>;
private var gravitationalDisplacementPeakPointer:int;

public function GravityTable(initialVelocity:Number, accelerationDueToGravity:Number, timeInMillisecondsAtWhichTerminalVelocityReached:int, incrementMultiplier:int = 1) {
 gravitationalDisplacementPixels = new Vector.<Number>();

 var tmpCalc;
 var oldCalc = 0;
 var foundPeak = false;
 var incLevel = (MILLISECONDS_PER_FRAME / 1000) * incrementMultiplier;
 var i = incLevel;
 var j = 0;
 while (j < (timeInMillisecondsAtWhichTerminalVelocityReached / 1000)) {
  tmpCalc = ((initialVelocity * i) - ((accelerationDueToGravity / 2) * (i * i))) - oldCalc;
  if (!foundPeak && i > 0) {
   if (tmpCalc <= 0) {
    if (tmpCalc < 0) {
     this.gravitationalDisplacementPixels.push(0);
    }
    this.gravitationalDisplacementPeakPointer = this.gravitationalDisplacementPixels.length;
    foundPeak = true;
   }
  }
  oldCalc += tmpCalc;
  this.gravitationalDisplacementPixels.push(tmpCalc * TILE_HEIGHT);
  if (foundPeak) {
   j += incLevel;
  }
  i += incLevel;
 }
}

What this code does is calculate out, based on the given parameters, the displacements due to gravity at every frame the game would render and handle. The basic formula as far as physics and math are concerned:

\[s=ut+\frac{1}{2}at^{2}\]

Where \(s\) is displacement, \(u\) is initial velocity, \(t\) is time and \(a\) is acceleration due to gravity.

In other words, at every point \(t\) that the game would need to know, a value for \(s\) is calculated and then the displacement between the new \(s\) and the old \(s\) is stored in a table. This way, it is the displacement and not the absolute position that is pulled on a lookup and this value can simply be added onto the current Y position of the object responding to gravity. So what the table ends up storing is:

\[\Delta s=s-s^{\prime}=(ut+\frac{1}{2}at^{2})-(ut^{\prime}+\frac{1}{2}at^{\prime}^{2})\]

In addition, various pointers to the frames of the lookup table are stored. There is a starting point but also a peak point where the top of the jump is reached. These pointers allow the same table to be used either for a jump or for a fall simply by entering the table at different initial frames and then just running through what is left.

This video demonstrates jumping. The game is in the engine
development stage, not the level/graphics stage. Please forgive
the ugliness; the only thing created by an artist is the character
animation and everything else is just thrown together for testing
the engine.

Here is a sample gravity table. It was built using a TILE_HEIGHT of 25, a MILLISECONDS_PER_FRAME of 40, an initial velocity of 8m/s, a gravitational acceleration of 9.8m/s2, a terminal velocity that is reached in 2000 milliseconds and a multiplier of 2. The multiplier is the only non-physics related part and it simply speeds everything up by two because these games feel a bit a slow to play otherwise. This means that each point on the table represents 80ms of time passed (40ms * the multiplier of 2). The above video is a demonstration of this gravity table being used. As you can see, the table is just a list of numbers that would be cycled through.

15.216
13.648000000000001
12.079999999999997
10.512000000000004
8.943999999999996
7.375999999999994
5.808000000000013
4.239999999999988
2.6719999999999966
1.104000000000005
0
-0.46400000000000885
-2.0319999999999894
-3.600000000000003
-5.167999999999995
-6.736000000000009
-8.304000000000023
-9.872000000000014
-11.439999999999984
-13.008000000000042
-14.576000000000011
-16.144000000000027
-17.71200000000004
-19.28000000000001
-20.847999999999978
-22.41599999999999
-23.984000000000094
-25.551999999999975
-27.120000000000033
-28.68800000000009
-30.25600000000006
-31.82400000000012
-33.391999999999996
-34.96000000000006
-36.52800000000003
-38.096

The following parabola is created using the function:

\[y=25(8x-\frac{1}{2}(9.8x^{2}))\]
The parabola is the motion parabola where the Y value is taken every 0.08 units across the X axis. The blue dots are the points on the generated table. Remember, the table does not show the Y value on the parabola, but the difference between the new Y value and the previous Y value. The Y is scaled to 25 pixel tile heights. You may also notice that when the blue line hits 0 on the Y axis, the line seems to be thrown off a bit. This is due to the fact that we make sure the first point below 0 becomes exactly 0 for the purposes of the game, as the approximate peak.

The code should be fairly self explanatory beyond that but a few notes: the TILE_HEIGHT variable is just the size of a tile in my side scrolling game and is used to scale the calculations. Likewise, my MILLISECONDS_PER_FRAME is based on a frame rate of 25 FPS. This can all be changed and the table will be different based on what frames it is calculating for. The source is released under the MIT License.

No comments:

Post a Comment