Physics for Game Programming

How to simulate gravity and air resistance, bounce, detect collisions, and convert between polar and cartesian coordinates in your games.

Many games don't require realistic physics. If you're a beginner in game programming, you should seriously start with something "simple" like tic-tac-toe or a board game where you move your piece to an adjacent square. No complicated physics to worry about there. I put "simple" in quotes because you'll quickly discover that even a "simple" game like tic-tac-toe can be a challenge to program, especially for a beginner.

But eventually, you'll want to know how to simulate physics. Don't be intimidated by the word physics or the code snippets that I throw around after 30 years of simulating physics in my games and simulations. This is really just basic stuff. Everything looks more complicated than it is.

Coordinate, Velocity, Acceleration

You probably already know about cartesian coordinates and the custom of using x for the position in the horizontal dimension and y for the position in the vertical dimension. Velocities are simply the values that we keep adding to our coordinates. We'll use vx and vy as the velocities in those dimensions. When younger, I preferred to use xx and yy for velocities to save time typing. Acceleration is simply the value that we add to a velocity. So there is a heirarchy of values related to any object: the coordinates, the velocities in each dimension, and the accelerations in each dimension. The accelerations change the velocities which change the coordinates.

So if we have a constant acceleration of an object in 2D space, we can represent that most simply like this:

vx=vx+ax;
vy=vy+ay;
x=x+vx;
y=y+vy;

where x and y are coordinates, vx and vx are the velocities, and ax and ay are the rates of acceleration. And we could shorten that to the following:

x+=vx+=ax;
y+=vy+=ay;

For every moving thing in your game, you need variables to store the coordinates and the velocities, but it would be unusual to store the accelerations. I usually calculate those during the frame, perform the acceleration, and discard the values. No need to store them in most cases.

Instead of storing these variables in separate arrays, storing them in a struct or object can improve efficiency where there are many moving things in your game or simulation because all of the data about any single object is right there in the same little area of memory, which can speed up the memory access.

Trig 101

Sometimes in your game or simulation, you know what angle and distance something should be at, but you need to convert that to x and y math. To convert an angle and a distance to x and y coordinates, you need to use a little trigonometry.

Normally, I use sine to find x and cosine to find y, and this works perfectly for angles where 0 is up as on maps, but you could do the reverse, using cosine for x and sine for y, for cases where 0 degrees is horizontal instead of vertical such as in cases where 0 degrees is at the horizon.

Functions for sine and cosine are provided by all the big modern programming languages. I think most modern chips provide a simple machine code instruction for it. I think it's slower than most instructions, especially on older hardware, so use it sparingly. Store the results instead of calling any trig functions repeatedly.

In C or C++, you include math.h to use sin, cos, and most other math functions. In Python, you import the math module. In JavaScript, sin and cos are methods of the Math object.

This is how we calculate x and y coordinates of any angle and distance in the C programming language:

x=d*sin(a);
y=d*cos(a);

where d is distance and a is angle in radians. In C, with most physics, all variables should be of type float or double.

Radians are used by most languages instead of degrees for angles for trig functions. Radians range from 0 to 2π instead of 0 to 360, so to convert degrees to radians, multiply by π/180. Conversely, to convert radians to degrees, multiply by 180/π

Think about it. We have an angle in degrees between 0 and 360, and we want to convert it to radians. First we divide by 360 to get a range between 0 and 1, then we multiply that by 2π to get the range between 0 and 2&;pi;. So we divide by 360 and multiply by 2π - that's the same as multiplying the degrees by 2*π/360 which simplifies to π/180, which is a constant. You will need to do that kind of math all the time - not just with radians.

Multiplying is more efficient than dividing, and putting π/180 in parentheses might help compilers or interpreters to see that the constant can be pre-calculated outside of loops to improve efficiency.

People normally think in terms of degrees, and it's easy to forget to convert your angles to radians, creating confusing bugs, so be careful with your angles.

In C, converting degrees to radians:

a=degrees*(M_PI/180);
x=distance*sin(a);
y=distance*cos(a);

In JavaScript, converting degrees to radians:

a=degrees*(Math.PI/180);
x=distance*Math.sin(a);
y=distance*Math.cos(a);

In some cases, you need to reverse the y direction so y originates at the bottom instead of the top of the screen, like this:

x=d*sin(a);
y=-d*cos(a);

Velocities

Sometimes in your game or simulation, you know what angle and speed something should be traveling, but you need to convert that to vx and vy math, where vx and vy are the velocities in the individual dimensions. It's the same trigonometry as above with the distances and angles.

Converting velocity v and angle a to vx and vy in C:

vx=v*sin(a);
vy=v*cos(a);

Distance

To calculate the distance between any two objects in two-dimensional space, use code like this in C:

dx=x1-x2;
dy=y1-y2;
d=sqrt(dx*dx+dy*dy);

where x1 and y1 are the coordinates of the first object and x2 and y2 are the coordinates of the second object, and d is the distance in 2D space.

Finding Angles

To find the angle when you know the x and y coordinates, use the atan2 function.

In C, that looks like this:

dx=x1-x2;
dy=y1-y2;
a=atan2(dx,dy)*(180/M_PI);

In JavaScript:

dx=x1-x2;
dy=y1-y2;
a=Math.atan2(dx,dy)*(180/Math.PI);

Note that the angle is provided in radians, so we must convert it to degrees by multiplying by 180/π

Some older languages only provided atan which uses the result of x/y or y/x as input, so you had to figure out for yourself which quadrant the result was in.

Anyway, now you know how to convert between polar coordinates of angle and distance into the x and y cartesian coordinates and vice versa.

Look Ma, No Trig!

Sometimes, you don't need trigonometry. Suppose you know the coordinates where you want an object to go. There's no need to convert to polar coordinates and back to cartesian coordinates. If we have a target destination at coordinates tx,ty and we want to go there at velocity v, we do this:

dx=tx-x;
dy=ty-y;
d=sqrt(dx*dx+dy*dy);
vx=v*dx/d;
vy=v*dy/d;

We are setting the velocity in each dimension in proportion to the distance in that dimension divided by the total distance.

3-Dimensional Math

Distance in 3D Space

It's the same as in 2D space except we add another term in the formula for the additional dimension like so:

dx=x1-x2;
dy=y1-y2;
dz=z1-z2;
d=sqrt(dx*dx+dy*dy+dz*dz);

Angles

We require not one but two angles to tell direction in 3D space. Imagine one angle telling where on the horizon you are facing and another angle telling how high above the horizon you are facing.

I'll reformulate that math from memory and check it so I can put it here when I have time.

Gravity

To simulate gravity, you can just add a constant to the vertical velocity every frame. At sea level, the acceleration of gravity is 32.2 feet per second per second, also represented as 32 ft/sec/sec or 32 ft/s2, which just means the falling object gains 32 feet per second of vertical velocity every second. 32 feet is a little less than 10 meters.

Of course, you don't want to add 32 ft/sec to the velocity every frame. You want to divide by the number of frames per second. If your game is designed to run at 60 frames per second, add 0.5367 feet per second to the vertical speed every frame for realistic gravity.

Your vertical speed uses the same units as your display functions, so if x and y are in pixels, then your vx and vy are in units of pixels per frame. How big is a pixel in your game world? Is it one foot in height? If so, vy is in feet per frame, which is 1/60 the speed as measured in feet per second.

For a simple game, you might just do this in the main loop to simulate gravity:

vy=vy+0.01;
x=x+vx;
y=y+vy;

which we could shorten to

x+=vx;
y+=vy+=0.01;

And tune 0.01 to a value that works best for your game.

If you need to have less gravity at higher altitudes, you need to use a more realistic formula. Like many but not all things in physics, gravity follows the inverse-square law, which means it's proportional to 1/d2, where d is distance, in this case the distance between the centers of mass of the two bodies. In other words, if you double the distance, the gravity is one quarter as much. 1/4th.

(Other things you can calculate using the inverse-square law include the brightness at a known distance from a light source and the strength of a radio signal in space at a known distance from the transmitter. Things that don't work according to inverse-square law include magnetism and the strong nuclear force.)

The distance we need to use to calculate gravity is not the altitude but the distance from the center of the Earth, and that's the altitude plus the radius of the Earth, which is 3959 miles. So we might use the following code:

dist=3959+alt;
grav=k/(dist*dist);
vy+=grav;

where alt is altitude in miles, vy is the vertical velocity, and k is a magic constant that you tune for your game. For even greater realism, set k according to the masses of the bodies as in the actual formula for gravitational force.

Gm1m2


d2

When applying the force to your spaceship or whatever, divide the force by the mass of your spaceship to find the acceleration, i.e. the change in velocity.

Drag

Air resistance, or drag, is needed for some games.

A simple approximation to air resistance is to simply multiply the vertical and horizontal velocities by a constant less than 1 such as 0.99 every frame. So we could approximate drag like this:

vx=vx*.99;
vy=vy*.99;
x=x+vx;
y=y+vy;

which we could also write as

x+=vx*=.99;
y+=vy*=.99;

But realistic air resistance at subsonic speeds is proportional to the speed squared, as in the following formula.

drag=constant*velocity2

To simulate drag in a program, we might do this:

v-=k*v*v;

where v is velocity and k is a drag constant for that object, typically near 0, such as 0.01 or so. For some games, that's enough. Maybe you are coding a little flight sim, so you're setting the vertical and horizontal speeds based on a total velocity that you're updating along with one or two angles in space, then the drag in the velocity variable will be translated into the various dimensions automatically.

But what about games where we must manually translate drag into multiple dimensions? Knowing the formula is nice, but how the hell do we translate that into the code of a game with a different variable for the velocity in each of 2 or 3 dimensions? Maybe you're thinking the solution to simulate drag is this:

vx-=k*vx*vx;
vy-=k*vy*vy;

No! Wrong! That results in the wrong angle and magnitude of drag.

What we want is to subtract k*v*v from vx and vy according to their proportion as part of the total velocity. Look at this:

v=sqrt(vx*vx+vy*vy);
vx-=k*(v*v)*(vx/v);
vy-=k*(v*v)*(vy/v);

And sqrt is found in the math module in Python or the Math object in JavaScript. We can of course simplify that to the following:

v=sqrt(vx*vx+vy*vy);
vx-=k*v*vx;
vy-=k*v*vy;

I wondered if that really simulates the drag formula accurately in two dimensions, so I tested it, but you can test it yourself.

The math simulating both drag and gravity can be reduced to only a few lines in many languages. Here it is in JavaScript:

v=Math.sqrt(vx*vx+vy*vy);
x+=vx-=k*v*vx;
y+=vy-=k*v*vy-g;

where k is a constant for drag and g is a constant for gravity.

The problem with this simplification is that we should do everything according to how much time has passed. The above way uses less math in your game loop, so runs faster, but it requires care in the math if you're aiming for realism, because vx and vy in this case represent velocity per frame, not per second.

For less confusing math, we should normally do it like this:

v=Math.sqrt(vx*vx+vy*vy);
vx-=t*k*v*vx;
vy-=t*k*v*vy-t*g;
x+=t*vx;
y+=t*vy;

where t is the amount of time that has passed since the last cycle, and all of the other variables are the same as above. You could perhaps hardcode t to equal 1/60th of a second or, for more computationally demanding games, actually measure how much time has passed since the last frame.

I think we get more accurate results by updating velocities in the middle of updating positions like this:

v=Math.sqrt(vx*vx+vy*vy);
x+=.5*t*vx;
y+=.5*t*vy;
vx-=t*k*v*vx;
vy-=t*k*v*vy-t*g;
x+=.5*t*vx;
y+=.5*t*vy;

We move the object forward by half a frame's worth of its velocities, update the velocities, then move the object another 1/2 frame's distance.

Realistic Gravity

If we need our gravity to weaken with altitude as in nature, we could change it from a constant to something calculated like this in JavaScript:

d=3959+y/5280;
g=m/(d*d);

where y is in feet, higher y is higher altitude, and m is a constant related to the mass of the Earth, frames per second, and the units being used.

For even greater realism, you might try to take into consideration the effect of reduced density of air at higher altitudes on air resistance.

Bouncing

To make something bounce off of the ground or edges of the screen, we check if x or y is out of bounds. If so, we reverse the speed and put it back in bounds. Things don't bounce 100% efficiently, some of the energy is lost by bouncing, so we multiply the velocity in that dimension by something less than 1 while reversing it.

To make an object bounce off the floor, assuming the floor is at y=0, we might do this:

if(y<0){
 y=0;
 vy*=-.9;

]

And to likewise bounce off the ceiling, we might do this:

if(y>h){
 y=h;
 vy*=-.9;

]

But something isn't quite right with that. We're missing some of the motion in the process of the bounce, because the object travels less distance in the frame when the bounce occurs. To correct this, we need the object to be in the correct location considering that our data points are only once per 60th of a second, and the bounce really happens between the frames almost all of the time. It needs to bounce away by the correct distance during the frame when it bounces instead of being stuck to the wall. This is what we want for more realistic bounces:

if(y<0){
 y=-y;
 vy*=-.9;
}else if(y>h){
 y=h+h-y;
 vy*=-.9;

]

To bounce off the sides, you obviously need to do the same for x and vx as we did for y and vy.

if(x<0){
 x=-x;
 vx*=-.9;
}else if(x>w){
 x=w+w-x;
 vx*=-.9;

]

Can you figure out how to take into account the width and height of an object when bouncing?

Collision Detection

A simple way to detect collisions between two objects is to check if the difference between their x coordinates is under a limit and the difference between their y coordinates is also under a limit. This is best for rectangular or square objects.

dx=Math.abs(x1-x2);
dy=Math.abs(y1-y2);
if(dx<4 && dy<4){
 //collision occurred

]

Another way to detect collisions is to check if the distance between the objects is less than a limit. This is best for circular or spherical objects.

dx=x1-x2;
dy=y1-y2;
d=sqrt(dx*dx+dy*dy);
if(d<5){
 //collision code here

]

However, we can make it more efficient by avoiding the sqrt function by checking if the distance squared is below the limit squared.

dx=x1-x2;
dy=y1-y2;
d2=dx*dx+dy*dy;
if(d<25){
 //collision code here

]

But you must remember that you're dealing with the distance squared and not the distance.

To detect collisions between oddly shaped objects, you could try to check if any pixels of the two objects are being drawn on top of each other. Otherwise, you could use an array describing which pixels of your sprite can collide.

If you have to check for a lot of collisions, you might need a way to reduce the work being done by the CPU. You could do half of the collision checks in alternating frames, but this risks missing a collision or maybe even not looking quite right when the collision is delayed by a frame.

A better strategy where the speeds of objects are limited is to only bother checking for collisions between objects that are in the same area or neighboring areas, where an area is simply a square within an arbitrary grid. This reduces the work that must be done by the CPU in cases where checking for collisions uses more CPU than checking if the objects are in the same or adjacent areas.

 ax1=Math.floor(x1*.01);
 ay1=Math.floor(y1*.01);
 ax2=Math.floor(x2*.01);
 ay2=Math.floor(y2*.01);
 if(Math.abs(ax1-ax2)<=1 && Math.abs(ay1-ay2)<=1){
  //collision may have occurred!
  //check for actual collision here!
 }

If all objects are in an array that is sorted by the x or y coordinates of the objects, you would theoretically only need to check for collisions between adjacent objects in the array, but where fineness of time simulation is only 1/60th of a second, there's a chance for mistakes. However, checking for collisions between nearby, not just adjacent, objects in the array should work all of the time, and should save a lot of CPU over checking for collisions between every potential pairing of objects. But you'd need to quicksort all collidable objects every few frames.

Normally, to check for collisions between every object and every other object, you need to do this:

for(i=0;i<a.length;++i){
 for(j=i+1;j<a.length;++j){
  //check here for collision between a[i] and a[j]
 }

]

Note that this avoids checking the same pairings, such as when we've checked for collision between i=50 and j=60, there's no need to also check for collision between i=60 and j=50, because that's the same two objects colliding, so don't do it like this:

for(i=0;i<a.length;++i){
 for(j=0;j<a.length;++j){
  //checking for collisions inefficiently
 }

]

Red Ball

Click in the page to move the red ball.

The ball is controlled entirely by the 857 bytes of JavaScript code found in ball.js.

Conclusion

Check back soon for more.


Ron Spain

rockymount.us

ronspain.crabdance.com




Page generated in 0.007313 seconds.

© 2024 RockyMount.US