Destination minus start, normalized: how to compute a direction, why the subtraction order decides between chasing and fleeing, what normalizing does and does not change, and how the dot product answers 'is it in front of me'.
Subject: Unity Game Engine · 60 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Unity - Lesson 7
Destination minus start, normalized. Four lines that move anything towards anything.
Objectives
The diagnostic scored 1 out of 3 here, and the one correct answer was marked a guess. All three recorded misconceptions are fixable in one sitting, and every number in this deck is checkable on paper.
.normalized changes and what it leaves aloneVector3.Dot to answer 'is it in front of me' without computing an angleWarm-up
No looking. These are the exact three the diagnostic tested.
Discussion prompt
1) Which axis is up in Unity? 2) To get a vector from A to B, do you write A - B or B - A? 3) Does normalizing a vector change its direction?
Hint: The second one has a rule you already know from arithmetic: displacement is destination minus start.
Answer:
If any of those was a guess, that is exactly what the diagnostic saw - and each one is about to become something you can derive rather than recall.
Section
Part 1
Concept
Figure (svg): Unity's coordinate axes drawn with Y pointing up, Z forward into the scene and X to the right
Vector3.up is (0, 1, 0). Vector3.forward is (0, 0, 1). Vector3.right is (1, 0, 0). Height is Y, and Z is depth.
This matters because most other 3D software is Z-up, so a model imported from Blender arrives lying on its face until the import settings are corrected - and 'add height' written as Z is a bug that moves things sideways.
Unity Manual - Positioning GameObjects (coordinate system) coordinate system
Concept
A Vector3 is three floats. What it means depends entirely on how you are using it.
| used as | means | example |
|---|---|---|
| a position | a place in the world | transform.position |
| a direction | which way, and how far | target.position - transform.position |
| a direction only | which way, length 1 | offset.normalized |
| a scale | a multiplier per axis | transform.localScale |
Nothing in the type tells you which. This is why naming matters more here than almost anywhere else: toTarget and targetPos are both Vector3 and mixing them up compiles perfectly.
Unity Scripting API - Vector3 Vector3
Prediction
You want an object to hover one unit above its current position.
Predict first
Which line does that in Unity?
Correct: new Vector3(0, 1, 0) - which is also Vector3.up.
Why: Y is height in Unity. The first and third options both move the object one unit deeper into the scene along Z, which looks like nothing at all from the default camera - and that silent non-effect is exactly why the z-is-up habit survives so long.
Matching
Match the pairs
What is each one?
Why: Vector3.one is the odd one out: it is not a direction at all, it is the multiplier you use for uniform scale - transform.localScale = Vector3.one * 2f. Using it as a direction gives you a diagonal of length 1.73, which is rarely what anyone meant.
Worked example
Five operations, and what each one gives you.
Vector3 me = transform.position; // (0, 0.5, 0)
Vector3 them = target.position; // (3, 0.5, 4)
Vector3 offset = them - me; // (3, 0, 4) the arrow to them
float distance = offset.magnitude; // 5.00 how far
Vector3 direction= offset.normalized; // (0.6, 0, 0.8) which way, length 1
float cheap = offset.sqrMagnitude; // 25.00 no square root
float sameThing= Vector3.Distance(me, them);// 5.00 readable alias| expression | value here | type | use it for |
|---|---|---|---|
them - me | (3, 0, 4) | Vector3 | the arrow between two points |
.magnitude | 5.00 | float | distance |
.normalized | (0.6, 0, 0.8) | Vector3 | pure direction |
.sqrMagnitude | 25.00 | float | comparing distances cheaply |
Vector3.Distance(a, b) | 5.00 | float | the same thing, more readable |
Check the arithmetic yourself
Why: 3 squared plus 4 squared is 25, and the square root is 5. Then (3, 0, 4) divided by 5 is (0.6, 0, 0.8). Every number in this deck is checkable like that.
Explain it to yourself
Discussion prompt
sqrMagnitude skips the square root. Explain when that is a legitimate optimisation and when it would be a bug.
Answer:
Legitimate when you only compare: if (offset.sqrMagnitude < 25f) is exactly if (distance < 5f), because squaring preserves order for non-negative numbers. Compare against the threshold squared.
A bug when you use the number itself - as a speed, a lerp fraction, a displayed distance. A sqrMagnitude of 25 is not 25 metres, and a UI that shows it will be wrong in a way that looks plausible.
Sorting
Sort each expression by what it means.
Sort into buckets
What kind of Vector3 is each?
Section
Part 2
Concept
Figure (svg): Two arrows between the same pair of points showing that target minus me points one way and me minus target points the other
Subtraction gives the displacement from the second operand to the first. So target - me points at the target, and me - target points away from it.
The mnemonic worth keeping: destination minus start, the same rule as 'final minus initial' in any change calculation.
Prediction
A seeker at the origin, a target at (3, 0, 4). The script computes Vector3 dir = (transform.position - target.position).normalized; and moves along dir.
Predict first
What does the seeker do?
Correct: Moves directly away - it flees.
Why: The operands are swapped, so the arrow points from the target to the seeker, and moving along it increases the distance. Nothing errors, nothing is null, and the object moves smoothly in exactly the wrong direction - which is why this bug is usually found by watching rather than by reading.
Worked example
This is the whole pattern, and it does not get more complicated in a real game.
void Update()
{
Vector3 offset = target.position - transform.position; // TO the target
float distance = offset.magnitude;
if (distance < 0.6f) return; // arrived
Vector3 direction = offset.normalized;
transform.position += direction * (speed * Time.deltaTime);
}Line 8 is where speed becomes distance
Why: direction has length 1, so multiplying by speed x deltaTime moves exactly that many metres this frame - no more, no less, on any machine.
| time | distance to target | direction | moved this frame at speed 2, 60 fps |
|---|---|---|---|
| 0.0s | 5.00 | (0.60, 0.00, 0.80) | 0.033 |
| 1.0s | 3.00 | (0.60, 0.00, 0.80) | 0.033 |
| 2.0s | 1.00 | (0.60, 0.00, 0.80) | 0.033 |
| 2.2s | 0.57 | - | stops: arrived |
The direction column never changes, because the target is not moving and the seeker is travelling in a straight line at it. That constancy is what makes normalizing worth doing.
Error analysis
A chase script. Two lines are wrong, in two different ways.
Annotate
Three fixes, one line each: swap the operands, add .normalized, multiply by Time.deltaTime. The result is the four-line pattern from the previous slide.
Trap
Both objects have a position, so subtract them and you get the direction between them.
Vector3 toTarget = transform.position - target.position;
transform.position += toTarget.normalized * speed * Time.deltaTime;| what you get | why |
|---|---|
| the enemy runs away | the arrow points target -> me |
| no error | the maths is perfectly valid |
| it looks deliberate | fleeing is a real behaviour, so nothing looks broken |
Name the variable after what it is, and the order writes itself.
Vector3 toTarget = target.position - transform.position; // TO the target
transform.position += toTarget.normalized * speed * Time.deltaTime;| variable name | the expression it forces |
|---|---|
toTarget | target.position - transform.position |
awayFromTarget | transform.position - target.position |
dir | nothing - which is why dir is a bad name here |
Discrimination
Sort each behaviour by which subtraction it needs.
Sort into buckets
target - me, or me - target?
Fill the middle
A rabbit runs from the player, at a constant speed.
Fill in the blanks
void Update()
-} player.position;
transform.position += away.normalized * (speed * Time.deltaTime);
}
Why: Fleeing is the same code as chasing with the operands swapped - which is why naming the variable away rather than dir is what stops the bug. And without normalized the rabbit would accelerate the further it got, since the vector's length is the distance.
Section
Part 3
Concept
Figure (svg): One long arrow and one short arrow along exactly the same line, showing normalizing changes length only
v.normalized divides each of the three components by the vector's magnitude. The result has length exactly 1 and points exactly where the original pointed.
(3, 0, 4) has length 5, so normalized is (3/5, 0/5, 4/5) = (0.6, 0, 0.8). Check: 0.6 squared plus 0.8 squared is 0.36 + 0.64 = 1.
Unity Scripting API - Vector3.normalized normalized
Prediction
Vector3.Angle(offset, offset.normalized) measures the angle between a vector and its own normalized version.
Predict first
What does it return?
Correct: 0 degrees, always.
Why: Dividing all three components by the same positive number scales the arrow without turning it - the ratio between x, y and z is untouched, and that ratio IS the direction. This is the third misconception from the diagnostic, and the lab prints this exact check as a PASS line.
Intuition
Normalizing is redrawing an arrow at a standard length. The bearing is identical; only the number of centimetres on the page changed.
Which is why the pattern is always direction * speed: take the pure bearing, then decide separately how fast to travel along it. Skipping the normalize means the distance to the target leaks into the speed.
Worked example
The same seeker, with and without .normalized, both at speed 2.
// A: normalized - constant speed
transform.position += offset.normalized * (speed * Time.deltaTime);
// B: not normalized - speed scales with distance
transform.position += offset * (speed * Time.deltaTime);| distance to target | A: metres per second | B: metres per second |
|---|---|---|
| 10.0 | 2.0 | 20.0 |
| 5.0 | 2.0 | 10.0 |
| 1.0 | 2.0 | 2.0 |
| 0.1 | 2.0 | 0.2 |
B is not broken - it is an ease-out, and it is sometimes exactly what you want for a camera or a UI element. The bug is using it without meaning to, and then wondering why the enemy crawls the last metre.
Edge cases
Discussion prompt
The seeker is exactly on top of its target, so offset is (0, 0, 0). What does .normalized return, and what should your code do about it?
Hint: Dividing by zero in floating point does not throw.
Answer:
Unity returns (0, 0, 0) rather than a NaN - it checks for a near-zero length and gives you zero back. So nothing crashes, and the object simply does not move.
But Quaternion.LookRotation(Vector3.zero) does complain, and any code that assumes a length of 1 gets a length of 0. The habit: guard with if (offset.sqrMagnitude > 0.0001f) before using a direction for rotation.
Trap
I want to move a fraction of the way there, so I will normalize and scale by the distance.
Vector3 offset = target.position - transform.position;
Vector3 step = offset.normalized * offset.magnitude * 0.1f;
transform.position += step;normalized * magnitude is just the original vector again, so this is offset * 0.1f written the long way - and the speed still scales with distance.
Decide which you want: constant speed, or a constant fraction. They are different behaviours with different code.
// constant speed - arrives at a predictable time
transform.position = Vector3.MoveTowards(
transform.position, target.position, speed * Time.deltaTime);
// constant fraction - eases in, never quite arrives
transform.position = Vector3.Lerp(
transform.position, target.position, 0.1f);| helper | behaviour | reaches the target? |
|---|---|---|
MoveTowards | constant speed, clamped so it cannot overshoot | yes, exactly |
Lerp towards a moving value | eases out, slows as it closes | asymptotically - never exactly |
Slerp | same, but along an arc - for directions and rotations | as above |
Comparison
Comparison matrix
| Returns | Length | Use for | |
|---|---|---|---|
| b - a | Vector3 | the distance | the arrow from a to b |
| (b - a).normalized | Vector3 | 1 | pure direction |
| (b - a).magnitude | float | - | how far apart |
Two truths and a lie
Eliminate the wrong options
Which claim about normalizing is false?
Survives elimination: B
Why: All three components are divided by the same positive number, so their ratios - which are the direction - are untouched. Only the length changes. That is the misconception the diagnostic recorded, and the lab asserts it with Vector3.Angle every run.
Faded example
Fill in the blanks
void Update()
magnitude} < arriveDistance) return;
transform.position += toTarget.normalized * (speed * Time.deltaTime);
}
Why: magnitude compared against a threshold is how you ask 'am I there yet?'. Time.deltaTime turns speed from per-frame into per-second, which is the whole reason the object moves at the same real speed on a 30 fps laptop and a 144 fps desktop.
Section
Part 4
Concept
Vector3.Dot(a, b) multiplies matching components and adds them up. When both vectors are normalized, the result is the cosine of the angle between them.
Vector3 toTarget = (target.position - transform.position).normalized;
float facing = Vector3.Dot(transform.forward, toTarget);
// facing > 0 ahead
// facing == 0 exactly to the side
// facing < 0 behind
// facing == 1 dead ahead| dot | angle | meaning |
|---|---|---|
| 1.00 | 0 degrees | dead ahead |
| 0.87 | 30 degrees | ahead and a bit off |
| 0.00 | 90 degrees | exactly to the side |
| -0.50 | 120 degrees | behind |
| -1.00 | 180 degrees | directly behind |
Unity Scripting API - Vector3.Dot Vector3.Dot
Intuition
Picture a torch shining straight down onto the second arrow. The dot product is the length of the first arrow's shadow on it.
Point the same way and the shadow is the full length (dot 1). Point at right angles and the shadow has no length at all (dot 0). Point the other way and the shadow falls behind the start (negative).
That is why the sign answers 'in front or behind' before any angle is computed - and why sign is usually all you need.
Worked example
A 60-degree cone means 30 degrees either side of forward, so the threshold is cos(30) = 0.866.
float threshold = Mathf.Cos(fovDegrees * 0.5f * Mathf.Deg2Rad); // 0.866 for 60
Vector3 toTarget = (target.position - transform.position).normalized;
bool visible = Vector3.Dot(transform.forward, toTarget) > threshold;| angle to target | dot | inside a 60-degree cone? |
|---|---|---|
| 9 degrees | 0.988 | yes |
| 26 degrees | 0.899 | yes |
| 35 degrees | 0.819 | no |
| 89 degrees | 0.017 | no |
| 137 degrees | -0.731 | no |
The threshold is computed once
Why: cos(30) does not change while the game runs, so it belongs in Start, not in Update - the same 'searching costs, doing does not' habit from lesson 5.
Prediction
A guard faces world +Z. A player stands directly behind them.
Predict first
What does Vector3.Dot(guard.forward, toPlayer) return, roughly?
Correct: About -1.
Why: Directly behind means 180 degrees, and cos(180) is -1. Distance does not appear because both vectors are normalized - which is exactly why the test is about direction only, and why forgetting to normalize turns a clean -1 into some large negative number that no longer means an angle.
Concept
Vector3.Cross(a, b) gives a vector perpendicular to both. You need it less often, but when you do, nothing else will substitute.
| want | use |
|---|---|
| is it in front of me? | Dot - sign of the result |
| is it to my left or my right? | Cross, then check the sign of the Y component |
| the angle between two directions | Vector3.Angle(a, b) - degrees, already computed |
| a normal to a surface from two edges | Cross |
The left/right test is the useful one: Vector3.Cross(transform.forward, toTarget).y is positive when the target is to your right and negative when it is to your left.
Discrimination
Sort into buckets
Which tool answers each question?
Notation
Annotate
Pattern
A watcher spins steadily. The dot readings so far: 0.99, 0.71, 0.02, -0.68.
Predict first
What comes next, roughly?
Correct: About -0.99 - the target is passing directly behind.
Why: The readings are tracking the cosine as the angle grows: 0 degrees, 45, 89, 133, and next around 180. The values must stay between -1 and 1 for normalized vectors, which is what rules out the last option - and a dot outside that range is a reliable sign that something was not normalized.
Section
Build
Concept
A seeker that computes its own direction and walks it, a twin with the subtraction reversed, and a watcher that prints the dot product as a target swings through its field of view.
Positions are a 3-4-5 triangle on purpose: seeker at (0, 0.5, 0), target at (3, 0.5, 4). Files: unity-labs/Assets/NaruhodoLabs/Lesson07_Vectors/.
Step zero
Discussion prompt
Seeker at (0, 0.5, 0), target at (3, 0.5, 4). Work out the offset, the distance and the direction on paper, showing each step.
Answer:
The y components cancelled because both objects are at the same height - which is why the offset has a clean zero in the middle.
Worked example
A capsule, a sphere and one component.
Target at (3, 0.5, 4)Seeker at (0, 0.5, 0), scale (0.6, 0.5, 0.6)Target into its Target slot; speed 2Fleer at (-2, 0.5, 0) and tick Reverse Subtraction| Console line | expected value |
|---|---|
| offset | (3.00, 0.00, 4.00) |
| magnitude (distance) | 5.00 |
| normalized (direction) | (0.60, 0.00, 0.80) length 1.00 |
| angle between offset and normalized | 0.000 degrees |
That last line is the misconception, checked
Why: The lab asserts it every run, so 'normalizing does not turn the vector' stops being something you were told and becomes something you watched.
Worked example
Select the seeker before pressing Play and look at the Scene view, not the Game view.
void OnDrawGizmos()
{
Gizmos.color = Color.cyan;
Gizmos.DrawLine(transform.position, target.position); // the offset
Vector3 direction = (target.position - transform.position).normalized;
Gizmos.color = Color.yellow;
Gizmos.DrawRay(transform.position, direction); // exactly 1 unit
Gizmos.color = Color.green;
Gizmos.DrawRay(transform.position, transform.forward * 1.5f);
}| colour | what it draws | what to notice |
|---|---|---|
| cyan | the full offset | its length shrinks as the seeker closes in |
| yellow | the normalized direction | always the same length - that is what length 1 looks like |
| green | the seeker's own forward | swings round as LookRotation turns it |
OnDrawGizmos runs in the editor, not in a build. It is the cheapest debugging tool in Unity and almost nobody uses it enough.
Hypothesis
The Fleer has the same speed and the same target, with Reverse Subtraction ticked.
Predict first
What will its distance-to-target reading do over the first three seconds?
Correct: Grow steadily, and never stop.
Why: The arrival check only runs for the non-reversed case, and the fleer is moving away, so the distance rises forever. Watching a smooth, confident, completely wrong behaviour is the point of this experiment - the bug produces no error and no hesitation.
Real world
Discussion prompt
An enemy should chase the player, but only when the player is in front of it and within 15 metres. Write the three checks in order, and say which vector tool each one uses.
Answer:
(player.position - transform.position).sqrMagnitude < 225f - squared, so no square root every frame. 15 squared is 225.Vector3.Dot(transform.forward, toPlayer) > 0.5f for a 120-degree cone.transform.position += toPlayer * (speed * Time.deltaTime) with toPlayer already normalized.Order matters for cost as well as logic: the cheapest test goes first, so most frames stop at line one and never compute a direction at all.
Explain it
Discussion prompt
A teammate asks why everyone writes .normalized before multiplying by speed. Answer in two sentences, without the word 'magnitude'.
Answer:
Model answer: 'The arrow between two objects is as long as the gap between them, so if you multiply it by speed the object moves faster when it is further away.'
'Normalizing shrinks the arrow to a standard length of one without turning it, so multiplying by speed gives you exactly that speed, whatever the distance.'
Concept
Seven experiments, each under a minute.
| change | what happens |
|---|---|
| Tick Reverse Subtraction | the seeker flees - smoothly, with no error |
Delete .normalized | it starts fast and slows as it arrives |
Delete Time.deltaTime | the speed depends on the frame rate |
| Move the target to (3, 5, 4) | the seeker flies - zero the Y before normalizing for ground movement |
Ask for Vector3.up expecting (0, 0, 1) | it is (0, 1, 0). Unity is Y-up |
Compare Distance < 5 with sqrMagnitude < 25 | same answer, one square root cheaper |
| Normalize a zero vector | you get (0, 0, 0) and no error - guard before using it for rotation |
Elimination
An enemy chases the player correctly but sinks into the floor as it goes.
Eliminate the wrong options
What is the most likely cause?
Survives elimination: A
Why: The direction is fully 3D, so any height difference between the two pivots becomes a downward component that the enemy dutifully follows. The standard fix is to flatten before normalizing: take the offset, set its y to 0, then normalize - which is worth knowing because almost every ground-based chase needs it.
Pattern
Four lines, in this order, every time.
Vector3 toTarget = target.position - transform.position; - destination minus start, and name it so the order is obvious.toTarget.magnitude, or sqrMagnitude against a squared threshold when it runs every frame.toTarget.normalized - length 1, same way. Flatten the Y first if the movement is on the ground.+= direction * speed * Time.deltaTime - or Vector3.MoveTowards, which does the clamping for you.And when you need to know whether to move: Vector3.Dot(transform.forward, direction) for in-front, Vector3.Cross(...).y for left-or-right.
Check
Check your understanding
A seeker at (0, 0, 0) wants the direction to a target at (3, 0, 4). Which expression gives a unit-length vector pointing at the target?
Answer: B
Why: Destination minus start gives (3, 0, 4), and normalizing gives (0.6, 0, 0.8) - length 1, pointing at the target. Both halves matter: the order decides the direction, the normalize decides the length.
Check
Check your understanding
What does .normalized change about a vector?
Answer: B
Why: Every component is divided by the same number, so their ratios - which are the direction - stay exactly as they were, and the length becomes 1. Vector3.Angle between a vector and its normalized form is always 0.
Socratic
Discussion prompt
Why does the dot product of two normalized vectors happen to equal the cosine of the angle between them?
Answer:
Because the dot product measures how much of one arrow lies along the other - its shadow - and for arrows of length 1 that shadow is exactly the cosine by definition of cosine as adjacent over hypotenuse, with the hypotenuse being 1.
That is also why forgetting to normalize breaks the interpretation but not the sign: the shadow gets scaled by the lengths, so 'in front or behind' survives while 'what angle' does not.
Check
Check your understanding
You want to move an object straight up. Which is correct in Unity?
Answer: B
Why: Vector3.up is (0, 1, 0), and Y is the vertical axis in Unity. Height is Y, depth is Z, sideways is X.
Estimation
An object moves at 4 m/s towards a target 10 metres away, in a straight line.
Predict first
Roughly how long until it arrives?
Correct: 2.5 seconds.
Why: 10 divided by 4. It is only that simple because the direction was normalized - the speed is genuinely constant. Without normalizing, the object would start at 40 m/s and slow continuously, and no arrival time could be worked out in your head at all.
Missing information
Discussion prompt
'Make the enemy face the player.' What do you need to establish before writing it?
Answer:
transform.rotation = Quaternion.LookRotation(dir); a turn rate needs Quaternion.RotateTowards or Slerp.The third one catches people for hours, and it is not a maths problem at all - it is an asset problem, which is worth recognising quickly.
Concept
Direction vectors are the language of gameplay code. Lesson 8's callbacks tell you that something touched you; vectors tell you where it came from and where to send it.
| you now have | what it enables |
|---|---|
| destination minus start | chasing, aiming, fleeing, knockback |
.normalized | constant speed, independent of distance |
magnitude / sqrMagnitude | range checks that are correct and cheap |
Dot and Cross | field of view, facing, left-or-right |
Analogy
Match the pairs
Match each idea to its everyday cousin.
Why: The bearing analogy is the one to keep. A bearing tells you which way to walk and says nothing about how far - which is exactly why you multiply it by a speed, and why the vector you multiply must have had its distance stripped out first.
Connect it up
Draw it
Draw two dots labelled me and target. Draw the offset arrow with its length written on it, then the normalized arrow starting from the same point with 'length 1' on it, then write the four lines of the move-towards pattern beside them.
Exit ticket
Predict first
Which is still shakiest?
Correct: Whichever you picked - open the lab and read that PASS line for yourself.
Why: This was 1 out of 3 in the diagnostic with the correct answer marked a guess, so expect one of these to still feel unsteady. The lab asserts the first three by name every run, and the watcher prints the fourth once a second.
Recap
You can compute a direction, check it on paper, and move something along it at a speed you chose.
Vector3.up is (0, 1, 0); Z is depth, not heighttarget - me points towards, me - target points away.normalized sets the length to 1 and never changes the directionspeed * Time.deltaTime to move at that speed on any machineVector3.Dot(forward, toTarget) answers 'in front of me' by its sign aloneLab: unity-labs/Assets/NaruhodoLabs/Lesson07_Vectors/SETUP.md. Check the 3-4-5 numbers by hand, then go to lesson 8.
Want this taught 1-on-1? Alexander tutors Unity Game Engine — $55/session, free consultation.