Skip to content

Published: 10/1/2026

Fix: ‘You are trying to read Input using the UnityEngine.Input class’

If you are following an older Unity tutorial, and you run into an error with code like this:

using UnityEngine;

public class OldInputMovement : MonoBehaviour
{
    [SerializeField] private float speed = 5f;

    private void Update()
    {
        float x = Input.GetAxis("Horizontal");
        float y = Input.GetAxis("Vertical");
        transform.Translate(new Vector3(x, y, 0) * speed * Time.deltaTime);
    }
}

You will be rudely greeted by this message in the console (Unity 6.3 and later):

InvalidOperationException: You are trying to read Input using the
UnityEngine.Input class, but you have switched active Input handling
to Input System package in Player Settings.

You may be wondering what happened.

The short answer is that this is using Unity’s old Input Manager. In a new Unity 6 project, the project is configured to use the newer Input System instead.

You may find tutorials telling you to simply enable both input systems. That will make the old code work, but that’s not what I want to do here.

Even today you still find popular articles with workarounds to call the Input System keyboard handlers directly like this :

// Equivalent to Input.GetKey() (held down)
if (Keyboard.current.wKey.isPressed)
{
    Debug.Log("W key is being held down.");
}

For a large project that has complex input, this makes complete sense. Refactoring complex input code is not trivial.

But, if we are starting a new project, I would rather update the code to use the new Input System instead of going back to the old one. Even if I need to spend some time on making the new system work.

This is because the new Input System gives you very robust functionality out of the box, saving you potentially many lines of code. For example, by using ‘Actions’ that are defined in the Input Asset, you potentially avoid having to then find code for gamepads, touch input, and so on.

So let’s take this simple movement example and update it.

What we’re starting with

Our starting point is a basic 2D movement script.

The important part is this:

float x = Input.GetAxis("Horizontal");
float y = Input.GetAxis("Vertical");

The old Input Manager gives us two values, one for horizontal movement and one for vertical movement.

We then combine those two values into a Vector3 and use it to move our player.

There isn’t anything particularly wrong with the idea here. We still need to get a movement value and use it to move the player.

What changes is how we get that input.

Instead of asking the old Input Manager for "Horizontal" and "Vertical", we’ll create an Input Action called Move.

Or better yet, the default installation of Unity 6 and above comes with a pre-defined input asset and a Move action that is already mapped to gamepads, touch, etc.

I cover this in detail in my separate 2D movement video here

Why Input.GetAxis doesn’t work

Input.GetAxis is part of Unity’s legacy Input Manager.

Unity has a newer Input System that uses Input Actions and bindings to separate what the player is trying to do from the physical device they are using.

For example, our game doesn’t really care whether the player presses the W key or moves a gamepad stick.

What the game cares about is:

The player wants to move.

So we’ll create a Move action that gives us a Vector2. Or just map to the default action Unity provides.

You can read more about Unity’s Input System in the official Unity documentation.

Should I just enable both input systems?

You can.

If you go to:

Edit → Project Settings → Player → Active Input Handling

you will find options for the old Input Manager and the new Input System.

Selecting Both allows older code using Input.GetAxis to continue working.

There are certainly situations where that practice is useful, particularly if you are working on an existing project that still uses the old Input Manager.

But that isn’t what we’re doing here.

We’re starting with a new Unity project, so let’s use the new Input System.

This also means that we don’t have to worry about learning an API that Unity considers to be the legacy system.

Create the Input Action

So, even though I recommend using the default asset, let’s create a blank asset. I think that is a useful exercise to show for this type of experiment.

I’ve done both, and I found that when I wanted to hook into the UI, switch action maps, create pause screens, etc. adjusting the asset started to become a lot more work ‘recreating the wheel’. So I only add/remove bindings after creating a copy of the default asset Unity provides. Just my two cents.

First Steps

First, we need an Input Actions asset.

If you don’t already have one, create an Input Actions asset in your project.

Open it and create an Action Map called:

Player

Then create an action called:

Move

For the Move action, set:

Action Type: Value
Control Type: Vector2

This is a little different from the old Input Manager.

Instead of having separate Horizontal and Vertical axes, our Move action gives us both values together as a Vector2.

Add the keyboard controls

For the keyboard, we’ll use a 2D Vector composite.

This lets us map four keys to the four directions:

Up       W
Down     S
Left     A
Right    D

You can also add the arrow keys if you want to support those.

At this point, our Input Action knows what Move means for a keyboard.

We could also add a gamepad binding to the same action.

For example, we could bind the gamepad’s left stick to Move.

This is one of the useful things about the new Input System.

Our movement code doesn’t need to know whether the player is using WASD or a gamepad.

Both controls are providing the same Move action.

Add PlayerInput

Now we need something to connect our Input Action to our player.

There are different ways of attaching our asset to code. I find that using PlayerInput is great to get you up and running, and it’s very beginner-friendly.

  • Add a PlayerInput component to the player GameObject.
  • Assign your Input Actions asset to the Actions field.
  • Set the Default Map to our Player action map.

The important setting for this tutorial is Behavior.

Set it to:

Invoke Unity Events

This will allow us to hook the Move action directly to our code.

Once you do this, you should see an Events section in the PlayerInput component.

Our Move action will have its own Unity Event.

This is what we’re going to use to connect the Input System to our movement script.

Create the movement script

Now we can create our movement script.

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMovement : MonoBehaviour
{
    [SerializeField] private float speed = 5f;

    private Vector2 movement;

    public void OnMove(InputAction.CallbackContext context)
    {
        movement = context.ReadValue<Vector2>();
    }

    private void Update()
    {
        transform.Translate(movement * speed * Time.deltaTime);
    }
}

Let’s walk through this.

First, we need to add:

using UnityEngine.InputSystem;

This gives us access to the Input System types, including InputAction.CallbackContext.

Next, instead of having two separate variables for horizontal and vertical movement, we have one:

private Vector2 movement;

This is because our Move action is returning a Vector2.

The OnMove function

Next we have:

public void OnMove(InputAction.CallbackContext context)
{
    movement = context.ReadValue<Vector2>();
}

This is the function that we’re going to connect to the Move Unity Event.

When the Input System invokes this event, it gives us an InputAction.CallbackContext.

We can then use:

context.ReadValue<Vector2>();

to get the movement value from the action.

So if the player presses W, we get a vector representing movement up.

If the player presses D, we get a vector representing movement to the right.

If the player presses W and D together, we get a vector containing both values.

The important thing here is that the movement script doesn’t need to know which keys are being pressed.

It just receives a Vector2 telling it how the player wants to move.

Connect the Unity Event

Now we need to connect our PlayerInput component to the OnMove function.

On the PlayerInput component, find the Move event under the Events section.

Drag the GameObject containing our PlayerMovement component into the event.

Then select:

PlayerMovement → OnMove

The method should accept the InputAction.CallbackContext that Unity provides.

Now our input flow looks like this:

Keyboard / Gamepad
        ↓
     Move Action
        ↓
     PlayerInput
        ↓
     Unity Event
        ↓
     OnMove()
        ↓
   movement Vector2

Why don’t we move the player inside OnMove?

You might be wondering why we don’t simply put the movement code inside OnMove.

For example, why not do this?

public void OnMove(InputAction.CallbackContext context)
{
    Vector2 movement = context.ReadValue<Vector2>();
    transform.Translate(movement * speed * Time.deltaTime);
}

The problem is that input and movement are two different things.

The OnMove function is concerned with receiving input.

Update is concerned with actually moving the player.

So we use OnMove to update our movement value:

movement = context.ReadValue<Vector2>();

and then Update uses that value:

transform.Translate(movement * speed * Time.deltaTime);

This is also useful because the player continues moving while the input is being held.

We don’t need an input event to fire every frame just to make the player move.

What happens when the player releases the key?

When the player releases the key, the Move action is canceled and the value we read is a zero vector.

So our movement value becomes:

(0, 0)

and the movement code effectively becomes:

transform.Translate(Vector2.zero * speed * Time.deltaTime);

which means the player stops moving.

What changed from the old code?

Let’s compare the two approaches.

The old code looked like this:

float x = Input.GetAxis("Horizontal");
float y = Input.GetAxis("Vertical");

transform.Translate(new Vector3(x, y, 0) * speed * Time.deltaTime);

The new code is:

public void OnMove(InputAction.CallbackContext context)
{
    movement = context.ReadValue<Vector2>();
}

private void Update()
{
    transform.Translate(movement * speed * Time.deltaTime);
}

The actual movement isn’t really the part that changed.

We still have a movement value, and we still use Time.deltaTime to make the movement frame-rate independent.

What changed is where the input comes from.

Instead of:

Input.GetAxis()

we now have:

Input Action → PlayerInput → Unity Event

And instead of having separate horizontal and vertical axes, our Move action gives us a Vector2.

Why I prefer this approach for a new project

There are several different ways you can work with Unity’s new Input System.

You can use C# callbacks, read Input Actions directly, use generated C# classes, and use different behaviors with PlayerInput.

For this example, I’m using PlayerInput with Invoke Unity Events because I think it provides a straightforward way to understand what is happening.

We have an Input Action called Move.

The PlayerInput component receives that action.

The Unity Event calls our OnMove function.

Our movement script then deals with the movement.

There is a clear separation between the input itself and what the game does with that input.

It also means that if we decide later that we want to support a gamepad, we don’t need to rewrite our movement code.

We just add another binding to the Move action.

So what should you do with older Unity tutorials?

If you are following an older tutorial and you see:

Input.GetAxis("Horizontal")

or:

Input.GetAxis("Vertical")

you don’t necessarily need to turn on the old Input Manager.

Instead, look at what the tutorial is actually trying to achieve.

In this case, the tutorial wants to know:

How does the player want to move?

With the new Input System, we can represent that as a Move action returning a Vector2.

That’s the important part.

The old code and the new code are solving the same problem. We’re just using the current Input System to do it.

Final Code

Here is the complete movement script:

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMovement : MonoBehaviour
{
    [SerializeField] private float speed = 5f;

    private Vector2 movement;

    public void OnMove(InputAction.CallbackContext context)
    {
        movement = context.ReadValue<Vector2>();
    }

    private void Update()
    {
        transform.Translate(movement * speed * Time.deltaTime);
    }
}

You should now be able to take an older tutorial that uses Input.GetAxis and update the movement portion to use Unity’s new Input System instead.

And if you are following along with my other Unity tutorials, this is also the approach we’ll use going forward.

If you need the scripts in a downloadable format, you can check out my GitHub.

Let me know if you have any questions!


If you find this content useful please consider supporting me.

☕ Buy Me a Coffee