Unity’s New Input System: Script Connection

Ryan McCoach
9 min readFeb 26, 2025

--

When using Unity’s New Input System, there are two main ways to handle input in scripts:

  1. Using the PlayerInput Component → Drag-and-drop approach with automatic method calling.
  2. Generating a C# Input Action Class (InputActionAsset C# script) → Manual input handling with better control.

Player Input Component vs. C# Input Class

Which Should You Use?

Use the Player Input Component if…

✅ You want quick setup without writing a lot of code.
✅ You prefer drag-and-drop Unity Events for handling input.
✅ You are making a local multiplayer game (easy setup with Player Input Manager).
✅ You are working on a small or simple project and don’t need advanced input handling.

Use the Generated C# Input Class if…

✅ You want better performance (avoids Unity’s messaging system).
✅ You need more control over input handling (e.g., input remapping, multiplayer networking).
✅ You want to use input in scripts that don’t have GameObjects (e.g., UI-only projects, state machines).
✅ You’re working on a large project where code maintainability is important.

Using the PlayerInput Component

The Player Input component is a high-level solution that automatically handles inputs and can call methods on your script.

  1. Add the Player Input component to a GameObject.

2. Assign an Input Actions Asset (created in the Input System).

🟦 You can choose which of the Actions Maps you would like to be the default set of inputs.

3. Choose how it processes input (Send Messages, Broadcast Messages, Unity Events, or C# Events).

🟦 Send Messages (The Player Input component looks for methods with matching action names (e.g., OnMove, OnJump) in scripts attached to the same GameObject.)

🟦 Broadcast Messages (Like Send Messages but includes children)

🟦 Invoke Unity Events (Connect input actions to Unity Events)

🟦Invoke C# Events (Recommended for best performance)

4. Unity automatically calls functions with matching names in attached scripts.

Send/Broadcast Messages Code Example:

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
private Vector2 moveInput;

void OnMove(InputValue value) // Matches the action name "Move"
{
moveInput = value.Get<Vector2>();
Debug.Log($"Move Input: {moveInput}");
}

void OnJump()
{
Debug.Log("Jump triggered!");
}
}
Vector2 returns a value of 1 or -1 when the input is used. 0 is returned when the input is released. Jump occurs when the jump input is used.

Unity Events

Using the Unity Events exposes Unity’s Built in events for: Device Lost, Device Regained, Controls. It will also display the Actions of the chosen Action Map (Jump, Movement).

  1. Expand the Events section in the Player Input component.
  2. Find the input actions under “Player” (e.g., “Move”, “Jump”).
  3. Drag the Player GameObject (or another script-holding GameObject) into the slot.
  4. Choose a public method from a script to handle input (e.g., PlayerInput.OnJump,PlayerInput.OnMovement ).

You need a script with public methods that match the input actions and for the Movement method to work properly a InputAction.CallbackContext type parameter is needed for it to identify which input is being used to produce the Vector2 value.

Unity Events Code Example:

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
private Vector2 moveInput;

// Called when Movement action is performed
public void Movement(InputAction.CallbackContext context)
{
moveInput = context.ReadValue<Vector2>();
Debug.Log($"Moving: {moveInput}");
}

// Called when Jump action is performed
public void OnJump()
{
Debug.Log("Jump!");
}
}

When this runs, you will notice that the Vector values are being printed twice per button press and the “jump!” is being printed three times per button press.

This is caused because Input Actions go through different phases, which can be accessed via context.phase in InputAction.CallbackContext. These phases allow you to determine the state of the input event.

The 3 Main Phases

  1. Started → The input has begun but is not yet completed.
  2. Performed → The input is active or completed (depends on input type).
  3. Canceled → The input was interrupted or stopped.

1️⃣ Started Phase

  • Triggers when the input begins (e.g., pressing a key, touching the screen).
  • Happens for continuous inputs (e.g., hold-based actions like Move).
  • Does not trigger for instant inputs like button presses (Press and Release).

✅ Example Use Case:

  • Detecting when a key/button is initially pressed.

2️⃣ Performed Phase

  • Triggers when the input action is completed or when an analog value changes (e.g., joystick movement).
  • For button presses, it triggers when the button is fully pressed or released, depending on the interaction type.
  • For continuous inputs (e.g., joystick, movement), it triggers multiple times while the input changes.

✅ Example Use Case:

  • Detecting when a player moves the joystick or completes a jump press.

3️⃣ Canceled Phase

  • Triggers when an input is interrupted or stopped.
  • Happens when:
  • 🔸 The player releases a button before an action is performed.
  • 🔸An analog input stops changing (e.g., joystick returns to the center).
  • 🔸A higher-priority input overrides the current one.

✅ Example Use Case:

  • Detecting when a player releases a button or stops moving.

Unity Events Code with Context Phases Example:

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
private Vector2 moveInput;

public void OnMove(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Started)
{
Debug.Log("Move started!");
}
else if (context.phase == InputActionPhase.Performed)
{
moveInput = context.ReadValue<Vector2>();
Debug.Log($"Moving: {moveInput}");
}
else if (context.phase == InputActionPhase.Canceled)
{
moveInput = Vector2.zero;
Debug.Log("Move canceled!");
}
}

public void OnJump(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Started)
{
Debug.Log("Jump button pressed (Started).");
}
else if (context.phase == InputActionPhase.Performed)
{
Debug.Log("Jump executed (Performed)!");
}
else if (context.phase == InputActionPhase.Canceled)
{
Debug.Log("Jump action canceled.");
}
}
}

When using the PlayerInput Component, it is best to use the Unity Event behaviors over the Message or C# Events behaviors. The C# Events are better used when used to interact with the InputAction C# script, instead of the PlayerInput component.

Generating a C# Input Action Class

Before we can start to script to new inputs, we will need to generate a C# script of all of the Action Maps and their Actions with the bindings. Luckily Unity handles this for us.

  1. Select the Input Actions Asset
  2. In the Inspector, check “Generate C# Class” . Click Apply
  3. Unity will generate a script (e.g., PlayerInputActions.cs) that contains all the action mappings.

Now we will need to create a script to handle and read those inputs and attach it to the game object we want the inputs for.

In the script, we will need to use the UnityEngine.InputSystem library to access the input action asset.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem; //need this to access the Input Action Asset

There are three things we will need to set up before we can start working with the Action Map(s).

  1. Get a reference the instance of our input actions
  2. Enable Input Action Map
  3. Subscribe to input events

1️⃣Get a reference to the instance of our input actions

Code Example:

using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting.Contexts;
using UnityEngine;
using UnityEngine.InputSystem; //need this to access the Input Action Asset

public class PlayerInput : MonoBehaviour
{
[SerializeField] private float _speed = 10f;
PlayerInputActions _input;

private void Awake()
{
_input = new PlayerInputActions();

}


private void OnEnable()
{
_input.Player.Enable();
_input.Player.Movement.performed += Movement_performed;
}

I create a new instance of the PlayerInputActions and store it into the variable _input in Awake because the rest of the script relies on that reference and Awake is executed first.

2️⃣Enable Input Action Map

Code Example:

using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting.Contexts;
using UnityEngine;
using UnityEngine.InputSystem; //need this to access the Input Action Asset

public class PlayerInput : MonoBehaviour
{
[SerializeField] private float _speed = 10f;
PlayerInputActions _input;

private void Awake()
{
_input = new PlayerInputActions();
}

private void OnEnable()
{
_input.Player.Enable();
}

In OnEnabled, the Player ActionMap of PlayerInputActions asset is enabled. This needs to be done so this Action Map can be used. Note: If you just you _input.Enable() will enable all of the Actions Maps for that Input Action Asset.

3️⃣Subscribe & Unsubscribe to input events

Code Example:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem; //need this to access the Input Action Asset

public class PlayerInput : MonoBehaviour
{
[SerializeField] private float _speed = 10f;
PlayerInputActions _input;

private void Awake()
{
_input = new PlayerInputActions();
}

private void OnEnable()
{
_input.Player.Enable();
_input.Player.Movement.performed += Movement_performed;

_input.Player.Jump.started += Jump_started;
_input.Player.Jump.performed += Jump_performed;
_input.Player.Jump.canceled += Jump_canceled;
}

private void OnDisable()
{
_input.Player.Movement.performed -= Movement_performed;

_input.Player.Jump.started -= Jump_started;
_input.Player.Jump.performed -= Jump_performed;
_input.Player.Jump.canceled -= Jump_canceled;
}

In Unity, subscribing and unsubscribing to events is an essential practice when working with C# events and delegates. This helps ensure that objects respond to events when needed and prevents unintended behavior, such as memory leaks or null reference exceptions.

Subscribing to an Event

When you subscribe to an event, you tell Unity that a specific method should be executed when that event is triggered. This is done using the += operator.

Unsubscribe from Events

If you don’t unsubscribe from events, you may encounter memory leaks, unintended method calls, or errors due to referencing destroyed objects. This is done using the -= operator.

Problems from Not Unsubscribing

  1. Memory Leaks:
  • If a listener object is destroyed but still subscribed, the event keeps a reference to it, preventing garbage collection.

2. Null Reference Exceptions:

  • If an event tries to invoke a method on a destroyed object, it can lead to runtime errors.

3. Unexpected Behavior:

  • If a script subscribes multiple times (without proper unsubscribing), the event may trigger the same method multiple times unintentionally.

Best Practices for Subscribing and Unsubscribing

  1. Subscribe in OnEnable() and unsubscribe in OnDisable()
  • This ensures proper lifecycle management when objects are enabled/disabled.

2. Use ?.Invoke() to prevent null reference errors

  • This ensures that the event is only called if there are active subscribers.

3. Unsubscribe before destroying objects

  • If an object is destroyed, remove event listeners to avoid lingering references.

Using the Input Events

In Unity’s New Input System actions trigger different input events based on their phase. These events correspond to different moments during an input’s lifecycle. An Event function is automatically created (Jump_started, Jump_performed, Jump_Canceled) by Unity after an event (started, performed, canceled) is added (+=) to the Action (Jump).

InputActions Phases:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem; //need this to access the Input Action Asset

public class PlayerInput : MonoBehaviour
{
[SerializeField] private float _speed = 10f;
PlayerInputActions _input;

private void Awake()
{
_input = new PlayerInputActions();
}

private void OnEnable()
{
_input.Player.Enable();
_input.Player.Movement.performed += Movement_performed;

_input.Player.Jump.started += Jump_started;
_input.Player.Jump.performed += Jump_performed;
_input.Player.Jump.canceled += Jump_canceled;
}

private void OnDisable()
{
_input.Player.Movement.performed -= Movement_performed;

_input.Player.Jump.started -= Jump_started;
_input.Player.Jump.performed -= Jump_performed;
_input.Player.Jump.canceled -= Jump_canceled;
}

private void Movement_performed(InputAction.CallbackContext context)
{
Debug.Log($"X: {context.ReadValue<Vector2>().x} Y: {context.ReadValue<Vector2>().y}");
}

private void Jump_started(InputAction.CallbackContext context)
{
Debug.Log($"Jump started: {context}");
}

private void Jump_performed(InputAction.CallbackContext context)
{
Debug.Log($"Jump Performed: {context}");
}

private void Jump_canceled(InputAction.CallbackContext context)
{
Debug.Log($"Jump Canceled: {context}");
}

Script the behaviors inside of these methods and they will be called when that input event is triggered.

Summary

Unity’s New Input System provides two main ways to handle player inputs:

1️⃣ Player Input Component

  • Visual, drag-and-drop setup (good for beginners).
  • Automatically connects input actions and supports event-driven input handling (e.g., Send Messages, Unity Events, Broadcast Messages).
  • Best for quick prototyping and multiplayer with PlayerInputManager.

2️⃣ Generated C# Input Class

  • Code-driven approach using a strongly typed C# input class.
  • Provides better performance (no reflection like SendMessage).
  • Ideal for complex input handling and custom scripting needs.

✅ Use Player Input Component for quick setup & event-driven logic.
✅ Use Generated C# Class for flexibility, efficiency, and custom handling.

--

--

No responses yet