Easy Learn C#

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:

  1. Install the package: Window → Package Manager → Unity Registry → "Input System" → Install
  2. Switch to the new system: Edit → Project Settings → Player → Active Input Handling → "Both" or "Input System Package"
  3. Create an Input Action Asset: Right-click in Project window → Create → Input Actions
  4. 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

  1. Separate input from behavior: Decouple input detection from the actions it triggers
  2. Use an input manager class: Centralize input handling for easier maintenance
  3. Test across devices: Verify input works correctly on all target platforms
  4. Add input buffering: Store inputs briefly to improve responsiveness
  5. Provide input rebinding: Allow players to customize controls
  6. Include accessibility options: Support alternative input methods
  7. Add input visualization: Show available controls to players
  8. Test for input conflicts: Ensure multiple inputs don't cause issues
  9. Add input settings: Let players adjust sensitivity and other preferences
  10. Implement fallbacks: Provide alternative input methods if primary ones fail