The component every GameObject has: what the Inspector's numbers actually mean, why local and world values differ the moment something is parented, how rotation is really stored, and what an object's own forward direction is.
Subject: Unity Game Engine · 62 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Unity - Lesson 6
The one component everything has - and the local/world split hiding inside it.
Objectives
You got the Transform question right in the diagnostic. This lesson exists because the next one - Vector3 - was 1 out of 3, and every direction calculation starts from a Transform.
position and localPosition for a parented objectlossyScale is read-only and why it is called lossyeulerAngles.x alone does not compiletransform.forward, right and up and know whose axes they areWarm-up
Before the deck tells you anything.
Discussion prompt
You drag a cube onto another cube in the Hierarchy, and it visibly jumps to a new place. What do you think happened, and what number changed?
Hint: The Inspector's Position field means something different after the drag.
Answer:
Nothing about the child's world position changed at all - dragging in the Hierarchy keeps the object exactly where it was on screen.
What changed is the meaning of the numbers: Position now reads relative to the parent, so the Inspector shows different figures for the same place. If the object DID visibly jump, that is the other case - SetParent(parent, false) from code, which keeps the numbers and moves the object.
Section
Part 1
Concept
The Transform answers where, which way and how big. It holds nothing else - no velocity, no mass, no momentum. Those live on the Rigidbody, and lesson 3 covered who is allowed to write which.
| Inspector field | property in code | type |
|---|---|---|
| Position | transform.localPosition | Vector3 |
| Rotation | transform.localEulerAngles | Vector3 of degrees |
| Scale | transform.localScale | Vector3 |
Notice every row says local. The Inspector shows local values - which is invisible until the object has a parent.
Intuition
'Third door on the left' is a local address. It is only meaningful once you know which corridor you are in - but it stays correct if the whole building is picked up and moved.
'52.3 degrees north, 4.9 east' is a world address. It is meaningful anywhere and never depends on context, but it has to be recomputed the moment anything above it moves.
Unity keeps both. The Inspector shows you the local one because that is the one you type; code usually wants the world one because that is where things actually are.
Prediction
Figure (svg): A parent cube rotated 90 degrees with a child offset along the parent's local X, showing the child's world position is along world minus Z
Parent at (2, 1, 0), rotated 90 degrees about Y, scaled 2. Child with localPosition = (1, 0, 0).
Predict first
What is the child's world position?
Correct: (2, 1, -2).
Why: Three things happen in order: the local offset is scaled by the parent's 2, giving (2, 0, 0); then rotated by the parent's 90 degrees about Y, which sends +X to -Z, giving (0, 0, -2); then added to the parent's own position (2, 1, 0). Scale, then rotate, then translate - always in that order.
Concept
Every one of the three questions has a local answer and a world answer.
| local (Inspector) | world (usually what code wants) | note |
|---|---|---|
localPosition | position | both writable |
localRotation | rotation | Quaternions |
localEulerAngles | eulerAngles | degrees, a view of the Quaternion |
localScale | lossyScale | lossyScale is read-only |
With no parent, each pair holds identical values - which is exactly why this distinction stays invisible right up until it matters.
Unity Scripting API - Transform Transform
Matching
Match the pairs
Which property answers each question?
Why: Anything involving another object - distance, direction, aiming - wants world values, because the two objects may sit under different parents. Anything about this object's own setup - an offset from its parent, a size you typed - is local.
Worked example
The lab's probe prints all six for a parented child and an unparented control.
void Start()
{
Debug.Log($"localPosition {transform.localPosition}"); // (1.00, 0.00, 0.00)
Debug.Log($"position {transform.position}"); // (2.00, 1.00, -2.00)
Debug.Log($"localScale {transform.localScale}"); // (0.50, 0.50, 0.50)
Debug.Log($"lossyScale {transform.lossyScale}"); // (1.00, 1.00, 1.00)
}| property | Child (parented) | Free cube (no parent) |
|---|---|---|
localPosition | (1.00, 0.00, 0.00) | (-3.00, 1.00, 0.00) |
position | (2.00, 1.00, -2.00) | (-3.00, 1.00, 0.00) |
localScale | (0.50, 0.50, 0.50) | (0.50, 0.50, 0.50) |
lossyScale | (1.00, 1.00, 1.00) | (0.50, 0.50, 0.50) |
eulerAngles | (0, 90, 0) | (0, 0, 0) |
Read the right column first
Why: With no parent every pair agrees. That column is what a beginner's whole world looks like, which is why the left column arrives as a shock.
Explain it to yourself
Discussion prompt
Nobody rotated the child. Its localEulerAngles are (0, 0, 0). So why does its world eulerAngles read (0, 90, 0)?
Answer:
Because 'not rotated relative to my parent' and 'not rotated relative to the world' are different claims. The child is perfectly aligned with its parent, and the parent is turned 90 degrees - so the child is too.
This is why transform.forward on a child points wherever the parent is facing. The child's own axes are its parent's axes, until it rotates itself.
Sorting
Sort each line of code by which kind of value it should use.
Sort into buckets
Which value does each job need?
Trap
Both objects have a position. Subtract them and you get the distance.
// player is under 'Level/Spawn', enemy is under 'Level/Rooms/Room3'
float distance = Vector3.Distance(
player.localPosition, enemy.localPosition); // meaninglessEach of those is an offset from a different parent, so the subtraction compares two numbers that were never in the same coordinate system.
Use world positions whenever two different objects are involved.
float distance = Vector3.Distance(
player.position, enemy.position); // correct| how many objects involved | use |
|---|---|
| one, relative to its parent | local values |
| two or more | world values, always |
| one, relative to the world | world values |
Error analysis
A door script. The door is a child of a moving lift.
Annotate
Vector3.right and transform.right differ by exactly one word and by the entire question of whose axes you meant.
Comparison
Comparison matrix
| With no parent | With a rotated, scaled parent | |
|---|---|---|
| localPosition vs position | identical | different |
| localScale vs lossyScale | identical | different |
| Can you write it? | both writable | lossyScale is read-only |
Section
Part 2
Concept
Parenting is not decoration in the Hierarchy panel. It changes what a child's numbers mean: from then on, the child's position, rotation and scale are all relative to the parent.
Move the parent and the child comes along, with its local numbers unchanged. That is the whole feature - and it is why a character's hand can hold a sword without any code.
Intuition
A passenger standing in a cabin does not move relative to the ship, however far the ship sails. Their cabin address never changes; their latitude and longitude change constantly.
localPosition is the cabin address. position is the latitude and longitude. Both are true at once, and asking which is 'the real one' is the wrong question.
Prediction
A sphere at world (0, 1, -3) is parented to a cube using child.SetParent(parent, worldPositionStays: false).
Predict first
What happens on screen?
Correct: It jumps.
Why: With worldPositionStays false, Unity keeps the child's LOCAL numbers and lets the world position land wherever those numbers point relative to the new parent. Pass true and you get the opposite: the object stays on screen and its local numbers are rewritten. Dragging in the Hierarchy is the true version.
Worked example
The lab runs the same call twice with only the flag changed.
Vector3 worldBefore = child.position;
Vector3 localBefore = child.localPosition;
child.SetParent(newParent, worldPositionStays);
Debug.Log($"world {worldBefore} -> {child.position}");
Debug.Log($"local {localBefore} -> {child.localPosition}");| flag | world position | localPosition | what it is for |
|---|---|---|---|
true | unchanged | rewritten | picking something up without moving it |
false | jumps | unchanged | snapping to a socket: hand, holster, slot |
Neither is the right answer in general
Why: They are two different intentions. 'Attach without moving' and 'snap into place' are both things you want, on different days.
Invariant
Parent moves from x = 2 to x = 5 with the child attached. One row does not move.
Step through it
Which value is invariant as the parent travels, and why is that the point of parenting?
localPosition is the invariant. That is what 'attached' means, and it is why you never need code to make a passenger ride a lift.
Concept
Figure (svg): Three nested boxes showing localScale multiplying down the hierarchy to give lossyScale
A child's world size is every localScale above it multiplied together. Unity exposes that product as lossyScale, and it is read-only.
Unity Scripting API - Transform.lossyScale lossyScale
Trap
Scale the parent (3, 1, 1) to make a long corridor, then rotate it 45 degrees. The children come along.
Parent scale (3, 1, 1) rotation Y 45
Child scale (1, 1, 1)
Child's lossyScale reports something like (2.2, 1, 2.2)
and the child renders visibly skewed.This is not a bug you can fix with a number. A non-uniform scale combined with a rotation is not expressible as a scale - hence 'lossy'.
Keep parents at uniform scale. Scale the mesh itself, or scale the child, or build the model at the right size.
Parent scale (1, 1, 1) rotation Y 45 <- uniform
Child scale (3, 1, 1) <- stretch the child instead
Child's lossyScale is exactly (3, 1, 1). No skew.| want | do |
|---|---|
| a stretched object | scale the object itself, not a rotated parent |
| a group you can move together | an empty parent at scale 1 |
| a child at a known world size | localScale = target / parent.lossyScale |
Discrimination
Sort each relationship by whether parenting is the right tool.
Sort into buckets
Parent it, or keep it separate and move it in code?
Ranking
Unity turns a child's local values into a world position in a fixed order. Put the steps in that order.
Put in order
First step at the top.
Why: Scale, rotate, translate - the standard order, and the reason (1, 0, 0) under a parent scaled 2 and turned 90 degrees becomes (0, 0, -2) rather than (0, 0, -1). Change the order and you get a different answer, which is why every graphics API fixes it.
Counterexample
Discussion prompt
'Parenting an object never changes how it looks.' Find a case where it does.
Hint: Two of the parent's three fields have consequences for appearance.
Answer:
Parent an object to something scaled and it changes size on screen - its localScale is unchanged, its lossyScale is not.
Parent it to something rotated with SetParent(p, false) and it both moves and turns. And in UI, parenting to a Canvas changes rendering order and layout entirely.
So the honest version: SetParent(p, true) preserves world position and rotation, but never promises anything about scale under a non-uniformly scaled parent.
Fill the middle
The player picks up a sword: it should snap into the hand socket, not stay where it was lying.
Fill in the blanks
void PickUp(Transform sword)
false});
sword.localPosition = Vector3.zero; // sit exactly at the socket
sword.localRotation = Quaternion.identity;
}
Why: false keeps the local numbers, so the sword's world position jumps to the hand - which is exactly what picking something up looks like. Then setting localPosition to zero puts it precisely at the socket's origin, and localRotation to identity aligns it with the hand. Using world position here would fight the hand every frame it moves.
Section
Part 3
Concept
Unity stores rotation as a Quaternion - four numbers that describe an orientation without gimbal lock and that interpolate smoothly. The Inspector shows you Euler angles because four numbers are not readable.
Quaternion q = transform.rotation; // (0.00, 0.71, 0.00, 0.71) for 90 about Y
Vector3 degrees = transform.eulerAngles; // (0, 90, 0) - a readable VIEW of q| property | type | use it when |
|---|---|---|
rotation | Quaternion | assigning from LookRotation, Slerp, identity |
eulerAngles | Vector3 degrees | reading a human-friendly value, setting a whole rotation |
Quaternion.Euler(x, y, z) | Quaternion | building a rotation from degrees |
Quaternion.identity | Quaternion | no rotation at all |
Unity Manual - Rotation and orientation in Unity Rotation and orientation
Intuition
Think of eulerAngles the way you think of a decimal printout of a stored fraction. The printout is useful and readable, and converting back and forth is not always exact.
Set a rotation to 361 degrees and read it back: you get 1. Set (0, 90, 0) two different ways and you may read back (0, 90, 0) or something with tiny floating-point noise in it.
So: assign whole rotations, read eulerAngles for display, and do not build logic on reading them back.
Prediction
transform.eulerAngles = new Vector3(0, 370, 0); then immediately Debug.Log(transform.eulerAngles.y);
Predict first
What prints?
Correct: 10.
Why: The value was converted into a Quaternion, which has no concept of 'more than one turn'. Reading back gives an equivalent angle in the 0-360 range. This is why a script that accumulates rotation by reading eulerAngles, adding, and writing back will eventually behave strangely - keep your own float and assign from it instead.
Worked example
transform.Rotate and transform.Translate both take a space argument, and the default is not the one people expect.
transform.Rotate(Vector3.up, 90f * Time.deltaTime, Space.World);
transform.Translate(Vector3.forward * 2f, Space.Self); // Self is the DEFAULT| call | space | meaning |
|---|---|---|
Translate(forward, Space.Self) | own axes | move the way I am facing |
Translate(forward, Space.World) | world axes | move along world +Z whatever I am facing |
Rotate(up, 90, Space.Self) | own axes | yaw around my own up |
Rotate(up, 90, Space.World) | world axes | yaw around world up - what you usually want |
Space.Self is the default for both
Why: Which is right for Translate on a character and often wrong for Rotate on an object that is already tilted - a tilted object rotating around its own up wanders off in a way that is hard to debug by eye.
Notation
You will see these in the Console. You do not need to do arithmetic on them, but you should not be alarmed by them.
Annotate
Trap
I only want to change the pitch, so I will set the x component.
transform.eulerAngles.x = 45f; // does not compileeulerAngles returns a copy of a struct. Assigning to its field would modify a temporary that is immediately discarded, so C# refuses.
Build a whole Vector3 and assign that, or build a Quaternion.
Vector3 angles = transform.eulerAngles;
angles.x = 45f;
transform.eulerAngles = angles;
// or, more explicit about intent:
transform.rotation = Quaternion.Euler(45f, angles.y, angles.z);| symptom | cause |
|---|---|
| 'cannot modify the return value' | the property returns a struct copy |
same error on transform.position.x = 5 | identical reason - build a Vector3 |
works on myVector.x = 5 | a local variable is not a property; that is fine |
Definition probe
Sort each expression by whether it means the world's axes or this object's.
Sort into buckets
World axes, or the object's own?
Two truths and a lie
Eliminate the wrong options
Which claim about the Transform is false?
Survives elimination: B
Why: The Transform holds where an object IS, not how it is moving. Velocity lives on the Rigidbody, and an object with no Rigidbody has no velocity at all - it just occupies a series of positions. This is the misconception the diagnostic recorded for this topic.
Concept
Three read-only properties give you this object's axes as world-space direction vectors, each of length 1.
transform.forward // the way it faces
transform.right // its own +X
transform.up // its own +Y
transform.position += transform.forward * speed * Time.deltaTime; // walk forwards| expression | for a cube with no rotation | for the same cube turned 90 about Y |
|---|---|---|
transform.forward | (0, 0, 1) | (1, 0, 0) |
transform.right | (1, 0, 0) | (0, 0, -1) |
transform.up | (0, 1, 0) | (0, 1, 0) |
The last row is worth noticing: rotating about Y leaves up alone, which is why yaw is the safe rotation for anything walking on the ground.
Pattern
A child with localEulerAngles = (0, 0, 0), under a parent turned 90 degrees about Y.
Predict first
What is the child's transform.forward in world coordinates?
Correct: (1, 0, 0) - the same way the parent faces.
Why: transform.forward is a WORLD direction, so it accounts for every rotation above the object as well as its own. A child that has not rotated itself faces exactly where its parent faces - which is what makes a gun parented to a hand fire in the direction the character is aiming, with no code at all.
Trade off
A turret with a base that yaws and a barrel that pitches. Weigh two designs.
Comparison matrix
| One object, combined rotation | Two objects: base parent, barrel child | |
|---|---|---|
| Yaw and pitch independently | needs care with Euler order | trivial - one axis each |
| Barrel follows the base | you compute it | free - parenting does it |
| Gimbal trouble | possible | avoided |
| Objects in the scene | 1 | 2 |
This is the standard answer to almost every awkward rotation problem in Unity: add a parent and give each object one axis to worry about.
Section
Build
Concept
A rotated, scaled parent with a child inside it, an identical free cube for comparison, and a sphere that gets adopted mid-play.
The parent's numbers are chosen so the arithmetic is checkable: position (2, 1, 0), rotation Y 90, scale 2, with the child at local (1, 0, 0). Files: unity-labs/Assets/NaruhodoLabs/Lesson06_Transform/.
Step zero
Discussion prompt
Work out the child's world position and lossyScale on paper, from parent (2, 1, 0) / rot Y 90 / scale 2 and child local (1, 0, 0) / localScale 0.5. Show the three steps.
Hint: Scale, then rotate, then translate.
Answer:
Now run the scene and check. Being right on paper before the Console agrees is the difference between knowing this and recognising it.
Worked example
Two cubes, one drag, four numbers.
Parent: Position (2, 1, 0), Rotation (0, 90, 0), Scale (2, 2, 2)Child: drag it onto Parent in the Hierarchy, then set Position (1, 0, 0) and Scale (0.5, 0.5, 0.5)Free cube at (-3, 1, 0), Scale 0.5, also with a probe, and no parent| you typed | Inspector field | which property |
|---|---|---|
| (1, 0, 0) | Position | localPosition |
| (0.5, 0.5, 0.5) | Scale | localScale |
| nothing | - | position = (2, 1, -2) |
| nothing | - | lossyScale = (1, 1, 1) |
Worked example
The sphere is adopted at t = 2s. Run it once with the flag true, once with it false.
child.SetParent(newParent, worldPositionStays);
Debug.Log($"world {worldBefore} -> {child.position}");
Debug.Log($"local {localBefore} -> {child.localPosition}");| flag | world before | world after | local before | local after |
|---|---|---|---|---|
| true | (0, 1, -3) | (0, 1, -3) | (0, 1, -3) | (-1.50, 0, -1.00) |
| false | (0, 1, -3) | jumps | (0, 1, -3) | (0, 1, -3) |
One of the two columns always survives
Why: true preserves the world column; false preserves the local column. Nothing preserves both, because under a new parent they cannot both be what they were.
Hypothesis
During Play you drag the Parent cube around in the Scene view.
Predict first
What does the child's localPosition do?
Correct: Stays exactly (1, 0, 0), no matter how far the parent goes.
Why: The child's local position is its offset from the parent, and that offset is unchanged by the parent moving. Its world position changes constantly. Watching those two numbers side by side while dragging is the single clearest demonstration of the local/world split there is.
Real world
Discussion prompt
A character carries a lamp that should swing slightly as they walk, and a UI health bar that should float above their head and always face the camera. Which is parented, which is not, and what are the local values in each case?
Answer:
The pattern: parent what should follow rigidly, and override in code only the one property that should not.
Explain it
Discussion prompt
A teammate says 'I moved the parent and now my child's Position shows the wrong numbers'. Answer in two sentences.
Answer:
Model answer: 'The Inspector's Position is the offset from the parent, not the world position, so it stays the same when the parent moves - which is exactly what you want, because the child keeps its place inside the group.'
'If you need where it actually is, read transform.position in code, or unparent it for a moment and look.'
Concept
Six experiments from the lab notes.
| change | what happens |
|---|---|
| Move the parent during Play | the child follows; its localPosition never changes |
| Set the parent's Scale to (3, 1, 1) and rotate it | the child skews and lossyScale reports odd numbers |
| Try to type into lossyScale | you cannot - it is read-only |
| Set eulerAngles.y to 361 and read it back | it reports 1 |
Write transform.eulerAngles.x = 45; | it does not compile - build a whole Vector3 |
| Add a Rigidbody to the child and write its position each frame | you are back in lesson 3's trap |
Elimination
A gun is parented to a character's hand. When the character turns, the gun ends up pointing the wrong way.
Eliminate the wrong options
What should you check first?
Survives elimination: A
Why: Vector3.forward is the world's +Z and never changes; transform.forward is the gun's own facing and rotates with the character. A gun that always fires along world +Z looks correct only while the character happens to face that way, which is why this bug reads as 'it works at the start of the level'.
Faded example
A camera that follows the player from behind without being parented.
Fill in the blanks
public Transform target;
public Vector3 offset = new Vector3(0f, 3f, -6f);
void LateUpdate()
position} + offset;
transform.LookAt(target);
}
Why: LateUpdate because the player moves in Update, and a camera that reads the position before the player has moved lags one frame - which players perceive as jitter. And world position because the camera and the player are two separate objects that may sit anywhere in the hierarchy, so their local values are not comparable.
Pattern
Four questions. They pick the right property every time.
Vector3.forward is the world's; transform.forward is the object's.Quaternion.Euler(...) or a whole Vector3 into eulerAngles. Never one component.And when parenting: SetParent(p, true) to attach without moving, SetParent(p, false) to snap into a socket.
Check
Check your understanding
A child has localPosition (1, 0, 0). Its parent is at (2, 1, 0), rotated 90 degrees about Y, with scale 2. What does the child's transform.position report?
Answer: C
Why: Scale first: (1, 0, 0) times 2 is (2, 0, 0). Then rotate: 90 degrees about Y sends +X to -Z, giving (0, 0, -2). Then translate by the parent's position: (2, 1, -2). Scale, rotate, translate, always in that order.
Check
Check your understanding
Which of these does the Transform component store?
Answer: B
Why: Where it is, which way it is facing, how big it is. Velocity and mass belong to the Rigidbody, and an object with no Rigidbody has neither - it simply occupies a position that something else decides.
Socratic
Discussion prompt
Why does Unity store rotation as a Quaternion rather than as the three angles the Inspector shows?
Answer:
Two reasons. Gimbal lock: with three sequential angles, certain orientations lose a degree of freedom, and rotation stops behaving as it should. Quaternions have no such orientation.
Interpolation: turning smoothly from one orientation to another is a clean operation on Quaternions and an ambiguous mess on Euler angles - there are several ways to get there and they look different.
The cost is readability, which is exactly why the Inspector converts for you and why eulerAngles exists as a view.
Check
Check your understanding
A cube is rotated 90 degrees about Y. What is transform.forward, and what is Vector3.forward?
Answer: B
Why: transform.forward is the object's own facing expressed in world coordinates, so it rotates with the object: a 90-degree yaw sends it to (1, 0, 0). Vector3.forward is the constant (0, 0, 1) and never changes for anything.
Estimation
A child sits under three parents, scaled 2, 0.5 and 3 from the top down. Its own localScale is 1.
Predict first
What is its lossyScale?
Correct: 3.
Why: 2 x 0.5 x 3 x 1 = 3. Scales multiply down the hierarchy, they do not add - which is why one carelessly scaled parent high up can make everything beneath it the wrong size, and why the usual advice is to keep group parents at scale 1.
Missing information
Discussion prompt
Someone asks you to 'make this object twice as big'. What do you need to establish first?
Answer:
The third bullet is the one that bites in practice: an object that looks twice as big but collides at its old size is a bug that takes a long time to see.
Concept
Lesson 7 is entirely built on this: every direction calculation subtracts two world positions, and every 'face this way' assigns a rotation.
| you now have | what it enables |
|---|---|
| local vs world | distances and directions that are actually correct |
| the parenting model | carried objects, sockets, UI hierarchies |
| Quaternion vs eulerAngles | rotation code that does not drift |
transform.forward | 'move the way I am facing' - the basis of lesson 7 |
Analogy
Match the pairs
Match each Unity idea to a familiar cousin.
Why: The path analogy carries further than it looks: a relative path breaks when you compare two of them from different folders, exactly like comparing two localPositions under different parents, and moving a folder keeps every relative path inside it valid, exactly like moving a parent.
Connect it up
Draw it
Draw world axes in one corner. Draw a parent box somewhere else, rotated, with its own axes marked. Put a child on the parent's local +X and label both its localPosition and its world position.
Exit ticket
Predict first
Which is still shakiest?
Correct: Whichever you picked - the lab prints all four in one Play session; go and read the one you named.
Why: This topic tested fine, so the goal is depth rather than repair - and the fourth option in particular is the one lesson 7 needs solid, because every direction calculation there starts by deciding whose axes you meant.
Recap
You can read any Transform in the Inspector and say what it means in the world.
lossyScale is the product of every scale above you, and it is read-onlyeulerAngles is a readable view, and you assign it wholetransform.forward is this object's facing; Vector3.forward is the world's constant +ZLab: unity-labs/Assets/NaruhodoLabs/Lesson06_Transform/SETUP.md. Do the paper prediction before you press Play, then go to lesson 7.
Want this taught 1-on-1? Alexander tutors Unity Game Engine — $55/session, free consultation.