Article

Unity input not working: the setting that isn't in your code

When input stops working in a Unity project, the search starts in the input code. That is usually the wrong file to be sitting in.

The scripts read correctly. The bindings look right. It compiles, it runs, and the key does nothing, or fires twice, or works in the game and not in the menu. The console stays quiet, because as far as C# is concerned nothing has gone wrong. Nothing has. The code is fine, and it is talking to a backend that somebody switched off in a settings file a long time before anyone opened this script.

Everything below is checked against the Input System package documentation, version 1.14.2, and against Unity’s own project files, on 24 August 2026.

Active Input Handling

Unity gives you three ways to read input: the old Input Manager (Input.GetKey, Input.GetAxis), the Input System package (Keyboard.current, InputAction, .inputactions assets), and device-specific APIs. That much is widely known, and I have written before about what it does to AI-generated code.

The part that gets missed is that your code does not decide which of them is live. One dropdown does:

Edit → Project Settings → Player → Other Settings → Active Input Handling

It takes three values, and changing it restarts the editor, which is the only part of this whole business that announces itself.

What each Active Input Handling value does to the same two calls Three boxes, one per setting. Under Input Manager (Old), Input.GetKey reads the device and Keyboard.current reads nothing. Under Input System Package (New), Input.GetKey throws at runtime and Keyboard.current reads the device. Under Both, both calls read the device. Input Manager (Old) Input.GetKey reads the device Keyboard.current reads nothing Input System Package (New) Input.GetKey throws at runtime Keyboard.current reads the device Both Input.GetKey reads the device Keyboard.current reads the device too
The same two lines of C# under each setting. Only the middle row produces an error message.

Input System Package (New)

Set Active Input Handling to Input System Package (New), leave a legacy call somewhere in a script, and you get this at runtime:

InvalidOperationException: You are trying to read Input using the UnityEngine.Input class, but you have switched active Input handling to Input System package in Player Settings.

That is a good error message. It names the class, the setting and the panel the setting lives in. Change the call site or change the dropdown and you are finished in a minute.

It is also the case most projects do not have. The other two throw nothing at all.

Input Manager (Old)

Take the reverse. The project is on Input Manager (Old), and somewhere in the codebase there is a PlayerInput component, an .inputactions asset and a handful of Keyboard.current calls.

All of it compiles. The package is installed, so the types resolve, the asset imports, and the inspector draws the component properly with its actions listed. What is switched off is the backend underneath, so no devices arrive, the actions never fire, and the callbacks are never called. Nothing is raised and nothing is logged. A PlayerInput sitting in a scene doing nothing looks identical to a PlayerInput waiting for a key.

This is the one that costs an afternoon. It arrives as a bug report saying the controller doesn’t work, which sends someone into the controller code, which is correct, which sends them into the bindings, which are also correct.

Both

Then there is Both, which is where a good number of projects live, usually because the exception above turned up once and Both was the first answer that made it go away.

It does work. Unity’s documentation is unusually blunt about the price: “To restore functionality you can change the setting to Both, but this means that Unity processes the input twice.”

Two backends, both live, both reading the same physical device. That is harmless while only one of them has code attached to it. The trouble starts the first time a feature gets built twice, which is more common than it sounds: a jump handled by an action callback and also polled in an Update somewhere, a menu that closes on both the legacy Escape key and the bound Cancel action. The player presses once and the game receives two. Neither code path is wrong, and neither of them shows up as an error.

The defines, and activeInputHandler

That is what makes this one persistent. You can read every C# file in the project and the answer is not in any of them.

The only trace the setting leaves in code is a pair of preprocessor defines. Unity defines ENABLE_INPUT_SYSTEM=1 when the new backend is enabled and ENABLE_LEGACY_INPUT_MANAGER=1 when the old one is, and on Both it defines both. Outside package code almost nobody writes against them, so most projects contain no mention anywhere of the thing governing their input.

This is a different problem from the more familiar one. A Unity scene really is unreadable to a text-based tool: the objects are 19-digit fileIDs, the scripts are GUIDs living in separate meta files, and there are no names in there to match against. Project settings are not like that. ProjectSettings/ProjectSettings.asset is YAML, it sits in the repository next to everything else, and the line is not hiding:

activeInputHandler: 1

0 is the old Input Manager, 1 is the Input System package, 2 is Both.

So the information was there and it went unread. That is not a failure of reasoning. Nothing in the prompt said which backend the project was on. Nothing in the C# points at that file. There is no reason to open it unless you already know it decides the answer, and knowing that is the whole trick. Asked to add a dash on shift, a model reaches for the API it has seen most, and across a very large body of tutorials, forum answers and sample projects that is Input.GetKey by a distance. It was a reasonable answer to the question it was given. The question left out the only part that mattered.

UI input

Everything so far has been gameplay scripts. Unity’s UI has its own input path, and it breaks on its own schedule.

uGUI is driven by an input module sitting on the EventSystem object. The legacy one, StandaloneInputModule, reads through UnityEngine.Input, so it stops the moment the old backend does. The package ships InputSystemUIInputModule as the drop-in replacement, and when it finds the legacy component in a scene it offers a button in the inspector to swap it over.

That button is the fix, and it is a scene edit. It is not something that happens by generating a script, which is why “the game plays fine, the menus are dead” is such a common way for this to present.

IMGUI is the other casualty. With Input System Package selected, OnGUI receives no input events at all. Most shipped games have no opinion about that. Every in-house debug overlay does.

Checking which backend is live

Three ways, in increasing order of how much you have to trust whoever is answering.

Open the dropdown, at Edit → Project Settings → Player → Other Settings → Active Input Handling. Definitive, and it wants the editor and the project open in front of you.

Grep for it instead: grep activeInputHandler ProjectSettings/ProjectSettings.asset. That works over SSH, in CI, and on a repository nobody has opened yet. It is also the version worth handing to an agent.

Or have the build say it out loud, which is the one that still answers on a device, where there is no dropdown to open. Drop this anywhere in the project and it reports itself on startup, with no GameObject to attach it to and no scene to edit:

using UnityEngine;

static class InputBackendCheck
{
    [RuntimeInitializeOnLoadMethod]
    static void Report()
    {
#if ENABLE_INPUT_SYSTEM
        Debug.Log("Input System package backend is enabled.");
#endif
#if ENABLE_LEGACY_INPUT_MANAGER
        Debug.Log("Legacy Input Manager backend is enabled.");
#endif
    }
}

Two lines in the console means Both. No lines at all means the file never got compiled into the build, which is its own conversation.

The fix

Pick one backend and stay on it. Both is a real setting with a real use, which is getting through a migration, and it is a poor place to be parked for years afterwards, because it keeps the double-fire case available in every feature added from then on. A project sitting on Both because an exception once told it to is carrying a migration nobody finished.

Then make the choice legible to whatever is writing the code. This is the cheap part and it tends to get skipped: one sentence in the conventions file your tool reads at the start of a session, naming the backend and the API to use with it, closes the whole failure mode. The setting is one line of YAML that any tool can read. Nothing makes it read that line except being told to.

And when input misbehaves in a project that has had a lot of generated code through it, check the setting before you read the code. It takes ten seconds, and it is right often enough to be the first thing you try.

Your game works. Changing it is the part that has got harder.

A fixed-price audit tells you what's wrong, why it's wrong, and what to do about it.

Book a discovery call

[email protected] · replies within 1 business day