Skip to lesson
Game Hacking Academy
Preparing lesson text…
Exit reader mode
Voice and speed

Preparing read-aloud controls…

Lesson 4.4 Listening edition

Coordinates, Vectors, and Directions

Build distance and direction from positions, use normalization and dot products to express spatial decisions, then blend values and test contact with simple shapes.

About 26 minutes

A map tells us which places exist. Coordinates let a tool compare those places: where is the player, how far away is an enemy, and which way does a movement point? Begin with two positions on a grid, then turn their difference into distance and direction. The same operations work in two dimensions or three.

Section: Extend 2D coordinates to three dimensions.

A 2D game often describes a position with x and y.

A 2D game world

Picture a player at (2, 3) on that map and an enemy at (5, 7). The enemy is 3 units farther along x and 4 along y. Those differences form an arrow from the player to the enemy. Its straight-line length is 5: a right triangle with sides 3 and 4 has a long side of square root of (3 to the power of 2 + 4 to the power of 2) equals square root of 25 equals 5.

A 3D game adds a third axis. If the enemy is also 12 units above the player, the arrow becomes (3, 4, 12) and its length becomes square root of (3 to the power of 2 + 4 to the power of 2 + 12 to the power of 2) equals square root of 169 equals 13. The same subtraction and distance idea grew by one coordinate; no new kind of measurement was required.

In brief: Subtract two positions to get an arrow between them, and its length is the distance. A third axis changes nothing about the method.

That arrow turns positions into useful questions: how far away is a target, which direction should a character move, and is the target inside a guard’s view? We will use the same spatial language for automation and N P C, non player character, decisions. The later camera lessons extend it to projecting the world onto a screen.

Rust can store the three numbers together:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Each field is an f32, a 32-bit floating-point number that can hold fractions as well as whole coordinates. #[derive(...)] asks Rust to generate conveniences such as copying and printing this small record; none of those traits changes the geometry. A Vec3 is not special hardware. It is a vector: an ordered group of numbers that we choose to treat as one mathematical object. The order matters because each slot has a role. Swapping x and z changes the meaning even when the three numbers stay the same.

Vectors are useful because one operation can apply to the whole idea. Adding a movement vector to a position moves the position. Subtracting two positions gives the difference between them.

Games disagree about which axis means “up.” Never assume. Move in one direction at a time and observe which field changes.

🧭 Coordinate check: change one axis while keeping the others still. The field that follows the movement tells you what that axis means in this game.

A 3D game world

Section: Position versus direction.

A position answers “where?” A direction answers “which way?” Both can use three numbers, but they mean different things.

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

Self inside impl Vec3 means Vec3; self is the vector whose method was called. The subtraction uses enemy minus player, so the result points from the player to the enemy. length() squares each difference, adds the three squares, and takes the square root. These exact coordinates produce the (3, 4, 12) arrow and distance 13 calculated above. Reversing the operands would point the arrow backward while leaving its length unchanged. The :? in println! uses the Debug implementation generated for Vec3, so the program prints all three fields alongside the distance. Put this block after the Vec3 declaration to run the example.

A point and a direction can both be stored as three floats, but the allowed math is different:

Lesson table

This is a good example of why a memory layout does not tell the whole story. Three adjacent floats could be a position, velocity, color, scale, or Euler angles. Watch how the game uses them before assigning a name.

Section: Length, normalization, and alignment.

The length of a direction is its distance. Normalizing divides by that length so the result keeps only direction and has length one. Check for a near-zero length first; there is no meaningful direction from a point to itself.

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

For (3, 4, 0), length is 5, so the returned vector is Some((0.6, 0.8, 0)): it points the same way and its length is 1. Option expresses the case where no usable direction exists. When the points coincide, division by zero cannot produce a meaningful aim direction, so the function returns None rather than letting invalid numbers spread into later camera calculations. is_finite also rejects infinity and NaN from a damaged or stale snapshot.

The dot product measures alignment. For two normalized directions it is near 1 when they face the same way, 0 when perpendicular, and -1 when opposite. A field-of-view test can compare the camera’s forward direction with the direction to a target. This avoids turning both vectors into angles merely to ask whether they point roughly the same way.

For vectors a equals (aₓ, aᵧ, a_z) and b equals (bₓ, bᵧ, b_z):

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

That second line is why a dot product can become an angle with acos(clamp(dot, -1, 1)). Clamp first because floating-point rounding can produce 1.the numeric value shown, which is outside the mathematical input range of acos.

Worked through, a field-of-view test is three multiplications and two additions. Suppose the camera faces straight along positive x, and a target sits ahead and a little to the side:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

0.8 is the cosine of roughly 37 degrees, so the target sits about 37 degrees off the centre of view. If visibility accepts directions at most 45 degrees from the centre of view, you never need acos at all: compare the dot product against cos(45 degrees), which is about 0.707, and anything above that threshold is inside the cone. Comparing cosines is cheaper than converting to angles, and it sidesteps the rounding trap described above.

Alternative: Think of two people pointing: the dot product is 1 when they point the same way, 0 at right angles, and minus 1 when they point opposite ways.

Section: Blending values: interpolation, easing, and following.

Subtracting positions gives a direction. The reverse question is just as common: given two values, what is the value some fraction of the way between them? A character walks from one tile to the next, a colour fades, a camera slides toward a target. The answer is linear interpolation, usually called lerp. Start at a and add the fraction t, from 0 to 1, of the way to b:

At t equals 0 the result is a, at t equals 1 it is b, and at t equals 0.25 it is a quarter of the way. For numbers, a equals 2 and b equals 10 give 2 + (10 minus 2) times 0.25 equals 4. For a vector, blend each coordinate on its own: from (2, 3) to (5, 7), halfway (t equals 0.5) is (2 + 3 times 0.5, 3 + 4 times 0.5) equals (3.5, 5). A colour is three numbers too, so a half blend of red rgb(255, 0, 0) and blue rgb(0, 0, 255) is (127.5, 0, 127.5), which a display stores as rgb(128, 0, 128), a purple.

Lesson 4.1 left 3.33 milliseconds over after a 16.67 milliseconds step, so t equals 3.33 divided by 16.67 equals 0.2. If a ball’s x was 794 after the earlier step and 796.5 after the later one, the picture shows 794 + (796.5 minus 794) times 0.2 equals 794.5. That is the blend the engine uses to draw motion between its steps.

Section: Easing.

A straight lerp moves at one speed, so it starts and stops abruptly. Easing reshapes t before it is used, so the motion can begin gently, end gently, or both. Here are four reshapings, with the value each gives at five moments:

Lesson table

Take an object that moves from x equals 0 to x equals 100 in one second. A quarter of the way through, the linear curve has put it at 25, the ease-in curve at 6.25 (it is still getting started), the ease-out curve at 43.75 (it is already slowing), and smoothstep at 15.625. Every curve starts at 0 and ends at 1, so every one reaches the same place; they differ in how they get there.

Section: Following a moving target.

A camera or a character that chases a target can close a fraction of the remaining gap on every update:

Code example. The surrounding prose explains this example; the full lesson contains the exact code.

With a gap of 10 and k equals 0.2, the gap after each update is 10, then 10 times 0.8 equals 8, then 6.4, then 5.12, then 4.096. The follower moves fast while it is far away and slows as it arrives, which looks smooth.

One fixed k has a flaw: a game running 120 updates a second closes the gap twice as often as one running 60. The fix is to tie k to the time that passed. With a stiffness s per second, use k equals 1 minus e^( minus s times dt). For s equals 10:

at 60 updates a second, dt equals 1/60, so k equals 1 minus e^( minus 0.1667) equals 1 minus 0.8465 equals 0.1535;

at 120 updates a second, dt equals 1/120, so k equals 1 minus e^( minus 0.0833) equals 1 minus 0.9200 equals 0.0800.

Two 120-per-second updates leave (1 minus 0.0800)² approximately 0.8465 of the gap, the same as one 60-per-second update, so the camera follows the same way on either machine.

Section: Past the two samples: extrapolation.

The lerp formula does not forbid a t above 1. With t equals 1.5 it continues the same line beyond b, and that is extrapolation: a guess about a value past the last one known. A game needs it when its newest information is already old. A client that receives another player’s position 10 times a second, every 0.10 s, still draws 60 pictures a second. Between two updates it can show a past state that lies between two known points (interpolation), or guess the present from the last two (extrapolation).

Take an update of x equals 100 at 0.00 s and one of x equals 106 at 0.10 s, so the speed is (106 minus 100) divided by 0.10 equals 60 units a second. At 0.15 s, t equals (0.15 minus 0.00) divided by 0.10 equals 1.5, and the guess is 100 + (106 minus 100) times 1.5 equals 109. Now suppose the player stopped at 106, and the update for 0.20 s says so. The interpolated view draws the state from 0.10 s earlier, so both neighbouring updates are always known:

ExampleA player who stops at x equals 106

Lesson table

The interpolated view is always behind the truth, by 0.10 s here, but it only ever shows places the player really was. The extrapolated view is never behind, but when the guess is wrong it overshoots and then snaps back: from 111.4 to 106 in one frame. When you read another player’s position from a client’s memory, the value may be one of these display values rather than the server’s own (Chapter 8).

Narration: That is why a smooth enemy on screen can be slightly behind, or briefly ahead of, where the server says it is: the client hides the gaps between updates.

Section: Curves through several points.

A path that turns smoothly, such as a camera flight or the arc of a thrown item, is built from curves called splines. The simplest piece is a quadratic Bézier curve. It starts at P0, ends at P2, and is pulled toward a control point P1 without reaching it:

For P0 equals (0, 0), P1 equals (5, 10), and P2 equals (10, 0), the point halfway along is 0.25 times (0, 0) + 0.5 times (5, 10) + 0.25 times (10, 0) equals (2.5 + 2.5, 5) equals (5, 5). The curve rises to 5 even though the control point is at 10. Longer paths chain many such pieces so that each one starts where the last ended, heading the same way; that is what a cubic spline does.

Blending turning angles needs more care than blending positions, because 350 degrees and 10 degrees are only 20 degrees apart. Lesson 7.1 shows how orientations are blended.

Section: Bounding volumes: distance as a contact test.

The length of a direction is a distance, and a distance answers a contact question. Games give objects cheap stand-in shapes, called bounding volumes, and test those instead of the full model. The simplest is a circle (a sphere in 3D). Two circles touch when the distance between their centres is less than the sum of their radii.

Use the player at (2, 3) and the enemy at (5, 7) from earlier in this lesson. Their distance is 5, and its square is 3 to the power of 2 + 4 to the power of 2 equals 25. With both radii 2, the sum is 4 and 4 to the power of 2 equals 16. Since 25 is more than 16, the circles do not touch. With radii 2 and 3.5 the sum is 5.5 and 5.5 to the power of 2 equals 30.25, which is more than 25, so they do. Comparing squared values gives the same answer without the square root.

A box that stays lined up with the axes is stored as its smallest and largest corner. Two such boxes overlap only if their ranges overlap on every axis. Box A spans x from 0 to 4 and y from 0 to 3, and box B spans x from 3 to 7 and y from 2 to 6. The x ranges share 3 to 4, and the y ranges share 2 to 3, so the boxes overlap. Move B to span x from 5 to 9 and they no longer do: 4 is less than 5, so one axis is enough to rule out contact.

These cheap tests are why a game’s collision tests can differ from its pictures, as Lesson 1.3 noted. Lesson 7.5 tests rays against such shapes, and Lesson 7.2 uses them to skip drawing things that are out of view.

Section: From geometry to an action.

A position names a place. Subtracting two positions gives a direction with a length; normalizing keeps only the direction. A dot product compares alignment, which lets a decision ask whether a target lies inside a view cone without calculating its angle first. Interpolation blends two values by a fraction, easing reshapes that fraction, and a bounding volume turns a distance into a contact test. Keep the coordinate meanings and units consistent before using any of these results.

From Coordinates to Target Selection now finds positions in an offline game and uses copied, validated values to choose an action.