Lesson 4: Player Character and Movement System

In Lesson 3 you set up your art pipeline and folder structure. Now you will put a playable character in the game and give it responsive 2D movement. This lesson focuses on one goal: a character that moves, jumps (if your design needs it), and feels good to control.

You will use Godot 4's CharacterBody2D, Input Map, and a simple movement script so that by the end you have a character you can run around the scene with keyboard or gamepad.


What You Will Build

  • A Player scene with a CharacterBody2D root and a sprite (or placeholder)
  • Input actions for move_left, move_right, jump (optional)
  • A GDScript that reads input, applies velocity, and uses move_and_slide() for movement and collisions
  • Smooth, responsive movement you can tune with a few variables

Step 1 – Create the Player Scene

  1. In the Godot 4 project you set up in Lesson 1, create a new scene.
  2. Choose Other Node and add a CharacterBody2D as the root. Name it Player.
  3. Add a CollisionShape2D as a child of Player. Give it a simple shape (e.g. rectangle or capsule) that matches the size you want for the character. You can resize it in the editor.
  4. Add a Sprite2D (or AnimatedSprite2D if you already have frames). Assign a texture from your art/ or sprites/ folder, or use a placeholder. Center it so the sprite aligns with the collision shape.

Your node tree should look like:

Player (CharacterBody2D)
├── CollisionShape2D
└── Sprite2D

Save the scene as scenes/player/player.tscn (or under whatever folder structure you defined in Lesson 3).


Step 2 – Define Input Actions

Godot 4 uses an Input Map so you can refer to actions like move_left instead of raw key codes.

  1. Go to Project → Project Settings → Input Map.

  2. Add these actions (if your game is a platformer or top-down with jump):

    • move_left
    • move_right
    • move_up (optional, for top-down or jump)
    • move_down (optional, for top-down)
    • jump (optional)
  3. For each action, add the keys you want (e.g. A/Left, D/Right, W/Up, S/Down, Space for jump). You can also add gamepad axes or buttons.

Using the Input Map keeps your script clean and makes it easy to support gamepad or remapping later.


Step 3 – Write the Movement Script

Attach a new script to the Player node (CharacterBody2D). Below is a minimal movement script you can adapt.

For a side-scrolling platformer (horizontal movement + jump):

extends CharacterBody2D

@export var move_speed := 280.0
@export var jump_velocity := -420.0
@export var gravity := 980.0

func _physics_process(delta: float) -> void:
    # Apply gravity when in air
    if not is_on_floor():
        velocity.y += gravity * delta

    # Jump
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity

    # Horizontal movement
    var direction := Input.get_axis("move_left", "move_right")
    velocity.x = direction * move_speed

    move_and_slide()

For a top-down game (no gravity, 8-direction or 4-direction):

extends CharacterBody2D

@export var move_speed := 200.0

func _physics_process(_delta: float) -> void:
    var direction := Vector2(
        Input.get_axis("move_left", "move_right"),
        Input.get_axis("move_up", "move_down")
    )
    direction = direction.normalized()
    velocity = direction * move_speed
    move_and_slide()
  • @export lets you tweak move_speed, jump_velocity, and gravity in the Inspector without editing code.
  • move_and_slide() handles collision and sliding along surfaces; call it once per frame after setting velocity.

Step 4 – Place the Player in a Test Room

  1. Open or create a test room (e.g. your first level scene).
  2. Instance the Player scene into the room. Place it above the floor so it can land (for platformer) or anywhere (for top-down).
  3. Ensure the room has a StaticBody2D or TileMapLayer with a CollisionShape2D so the player has something to stand on or collide with.
  4. Run the scene. You should be able to move (and jump if you used the platformer script).

If the character falls through the floor, check that the floor has a collision layer and that the player's collision mask includes that layer (default is usually correct).


Pro Tips

  • Tuning feel: Adjust move_speed, jump_velocity, and gravity in the Inspector. Slightly higher jump and lower gravity often feel better for platformers.
  • Coyote time: To allow a short window to jump after leaving a ledge, use a timer that keeps “was on floor” true for a few frames after leaving the floor, and allow jump while that timer is active.
  • Jump buffer: To make jump feel responsive when pressing jump just before landing, buffer the jump input for 1–2 frames and apply it when you land.

Common Mistakes to Avoid

  • Forgetting to set collision layers: If the player does not collide with the level, check both the player and the level collision layers and masks in the Inspector.
  • Using _process instead of _physics_process: Movement and move_and_slide() should run in _physics_process so they stay in sync with the physics engine.
  • Not normalizing direction: In top-down movement, if you do velocity = direction * move_speed without direction.normalized(), diagonal movement will be faster than horizontal or vertical.

Mini-Task

Create smooth character movement for your 2D action game:

  1. Build the Player scene with CharacterBody2D, collision shape, and sprite.
  2. Add input actions and the movement script (platformer or top-down).
  3. Place the player in a test room and tune speed and jump until it feels good.
  4. Optionally add one small polish (e.g. a simple animation when moving or a dust effect when landing).

Troubleshooting

  • Player does not move: Confirm input actions are named exactly as in the script (move_left, move_right, etc.) and that keys are assigned in Project Settings → Input Map.
  • Player slides or sticks: Reduce move_speed or ensure you are not applying velocity twice. For platformers, make sure gravity is applied only when not on floor.
  • Jitter on slopes: CharacterBody2D with move_and_slide() is designed for this; if you still see jitter, check that the floor collision shape is not too thin or overlapping.

Recap and Next Steps

You now have a playable character with input-driven movement and (optionally) jump. The same pattern—read input, set velocity, call move_and_slide()—will carry through the rest of the course.

In Lesson 5: Combat System and Weapons you will add attacks, damage, and weapon logic so the player can interact with enemies. Before that, spend a few minutes getting the movement feel right; it will make every later lesson more enjoyable.

Found this useful? Bookmark this lesson and share your movement demo in the community. Next up: Lesson 5: Combat System and Weapons.