Input Setup

The foundation for all Code Bank scripts. Set this up once, and all other scaffolds will work with both gamepad and keyboard+mouse automatically.

Do this first! All Code Bank scripts expect this input setup. Complete this page before using any other scaffold.
Practice - what you do

Step 1: Create the Input Actions Asset

  1. In Unity: Assets → Create → Input Actions
  2. Name it GameInputActions
  3. Double-click to open the Input Actions editor

Step 2: Create Action Maps

We'll create two action maps: Gameplay (movement, actions) and UI (menus).

  1. Click + next to "Action Maps"
  2. Name it Gameplay
  3. Click + again, name it UI

Step 3: Add Actions to Gameplay Map

Select the Gameplay action map, then add these actions:

Action Name Action Type Control Type Purpose
Move Value Vector2 WASD / Left Stick movement
Look Value Vector2 Mouse / Right Stick camera
Jump Button - Space / South Button (A/X)
Dash Button - Shift / Right Trigger
Attack Button - Left Click / West Button (X/Square)
Interact Button - E / East Button (B/Circle)

Step 4: Add Bindings

For each action, add bindings for keyboard+mouse AND gamepad:

Move Action Bindings:

  1. Select Move action
  2. Click + → Add 2D Vector Composite → name it "WASD"
  3. Bind: Up=W, Down=S, Left=A, Right=D
  4. Click + → Add Binding → path: Gamepad/leftStick

Look Action Bindings:

  1. Select Look action
  2. Click + → Add Binding → path: Mouse/delta
  3. Click + → Add Binding → path: Gamepad/rightStick

Jump Action Bindings:

  1. Select Jump action
  2. Click + → Add Binding → path: Keyboard/space
  3. Click + → Add Binding → path: Gamepad/buttonSouth

Dash Action Bindings:

  1. Select Dash action
  2. Click + → Add Binding → path: Keyboard/leftShift
  3. Click + → Add Binding → path: Gamepad/rightTrigger

Attack Action Bindings:

  1. Select Attack action
  2. Click + → Add Binding → path: Mouse/leftButton
  3. Click + → Add Binding → path: Gamepad/buttonWest

Interact Action Bindings:

  1. Select Interact action
  2. Click + → Add Binding → path: Keyboard/e
  3. Click + → Add Binding → path: Gamepad/buttonEast

Step 5: Save and Generate C# Class

  1. Click Save Asset in the Input Actions window
  2. Select your GameInputActions asset in the Project window
  3. In the Inspector, check Generate C# Class
  4. Click Apply

Unity generates GameInputActions.cs - you won't edit this directly.

Step 6: Create the InputReader Script

This is the bridge between Unity's Input System and your gameplay scripts. All Code Bank scripts read from this.

Create a new C# script called InputReader.cs:

InputReader.cs
// ============================================================
// InputReader.cs
// Central input hub - all gameplay scripts read from this.
// Handles gamepad, keyboard, and mouse seamlessly.
// Unity 6.x + New Input System
// ============================================================

using UnityEngine;
using UnityEngine.InputSystem;
using System;

/// <summary>
/// Reads input from the New Input System and exposes it
/// in a clean, device-agnostic way. Attach to a GameObject
/// and reference from other scripts.
/// </summary>
public class InputReader : MonoBehaviour
{
    // ========================================================
    // SINGLETON (optional - convenient for prototyping)
    // ========================================================

    public static InputReader Instance { get; private set; }

    [Header("Settings")]
    [Tooltip("Use singleton pattern for easy global access")]
    [SerializeField] private bool useSingleton = true;

    [Tooltip("Mouse look sensitivity multiplier")]
    [SerializeField] [Range(0.1f, 5f)] private float mouseSensitivity = 1f;

    [Tooltip("Gamepad look sensitivity multiplier")]
    [SerializeField] [Range(0.1f, 10f)] private float gamepadLookSensitivity = 3f;

    // ========================================================
    // INPUT VALUES - Read these from other scripts
    // ========================================================

    /// <summary>
    /// Movement input as Vector2 (-1 to 1 on each axis)
    /// WASD or Left Stick
    /// </summary>
    public Vector2 MoveInput { get; private set; }

    /// <summary>
    /// Look/camera input as Vector2
    /// Mouse delta or Right Stick (sensitivity-adjusted)
    /// </summary>
    public Vector2 LookInput { get; private set; }

    /// <summary>
    /// True while jump button is held
    /// </summary>
    public bool JumpHeld { get; private set; }

    /// <summary>
    /// True while dash button is held
    /// </summary>
    public bool DashHeld { get; private set; }

    /// <summary>
    /// True while attack button is held
    /// </summary>
    public bool AttackHeld { get; private set; }

    /// <summary>
    /// True while interact button is held
    /// </summary>
    public bool InteractHeld { get; private set; }

    // ========================================================
    // EVENTS - Subscribe for button press/release
    // ========================================================

    /// <summary>Fired when jump is pressed</summary>
    public event Action OnJumpPressed;

    /// <summary>Fired when jump is released</summary>
    public event Action OnJumpReleased;

    /// <summary>Fired when dash is pressed</summary>
    public event Action OnDashPressed;

    /// <summary>Fired when dash is released</summary>
    public event Action OnDashReleased;

    /// <summary>Fired when attack is pressed</summary>
    public event Action OnAttackPressed;

    /// <summary>Fired when attack is released</summary>
    public event Action OnAttackReleased;

    /// <summary>Fired when interact is pressed</summary>
    public event Action OnInteractPressed;

    // ========================================================
    // INTERNAL
    // ========================================================

    private GameInputActions inputActions;
    private bool isGamepad = false;

    // ========================================================
    // UNITY LIFECYCLE
    // ========================================================

    void Awake()
    {
        // Singleton setup
        if (useSingleton)
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
                return;
            }
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }

        // Create input actions
        inputActions = new GameInputActions();
    }

    void OnEnable()
    {
        inputActions.Gameplay.Enable();

        // Subscribe to actions
        inputActions.Gameplay.Move.performed += OnMove;
        inputActions.Gameplay.Move.canceled += OnMove;

        inputActions.Gameplay.Look.performed += OnLook;
        inputActions.Gameplay.Look.canceled += OnLook;

        inputActions.Gameplay.Jump.performed += OnJump;
        inputActions.Gameplay.Jump.canceled += OnJumpCancel;

        inputActions.Gameplay.Dash.performed += OnDash;
        inputActions.Gameplay.Dash.canceled += OnDashCancel;

        inputActions.Gameplay.Attack.performed += OnAttack;
        inputActions.Gameplay.Attack.canceled += OnAttackCancel;

        inputActions.Gameplay.Interact.performed += OnInteract;

        // Track device changes
        InputSystem.onActionChange += OnActionChange;
    }

    void OnDisable()
    {
        inputActions.Gameplay.Disable();

        inputActions.Gameplay.Move.performed -= OnMove;
        inputActions.Gameplay.Move.canceled -= OnMove;

        inputActions.Gameplay.Look.performed -= OnLook;
        inputActions.Gameplay.Look.canceled -= OnLook;

        inputActions.Gameplay.Jump.performed -= OnJump;
        inputActions.Gameplay.Jump.canceled -= OnJumpCancel;

        inputActions.Gameplay.Dash.performed -= OnDash;
        inputActions.Gameplay.Dash.canceled -= OnDashCancel;

        inputActions.Gameplay.Attack.performed -= OnAttack;
        inputActions.Gameplay.Attack.canceled -= OnAttackCancel;

        inputActions.Gameplay.Interact.performed -= OnInteract;

        InputSystem.onActionChange -= OnActionChange;
    }

    // ========================================================
    // INPUT HANDLERS
    // ========================================================

    private void OnMove(InputAction.CallbackContext ctx)
    {
        MoveInput = ctx.ReadValue<Vector2>();
    }

    private void OnLook(InputAction.CallbackContext ctx)
    {
        Vector2 raw = ctx.ReadValue<Vector2>();

        // Apply appropriate sensitivity based on device
        float sensitivity = isGamepad ? gamepadLookSensitivity : mouseSensitivity;
        LookInput = raw * sensitivity;
    }

    private void OnJump(InputAction.CallbackContext ctx)
    {
        JumpHeld = true;
        OnJumpPressed?.Invoke();
    }

    private void OnJumpCancel(InputAction.CallbackContext ctx)
    {
        JumpHeld = false;
        OnJumpReleased?.Invoke();
    }

    private void OnDash(InputAction.CallbackContext ctx)
    {
        DashHeld = true;
        OnDashPressed?.Invoke();
    }

    private void OnDashCancel(InputAction.CallbackContext ctx)
    {
        DashHeld = false;
        OnDashReleased?.Invoke();
    }

    private void OnAttack(InputAction.CallbackContext ctx)
    {
        AttackHeld = true;
        OnAttackPressed?.Invoke();
    }

    private void OnAttackCancel(InputAction.CallbackContext ctx)
    {
        AttackHeld = false;
        OnAttackReleased?.Invoke();
    }

    private void OnInteract(InputAction.CallbackContext ctx)
    {
        InteractHeld = true;
        OnInteractPressed?.Invoke();
        // Auto-release interact after one frame
        Invoke(nameof(ReleaseInteract), 0.1f);
    }

    private void ReleaseInteract()
    {
        InteractHeld = false;
    }

    // ========================================================
    // DEVICE DETECTION
    // ========================================================

    private void OnActionChange(object obj, InputActionChange change)
    {
        if (change == InputActionChange.ActionPerformed)
        {
            var action = obj as InputAction;
            if (action?.activeControl?.device != null)
            {
                isGamepad = action.activeControl.device is Gamepad;
            }
        }
    }

    /// <summary>
    /// Returns true if current input device is a gamepad
    /// </summary>
    public bool IsUsingGamepad => isGamepad;

    // ========================================================
    // PUBLIC METHODS
    // ========================================================

    /// <summary>
    /// Enable gameplay input (call when unpausing)
    /// </summary>
    public void EnableGameplayInput()
    {
        inputActions.Gameplay.Enable();
    }

    /// <summary>
    /// Disable gameplay input (call when pausing/in menus)
    /// </summary>
    public void DisableGameplayInput()
    {
        inputActions.Gameplay.Disable();
        MoveInput = Vector2.zero;
        LookInput = Vector2.zero;
    }
}

// ============================================================
// USAGE:
//
// 1. Create empty GameObject, add InputReader component
// 2. Other scripts read input like this:
//
//    // Option A: Using singleton (quick prototyping)
//    Vector2 move = InputReader.Instance.MoveInput;
//    if (InputReader.Instance.JumpHeld) { ... }
//
//    // Option B: Using reference (better for production)
//    [SerializeField] private InputReader input;
//    Vector2 move = input.MoveInput;
//
//    // Option C: Subscribe to events
//    void OnEnable() {
//        InputReader.Instance.OnJumpPressed += HandleJump;
//    }
//    void OnDisable() {
//        InputReader.Instance.OnJumpPressed -= HandleJump;
//    }
//
// 3. Works automatically with both keyboard+mouse and gamepad!
// ============================================================

Step 7: Add InputReader to Your Scene

  1. Create empty GameObject: GameObject → Create Empty
  2. Name it InputReader
  3. Add the InputReader component
  4. It will persist across scenes if useSingleton is checked
Done! All Code Bank scripts can now read from InputReader.Instance and will automatically work with gamepad or keyboard+mouse.

Quick Reference: Reading Input

Quick Reference
// Movement (WASD or Left Stick)
Vector2 moveInput = InputReader.Instance.MoveInput;
float horizontal = moveInput.x;  // -1 to 1
float vertical = moveInput.y;    // -1 to 1

// Camera (Mouse or Right Stick)
Vector2 lookInput = InputReader.Instance.LookInput;

// Buttons - check if held
if (InputReader.Instance.JumpHeld) { }
if (InputReader.Instance.DashHeld) { }
if (InputReader.Instance.AttackHeld) { }

// Buttons - subscribe to press events
InputReader.Instance.OnJumpPressed += MyJumpHandler;
InputReader.Instance.OnJumpReleased += MyJumpReleaseHandler;

Perspective-Specific Notes

2D Platformer

  • Use MoveInput.x for horizontal movement
  • Ignore MoveInput.y (or use for ladder climbing)
  • LookInput typically unused

3D Third-Person

  • Use MoveInput relative to camera forward
  • Use LookInput to orbit camera around player

3D First-Person

  • Use MoveInput relative to player forward
  • Use LookInput to rotate player view directly
  • Consider locking and hiding cursor

Isometric / Top-Down

  • Use MoveInput directly as world-space direction
  • LookInput can aim independently of movement

Related