New Unity Input System: Vector2 Movement

Ryan McCoach
7 min readMar 1, 2025

--

Create a Movement Action

  1. Inside the “Player” action map, click “+” to add a new action.
  2. Name it “Movement”.
  3. Set the Action Type to “Value”.
  4. Set the Control Type to “Vector2”.

Bind Controls to the Move Action

  • Click “Binding” > “+” to add a new binding.
  • In the Path, choose “Up//Down/Left/Right Composite”. This will create a 2D Vector binding where you can assign keys for the Vector2 movements of: Up, Down, Left, Right.
  1. The Binding Properties of 2D Vector allows returns those values in different ways using the Modes: Digital Normalized, Analog, Digital
  2. Assign keys:
  • Up → W or Up Arrow
  • Down → S or Down Arrow
  • Left → A or Left Arrow
  • Right → D or Right Arrow

3. In Unity’s New Input System, when using the Composite Binding for a 2D Vector, you can set the “Mode” to one of three types:

  • Digital Normalized
  • Analog
  • Digital

These modes affect how the input values are processed when combining multiple button or axis inputs into a 2D vector.

4. Don’t forget to Save Input Action Asset when a change is made.

Digital Normalized

How it works:

  • Treats the input as digital (on/off) but normalizes diagonal inputs so that they have a magnitude of 1 rather than a higher value.
  • This ensures smooth diagonal movement instead of being faster when pressing two directions at once (e.g., Up + Right).

Use Case:

  • Best for movement using arrow keys or D-pad where consistent speed is needed.

Analog

How it works:

  • Uses raw analog values from an input device (e.g., joystick or gamepad thumbstick).
  • No normalization; the values reflect the actual joystick movement range (e.g., values can be between -1 and 1).
  • Allows for finer control, such as gradual movement speed when slightly moving the joystick.

Use Case:

  • Best for joysticks, gamepads, or touch inputs where analog precision is needed.

Digital

How it works:

  • Treats inputs as binary (on/off) without normalizing diagonal movement.
  • If two keys are pressed (e.g., Up and Right), the resulting vector will have a magnitude of √2 (about 1.41) instead of being normalized to 1.

Use Case:

  • Used for non-normalized movement, such as when different actions are mapped to buttons (not just directional movement).

Creating An Input Script

There are several ways to connect a script to the Input Action Asset, which I have covered in a previous article. I will be creating a script that will use connect to the C# Input Action Class script that is generated by the Action Asset.

  1. Create an Input script
  2. Attach it to the game object you want to move

In the script, we will need to make use of the InputSystem name to gain access to the Actions created in our Action Maps.

Then the 3 things to connect the script to the Action Map/Actions

  1. Create an instance of the PlayerInputActions and get a reference of it _input.
  2. Enable the Player Action Map.
  3. Subscribe/Unsubscribe to the Input Event functions (Movement_performed)

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;
}


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

When subscribing to the Input Events use the tab key to create the Movement_performed method. This method will be called when the Movement action occurs.

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

}

Using the context parameter, we can get the Vector2 X and Y values by using the dot operator called the ReadValue method, which returns the Vector2 values from the Action Map assigned keys.

  • Up = X: 0 Y: 1
  • Down = X: 0 Y: -1
  • Left = X: -1 Y: 0
  • Right = X: 1 Y: 0

To use these Vector2 values to move the game object, we will need to create a Vector2 type variable move that will store the read Vector2 value from the context parameter.

Using transform.Translate, we can pass in the current Vector2 value of move and multiply it by deltaTime * speed variable. The speed variable will determine how many meters the game object will move each time the Movement_performed is called.

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

Vector2 move = context.ReadValue<Vector2>();
transform.Translate(move * (Time.deltaTime * _speed));

}

Limitation

This Vector2 will only return an integer of -1, 0 or 1. Unlike the Legacy Input System when using the Input.GetAxis which returns a float between those values and allows for accelerated movement.

Smooth Movement

For smooth constant movement we will need to poll or check input readings in Update.

We move the movement code out of the Movement_performed method where it is only checked once when the Action is performed and placed inside of Update.

private void Update()
{
Vector2 move = _input.Player.Movement.ReadValue<Vector2>();
transform.Translate(move * (Time.deltaTime * _speed));
}

Moving Back and Forth

Even though the Input returns a Vector2 value, we can use it with a Vector3 value to move on different axes.

Still using transform.Translate, we pass in a Vector3 and set the X value of the Vector2 to the X value of the Vector3 and the Y value of the Vector2 to the Z value of the Vector3, while keeping the Y value of the Vector3 at 0. This will have the game object move back and forth when using the Up and Down inputs.

    private void Update()
{
Vector2 move = _input.Player.Movement.ReadValue<Vector2>();

transform.Translate(new Vector3(move.x, 0, move.y) * (Time.deltaTime * _speed));
//transform.Translate(move * (Time.deltaTime * _speed));
}

Vector2 with Physics

To move a game object using the physics engine will require a Rigidbody component to be added to the object using the Vector2 inputs. Gravity, Interpolate, and Continuous Collision Detection will be used for this Rigidbody.

Jump

To create a jump action using the previous script, we will need to do two things.

  1. Subscribe/Unsubscribe to the Jump performed event (Jump is a created Action in the Player Action Map; it uses a Button Action Type).
  2. Reference the Rigidbody component to add force to the player.

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;
[SerializeField] private float _jumpForce = 5f;

PlayerInputActions _input;

Rigidbody _rb;
private bool _isGrounded;
Vector2 _move;

private void Awake()
{
_input = new PlayerInputActions();
_rb = GetComponent<Rigidbody>();

if(_rb == null)
{
Debug.LogError("Rigidbody is null");
}
}

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

_input.Player.Jump.performed += Jump_performed;
}


private void OnDisable()
{
_input.Player.Jump.performed -= Jump_performed;
}

When the Jump_performed method is called when the Jump button is pressed, we can use the AddForce to the Rigidbody and pass in the Vector3 direction we want to jump in (up) and multiply it by a _jumpForce variable. The ForceMode.Impulse adds an instance force.

private void Jump_performed(InputAction.CallbackContext context)
{
_rb.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
}

This will have the player pop up everytime the jump input is used. But, what if we only want it to happen once when the player is on the ground?

We can do that using the OnCollisionEnter and OnCollisionExit methods which detect when the collider has overlapped and stopped overlapping with another collider (ground).

private void OnCollisionEnter(Collision collision)
{
_isGrounded = true;
}

private void OnCollisionExit(Collision collision)
{
_isGrounded = false;
}
private void Jump_performed(InputAction.CallbackContext context)
{
if (_isGrounded)
{
_rb.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
}
}

Using a variable, isGrounded, and setting it true when a collision is detected and false when it is no longer detected will keep track of if the player is currently on the ground or not.

In the Jump method, we perform a check to see if isGrounded and add the force if it is.

Movement

For the movement using physics and the Rigidbody, we will want to make a variable to store the Vector2 ( _move) we will be getting from the Inputs.

[SerializeField] private float _speed = 10f;
[SerializeField] private float _jumpForce = 5f;

private bool _isGrounded;
PlayerInputActions _input;
Rigidbody _rb;
Vector2 _move;

In Fixed Update, we AddForce to the Rigidbody and create a new Vector3.

  • The X value of the input Vector2 being the X for the Vector3
  • The Y value of the input Vector2 being the Z for the Vector3

This creates the left/right and forward/backward movement in 3D space with _speed being determined by the variable and the Forcemode.Force continuously adding that much force to the Rigidbody in that direction.

Conclusion

That was a lot…no further thoughts.

--

--

No responses yet