Moving the player with codeΒΆ

It's time to code! We're going to use the input actions we created in the last part to move the character.

Right-click the Player node and select Attach Script to add a new script to it. In the popup, set the Template to Empty before pressing the Create button.


Let's start with the class's properties. We're going to define a movement speed, a fall acceleration representing gravity, and a velocity we'll use to move the character.

extends CharacterBody3D

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration while in the air, in meters per second squared.
@export var fall_acceleration = 75

var target_velocity = Vector3.ZERO

These are common properties for a moving body. The velocity is a 3D vector combining a speed with a direction. Here, we define it as a property because we want to update and reuse its value across frames.


The values are quite different from 2D code because distances are in meters. While in 2D, a thousand units (pixels) may only correspond to half of your screen's width, in 3D, it's a kilometer.

Let's code the movement. We start by calculating the input direction vector using the global Input object, in _physics_process().

func _physics_process(delta):
    # We create a local variable to store the input direction.
    var direction = Vector3.ZERO

    # We check for each move input and update the direction accordingly.
    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        # Notice how we are working with the vector's x and z axes.
        # In 3D, the XZ plane is the ground plane.
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

Here, we're going to make all calculations using the _physics_process() virtual function. Like _process(), it allows you to update the node every frame, but it's designed specifically for physics-related code like moving a kinematic or rigid body.

See also

To learn more about the difference between _process() and _physics_process(), see Idle and Physics Processing.

We start by initializing a direction variable to Vector3.ZERO. Then, we check if the player is pressing one or more of the move_* inputs and update the vector's x and z components accordingly. These correspond to the ground plane's axes.

These four conditions give us eight possibilities and eight possible directions.

In case the player presses, say, both W and D simultaneously, the vector will have a length of about 1.4. But if they press a single key, it will have a length of 1. We want the vector's length to be consistent, and not move faster diagonally. To do so, we can call its normalize() method.

#func _physics_process(delta):

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(position + direction, Vector3.UP)

Here, we only normalize the vector if the direction has a length greater than zero, which means the player is pressing a direction key.

In this case, we also get the Pivot node and call its look_at() method. This method takes a position in space to look at in global coordinates and the up direction. In this case, we can use the Vector3.UP constant.


A node's local coordinates, like position, are relative to their parent. Global coordinates, like global_position are relative to the world's main axes you can see in the viewport instead.

In 3D, the property that contains a node's position is position. By adding the direction to it, we get a position to look at that's one meter away from the Player.

Then, we update the velocity. We have to calculate the ground velocity and the fall speed separately. Be sure to go back one tab so the lines are inside the _physics_process() function but outside the condition we just wrote above.

func _physics_process(delta):
    if direction != Vector3.ZERO:

    # Ground Velocity
    target_velocity.x = direction.x * speed
    target_velocity.z = direction.z * speed

    # Vertical Velocity
    if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
            target_velocity.y = target_velocity.y - (fall_acceleration * delta)

    # Moving the Character
    velocity = target_velocity

The CharacterBody3D.is_on_floor() function returns true if the body collided with the floor in this frame. That's why we apply gravity to the Player only while he is in the air.

For the vertical velocity, we subtract the fall acceleration multiplied by the delta time every frame. This line of code will cause our character to fall in every frame, as long he is not on the floor, or collides with it.

The physics engine can only detect interactions with walls, the floor, or other bodies during a given frame if movement and collisions happen. We will use this property later to code the jump.

On the last line, we call