【Unity】Mastering Input Handling in Unity - Keyboard, Mouse, and Gamepad

Created: 2025-12-07Last updated: 2026-07-13

Jump fires repeatedly while the key is held, or Input.GetKey suddenly throws an exception after installing the Input System package—this guide solves the classic input-handling pitfalls. Covers when to use GetKeyDown/GetKey/GetKeyUp, how virtual axes work, the difference between GetAxis and GetAxisRaw, and when to migrate to the new Input System.

You write jump with Input.GetKey(KeyCode.Space), and the jump fires repeatedly the whole time the key is held. You fix that, and then the moment you install the Input System package, Input.GetKey starts throwing exceptions—input handling is the "first code you ever write," yet it's surprisingly full of pitfalls.

This article is a textbook-style guide to input handling with Unity's long-standing built-in Input Manager (the Input class). Let's be clear about its role up front: this is a guide for reading, running, and eventually migrating away from code in older projects and tutorials. If you're starting a new project today, your entry point should be the New Input System introduction. That said, the vast majority of tutorials and existing assets out there are written with the legacy Input class — being able to read it remains an essential skill.

Conceptual image of input handling: signals from a keyboard, mouse, and gamepad flowing into a game character

What You'll Learn

  • How Unity's two input systems relate, and what this article covers
  • Exactly when to use GetKeyDown / GetKey / GetKeyUp ("moment" vs. "duration")
  • Why virtual axes exist, and the difference between GetAxis and GetAxisRaw
  • How to read mouse input
  • Criteria for deciding when to migrate to the new Input System

Sponsored

Unity Has Two Input Systems

Let's start with the big picture. Unity has two input mechanisms living side by side.

Comparison diagram of the legacy Input Manager and the new Input System. The legacy one is a simple setup that polls the Input class in Update; the new one is event-based, going through InputAction assets
  • Input Manager (legacy, built-in): You query it every frame inside Update, like Input.GetKey(...) (polling style). No extra installation needed—you can start with a single line of code
  • Input System (new, package): You define actions like "Jump" or "Move" and receive them as events. Strong for key rebinding and multi-device support (see Intro to the New Input System)

This article is a textbook for the former, the Input Manager. It has a low learning curve and is still a perfectly valid choice for prototypes and small games.

Note (a classic gotcha): When you install the Input System package, a confirmation dialog switches Active Input Handling to "Input System Package (New)", and the legacy Input class starts throwing InvalidOperationException. If you want to use both, change "Edit > Project Settings > Player > Other Settings > Active Input Handling" to Both. If input that "worked fine yesterday" suddenly starts throwing exceptions, check here first.

Input Manager Basics: The Virtual Axis Concept

You can view the Input Manager's settings under "Edit > Project Settings > Input Manager". It comes pre-populated with Virtual Axes like Horizontal, Vertical, Jump, and Fire1.

Why bother going through an "axis" instead of reading keys directly? To decouple your code from specific keys.

Conceptual diagram of virtual axes. Multiple input sources—keyboard A/D keys, arrow keys, and a gamepad stick—are bundled into a single "Horizontal" axis, which drives character movement

For example, the Horizontal axis bundles the a/d keys, the left/right arrow keys, and the gamepad stick all together. On the code side, you just write Input.GetAxis("Horizontal") and receive "left/right input" without caring which input source it came from. This abstraction saves you from the classic disaster: writing keyboard-only code, then being asked to "add gamepad support" and having to rewrite everything.

Reading Keyboard Input

Keyboard input is read with three methods on the Input class. The key distinction is the moment vs. the duration.

Timing comparison diagram of GetKeyDown, GetKey, and GetKeyUp. On a timeline from press to release, GetKeyDown fires only on the frame the key is pressed, GetKey fires the entire time the key is held, and GetKeyUp fires only on the frame the key is released
MethodWhen It RespondsTypical Uses
Input.GetKeyDown(KeyCode)Only the single frame the key is pressedOne-shot actions like jumping, shooting, confirming
Input.GetKey(KeyCode)The entire time the key is heldCharging, dashing, hold-to-move
Input.GetKeyUp(KeyCode)Only the single frame the key is releasedReleasing a charged shot, ending a long press

Here's the culprit behind the "jump spamming" bug from the intro. GetKey returns true every frame while the key is held, so using it for jump means holding the key for one second triggers dozens of jumps. One-shot actions should always use GetKeyDown.

KeyCode is an enum that defines the keys (e.g. KeyCode.Space, KeyCode.W, KeyCode.Return).

using UnityEngine;

public class KeyboardInputExample : MonoBehaviour
{
    void Update()
    {
        // Jump the moment the space key is pressed (only once)
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.Log("Jump!");
        }

        // While the R key is held down, reloading
        if (Input.GetKey(KeyCode.R))
        {
            Debug.Log("Reloading...");
        }

        // Close the pause menu the moment the Escape key is released
        if (Input.GetKeyUp(KeyCode.Escape))
        {
            Debug.Log("Closing pause menu");
        }
    }
}

tips: Always check input in Update. FixedUpdate is not guaranteed to run every frame, so it can miss "true for one frame" inputs like GetKeyDown. See the Update vs FixedUpdate article for details.

Sponsored

Reading Input via Virtual Axes

Input that has "direction and magnitude," like movement, is read through virtual axes. There are two similar-looking methods, and the difference is how the input value changes.

Diagram of the response difference between GetAxis and GetAxisRaw. When a key is pressed, GetAxis ramps smoothly from -1 to 1 like a slope, while GetAxisRaw switches instantly between -1, 0, and 1 like a staircase
  • Input.GetAxis(string): Returns a value from -1.0 to 1.0 that changes smoothly. Even with a keyboard, interpolation is applied internally, so the value ramps gradually from 0 to 1 when a key is pressed. Suited for smooth movement with a sense of inertia
  • Input.GetAxisRaw(string): Returns -1, 0, or 1 immediately, with no interpolation. You move at full speed the instant the key is pressed—great for snappy 2D action games

Note that with a gamepad stick, both methods return intermediate values based on how far the stick is tilted (like 0.3 or 0.7). The interpolation difference mainly matters for keyboard input.

using UnityEngine;

public class AxisInputExample : MonoBehaviour
{
    public float speed = 10f;

    void Update()
    {
        // Get the value of the "Horizontal" axis (A/D keys, arrow keys, gamepad stick)
        float horizontalInput = Input.GetAxis("Horizontal");

        // Move left/right using the input value
        transform.Translate(Vector3.right * horizontalInput * speed * Time.deltaTime);
    }
}

A practical way to choose: if the controls feel sluggish, switch to GetAxisRaw; if the movement starts too abruptly, switch to GetAxis. Let the feel decide.

Reading Mouse Input

Mouse clicks and position are also available through the Input class.

  • Input.GetMouseButtonDown(int button): The moment a button is pressed. 0 is left, 1 is right, 2 is the middle button
  • Input.GetMouseButton(int button): While a button is held down
  • Input.GetMouseButtonUp(int button): The moment a button is released
  • Input.mousePosition: Returns the cursor's screen coordinates (in pixels, origin at bottom-left) as a Vector3. The Z coordinate is always 0
using UnityEngine;

public class MouseInputExample : MonoBehaviour
{
    void Update()
    {
        // When left-clicked
        if (Input.GetMouseButtonDown(0))
        {
            // Get the mouse's screen coordinates
            Vector3 mousePos = Input.mousePosition;
            Debug.Log("Left click! Position: " + mousePos);
        }
    }
}

Since mousePosition is a pixel coordinate on the screen, to find out "which object was clicked" in 3D space, you cast a Ray from that coordinate. This classic pattern is covered in detail in the Physics.Raycast article.

Hands-On: Reading and Running Legacy 2D Move + Jump Code

To finish, let's build the most common shape you'll see in older tutorials — 2D movement plus jump — following this series' policy: read input in Update, apply physics in FixedUpdate. Once you can read this shape, you can read almost any legacy Input code in the wild.

How legacy-Input 2D movement and jumping work: A/D/SPACE input is read in Update, stored in a jumpPressed flag, and applied to physics in FixedUpdate
// File name: LegacyPlayerMove.cs — the classic legacy Input pattern
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class LegacyPlayerMove : MonoBehaviour
{
    public float moveSpeed = 6f;
    public float jumpForce = 12f;

    private Rigidbody2D rb;
    private float moveInput;
    private bool jumpPressed;

    void Awake() { rb = GetComponent<Rigidbody2D>(); }

    void Update()
    {
        // Read input in Update (never miss GetButtonDown's single true frame)
        moveInput = Input.GetAxisRaw("Horizontal");
        if (Input.GetButtonDown("Jump")) jumpPressed = true; // Bank it in a flag
    }

    void FixedUpdate()
    {
        // Apply to physics in FixedUpdate
        rb.linearVelocity = new Vector2(moveInput * moveSpeed, rb.linearVelocity.y);

        if (jumpPressed)
        {
            jumpPressed = false; // Always consume the flag (or you'll auto-jump after landing)
            rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
        }
    }
}

Note: Older code writes rb.velocity, which Unity 6 renamed to rb.linearVelocity (see the API migration guide). Also, calling GetButtonDown directly inside FixedUpdate can miss presses — the heart of this pattern is read in Update, bank into a flag, consume in FixedUpdate.

One more crucial reading skill: the strings "Horizontal" and "Jump" depend on that project's axis names in Project Settings > Input Manager. Delete or rename an axis on the settings side, and perfectly correct code fails at runtime with something like ArgumentException: Input Axis Horizontal is not setup. When "I copied just the code and it doesn't work," check here first.

Migration Table to the New Input System

When the day comes to migrate this code, translate it with the following correspondences (implementation steps are in the New Input System introduction):

Legacy Input ManagerNew Input System
Input.GetAxisRaw("Horizontal")The Move action's (Vector2) ReadValue<Vector2>().x
Input.GetButtonDown("Jump")The Jump action's performed callback (or WasPressedThisFrame())
Defined in: Project Settings > Input ManagerDefined in: an Input Actions asset
New device = editing the settingsNew device = adding a Binding (no code change)

To finish, tap jump in quick short bursts. If not a single press gets dropped, "read in Update, store in a flag" is doing its work. And if an ArgumentException sends your eyes straight to the axis names in Project Settings instead of the code — then the world's legacy Input code is now a language you can read.

Sponsored

Caveats: When to Move On from the Input Manager

Simplicity is the Input Manager's strength, but its limits show as your project grows.

  • Key rebinding is hard to build: Virtual axis assignments can't be changed at runtime, so building your own "key settings screen" is a serious undertaking
  • Serious multi-device support gets painful: Handling keyboard + gamepad + touch cleanly in one codebase means an ever-growing pile of conditionals
  • Input logic gets scattered: Polling in Update is convenient, but your input definitions end up buried throughout the code

When these requirements appear, it's time to consider migrating to the event-based new Input System. Key rebinding in particular can be implemented with the official rebinding API in the new Input System (see the Key Rebinding Implementation Guide).

The decision comes down to one line: "Do you want players to remap their keys, or do you need serious multi-device support?" When either answer becomes Yes, that's your cue to migrate to the new Input System. Conversely, for a fixed-key small PC game or a prototype, there's nothing wrong with riding the Input Manager all the way to the finish line.

Bonus: Good to Know for Later

Once you can handle the basics of input, these topics come into view next.

  • Input and pausing: Even while paused with Time.timeScale = 0, input keeps working. That's precisely why you can still accept the unpause command. The mechanics are laid out in the Pause Implementation Guide.
  • Conflicts with UI clicks: If clicking a button also fires your in-game shooting, the standard fix is to check EventSystem.current.IsPointerOverGameObject() and ignore input when the pointer is over UI.
  • Touch input on mobile: Input.GetMouseButtonDown(0) works with touch too, but if you need multi-touch (like pinch gestures), use Input.touches—and for full-fledged support, that's the new Input System's job.

Summary

Input handling is the first interface connecting the player to your game world.

  • Unity has two systems: the legacy Input Manager and the new Input System. If the legacy Input throws exceptions after installing the Input System package, set Active Input Handling to Both.
  • GetKeyDown is the moment of press, GetKey is the duration, GetKeyUp is the moment of release. Writing one-shot actions with GetKey gives you the rapid-fire bug.
  • Virtual axes decouple your code from specific keys. GetAxis is smooth, GetAxisRaw is instant—choose by feel.
  • For the mouse, use GetMouseButtonDown and mousePosition. To click 3D objects, Raycast from mousePosition.
  • When you need key rebinding or serious multi-device support, migrate to the new Input System.

Start by enjoying how good it feels to move a character with the Input Manager, and move to the new Input System when your project's requirements grow—that's the steady path forward, even if it looks like a detour.