Section 1: Vectors and directions
Vector operations you use every day
Let us make an enemy move toward the player, and use that to learn the core vector operations.
Subtraction gives the offset
var to_player: Vector2 = player.position - enemy.position
The result points from the enemy to the player. Remember the order: target minus origin. Reversing it gives a vector pointing the wrong way, a very common bug.
Length is distance
var distance: float = to_player.length()
The length uses Pythagoras: the square root of x * x + y * y. When you only need to compare distances, for example "is the player within 200 pixels", compare squared lengths and skip the square root:
if to_player.length_squared() < 200.0 * 200.0:
start_chasing()
Normalising gives a pure direction
A normalised vector, also called a unit vector, has a length of 1. It keeps the direction but loses the distance:
var direction: Vector2 = to_player.normalized()
Now multiply by a speed to get a velocity:
enemy.velocity = direction * 120.0
Without normalising, the enemy would move faster when the player is far away and slow down as it gets close, because the offset vector is longer at a distance.
Be careful when the vector has zero length, for example when enemy and player overlap. Normalising a zero vector cannot produce a direction. Godot returns a zero vector; other engines may return NaN values that spread through your game. Check the length first when this can happen.
Scaling and adding
Multiplying a vector by a number scales its length. Adding vectors combines movements, such as walking plus a conveyor belt:
var final_velocity := input_velocity + belt_velocity
Diagonal movement
A classic bug: reading input as Vector2(right - left, down - up) gives (1, 1) when two keys are held, which has a length of about 1.41. Players move 41 percent faster diagonally. The fix is to normalise or clamp the input vector:
var input := Vector2(Input.get_axis("left", "right"), Input.get_axis("up", "down"))
input = input.limit_length(1.0)
limit_length keeps analog stick values below 1 intact, which normalising would not.
In Unity
The same operations in C#: target - origin, .magnitude, .sqrMagnitude, .normalized and Vector3.ClampMagnitude. The ideas are identical; only the names change.