Input Systems in Unity
Introduction to Unity Input Systems
Input handling is a critical part of any interactive application. Unity provides multiple input systems to accommodate different project needs, from the classic Input Manager to the newer Input System package. Understanding how to effectively handle user input is essential for creating responsive and intuitive games.
In this guide, you'll learn:
- How to use the classic Input Manager
- How to set up and use the new Input System package
- Input handling best practices for different game types
- How to handle multi-platform input (keyboard, controller, touch)
- Creating rebindable controls and input settings
The Classic Input Manager
Unity's traditional input system has been around since the beginning and provides a straightforward way to handle common input scenarios.
Using the Input Manager:
using UnityEngine;
public class ClassicInputExample : MonoBehaviour
{
public float moveSpeed = 5f;
public float jumpForce = 10f;
private Rigidbody rb;
void Start()
{
// Get the Rigidbody component
rb = GetComponent();
// This will be used for physics-based movement
}
void Update()
{
// BUTTON INPUT
// Check for a button press (returns true during the frame the button is first pressed)
if (Input.GetButtonDown("Jump"))
{
// Execute a jump
Jump();
// This detects the moment the jump button is pressed
}
// Check for a button held down (returns true while the button is held)
if (Input.GetButton("Fire1"))
{
// Execute continuous firing
Fire();
// This allows for automatic firing while the button is held
}
// Check for a button release (returns true during the frame the button is released)
if (Input.GetButtonUp("Fire1"))
{
// Stop firing
StopFire();
// This detects when the fire button is released
}
// AXIS INPUT (returns a float between -1 and 1)
// Get horizontal input (A/D, Left/Right arrows, or gamepad)
float horizontalInput = Input.GetAxis("Horizontal");
// Get vertical input (W/S, Up/Down arrows, or gamepad)
float verticalInput = Input.GetAxis("Vertical");
// Create a movement vector
Vector3 movement = new Vector3(horizontalInput, 0f, verticalInput);
// Apply movement
transform.Translate(movement * moveSpeed * Time.deltaTime);
// This moves the character based on axis input
// KEY INPUT
// Check for specific keyboard keys
if (Input.GetKeyDown(KeyCode.R))
{
// Reload weapon
Reload();
// This detects when the R key is pressed
}
// MOUSE INPUT
// Get mouse position in screen coordinates
Vector3 mousePos = Input.mousePosition;
// Convert to world position (for 3D)
Vector3 worldPos = Camera.main.ScreenToWorldPoint(new Vector3(
mousePos.x, mousePos.y, Camera.main.transform.position.y));
// Get mouse button input
if (Input.GetMouseButtonDown(0)) // 0 = left, 1 = right, 2 = middle
{
// Handle left click
Debug.Log("Left mouse button clicked at: " + worldPos);
}
// Get mouse scroll wheel
float scrollWheel = Input.GetAxis("Mouse ScrollWheel");
if (scrollWheel != 0)
{
// Zoom camera or change weapon
Zoom(scrollWheel);
}
}
// Example methods that would be implemented in a real game
void Jump()
{
Debug.Log("Jump button pressed");
// Add upward force to the Rigidbody
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
void Fire()
{
Debug.Log("Firing weapon");
// Weapon firing logic would go here
}
void StopFire()
{
Debug.Log("Stopped firing");
// Logic to stop firing would go here
}
void Reload()
{
Debug.Log("Reloading weapon");
// Weapon reload logic would go here
}
void Zoom(float amount)
{
Debug.Log("Zooming by amount: " + amount);
// Camera zoom logic would go here
}
}
The classic Input Manager features:
- Predefined input axes and buttons that can be configured in Edit → Project Settings → Input Manager
- GetButton/GetButtonDown/GetButtonUp for digital button input
- GetAxis/GetAxisRaw for analog input with or without smoothing
- GetKey/GetKeyDown/GetKeyUp for keyboard-specific input
- GetMouseButton/GetMouseButtonDown/GetMouseButtonUp for mouse buttons
- mousePosition for cursor coordinates
Input Manager Configuration
The Input Manager is configured in Edit → Project Settings → Input Manager. Default configurations include:
- Horizontal: A/D keys, left/right arrows, and gamepad left stick X
- Vertical: W/S keys, up/down arrows, and gamepad left stick Y
- Fire1: Left Ctrl, mouse button 0, gamepad button 0
- Jump: Space key, gamepad button 0
- Mouse X/Y: Mouse movement
- Submit: Enter key, gamepad button 0
- Cancel: Escape key, gamepad button 1
You can add, edit, or remove these inputs to match your game's requirements.
The New Input System Package
Unity's new Input System is a package that provides a more flexible, modern approach to input handling with support for a wider range of devices, action mapping, and input rebinding.
Setting Up the New Input System:
- Install the package: Window → Package Manager → Unity Registry → "Input System" → Install
- Switch to the new system: Edit → Project Settings → Player → Active Input Handling → "Both" or "Input System Package"
- Create an Input Action Asset: Right-click in Project window → Create → Input Actions
- Configure your input actions: Double-click the asset to open the Input Actions editor
Using the New Input System:
using UnityEngine;
using UnityEngine.InputSystem; // Requires the Input System package
public class NewInputSystemExample : MonoBehaviour
{
// Reference to the generated C# class from your Input Actions asset
private PlayerControls controls;
private Vector2 moveInput;
private bool isJumping;
public float moveSpeed = 5f;
public float jumpForce = 10f;
private Rigidbody rb;
void Awake()
{
// Initialize the controls
controls = new PlayerControls();
// Set up callbacks for control events
// Movement input callback (performed = while action is active)
controls.Gameplay.Move.performed += ctx => moveInput = ctx.ReadValue();
controls.Gameplay.Move.canceled += ctx => moveInput = Vector2.zero;
// Jump input callbacks (started = when action begins)
controls.Gameplay.Jump.started += ctx => OnJumpPerformed();
controls.Gameplay.Jump.canceled += ctx => isJumping = false;
// Fire callback with alternate syntax
controls.Gameplay.Fire.performed += OnFirePerformed;
// Get the Rigidbody component
rb = GetComponent();
}
// Enable controls when the GameObject is enabled
void OnEnable()
{
controls.Gameplay.Enable();
}
// Disable controls when the GameObject is disabled
void OnDisable()
{
controls.Gameplay.Disable();
}
void Update()
{
// Apply movement based on input
Vector3 movement = new Vector3(moveInput.x, 0f, moveInput.y);
transform.Translate(movement * moveSpeed * Time.deltaTime);
}
// Jump callback method
void OnJumpPerformed()
{
Debug.Log("Jump performed");
isJumping = true;
// Add upward force to the Rigidbody
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
// Fire callback method
void OnFirePerformed(InputAction.CallbackContext context)
{
// The context parameter contains information about the input event
Debug.Log("Fire performed with value: " + context.ReadValue());
// Get the phase of the input action
InputActionPhase phase = context.phase;
Debug.Log("Phase: " + phase.ToString());
// Fire logic would go here
}
// Example of handling control scheme changes
void OnControlsChanged(PlayerInput input)
{
if (input.currentControlScheme == "Keyboard&Mouse")
{
Debug.Log("Switched to Keyboard and Mouse");
}
else if (input.currentControlScheme == "Gamepad")
{
Debug.Log("Switched to Gamepad");
}
}
}
// Auto-generated code from the Input Actions asset would define PlayerControls
// This is just an example structure of what it would look like
/*
public class PlayerControls : IInputActionCollection
{
public InputActionAsset asset { get; }
public InputActionMap Gameplay;
public InputAction Move;
public InputAction Jump;
public InputAction Fire;
// Methods to enable/disable action maps
public void Enable() { ... }
public void Disable() { ... }
}
*/
The new Input System advantages:
- Device-agnostic input: Design actions rather than specific device inputs
- Action Maps: Group actions by context (e.g., Gameplay, UI, Menus)
- Binding Composites: Complex bindings (e.g., WASD for 2D movement)
- Control Schemes: Define different input methods (Keyboard/Mouse, Gamepad)
- Runtime Rebinding: Allow players to change control bindings
- Event-driven architecture: Use callbacks for cleaner code
Input Rebinding Example:
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
public class InputRebindingExample : MonoBehaviour
{
public PlayerControls controls;
public InputActionReference jumpAction;
public Text currentBindingText;
public Button rebindButton;
private InputActionRebindingExtensions.RebindingOperation rebindOperation;
void Awake()
{
controls = new PlayerControls();
}
void OnEnable()
{
controls.Enable();
// Update the display of the current binding
UpdateBindingDisplay();
// Set up the rebind button
if (rebindButton != null)
{
rebindButton.onClick.AddListener(StartRebinding);
}
}
void OnDisable()
{
controls.Disable();
// Clean up the rebind button
if (rebindButton != null)
{
rebindButton.onClick.RemoveListener(StartRebinding);
}
// Clean up any ongoing rebinding operation
rebindOperation?.Dispose();
rebindOperation = null;
}
void UpdateBindingDisplay()
{
if (currentBindingText != null && jumpAction != null)
{
// Get the path of the current binding for display
string bindingPath = jumpAction.action.bindings[0].effectivePath;
string displayString = InputControlPath.ToHumanReadableString(bindingPath);
currentBindingText.text = "Jump: " + displayString;
}
}
void StartRebinding()
{
if (jumpAction == null)
return;
// Disable the action while rebinding
jumpAction.action.Disable();
// Start the rebinding process
rebindOperation = jumpAction.action.PerformInteractiveRebinding()
// Configure to ignore mouse movement
.WithControlsExcluding("Mouse")
// Set callbacks
.OnComplete(operation => RebindComplete())
.OnCancel(operation => RebindComplete())
// Start the rebinding
.Start();
}
void RebindComplete()
{
// Clean up the rebinding operation
rebindOperation.Dispose();
rebindOperation = null;
// Re-enable the action
jumpAction.action.Enable();
// Update the display with the new binding
UpdateBindingDisplay();
// Save the new bindings (to PlayerPrefs, for example)
SaveBindings();
}
void SaveBindings()
{
// Convert the bindings to JSON
string rebinds = controls.asset.SaveBindingOverridesAsJson();
// Save to PlayerPrefs
PlayerPrefs.SetString("inputBindings", rebinds);
PlayerPrefs.Save();
}
void LoadBindings()
{
// Check if we have saved bindings
if (PlayerPrefs.HasKey("inputBindings"))
{
// Get the saved JSON
string rebinds = PlayerPrefs.GetString("inputBindings");
// Apply the bindings to the action asset
controls.asset.LoadBindingOverridesFromJson(rebinds);
}
}
}
Touch Input for Mobile Games
Mobile games require specific input handling for touch screens. Both input systems provide ways to handle touch input, but with different approaches.
Touch Input with Classic Input Manager:
using UnityEngine;
using System.Collections.Generic;
public class ClassicTouchInput : MonoBehaviour
{
// For tracking active touches
private Dictionary activeTouches = new Dictionary();
// Movement speed for objects
public float moveSpeed = 5f;
// Reference to the object being controlled
public Transform controlledObject;
void Update()
{
// Get all current touches
if (Input.touchCount > 0)
{
// Loop through all touches
for (int i = 0; i < Input.touchCount; i++)
{
Touch touch = Input.GetTouch(i);
// Handle different touch phases
switch (touch.phase)
{
case TouchPhase.Began:
// New touch started
OnTouchBegan(touch);
break;
case TouchPhase.Moved:
case TouchPhase.Stationary:
// Touch moving or held in place
OnTouchMoved(touch);
break;
case TouchPhase.Ended:
case TouchPhase.Canceled:
// Touch finished
OnTouchEnded(touch);
break;
}
}
}
// Handle multi-touch gestures
HandlePinchZoom();
}
void OnTouchBegan(Touch touch)
{
// Store initial touch position
activeTouches[touch.fingerId] = touch.position;
// Example: Raycast to detect object at touch position
Ray ray = Camera.main.ScreenPointToRay(touch.position);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
Debug.Log("Touched object: " + hit.collider.gameObject.name);
// Select the object that was touched
SelectObject(hit.collider.gameObject);
}
}
void OnTouchMoved(Touch touch)
{
// Update touch position
activeTouches[touch.fingerId] = touch.position;
// If we have a controlled object and this is the first finger
if (controlledObject != null && touch.fingerId == 0)
{
// Move the object based on touch delta
Vector3 worldDelta = Camera.main.ScreenToWorldPoint(new Vector3(
touch.deltaPosition.x, touch.deltaPosition.y, 10));
// Apply movement
controlledObject.Translate(
worldDelta.x * moveSpeed * Time.deltaTime,
worldDelta.y * moveSpeed * Time.deltaTime,
0);
}
}
void OnTouchEnded(Touch touch)
{
// Remove the touch from our dictionary
if (activeTouches.ContainsKey(touch.fingerId))
{
activeTouches.Remove(touch.fingerId);
}
// If this was the controlling touch, release the object
if (touch.fingerId == 0)
{
ReleaseObject();
}
}
void HandlePinchZoom()
{
// Need at least two touches for pinch
if (activeTouches.Count < 2)
return;
// Get the first two touches
Touch touch1 = Input.GetTouch(0);
Touch touch2 = Input.GetTouch(1);
// Find the previous positions
Vector2 touch1PrevPos = activeTouches[touch1.fingerId];
Vector2 touch2PrevPos = activeTouches[touch2.fingerId];
// Calculate the previous and current distance between touches
float prevDistance = Vector2.Distance(touch1PrevPos, touch2PrevPos);
float currentDistance = Vector2.Distance(touch1.position, touch2.position);
// Calculate the zoom factor
float zoomFactor = currentDistance / prevDistance;
// Apply zoom
if (Mathf.Abs(zoomFactor - 1) > 0.01f) // Small threshold to avoid jitter
{
ApplyZoom(zoomFactor);
}
}
// Example methods that would be implemented in a real game
void SelectObject(GameObject obj)
{
Debug.Log("Selected: " + obj.name);
// Object selection logic
}
void ReleaseObject()
{
Debug.Log("Released object");
// Object release logic
}
void ApplyZoom(float zoomFactor)
{
Debug.Log("Zoom: " + zoomFactor);
// Camera zoom logic
}
}
Touch input features:
- Input.touchCount: Number of current touches
- Input.GetTouch(index): Get a specific touch
- Touch phases: Began, Moved, Stationary, Ended, Canceled
- Touch properties: position, deltaPosition, fingerId, tapCount
Common Mobile Input Gestures
Common touch gestures to implement in mobile games:
- Tap: Quick touch and release for selection
- Double Tap: Two taps in quick succession
- Long Press: Touch and hold for a duration
- Drag: Touch, hold, and move
- Swipe: Quick movement in a direction
- Pinch: Two-finger movement for zoom
- Rotate: Two-finger circular movement
Best Practices for Input Handling
- Separate input from behavior: Decouple input detection from the actions it triggers
- Use an input manager class: Centralize input handling for easier maintenance
- Test across devices: Verify input works correctly on all target platforms
- Add input buffering: Store inputs briefly to improve responsiveness
- Provide input rebinding: Allow players to customize controls
- Include accessibility options: Support alternative input methods
- Add input visualization: Show available controls to players
- Test for input conflicts: Ensure multiple inputs don't cause issues
- Add input settings: Let players adjust sensitivity and other preferences
- Implement fallbacks: Provide alternative input methods if primary ones fail