From unity
Unity 6 Input System guide. Use when handling player input, controls, gamepad, keyboard, mouse, touch, or XR controllers. Covers the new Input System package (recommended), Input Actions, Action Maps, Control Schemes, PlayerInput component, and input debugging. Based on Unity 6.3 LTS documentation.
npx claudepluginhub cdata/aria-skills --plugin unityThis skill uses the workspace's default tool permissions.
Unity provides two input systems:
Unity New Input System correctness patterns. Catches common mistakes with action reading (triggered vs IsPressed vs WasPressedThisFrame), action map switching, rebinding persistence, InputValue lifetime, PassThrough vs Value, local multiplayer device assignment, and control scheme auto-switching. PATTERN format: WHEN/WRONG/RIGHT/GOTCHA. Based on Unity 6.3 LTS.
Implements Unity input with explicit action maps, device support, and UI/gameplay separation. Use when supporting multiple devices, rebinding, or accessibility in Unity projects.
Guides Godot 4.3+ input handling with InputEvent system, Input Map actions, controllers/gamepads, mouse/touch, action rebinding, and architecture.
Share bugs, ideas, or general feedback.
Unity provides two input systems:
| Feature | New Input System (Recommended) | Legacy Input Manager |
|---|---|---|
| Package | com.unity.inputsystem (v1.19.0 for Unity 6.3) | Built-in (UnityEngine.Input) |
| Architecture | Action-based, event-driven | Polling-based |
| Device Support | Gamepad, keyboard, mouse, touch, XR, custom | Keyboard, mouse, joystick |
| Multiplayer | Built-in local multiplayer via PlayerInput | Manual implementation |
| Rebinding | Runtime rebinding support | Not supported |
| Cross-platform | Control Schemes per device type | Manual per-platform code |
The new Input System is a package installed via Package Manager. It replaces the legacy Input.GetKey/Input.GetAxis API with an action-based model that separates input purpose from device controls.
Namespace: UnityEngine.InputSystem
Install via Window > Package Manager > Unity Registry > Input System.
Go to Edit > Project Settings > Input System Package > Input Actions and click "Create and assign a default project-wide Action Asset".
This creates default Action Maps:
Each action includes bindings for keyboard, gamepad, XR controllers, and touchscreen.
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
InputAction moveAction;
InputAction jumpAction;
void Start()
{
moveAction = InputSystem.actions.FindAction("Move");
jumpAction = InputSystem.actions.FindAction("Jump");
}
void Update()
{
Vector2 moveValue = moveAction.ReadValue<Vector2>();
transform.Translate(new Vector3(moveValue.x, 0, moveValue.y) * Time.deltaTime * 5f);
if (jumpAction.IsPressed())
{
// Jump logic
}
}
}
When multiple actions share names across maps, specify both: "Player/Move".
Method 1 — Input Actions Editor (Recommended): Configure via Project Settings > Input System Package.
Method 2 — MonoBehaviour fields: Declare InputAction fields directly in scripts (configurable in Inspector).
Method 3 — Code:
var moveAction = new InputAction("Move", InputActionType.Value);
moveAction.AddCompositeBinding("Dpad")
.With("Up", "<Keyboard>/w").With("Down", "<Keyboard>/s")
.With("Left", "<Keyboard>/a").With("Right", "<Keyboard>/d");
moveAction.Enable();
Method 4 — JSON: var map = InputActionMap.FromJson(jsonString);
Actions begin disabled. You must call .Enable() before they respond to input. You cannot modify bindings while enabled; call .Disable() first.
These are JSON files storing actions, bindings, and control schemes. Create via Assets > Create > Input Actions.
Auto-generated C# class: Enable "Generate C# Class" in the asset's importer to get type-safe access:
public class MyPlayerScript : MonoBehaviour, IGameplayActions
{
MyPlayerControls controls;
public void OnEnable()
{
controls = new MyPlayerControls();
controls.gameplay.SetCallbacks(this);
controls.gameplay.Enable();
}
public void OnDisable()
{
controls.gameplay.Disable();
}
public void OnMove(InputAction.CallbackContext context)
{
Vector2 move = context.ReadValue<Vector2>();
// Handle movement
}
}
The PlayerInput component maps Input Actions to script methods and handles local multiplayer with device filtering and screen-splitting.
PlayerInput component to your player GameObject| Behavior | Mechanism | Best For |
|---|---|---|
| Send Messages | GameObject.SendMessage() | Simple prototyping |
| Broadcast Messages | BroadcastMessage() down hierarchy | Component hierarchies |
| Invoke Unity Events | Inspector-configured event routing | Designer-friendly wiring |
| Invoke C# Events | onActionTriggered, onDeviceLost, onDeviceRegained | Programmer control |
public class PlayerActions : MonoBehaviour
{
public void OnJump()
{
// Called when Jump action triggers (no parameters)
}
public void OnMove(InputValue value)
{
Vector2 v = value.Get<Vector2>();
// InputValue is only valid during this callback
}
}
public void OnFire(InputAction.CallbackContext context)
{
if (context.performed)
{
// Fire logic
}
}
PlayerInput playerInput = GetComponent<PlayerInput>();
playerInput.DeactivateInput(); // Disable all input
playerInput.ActivateInput(); // Re-enable with default map
playerInput.SwitchCurrentActionMap("Vehicle"); // Switch action map
// 2D movement axis
Vector2 move = moveAction.ReadValue<Vector2>();
// Button state
if (jumpAction.IsPressed()) { /* held down */ }
if (jumpAction.WasPressedThisFrame()) { /* just pressed */ }
if (jumpAction.WasReleasedThisFrame()) { /* just released */ }
void OnEnable()
{
fireAction.started += OnFireStarted;
fireAction.performed += OnFirePerformed;
fireAction.canceled += OnFireCanceled;
fireAction.Enable();
}
void OnDisable()
{
fireAction.started -= OnFireStarted;
fireAction.performed -= OnFirePerformed;
fireAction.canceled -= OnFireCanceled;
fireAction.Disable();
}
void OnFireStarted(InputAction.CallbackContext ctx) { /* Input began */ }
void OnFirePerformed(InputAction.CallbackContext ctx) { /* Completed */ }
void OnFireCanceled(InputAction.CallbackContext ctx) { /* Interrupted */ }
using UnityEngine.InputSystem;
// Current gamepad (null if none connected)
var gp = Gamepad.current;
if (gp == null) return;
Vector2 leftStick = gp.leftStick.ReadValue();
bool southPressed = gp.buttonSouth.isPressed;
// Access by enum
gp[GamepadButton.LeftShoulder].isPressed;
gp[GamepadButton.Y].isPressed; // Xbox style
gp[GamepadButton.Triangle].isPressed; // PlayStation style
// Rumble / Haptics
gp.SetMotorSpeeds(0.25f, 0.75f); // low-frequency, high-frequency
gp.ResetHaptics();
// Global haptics control
InputSystem.PauseHaptics();
InputSystem.ResumeHaptics();
InputSystem.ResetHaptics();
Supported gamepads: DualShock 3/4, DualSense, Xbox (XInput/Bluetooth), Switch Pro. Generic HID gamepads appear as generic joysticks, not Gamepad devices.
var kb = Keyboard.current;
if (kb.spaceKey.wasPressedThisFrame) { /* space pressed */ }
var mouse = Mouse.current;
Vector2 mousePos = mouse.position.ReadValue();
float scroll = mouse.scroll.ReadValue().y;
bool leftClick = mouse.leftButton.isPressed;
Two API levels:
Low-level — Touchscreen device:
var ts = Touchscreen.current;
if (ts.primaryTouch.press.isPressed)
{
Vector2 pos = ts.primaryTouch.position.ReadValue();
}
High-level — EnhancedTouch API:
using UnityEngine.InputSystem.EnhancedTouch;
void OnEnable() => EnhancedTouchSupport.Enable();
void OnDisable() => EnhancedTouchSupport.Disable();
void Update()
{
foreach (var touch in Touch.activeTouches)
{
Debug.Log($"{touch.touchId}: {touch.screenPosition}, {touch.phase}");
}
}
Touch phases: Began, Moved, Stationary, Ended, Cancelled.
Multi-touch with Actions: Bind <Touchscreen>/touch*/press and set action type to PassThrough to receive callbacks per touch.
Touch simulation: Enable via TouchSimulation.Enable() to simulate touch from mouse/pen during development.
// Monitor device connections
InputSystem.onDeviceChange += (device, change) =>
{
if (change == InputDeviceChange.Added)
Debug.Log($"Device connected: {device.displayName}");
};
Interactions define input patterns that drive action phases.
| Phase | Meaning |
|---|---|
| Waiting | Awaiting input |
| Started | Input received, not yet complete |
| Performed | Interaction complete — primary response trigger |
| Canceled | Interaction interrupted |
| Interaction | Description | Key Parameters |
|---|---|---|
| Default | Auto-applied; behavior varies by action type | — |
| Press | Explicit button press behavior | pressPoint, behavior (PressOnly/ReleaseOnly/PressAndRelease) |
| Hold | Sustained press for minimum duration | duration, pressPoint |
| Tap | Quick press-and-release | duration, pressPoint |
| SlowTap | Hold then release | duration, pressPoint |
| MultiTap | Repeated tap sequences | tapTime, tapDelay, tapCount, pressPoint |
var action = new InputAction("fire");
action.AddBinding("<Gamepad>/buttonSouth")
.WithInteractions("tap(duration=0.8)");
var fireAction = new InputAction("fire");
fireAction.AddBinding("<Gamepad>/buttonSouth").WithInteractions("tap,slowTap");
fireAction.started += ctx => { if (ctx.interaction is SlowTapInteraction) ShowChargingUI(); };
fireAction.performed += ctx => {
if (ctx.interaction is SlowTapInteraction) ChargedFire(); else Fire();
};
fireAction.canceled += _ => HideChargingUI();
fireAction.Enable();
Implement IInputInteraction, then register: InputSystem.RegisterInteraction<T>(). See references/input-system-api.md for full example.
public class GameStateManager : MonoBehaviour
{
PlayerInput playerInput;
void Start() => playerInput = GetComponent<PlayerInput>();
public void EnterMenu() => playerInput.SwitchCurrentActionMap("UI");
public void ExitMenu() => playerInput.SwitchCurrentActionMap("Player");
}
var rebindOp = action.PerformInteractiveRebinding()
.WithControlsExcluding("Mouse")
.OnMatchWaitForAnother(0.1f)
.OnComplete(op =>
{
Debug.Log($"Rebound to: {op.action.bindings[0].effectivePath}");
op.Dispose();
})
.Start();
var moveAction = new InputAction("Move", InputActionType.Value);
moveAction.AddCompositeBinding("2DVector")
.With("Up", "<Keyboard>/w").With("Down", "<Keyboard>/s")
.With("Left", "<Keyboard>/a").With("Right", "<Keyboard>/d");
moveAction.AddCompositeBinding("2DVector")
.With("Up", "<Keyboard>/upArrow").With("Down", "<Keyboard>/downArrow")
.With("Left", "<Keyboard>/leftArrow").With("Right", "<Keyboard>/rightArrow");
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
Using Input.GetKey() (legacy) | Not compatible with new Input System; no rebinding, no multi-device | Use InputAction with bindings |
Reading actions without .Enable() | Actions start disabled and return no values | Always call action.Enable() in OnEnable() |
Forgetting .Disable() on cleanup | Memory leaks and ghost callbacks | Call action.Disable() in OnDisable() |
| Modifying bindings while action is enabled | Throws exception | Call .Disable() before modifying, then .Enable() |
| Hardcoding device paths in gameplay code | Breaks cross-platform support | Use Actions + Control Schemes instead |
Using InputValue outside its callback | Value is only valid during the callback frame | Copy the value to a field immediately |
Accessing Gamepad.current without null check | Crashes if no gamepad connected | Always check if (Gamepad.current == null) return; |
| Not unsubscribing from action callbacks | Causes errors on scene reload | Unsubscribe in OnDisable() |
Using SendMessages behavior in production | Performance overhead from reflection | Use Invoke Unity Events or Invoke C# Events |
Polling EnhancedTouch without enabling it | Returns empty collections | Call EnhancedTouchSupport.Enable() first |
| API | Purpose |
|---|---|
InputSystem.actions | Project-wide actions |
InputSystem.actions.FindAction("name") | Find action by name |
action.ReadValue<T>() | Read current value |
action.IsPressed() | Button held check |
action.WasPressedThisFrame() | Button just pressed |
action.WasReleasedThisFrame() | Button just released |
action.Enable() / action.Disable() | Activate/deactivate |
action.started / performed / canceled | Callback events |
action.AddBinding("path") | Add binding in code |
action.AddCompositeBinding("type") | Add composite (Dpad, 2DVector) |
.WithInteractions("tap(duration=0.5)") | Add interaction to binding |
Gamepad.current | Current gamepad reference |
Keyboard.current / Mouse.current | Current keyboard/mouse |
Touchscreen.current | Current touchscreen |
EnhancedTouchSupport.Enable() | Enable enhanced touch API |
Touch.activeTouches | All active touches (enhanced) |
InputSystem.onDeviceChange | Device connection events |
PlayerInput.SwitchCurrentActionMap() | Change active action map |
action.PerformInteractiveRebinding() | Start runtime rebinding |
InputSystemUIInputModule