| uid | input-system-architecture |
|---|
The Input System has a layered architecture that consists of a low-level layer and a high-level layer.
The foundation of the Input System is the native backend code. This is platform-specific code that collects information about available devices and input data from devices. This code isn't part of the Input System package; it's included with Unity itself. It has implementations for each runtime platform supported by Unity. This is why some platform-specific input bugs can only be fixed by an update to Unity, rather than a new version of the Input System package.
The Input System interfaces with the native backend using events that the native backend sends. These events notify the system of the creation and removal of Input Devices and any updates to the Device states. For efficiency and to avoid creating garbage, the native backend reports these events as a simple buffer of raw, unmanaged memory containing a stream of events.
The Input System can also send data back to the native backend in the form of commands sent to devices, which are also buffers of memory that the native backend interprets. These commands can have different meanings for different device types and platforms.
The diagram of the low-level Input System reads top-to-bottom as a layered pipeline:
- The native platform backends at the bottom feed the InputManager.
- The InputManager uses layouts to build devices.
- The device's state is stored in Input State Memory at the top.
flowchart TB
%% ---------- Input State Memory (top) ----------
StateMemory["Input State Memory
(unmanaged raw memory — each device and control
gets a chunk that stores its current state)"]
%% ---------- Devices (built from layouts) ----------
Gamepad_D["Gamepad (device)"] --> leftStick
leftStick --> x & y & up & down & left & right
Keyboard_D["Keyboard (device)"] --> a & b & c & d
leftStick -->|"state written to"| StateMemory
Keyboard_D -->|"state written to"| StateMemory
%% ---------- Layouts (describe how to build controls and devices) ----------
Mouse -->|derives| Pointer
Pen -->|derives| Pointer
Touchscreen -->|derives| Pointer
DS_PS4["DualShock (PS4)"] --> DualShock
DS_HID["DualShock (HID)"] --> DualShock
DualShock --> Gamepad_L["Gamepad (layout)"]
Keyboard_L["Keyboard (layout)"]
Stick(("Stick"))
Axis(("Axis"))
Button(("Button"))
Dpad(("Dpad"))
%% Layout building blocks build the individual device controls
Gamepad_L -.->|builds| Gamepad_D
Stick -.->|builds| leftStick
Axis -.->|builds| x & y
Button -.->|builds| up & down & left & right
Keyboard_L -.->|builds| Keyboard_D
%% ---------- InputManager (middle) ----------
InputManager(["<b>InputManager</b>
Matches layouts to devices (InputDeviceMatcher),
builds devices (InputDeviceBuilder), creates & updates them"])
Gamepad_L -->|"searched by"| InputManager
InputManager -->|"creates & updates"| Gamepad_D
InputManager -->|"creates & updates"| Keyboard_D
%% ---------- Input Runtime (bottom) ----------
DDQ["Device Discovery Queue"]
EQ["Event Queue"]
BEQ["Background Event Queue
(async; flushes into the foreground queue)"]
Backends["Platform Backends
Windows · macOS · Linux · UWP · iOS · Android
· Switch · Xbox · PS4 · WebGL · XR"]
Backends --> DDQ & EQ & BEQ
DDQ -->|"Device Discovered"| InputManager
EQ -->|"Update (flushes event buffers)"| InputManager
InputManager -->|"Queue Event"| EQ
InputManager -->|"Device Command (IOCTL-style)"| Backends
%% ---------- Grouping by color ----------
classDef layout fill:#e6f0ff,stroke:#4a78c0,color:#000;
classDef device fill:#e8f7e8,stroke:#4aa04a,color:#000;
classDef runtime fill:#fdf3d0,stroke:#b9962e,color:#000;
classDef mem fill:#f0e6f6,stroke:#8a5ea0,color:#000;
classDef manager fill:#ffffff,stroke:#e0403f,stroke-width:2px,color:#000;
class Mouse,Pen,Touchscreen,Pointer,DS_PS4,DS_HID,DualShock,Gamepad_L,Keyboard_L,Stick,Axis,Button,Dpad layout;
class Gamepad_D,leftStick,x,y,up,down,left,right,Keyboard_D,a,b,c,d device;
class DDQ,EQ,BEQ,Backends runtime;
class StateMemory mem;
class InputManager manager;
The low-level Input System code processes and interprets the memory from the event stream that the built-in back end provides, and dispatches individual events.
When the Input System discovers a device in the event stream, it creates a device representation for it. The low-level code sees a device as a block of raw, unmanaged memory. When the low-level code receives a state event for a device, it writes the data from the state event into the device's state representation in memory. This means that the state always contains an up-to-date representation of the device and all its controls.
The low-level system code also contains structs that describe the data layout of commonly known devices.
The high-level system is easiest to understand in two parts:
- How input flows through the system at runtime.
- How actions are authored as assets.
The diagram shows both parts for a single player; each additional player gets its own InputActionState and a cloned InputActionAsset with its own device list and binding mask.
The first diagram is for the runtime data flow:
- A device's control state is written into Input State memory.
- A State Change Monitor notices the change.
- The
InputActionStateis updated. - The resulting action fires a callback on the
PlayerInputcomponent in the scene.
flowchart LR
%% ---------- Devices ----------
Keyboard["Keyboard"] --> space["space"]
%% ---------- Input State (unmanaged raw memory) ----------
KBbit["KeyboardState
1 Bit for Space Key"]
space -->|"stored in"| KBbit
%% ---------- Runtime ----------
StateEvent["StateEvent (KeyboardState)
NativeInputSystem.onUpdate"] -->|feeds| OnUpdate["InputManager.OnUpdate()"]
%% ---------- Action state ----------
AState(["InputActionState
NotifyControlStateChanged()"])
trig["triggerStates[]"]
bind["bindingStates[]"]
ctrl["controls[]"]
AState --> trig & bind & ctrl
OnUpdate ==>|"NotifyControlStateChanged()"| AState
KBbit -->|"State Change Monitor"| bind
space -->|"State Change Monitor"| ctrl
%% ---------- Callback out to the scene ----------
trig -->|triggers| IU["InputUser"]
IU -->|"OnActionTriggered()"| PI["PlayerInput
(Scene GameObject)"]
classDef device fill:#e6f0ff,stroke:#4a78c0,color:#000;
classDef state fill:#cfcfcf,stroke:#555,color:#000;
classDef astate fill:#efe9ff,stroke:#7a5ec0,color:#000;
classDef runtime fill:#fdf3d0,stroke:#b9962e,color:#000;
classDef go fill:#d9d9d9,stroke:#666,color:#000;
class Keyboard,space device;
class KBbit state;
class AState,trig,bind,ctrl astate;
class StateEvent,OnUpdate runtime;
class IU,PI go;
The second diagram is for the asset structure:
- An
InputActionAssetcontains maps, actions, and bindings. - At runtime these populate the arrays inside the
InputActionStateshown in the previous diagram (m_State).
flowchart LR
%% ---------- Asset hierarchy ----------
Asset["InputActionAsset: MyGame.inputactions
devices = [ Keyboard ]
bindingMask = { groups: KeyboardMouse }"]
Map["InputActionMap: gameplay"]
Act["InputAction (m_Actions[])
gameplay/jump"]
Bind_a["InputBinding (m_Bindings[])
path: <Keyboard>/space
action: jump — groups: KeyboardMouse"]
Bind_b["InputBinding (m_Bindings[])
path: <Gamepad>/buttonSouth
action: jump — groups: Gamepad"]
Asset --> Map
Map --> Act
Map --> Bind_a & Bind_b
%% ---------- Populates the runtime action state ----------
Act -.->|populates| trig["triggerStates[]"]
Bind_a -.->|populates| bind["bindingStates[]"]
Bind_b -.->|populates| bind
bind -.->|resolves controls| ctrl["controls[]"]
State(["InputActionState
(m_State)"]) --- trig & bind & ctrl
classDef asset fill:#e8f7e8,stroke:#4aa04a,color:#000;
classDef astate fill:#efe9ff,stroke:#7a5ec0,color:#000;
class Asset,Map,Act,Bind_a,Bind_b asset;
class trig,bind,ctrl,State astate;
The high-level Input System code interprets the data in a Device's state buffers by using layouts, which describe the data layout of a Device and its Controls in memory. The Input System creates layouts from either the pre-defined structs of commonly known Devices supplied by the low level system, or dynamically at runtime, as in the case of generic HIDs.
Based on the information in the layouts, the Input System then creates Control representations for each of the Device's controls, which let you read the state of each individual Control in a Device.
As part of the high-level system, you can also build another abstraction layer to map Input Controls to your application mechanics. Use Actions to bind one or more Controls to an input in your application. The Input System then monitors these Controls for state changes, and notifies your game logic using callbacks. You can also specify more complex behaviors for your Actions using Processors (which perform processing on the input data before sending it to you) and Interactions (which let you specify patterns of input on a Control to listen to, such as multi-taps).