Monday 26 December 2011

Direction

Many games need to calculate the direction, or bearing, between game objects. For example the direction of the player or other game character from a weapon, so the game knows  where to aim the weapon.


The natural way to do this is using angles: when describing the direction of something we commonly use an angular measure, often a crude one such as "North West" or "Two O'clock", distinguishing between eight, twelve or more directions. For more precision degrees can be used, which as numbers can have arbitrary precision.


But often angles are a poor way to measure direction. In particular in games it is rarely best to use angles, for two reasons.



One reason is angles generalise poorly to three dimensions, where the Euler angles used have many problems, not least that Euler angles are not uniquely determined and depend on the coordinate axes and on a further convention. But the main reason is performance; angles are slow.


A simple example is the one described above, finding the direction from a gun to a target. If direction is stored as an angle the code looks like this


var fBearing:Number;


fBearing = Math.atan2(target.x - gun.x, target.y - gun.y);


But instead of using an angle two numbers can be used, so the direction is stored as a vector (see my previous post for why I don't use a vector class for this):


var fDx:Number, fDy:Number;


var fScale:Number;
fDx = target.x - gun.x;
fDy = target.y - gun.y;
fScale = 1 / Math.sqrt(fDx * fDx + fDy * fDy);
fDx *= fScale;
fDy *= fScale;


This looks more complex but it uses only basic arithmetic except for calculating the scaling factor. This is the reciprocal of the distance between the gun and target, which might already be calculated (for example for the range or how long the shot will take). It uses a few bytes more for storage but this is hardly an issue on modern platforms.


The main benefit of this approach comes when the direction is used. If the direction is stored as an angle the code looks like this


bullet.x += fSpeed * Math.cos(fBearing);
bullet.y += fSpeed * Math.sin(fBearing);

Requiring two expensive trigonometric functions every update. But if the direction is stored as a vector the code becomes

bullet.x += fSpeed * fDx;
bullet.y += fSpeed * fDy;

Which uses only simple arithmetic.

No comments:

Post a Comment