◀ 1.41.51.6 ▶

1.5 — Movement II: Speed, deltaTime & Tuning

Your character moves, and one thing about that is wrong: hold two direction keys at once and it travels 41% faster. Today you find out why, fix it in one line, and tune the number that decides how the character feels.

In this lesson you will be able to answer“why does my character move faster diagonally, and how do I fix it?”Full walkthrough
Break It Down~10 minFull walkthrough

My character moves faster on the diagonal than in a straight line. The movement from 1.4 already works, and that is what makes the wrongness hard to see.

First, before any code: cut that into parts small enough that you could look each one up on its own.

  1. Read which way the player is pushingThe two axis reads from 1.4, assigned to the inputX and inputY fields you already declared. Unchanged — nothing here is the bug.Read: the Input.GetAxis("Horizontal") row in the quick reference.
  2. Build a direction vector out of the two numbersPack both axes into one Vector2: pure direction, with no speed and no time multiplied in. Its length is whatever the input happens to be — the whole problem.Read: the Vector2 row in the quick reference, and the vector you built in 1.4.
  3. Rescale that vector to a length of exactly 1direction = direction.normalized; keeps the arrow pointing the same way and throws its length away, so "up and right" is (0.707, 0.707) instead of (1, 1). The fix is general, not a special case for diagonals.Read: the (a - b).normalized row in the quick reference — direction, length exactly 1.
  4. Scale that unit direction into a distance, and apply itOne multiplication: direction * moveSpeed * Time.deltaTime. Because the direction is always length 1, this distance is identical whichever way you face. Then the Transform applies it, as in 1.4.Read: the Time.deltaTime and translate rows in the quick reference.

The new part sits in the middle rather than at the end. The vector from 1.4 was two numbers stapled together, which is fine until you do arithmetic with them: (1, 0) is length 1, (0, 1) is length 1, but (1, 1) is 1.41 — so two keys moved you 41% further. Keep direction and distance in separate parts and the fix is one line.

Is "which way" still in the same part as "how far"? If you cannot point at the one line where speed is applied, the two are still tangled.

Your turn

The fix was one line, and it landed in the middle part rather than the last one. Why does a bug get easy to find exactly when the problem has been split into the right parts?

In the Editor~20 min
  1. Project window → Assets/Scripts, and open PlayerController.cs — the file you built in 1.4.
  2. Game view → press Play and hold D for two seconds, then W and D together. The diagonal covers visibly more ground — you can see the bug before you write anything.
  3. Hierarchy → select the Player, then Inspector → Move Speed. Change the number while the game runs and walk again: too low is sluggish, too high is twitchy.
    Inspector showing PlayerInspectorPlayerTransformPlayer Controller (Script)3Move Speed5
    The Player's Inspector. Move Speed is the only field Player Controller (Script) has, and this is the box — a serialized value beats the code default, so the number you leave in it is the one the game runs with.
  4. Inspector → Move Speed again, bisecting toward a number that feels right for a world this size — 4 to 7 is a good starting range. Write it down.
  5. Game view → stop the game, then enter that number in the same box in edit mode so it persists. Then GitHub Desktop → commit and push the fix and the tuned speed.
Play mode is a dream, edit mode is reality (1.2). A value changed while the Play button is blue resets when you stop: play mode to find your number, edit mode to keep it.
In the Code~15 min

Unity offers four ways to move an object, each because it handles something the others handle badly. You use the first today.

TechniqueHow it worksNeeds a Rigidbody2D?Best for
transform.Translate(vector)Offsets coordinates directly, skipping the physics engine.NoSimple top-down games, moving UI, linear projectiles
Rigidbody2D.MovePosition(position)Drives the body to a target position, so whatever is in the way gets a say: a Dynamic body is stopped by a solid collider, a Kinematic one arrives anyway.Yes — Dynamic if you want a wall to stop itMoving platforms and scripted set pieces that must arrive at an exact position
Rigidbody2D.linearVelocity = vectorAssigns a velocity and lets the physics engine move the body. The number is per second, not per frame.Yes — Dynamic if you want a wall to stop itSnappy platformers, arcade physics, momentum across surfaces
Rigidbody2D.AddForce(vector)Applies a physical push, factoring in mass, drag and inertia.Yes — Dynamic; a Kinematic body ignores forcesJumping, thrusters, ice physics, knockback
Do not switch methods today. Three of these need a Rigidbody2D your Player does not have, and you will not add one yet: 2.1 adds it for a different purpose (coins) and 2.2 is where it stops you walking through walls.

Socratic: Choosing the Right Tool for the Job

  1. You proved in 1.4 that the translate call walks through walls, while Rigidbody2D.MovePosition(position) is stopped by one — on a Dynamic body. What does that call consult that the Transform never does?
  2. Three of these four need a component your Player does not have. What would you add, and where — on the Player, or on the child Sprite?
  3. Why would a spaceship drifting through space prefer Rigidbody2D.AddForce(vector) to the translate call?
In the CodeFull walkthrough

The question this answers“why does my character move faster diagonally, and how do I fix it?”

You are editing Assets/Scripts/PlayerController.cs. You have working movement from 1.4. This replaces two of those lines; the fields and the input reads do not change.

Step 1Separate direction from speed

In 1.4 you multiplied the input by speed in two separate places. Pull those apart: first work out which way to go, then decide how far.

// PlayerController.cs — inside Update(), replacing the two displacement lines from 1.4.
        Vector2 direction = new Vector2(inputX, inputY);

        direction = direction.normalized;

        Vector2 movement = direction * moveSpeed * Time.deltaTime;

// PlayerController.cs — unchanged from 1.4: the finished distance goes to the Transform.
        transform.Translate(movement);
  1. 2A vector pointing whichever way the player holds. Pure direction — no speed, no time — and its length is whatever the input happens to be: D alone gives (1, 0), but W and D together give (1, 1), which is 1.41 long, not 1. That extra 41% is the bug you can feel.
  2. 4The fix: .normalized keeps the vector pointing the same way but rescales it to a length of exactly 1, so "up and right" becomes (0.707, 0.707).There is no second Vector2 in front of direction — the variable already exists, so this line assigns to it, as inputX did in 1.3.
  3. 6Scales that unit direction up to a real distance: one second of travel (moveSpeed), cut down to one frame (Time.deltaTime).Because direction is always length 1, and Time.deltaTime is the seconds the last frame took, this is the same real-world speed at 30 FPS and at 240 FPS.
  4. 9Hands the distance to the Player's own Transform, which adds it to the current position. Unchanged from 1.4.
Check your work: the finished filebuild it as you read — then open this to check every line

The only lines that should differ from 1.4 are the ones building direction and movement — fields, input reads and translate call are unchanged.

// ------------------------------------------------------------------
//  REFERENCE FILE — Assets/Scripts/PlayerController.cs
//  Written to be read, not pasted. Build it with the walkthrough
//  above, one step at a time, then open this and compare.
//  If it is sitting in your project unchanged, that is the thing
//  to go and do now — the walkthrough is still above you.
// ------------------------------------------------------------------
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;

    private float inputX;
    private float inputY;

    void Update()
    {
        inputX = Input.GetAxis("Horizontal");
        inputY = Input.GetAxis("Vertical");

        Vector2 direction = new Vector2(inputX, inputY);
        direction = direction.normalized;

        Vector2 movement = direction * moveSpeed * Time.deltaTime;

        transform.Translate(movement);
    }
}

Checkpoint — before moving on, you must be able to:

Reference Docs

Need a deeper explanation? These pages cover the same concepts:

Next: 1.6 — Animation States: IsWalking, WalkDir, Attack

0/4 answered

Answers save on this device as you type. At the end of class, click Download and upload the file to the Canvas assignment.

Saved on this device only — the Dashboard tracks your progress.