Skip to content

Published: 9/23/2026

How to Make a 2D Player Move in Unity 6

Getting a player to move is one of the first things you need to figure out when learning Unity.

It sounds simple, but there are several pieces involved:

  • Getting input from the keyboard
  • Turning that input into a direction
  • Passing that input into your player script
  • Moving the player using Unity’s physics system

In this tutorial, we’re going to build the smallest, useful version of that system.

We’re going to make a 2D player that moves with WASD or the arrow keys using Unity 6’s Input System and a Rigidbody2D. This is typical for a 2D top-down style game.

Here are the tools we plan to use :

  • Unity’s New Input System
  • A Rigidbody2D to handle movement and collisions
  • The PlayerInput component that comes with Unity 6

We’re not going to build a complete character controller. There won’t be animation, jumping, acceleration, camera movement, or any other features.

The goal is to understand the basic pipeline:

keyboard input → Vector2 → player script → Rigidbody2D

Once you understand that, adding more features becomes much easier.

The Scene

We’ll start with a basic scene.

Create a player GameObject along with a few walls or obstacles around it. Be sure that your walls and/or your obstacles have a BoxCollider2D attached.

For example:

PlayerMovement
├── Main Camera
├── Player
├── TopWall
├── BottomWall
├── LeftWall
└── RightWall

The exact appearance of the scene doesn’t matter. A simple square for the player and a few rectangles for walls are enough. For this demo we have a sprite from Kenney’s Platformer Assets, but any sprite you have will work.

The important part is that the player has a Rigidbody2D.

Set the Rigidbody2D to:

  • Body Type: Dynamic
  • Gravity Scale: 0
  • Freeze Rotation: Z

Since this is a top-down movement example, we don’t want gravity pulling the player downward.

Setting Up The Player Input Component

Unity’s Input System provides a useful way to separate your controls from your gameplay code.

Unity also supplies a default Input Actions asset that already contains several useful actions and action maps. This means you can get started without creating every input action yourself.

For this project, however, I recommend making your own copy of that asset. In addition, the PlayerInput component that we will be using requires its own custom Input Actions asset.

That gives your project its own Input Actions configuration that you can inspect and modify without changing Unity’s supplied asset.

Place the asset somewhere like:

Assets/
├── Input/
│   └── GameInput.inputactions
├── Scenes/
│   └── PlayerMovement.unity
└── Scripts/
    └── PlayerMovement.cs

Open the Input Actions asset and look at the Player action map.

The action we’re interested in is Move.

The Move action should be configured as a Value action with a Vector2 control type.

The keyboard bindings are represented using a 2D Vector Composite.

Conceptually, that gives us this:

W       → ( 0,  1)
S       → ( 0, -1)
A       → (-1,  0)
D       → ( 1,  0)

Instead of receiving four separate button presses in our movement script, the Input System combines them into one Vector2.

That’s exactly what we want.

What Is a Vector2?

A Vector2 is simply a value containing two numbers:

(x, y)

For movement, we can think of those numbers as horizontal and vertical direction.

For example:

(1, 0)

means move to the right.

(-1, 0)

means move to the left.

And:

(0, 1)

means move upward.

The useful thing about this is that we can represent both direction and input with the same type.

Pressing W gives us an upward vector. Pressing D gives us a rightward vector. Pressing both gives us a diagonal vector.

A Note About Diagonal Movement

There’s one detail worth checking before we start writing the movement code.

When you press W and D at the same time, the resulting vector needs to represent diagonal movement without making the player move faster.

The default Move action’s 2D Vector Composite handles this for us by normalizing the result.

So instead of getting:

(1, 1)

we get approximately:

(0.707, 0.707)

The exact numbers aren’t particularly important here. What matters is that the diagonal vector has a magnitude of about 1, just like the horizontal and vertical vectors.

If you’re creating your own 2D Vector Composite, pay attention to its normalization or mode setting. If your composite produces (1, 1) for diagonal input, diagonal movement will be faster.

You can solve that either by configuring the Input System to normalize the composite or by normalizing the vector in code.

For this tutorial, we’ll let the Input System handle it.

Connecting PlayerInput

Add a PlayerInput component to the player.

Set its Actions asset to your GameInputActions asset.

For the behavior, use:

Invoke Unity Events

This gives us an event for our Move action that we can connect directly to a method on our player movement script.

The flow we’re building looks like this:

Keyboard
   ↓
2D Vector Composite
   ↓
Vector2
   ↓
PlayerInput
   ↓
OnMove()
   ↓
moveInput
   ↓
FixedUpdate()
   ↓
Rigidbody2D

Before we actually move anything, it’s useful to see the data we’re receiving.

You can temporarily add a Debug.Log to OnMove():

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

    Debug.Log($"Move Input: {input}");

    moveInput = input;
}

Enter Play mode and press W, A, S, and D.

You should see the corresponding Vector2 values in the Console.

Try pressing two keys together as well.

This is a useful debugging technique when working with Unity’s Input System. Instead of guessing whether your input is configured correctly, you can inspect the actual data being sent to your script.

Once you’re satisfied that the input is working, remove the debug logging.

Creating the Movement Script

Now we can move the player.

Here’s the complete script:

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(Rigidbody2D))]
public class PlayerMovement : MonoBehaviour
{
    // Editor Fields
    [SerializeField] private float moveSpeed = 5f;
    
    // Internal Fields
    private Vector2 moveInput;
    private Rigidbody2D playerRigidbody2D;
    
    // Unity Events
    private void Awake()
    {
        playerRigidbody2D = GetComponent<Rigidbody2D>();
    }
    
    private void FixedUpdate()
    {
        playerRigidbody2D.linearVelocity = moveInput * moveSpeed;
    }
    
    // External Functions
    public void OnMove(InputAction.CallbackContext context)
    {
        moveInput = context.ReadValue<Vector2>();
    }
}

Let’s break down what’s happening.

Requiring a Rigidbody2D

At the top of the class, we have:

[RequireComponent(typeof(Rigidbody2D))]

This tells Unity that this script requires a Rigidbody2D.

It’s a small addition, but it’s useful for scripts that depend on specific components.

If you put PlayerMovement on a GameObject that doesn’t have a Rigidbody2D, Unity will add the required component rather than leaving the script with a missing dependency. You won’t have our custom settings, but it’s a little safer. This way when we play the game, it won’t crash.

Reading the Input

Our OnMove() method is called by the PlayerInput component when the Move action changes.

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

The important part is:

context.ReadValue<Vector2>();

We’re telling the Input System:

Give me the current value of this action as a Vector2.

That value is then stored in:

moveInput

We’re not moving the player here.

We simply remember what direction the player wants to move.

Applying the Movement

The actual movement happens in FixedUpdate():

private void FixedUpdate()
{
    playerRigidbody2D.linearVelocity = moveInput * moveSpeed;
}

moveInput gives us the direction and magnitude.

moveSpeed determines how quickly the player should move.

Multiplying the two gives us the velocity we want to give the Rigidbody2D.

For example, if:

moveInput = (1, 0)
moveSpeed = 5

then the resulting velocity is:

(5, 0)

The player moves to the right at five units per second.

If the player releases the movement keys, the Input System gives us a zero vector:

(0, 0)

The resulting velocity becomes zero, so the player stops.

Why FixedUpdate?

Because we’re moving a Rigidbody2D, we’re working with Unity’s physics system.

FixedUpdate() is Unity’s physics-oriented update loop, so it’s a natural place to apply the Rigidbody’s movement.

Our input event and physics movement therefore have separate responsibilities:

OnMove()
    ↓
"What direction does the player want to move?"
    ↓
moveInput
    ↓
FixedUpdate()
    ↓
"Apply that direction to the Rigidbody2D."

This separation also makes the code easier to understand.

The input callback deals with input.

FixedUpdate() deals with physics.

Testing the Player

At this point, enter Play mode and test the controls.

The player should move using:

  • W / Up Arrow — up
  • S / Down Arrow — down
  • A / Left Arrow — left
  • D / Right Arrow — right

Try moving diagonally as well.

The player should move diagonally without getting an unintended speed boost, assuming the Move action’s 2D Vector Composite is configured to normalize the input.

You can also move the player into your walls to verify that the Rigidbody2D is interacting with the scene’s colliders.

The Complete Movement Pipeline

There’s not actually that much code involved in moving the player.

The interesting part is understanding how the pieces connect.

The keyboard doesn’t directly move the player.

Instead, the Input System turns the keyboard input into a Vector2.

PlayerInput passes that value to our OnMove() method.

We store the value in moveInput.

Then FixedUpdate() uses that value to set the Rigidbody2D’s linearVelocity.

In other words:

WASD
 ↓
Input System
 ↓
Vector2
 ↓
OnMove()
 ↓
moveInput
 ↓
FixedUpdate()
 ↓
linearVelocity
 ↓
Player moves

Advanced Information

Because of the way Rigidbody2D.linearVelocity works we don’t need to actually set it during FixedUpdate. We can simply set it in the OnMove() event instead like this :

 public void OnMove(InputAction.CallbackContext context)
{
        moveInput = context.ReadValue<Vector2>();
        playerRigidbody2D.linearVelocity = moveInput * moveSpeed;
}

This removes the FixedUpdate() calls if our structure is simple enough. This is because the Physics system will continually use the velocity you set until you change it. So you do not need to set the movement velocity every frame unless something changes. A lot of the time, though, you may need to change the player facing, animate, etc. so you don’t lose much by keeping it there. So this is one optimization that is generally important when you’re trying to reduce/elimate calls to Update() and/or FixedUpdate() entirely.

Summary

That’s the foundation for a 2D player controller.

There are plenty of things we could add from here—animation, gamepad controls, acceleration, jumping, dashing, camera movement, and so on.

But those are separate problems.

For now, we have a small player controller that does one thing: take directional input and use it to move a 2D Rigidbody player.

And, more importantly, we can explain how every part of that system works.

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