Transforms, Input & Movement
Your collect-the-pickups game has a scene, a player sitting in it, and (from Phase 4) a script the engine calls every frame. But the player just sits there. Time to make it move when you press a key — the moment a project stops being a tutorial and starts feeling like a game.
Here's the whole idea before any code:
Moving an object = changing its Transform a little bit every frame, scaled by
Time.deltaTime, in a direction the player chose with input.
That's it. There's no "move" command that animates the object for you. You nudge its position by hand, 60-ish times a second, and the eye reads those tiny jumps as smooth motion — the same illusion as a flip-book. Once you internalize that movement is "read input → pick a direction → adjust the Transform," every movement system in Unity (player, enemies, projectiles, the camera) is a variation on the same three steps.
The Transform: where an object lives
Every GameObject has exactly one component it can never lose: the Transform. It holds the object's place in the world — position, rotation, and scale. And because your scripts inherit from MonoBehaviour, every script gets a free shortcut to its own GameObject's Transform through the word transform (lowercase t).
public class Probe : MonoBehaviour
{
void Start()
{
Debug.Log(transform.position); // where am I? -> e.g. (0.0, 1.0, 0.0)
Debug.Log(transform.rotation); // which way am I facing? (a Quaternion)
Debug.Log(transform.localScale); // how big am I? -> (1.0, 1.0, 1.0)
}
}
What just happened: transform is a property the engine wires up for you — it always points at the Transform attached to the same GameObject as this script. transform.position is the object's location in the world. We logged it once in Start so it prints when the object wakes up. (rotation uses a Quaternion, a four-number representation of orientation; you rarely set it by hand this early, so we'll leave it alone.)
The three pieces you'll actually touch:
transform.position— aVector3, the object's spot in the world.transform.rotation— its orientation.transform.localScale— its size, relative to its original.
Vector3: a position is three numbers
transform.position isn't one number — it's a Vector3, a little bundle of three: x, y, and z. In Unity's default 3D world, x is right/left, y is up/down, and z is forward/back. You build one with new:
Vector3 spot = new Vector3(2f, 0f, 5f); // x=2, y=0, z=5
transform.position = spot; // teleport this object there
transform.position = new Vector3(0f, 1f, 0f); // or inline, no variable
What just happened: new Vector3(2f, 0f, 5f) creates a position 2 units right and 5 units forward, at floor level (y=0). Assigning it to transform.position snaps the object to that exact place instantly. The f after each number marks it as a float (Unity's positions are floats, not whole numbers) — leave it off and C# will complain.
Unity hands you named shortcuts for the common directions so you don't memorize which axis is which:
Vector3.up; // (0, 1, 0) -> straight up
Vector3.right; // (1, 0, 0) -> to the right
Vector3.forward; // (0, 0, 1) -> into the screen
Vector3.zero; // (0, 0, 0) -> the origin
What just happened: these are just pre-made Vector3 values with friendly names. Vector3.up is identical to new Vector3(0f, 1f, 0f) — it reads better and is harder to get wrong.
💡 Making a 2D game instead? Unity has a parallel type,
Vector2(justxandy), and 2D games live on the x/y plane with the camera looking down the z-axis. Everything below works the same — swapVector3forVector2and ignore z. Our collect-the-pickups game is top-down, so we'll useVector3and move on the x/z floor.
Reading input
Movement needs a direction, and the direction comes from the player. Unity has two input systems, and it's worth knowing both exist before you pick one.
1. The legacy Input Manager. The old, built-in, always-available way. You ask Input what's happening right now:
float h = Input.GetAxis("Horizontal"); // -1 (left) .. 0 .. 1 (right)
float v = Input.GetAxis("Vertical"); // -1 (down) .. 0 .. 1 (up)
bool jumping = Input.GetKeyDown(KeyCode.Space); // true the frame Space is pressed
bool held = Input.GetKey(KeyCode.Space); // true every frame Space is held
What just happened: Input.GetAxis("Horizontal") reads the arrow keys and A/D (and a gamepad stick) and returns a number from -1 to 1 — and it smooths the value, ramping up and easing back to zero, so motion built on it feels less robotic. "Vertical" does the same for up/down via the arrows and W/S. GetKeyDown fires true for exactly one frame, the instant a key goes down (great for "jump" or "shoot"); GetKey stays true the whole time it's held (great for "keep moving").
📝 The newer Input System package is the current, recommended choice. It's action-based (you bind an action like "Move" to keys, a stick, and a touch control at once), handles multiple devices and rebinding cleanly, and is where Unity is investing. It's a bit more setup, so for learning the movement idea we'll use the legacy
Input.GetAxis— the concept is identical, and you can migrate later. Just know that when a real project asks you to add the Input System package, that's not a detour: it's the modern path.
Putting it together: PlayerMovement
Now the three steps — read input, build a direction, adjust the Transform — in one script. Attach this to your player GameObject (Phase 3 covered the Add Component flow).
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float speed = 5f;
void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
Vector3 move = new Vector3(h, 0f, v);
transform.position += move * speed * Time.deltaTime;
}
}
What just happened: every frame, Update reads horizontal and vertical input into h and v. We pack them into a Vector3 — x from horizontal, z from vertical (top-down, so vertical input drives forward/back), and y left at 0 so the player stays on the floor. Then we add that direction to the current position. The speed * Time.deltaTime part is the Phase 4 lesson: Time.deltaTime is how long the last frame took, so multiplying by it makes the player travel the same real-world distance per second whether the game runs at 30 or 144 fps. [SerializeField] private float speed = 5f; exposes speed as a tweakable slider in the Inspector while keeping the field private to other code — so you can balance the feel without recompiling. When no key is pressed, h and v are 0, move is (0,0,0), and the player holds still.
Press Play, tap the arrow keys, and the player slides around the floor. That's a real game responding to you.
One catch: diagonals are too fast
Hold Right and Up together. The player moves faster diagonally than straight — because new Vector3(1f, 0f, 1f) has a length of about 1.41, not 1. The two inputs stack.
Vector3 move = new Vector3(h, 0f, v);
if (move.magnitude > 1f)
move = move.normalized; // clamp diagonal length back to 1
transform.position += move * speed * Time.deltaTime;
What just happened: move.normalized returns the same direction but with length exactly 1, so diagonal movement matches straight-line speed. We only normalize when the length exceeds 1, so partial stick input (a gamepad nudged halfway) still moves slowly instead of being forced to full speed.
💡
.normalizedgives you a copy at length 1;.Normalize()changes the vector in place. Reach for normalizing any time you have a direction whose length you don't want to matter — movement, aiming, knockback. It's one of those small habits that quietly fixes a "why does this feel off" bug before it happens.
The wall you'll walk straight through
Run the game and steer the player into a wall. It glides right through it.
⚠️ Setting
transform.positiondirectly ignores physics entirely. You are teleporting the object frame by frame — colliders, walls, and gravity have no say. It's perfect for learning input and motion (and fine for things like a free-flying camera), but it is not how you move a character that should bump into things. For physics-correct movement — stopping at walls, sliding along them, being pushed — you move a Rigidbody instead, which is exactly what Phase 6 covers next. Don't try to bolt collision detection onto direct transform movement; that's a rabbit hole. Learn the input here, then hand movement to the physics engine there.
For now, direct transform movement is the right tool: it taught you the loop without a pile of physics setup in the way. The pickups in our game don't need the player to collide with walls yet — they need the player to reach them, and now it can.
Recap
- Every GameObject has a Transform; your script reaches its own via
transform, andtransform.positionis its spot in the world. - A position is a
Vector3(x,y,z); build one withnew Vector3(...)or use shortcuts likeVector3.upandVector3.forward. 2D usesVector2on the x/y plane. - Input comes from the legacy
Input.GetAxis/GetKey(simple, always there) or the newer Input System package (action-based, multi-device, the modern recommendation). - Move by reading input, building a direction
Vector3, and addingdirection * speed * Time.deltaTimetotransform.positionevery frame. - Normalize the direction so diagonal movement isn't faster than straight movement.
- Setting
transform.positiondirectly ignores physics and walks through walls — fine for learning input, but real characters move via a Rigidbody (Phase 6).
Quick check
[
{
"q": "Why multiply movement by Time.deltaTime each frame?",
"choices": ["To make the object move faster", "So speed stays consistent regardless of frame rate", "To convert the Vector3 to a Vector2", "It is required for Input.GetAxis to work"],
"answer": 1,
"explain": "Time.deltaTime is the duration of the last frame, so multiplying by it makes the object travel the same distance per second whether the game runs at 30 or 144 fps."
},
{
"q": "What does Input.GetAxis(\"Horizontal\") return?",
"choices": ["true or false", "A Vector3 direction", "A number from -1 to 1 based on left/right input", "The number of keys pressed"],
"answer": 2,
"explain": "It returns a smoothed float from -1 (left) to 1 (right), reading arrow keys, A/D, or a gamepad stick."
},
{
"q": "What happens if you steer a player into a wall using transform.position directly?",
"choices": ["The player stops at the wall automatically", "The player slides along the wall", "The player passes straight through it", "Unity throws an error"],
"answer": 2,
"explain": "Setting transform.position directly ignores physics and colliders, so the object teleports through walls. Physics-correct movement uses a Rigidbody (Phase 6)."
}
]
← Phase 4: MonoBehaviour & the Game Loop · Guide overview · Phase 6: Physics & Collisions →
Check your understanding
1. Why multiply movement by Time.deltaTime each frame?
2. What does Input.GetAxis("Horizontal") return?
3. What happens if you steer a player into a wall using transform.position directly?