The Input System in Unity: Actions Instead of Keys
Unity's newer Input System asks a simple change of habit: stop writing code about keys and buttons, and start writing code about what the player means, leaving the mapping from devices to meaning in data.

For many years the first line of input code most Unity developers ever wrote looked very much alike. Somewhere inside Update, a script asked whether a particular key was held, Input.GetKey with a KeyCode for the space bar perhaps, and if it was, the character jumped. The line was short, it was honest, and it worked. It also fused together two questions that a growing game eventually needs to keep apart: what physical thing the player touched, and what the player was trying to do.
The Input System package, which Unity introduced as a replacement for the older Input Manager, is built on the separation of those two questions. Code speaks of actions such as Jump, Move and Interact. A data asset decides which keys, sticks, buttons and touches produce those actions. Between them sits a layer that knows about devices, schemes and players, and that can change its mind at runtime when someone plugs in a controller or asks to move jump onto another key.
This piece follows that change of habit carefully. It looks first at what the old system did well and where it strained, then at the Input Actions asset and its parts, at control schemes and the PlayerInput component, at rebinding, and finally at the delicate business of several devices and several players sharing one game. The aim is not to condemn the older approach but to understand why the newer one is shaped the way it is.
The old Input Manager
The legacy system lives in the project settings, in a list of named axes such as Horizontal, Vertical, Fire1 and Jump, each given positive and negative buttons, a sensitivity, a gravity value and a dead zone. Scripts read these through Input.GetAxis and Input.GetButtonDown, or bypass them altogether with Input.GetKey and Input.GetMouseButton. The appeal was immediacy: there was nothing to set up, and a single call answered a single question at any moment, from anywhere in the code.
The trouble appeared gradually, as games grew. Gamepads reported their axes and buttons differently from one platform to another, so the same Fire1 might need several entries to cover several controllers. Supporting rebinding meant writing one's own layer on top, because the axis list could not be edited from within a built game in any convenient way. Local multiplayer meant distinguishing one controller from another by joystick number, a fragile arrangement that fell apart whenever devices were connected in an unexpected order.
Above all, polling scattered input logic across many scripts. Each component that cared about jumping asked its own question about the space bar, so changing the key meant searching the codebase for every place it was named. None of this made the old system wrong for small projects, and it remains available today. In the Player settings, Active Input Handling can be set to the old system, the new package, or both at once, which is how many projects migrate gradually rather than all in one painful afternoon.
Actions, maps and bindings
The heart of the newer system is the Input Actions asset, a file that the editor opens in its own window. Inside it are action maps, and inside each map are actions. An action is a named intention, such as Move or Attack, and it has a type: a Button for things that are pressed and released, a Value for continuous readings like a stick or a mouse delta, or Pass Through when every change from every control should be reported without being reduced to a single winner.
Each action has one or more bindings, and a binding is the rule that ties the action to a concrete control on a device. Jump might be bound to the space key and also to the south button of a gamepad, which on many controllers is the button nearest the thumb. Some bindings are composites, assembled from several controls: four keyboard keys can be combined into a single two-dimensional vector, so that Move reads the same Vector2 whether the player uses the keyboard or a thumbstick.
Bindings can carry interactions and processors as well. An interaction changes when an action is considered performed, so a Hold interaction fires only after the button has stayed down for a set duration, and a Tap only if it is released quickly. A processor transforms the value on its way through, normalising a vector, inverting an axis or applying a dead zone. All of this lives in data, adjusted by a designer without anyone opening a script.
Action maps exist because a game's intentions change with its state. Walking around a village and navigating a pause menu use many of the same physical buttons for entirely different purposes. Putting the gameplay actions in one map and the interface actions in another lets code enable one and disable the other with a single call each, so that pressing the south button confirms a menu choice instead of also making the hidden character jump in the background.
Reading actions in code
Code meets the asset in a few ways. The most direct is to hold an InputActionReference or an InputAction field, call Enable on it, and then either poll it or subscribe to its callbacks. Polling looks familiar: WasPressedThisFrame answers the old GetButtonDown question, and ReadValue, asked for a Vector2, returns the current movement. The difference is that the script now asks about Jump or Move, and the asset alone knows which keys those words currently mean.
Callbacks offer an event-driven alternative. Every action raises started, performed and canceled at the appropriate moments, and a script can subscribe a method to each, receiving a context object from which it reads the value and the control that caused the change. For a button with a Hold interaction, started fires when the press begins, performed when the hold duration has passed, and canceled if the button is released too early, which maps neatly onto a charging attack or a slow interaction.
The editor can also generate a C# class from the asset, with a property for every map and action, so that code refers to them by name with the compiler checking the spelling. This is a matter of taste rather than necessity, but it removes a whole category of silent failures in which a misspelled action name simply returns nothing. Whichever route is chosen, one discipline remains: an action that has not been enabled will never report anything at all.
Control schemes and PlayerInput
A control scheme is a named description of the devices a player needs, such as Keyboard and Mouse, or Gamepad. Bindings can be tagged with the schemes they belong to, so that the keyboard binding of Jump belongs to one and the gamepad binding to the other. Schemes are what allow the system to say that a given player is currently using a controller, and therefore that the prompts on screen should show controller buttons rather than letters.
The PlayerInput component brings these ideas together without much code. Given an actions asset, it pairs devices with a player, activates a default map, and tracks which control scheme is in use, switching automatically when the player picks up a gamepad after using the keyboard. It then notifies scripts in one of several ways, chosen by its Behavior setting: sending messages to methods named after actions, broadcasting them down the hierarchy, invoking UnityEvents set up in the inspector, or raising plain C# events.
For a single player game, PlayerInput is often all the plumbing required, and the choice of behavior is mostly about how a team likes to wire things. Messages are quickest to start with, since a method called OnJump will simply be called, while UnityEvents make the connections visible in the inspector. C# events give the most control and the clearest flow for programmers. None is more correct than the others; they are different conveniences laid over the same underlying actions.
Rebinding at runtime
Letting players choose their own keys used to be a project in itself. The Input System makes it an operation. Calling PerformInteractiveRebinding on an action starts a rebinding operation that waits for the player to actuate some control and then rewrites the chosen binding to point at it. The operation can be told to ignore the mouse, to cancel when Escape is pressed, or to accept only controls of a certain kind, and it reports through a completion callback when it is done.
Two practical details matter here. The action generally needs to be disabled while it is being rebound, so that the very key the player presses to choose a new binding does not also trigger the old behavior. And the rebinding operation holds resources that must be released, so it should be disposed of once complete or cancelled. Forgetting either produces small, confusing bugs, the kind that only appear when a tester rebinds three keys in a row.
A rebinding is stored as an override on top of the original binding, not as a change to the asset, which keeps the designer's defaults intact. The methods SaveBindingOverridesAsJson and LoadBindingOverridesFromJson turn those overrides into a string and back, so a game can write them to player preferences or a settings file and restore them at startup. A reset button needs only to remove the overrides, and the original layout returns exactly as it was authored.
Many devices, many hands
The newer system treats devices as first-class objects that appear and disappear while the game runs. Keyboards, mice, gamepads, touchscreens, pens and more each have a layout describing their controls, and the system raises an event whenever a device is added, removed or reconnected. A game can respond to a controller being unplugged mid-battle by pausing and asking for it back, rather than letting the character drift off a cliff with its last stick reading still applied.
For local multiplayer, the PlayerInputManager component handles joining. When a new device presses a join button, it spawns a player prefab with its own PlayerInput and pairs that device to it, so that the first gamepad controls the first player and the second controls the second, with no joystick numbers in sight. Keyboard players can be given different schemes, one for each half of the keyboard, though in practice that arrangement is easier to describe than to make comfortable.
Even a single player game benefits from this care. Someone playing a slow strategy game such as Crown & Ashes may lay out a village with mouse and keyboard and later lean back with a controller in hand, and the game should follow them without a menu visit. That small courtesy depends entirely on the separation the system was built around: code that asks what the player intends, and data that decides, device by device, how that intention arrives.
There is a broader lesson in the design, one that reaches beyond input. Whenever code names a specific key, it records a decision that belongs to the player and the designer rather than the programmer, and it records it in the one place hardest to change. Moving that decision into an asset, behind a name like Jump, does not make the game smarter. It makes it more honest about who decides what, and that honesty pays for itself the first time someone asks to change a key.


