How to use the new Unity Input System

I recently updated the unity version from 2019.01 to 2020 and had to migrate a few things. Most things worked out of the box but one thing needed a major overhaul in Evenorn. The Unity Input System!

So lets dive right into the new Input System unity has to offer. We wont dive too much into installing the new system. That is described quiet well in the documentation under installation.

You can find the public repository to this tutorial project on github.
https://github.com/Mijdax/tut-unity-new-input-system-demo

Here is the documentation of it: New Unity Input System Documentation

How does the new Input System of Unity work?

Without going too much into detail, the new Input System simply covers a wide variety of devices and handles the low level technical stuff and allowing us to create control schemes which are more suitable for the logical part. It can also group multiple actions together and trigger events if any one of them got triggered. It can even fire functions with the right naming convention automatically. While thats enough for some people, its not suitable for me, thats why I recommend encapsulating the input events and write a script just for handling and further processing the inputs.

How to setup our Inputs?

For our setup we will need to create three things:

  • Input Actions Asset
  • Action Asset Class (Unity will generate this for us)
  • InputHandlerClass (Contains further logic from us, this will later get read by our scripts for input)

Here is a small visualization to what we are about to code

Unity Input System: Setup
Unity Input System: Setup

Setup Input Action Asset

The inputactions are fairly easy to setup. Simply rightclick and select Create/Input Action. After it has been created, give it a good name and double click it to open and you will be confronted with something like this:

Input Action Asset Window
Input Action Asset Window

Here you can setup your actions as you please. For simplicity, we will setup one action named attack and a move action for left stick controls which works with Vector2.

1.) General Setup of action map for Player:

ActionMap and Action Setup
ActionMap and Action Setup

2.) Setup move:

Move setup so that it works with vector2 inputs
Move setup so that it works with vector2 inputs
Left Stick binding setup
Left Stick binding setup

3.) Setup Attack

Attack action setup
Attack action setup

When you have done this dont forget to save the asset or set it to auto save

saving the InputActionAsset
saving the InputActionAsset

With this out of our way we can add the inputs to a gameobject. I also created a new UserInputHandler script which will handle the inputs.

Setting up our gameobject
Setting up our gameobject
Don't forget to generate a C# class for out new InputActionAsset. Select the asset and you will see this in the inspector
Don’t forget to generate a C# class for out new InputActionAsset. Select the asset and you will see this in the inspector

Handle inputs from the new Unity Input System

First prepare the member variables

UserInputs userInputs;
public bool AttackPressed = false; // If was pressed this frame
public bool AttackPressing = false; // Is getting pressed (holding it down, true longer than one frame)

public Vector2 Move = new Vector2(); // The move vector of the sticks. Normalized
public Action<Vector2> OnMove; // An event that gets called with the move Vector as a parameter.

Next setup OnEnable and listen to events from the input

void OnEnable()
    {
        userInputs = new UserInputs(); // This automatically works out of the box and reads user inputs as described in the action asset
        userInputs.Player.Attack.performed += context => OnAttackPerformed(context); // Bind a function to the attack performed event.
        userInputs.Player.Attack.canceled += context => OnAttackCanceled(context); // Bind a function to the attack canceled event.

        userInputs.Player.Move.performed += context => OnMovePerformed(context); // Bind a function to the move performed event.
        userInputs.Player.Move.canceled += context => OnMoveCanceled(context); // Bind a function to the move canceled event.

        userInputs.Enable(); // Dont forget to enable the inputs. It wont work otherwise.
    }

Handle the incoming input values and events

private void OnAttackPerformed(InputAction.CallbackContext context)
    {
        AttackPressed = true; // We set our button variables to true because it got performed. No need to readvalue from context here
        AttackPressing = true;
    }

    private void OnAttackCanceled(InputAction.CallbackContext context)
    {
        AttackPressed = false; // We set our button variables to false because it got canceled. No need to readvalue from context here
        AttackPressing = false;
    }

    private void OnMovePerformed(InputAction.CallbackContext context)
    {
        // We check if OnMove has functions bind to that event and if yes, we will fire them and input the Vector2 value delivered by our context
        if (OnMove != null)
        {
            OnMove(context.ReadValue<Vector2>()); // context.ReadValue<Vector2>() is how you read the value from the input. Context got passed through in our OnEnable event binding before.
        }
    }

    private void OnMoveCanceled(InputAction.CallbackContext context)
    {
        // We check if OnMove has functions bind to that event and if yes, we will fire them and input the Vector2 value delivered by our context
        if (OnMove != null)
        {
            OnMove(context.ReadValue<Vector2>()); // context.ReadValue<Vector2>() is how you read the value from the input. Context got passed through in our OnEnable event binding before.
        }
    }

    void Update()
    {
        // Since we want to update the value not only when it gets performed. We want to store it even if it just changed.
        // Thats why we need to read it through update. If we did this onPerformed, it wont update if the stick moved from (1/1) to (1/0) since it was already performing and never canceled.
        Move = userInputs.Player.Move.ReadValue<Vector2>(); 
    }

    void LateUpdate()
    {
        AttackPressed = false; // cleanup the pressed vars again in late update so that it will trigger only when pressed and does not stay true
    }

Clean up the controls OnDisable

void OnDisable()
    {
        userInputs.Enable(); // Dont forget to disable the inputs. You will have memory leaks otherwise
    }

And here its all together:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class UserInputHandler : MonoBehaviour
{
    UserInputs userInputs;
    public bool AttackPressed = false;
    public bool AttackPressing = false;

    public Vector2 Move = new Vector2();
    public Action<Vector2> OnMove;

    void OnEnable()
    {
        userInputs = new UserInputs(); // This automatically works out of the box and reads user inputs as described in the action asset
        userInputs.Player.Attack.performed += context => OnAttackPerformed(context); // Bind a function to the attack performed event.
        userInputs.Player.Attack.canceled += context => OnAttackCanceled(context); // Bind a function to the attack canceled event.

        userInputs.Player.Move.performed += context => OnMovePerformed(context); // Bind a function to the move performed event.
        userInputs.Player.Move.canceled += context => OnMoveCanceled(context); // Bind a function to the move canceled event.

        userInputs.Enable(); // Dont forget to enable the inputs. It wont work otherwise.
    }

    private void OnAttackPerformed(InputAction.CallbackContext context)
    {
        AttackPressed = true; // We set our button variables to true because it got performed. No need to readvalue from context here
        AttackPressing = true;
    }

    private void OnAttackCanceled(InputAction.CallbackContext context)
    {
        AttackPressed = false; // We set our button variables to false because it got canceled. No need to readvalue from context here
        AttackPressing = false;
    }

    private void OnMovePerformed(InputAction.CallbackContext context)
    {
        // We check if OnMove has functions bind to that event and if yes, we will fire them and input the Vector2 value delivered by our context
        if (OnMove != null)
        {
            OnMove(context.ReadValue<Vector2>()); // context.ReadValue<Vector2>() is how you read the value from the input. Context got passed through in our OnEnable event binding before.
        }
    }

    private void OnMoveCanceled(InputAction.CallbackContext context)
    {
        // We check if OnMove has functions bind to that event and if yes, we will fire them and input the Vector2 value delivered by our context
        if (OnMove != null)
        {
            OnMove(context.ReadValue<Vector2>()); // context.ReadValue<Vector2>() is how you read the value from the input. Context got passed through in our OnEnable event binding before.
        }
    }

    void Update()
    {
        // Since we want to update the value not only when it gets performed. We want to store it even if it just changed.
        // Thats why we need to read it through update. If we did this onPerformed, it wont update if the stick moved from (1/1) to (1/0) since it was already performing and never canceled.
        Move = userInputs.Player.Move.ReadValue<Vector2>(); 
    }

    void LateUpdate()
    {
        AttackPressed = false; // cleanup the pressed vars again in late update so that it will trigger only when pressed and does not stay true
    }

    void OnDisable()
    {
        userInputs.Enable(); // Dont forget to disable the inputs. You will have memory leaks otherwise
    }
}

You can read this component from your scripts by simply using GetComponent and attach functions to OnMove or check for bool values.

void Start()
    {
        var userInputHandler = GetComponent<UserInputHandler>();
        if (userInputHandler.AttackPressed)
        {
            Debug.Log("Attack! HAYAAAAA");
        }
        if (userInputHandler.Move.x != 0)
        {
            Debug.Log("Move Me Horizontally by " + userInputHandler.Move.x);
        }
        userInputHandler.OnMove += moveVector => MoveMe(moveVector);
    }

You May Also Like