Lesson 4: Player Controller and Input Architecture

Your core loop prototype proved the game is worth making. Now players need tight, predictable control. This lesson replaces ad-hoc input with an architecture that scales when you add dash, aim, interact, and pause without turning Update() into spaghetti.

Lesson Objective

By the end of this lesson you will have:

  1. An Input Actions asset describing Move, Look, Jump, and Interact (or your genre equivalents)
  2. A player motor script that reads a movement vector, not raw keys
  3. A clear choice between CharacterController and Rigidbody for your project, with one implemented path

Why This Matters

Input bugs are the fastest way to lose reviews. Separating input reading from movement physics lets you rebind controls, support gamepad, and fix feel without rewriting gameplay systems.

Step-by-Step

Step 1: Install and enable the Input System

  1. Open Window > Package Manager and install Input System if prompted.
  2. When Unity asks to switch backends, choose Input System Package (or Both during migration).
  3. In Edit > Project Settings > Player > Active Input Handling, align with your choice so Editor and builds behave the same.

Pro tip: Commit the InputSystem_Actions.inputactions (or your named asset) to Git so the team shares one source of truth.

Step 2: Create an Input Actions asset

  1. In Assets/_Project/Settings/ (or similar), create Input Actions named PlayerControls.

  2. Add an Action Map called Player.

  3. Inside it, add actions:

    • Move (Action Type: Value, Control Type: Vector2) – bind WASD and left stick
    • Look (Vector2) – mouse delta and right stick
    • Jump (Button)
    • Interact (Button) – adjust for your design
  4. Click Save Asset and generate a C# class from the asset (checkbox in the importer or Generate C# Class in the inspector) if you prefer strongly typed access. Alternatively, use PlayerInput with Unity Events for rapid iteration.

Step 3: Wire PlayerInput on the player object

  1. Add a PlayerInput component to your player root.
  2. Assign Actions to your PlayerControls asset.
  3. Set Default Map to Player and Behavior to Invoke Unity Events or Broadcast Messages based on team preference.

For small teams, Invoke Unity Events keeps logic visible in the Inspector. For larger codebases, the generated wrapper class keeps signatures strict.

Step 4: Implement a movement reader

Create PlayerMotor.cs that exposes:

public void OnMove(InputAction.CallbackContext ctx)
{
    _moveInput = ctx.ReadValue<Vector2>();
}

In Update or FixedUpdate (see Step 5), convert _moveInput into world-space direction using the camera forward/right flattened on the XZ plane for a third-person or top-down game.

Common mistake: Reading Input.GetAxis in the same project as Input System without Both mode. Pick one path per script.

Step 5: Choose CharacterController or Rigidbody

CharacterController – Best when you want kinematic-style movement, stairs and slopes handled without full physics tuning, and you do not need realistic pushes from every object.

Rigidbody – Best when forces, collisions, and physics puzzles define your game. You will tune drag, interpolation, and MovePosition / forces carefully.

Pick one for Lesson 4 and stick to it until a design document says otherwise.

Step 6: Apply motion in the right phase

  • For CharacterController, use controller.Move(motion * Time.deltaTime) in Update or scale with deltaTime consistently with your feel tests.
  • For Rigidbody, prefer FixedUpdate for force and velocity changes to stay in sync with the physics step.

Step 7: Jump and grounded checks

Implement a grounded test appropriate to your body:

  • CharacterControllerisGrounded with small caveats; combine with a short sphere cast downward if you see false air states.
  • Rigidbody – ray or sphere cast from the feet; cache ground layers with a LayerMask.

Jump should apply one impulse or velocity change on press, with a short coyote time optional for forgiving feel.

Step 8: Folder and naming discipline

Place scripts under Assets/_Project/Scripts/Player/ with clear names:

  • PlayerInputRouter.cs (optional – if you centralize callbacks)
  • PlayerMotor.cs
  • PlayerCameraDriver.cs (if you split look logic)

Document in README which scene object owns PlayerInput.

Mini Challenge

Add Interact without hard-coding a key check in PlayerMotor. Fire an event or call IInteractable.TryInteract() only when the action performed this frame. You will reuse the same path for gamepad X later.

Pro Tips

  • Keep sensitivity for Look in ScriptableObject or a simple config so QA can tune without code.
  • Use dead zones on sticks in the Input Actions binding screen.
  • Log input device switches in development builds to catch gamepad unplug edge cases early.

Common Mistakes

  • Mixing legacy and new input in different scripts on the same player
  • Scaling movement with Time.deltaTime twice
  • Putting camera rotation inside the motor script without an invert-Y option

Troubleshooting

"NullReference on PlayerInput"

Assign the Actions asset on the component and ensure the enabled checkbox is on at runtime.

"Build has no input but Editor works"

Check Active Input Handling and that InputActionAsset is included in Resources or referenced by a scene object, not only an Editor-only prefab.

"Character slides forever"

Increase friction settings for Rigidbody projects or zero horizontal velocity when input magnitude is below a threshold.

Recap

You defined Input Actions, routed them through PlayerInput, and drove a motor that consumes vectors instead of device-specific polling.

Next Lesson Teaser

Next you will give the player something to react against with enemy behaviors and encounter design that respect your new control scheme.

FAQ

Should solo devs skip the Input System?
No. The migration cost is lower early. You will want rebinding and gamepad before release.

Is Cinemachine required?
Not for this lesson. Add it when camera complexity grows.

What about mobile touch?
Map virtual sticks to the same Move and Look actions so gameplay code stays identical.

Related Links

Bookmark this lesson when you start tuning acceleration curves. Small changes here multiply across every level you ship.