3D movement
CharacterBody3D: input, collisions, gravity and jumping.
Requires a C# project: script and build setup.
1. Build a separate level
Use File → Create Scene, keep an Entity3D root, name it World and save as World.rscene. Create this hierarchy; indentation means a child object:
World (Entity3D)
├── Floor (StaticBody3D)
│ ├── Visual (MeshInstance3D)
│ └── CollisionShape3D
├── Player (CharacterBody3D)
│ ├── Visual (MeshInstance3D)
│ └── CollisionShape3D
├── Camera3D
└── DirectionalLight3DSelect the correct parent before adding children. The camera is a child of World in this lesson, so it does not follow the character.
2. Configure the floor and character
| Object | Settings |
|---|---|
Floor | Position: (0, -0.1, 0) |
Floor/Visual | Mesh: new BoxMesh, with Size: (20, 0.2, 20) |
Floor/CollisionShape3D | Shape: new BoxShape3D, with Size: (20, 0.2, 20) |
Player | Position: (0, 0.9, 0) |
Player/Visual | Mesh: new CapsuleMesh, Radius: 0.35, Height: 1.8 |
Player/CollisionShape3D | Shape: new CapsuleShape3D, Radius: 0.35, Height: 1.8 |
Camera3D | Position: (0, 6, 8), Rotation: (-35, 0, 0), Current: enabled |
DirectionalLight3D | Rotation: (-45, -30, 0) |
Keep all child meshes and collision shapes at local position (0, 0, 0) and scale (1, 1, 1). Change dimensions inside Mesh and Shape resources rather than scaling the physics body. Leave collision layers and masks at their defaults.
Mesh draws a surface. Shape defines collision geometry and is invisible in normal gameplay. StaticBody3D suits a stationary floor. CharacterBody3D is designed for movement controlled by your code.
3. Create input actions
Open Project → Project Settings → Input Map. Add actions with the exact names below, then assign keyboard events using each action's add-event button.
| Action | Keys |
|---|---|
move_left | A and Left arrow |
move_right | D and Right arrow |
move_forward | W and Up arrow |
move_back | S and Down arrow |
jump | Space |
An action names a command; a key is one way to trigger it. You can later add a controller without rewriting the main movement code. Physical key bindings are convenient for WASD because they keep the same positions across keyboard layouts.
4. Attach Player.cs
Attach Scripts/Player.cs to Player, using base class CharacterBody3D. Replace the script contents:
using Reckfell;
public partial class Player : CharacterBody3D
{
[Export] public float Speed { get; set; } = 4.0f;
[Export] public float Gravity { get; set; } = 20.0f;
[Export] public float JumpSpeed { get; set; } = 6.0f;
public override void _PhysicsProcess(double delta)
{
Vector3 velocity = Velocity;
if (!IsOnFloor())
velocity.Y -= Gravity * (float)delta;
if (Input.IsActionJustPressed("jump") && IsOnFloor())
velocity.Y = JumpSpeed;
Vector2 input = Input.GetVector("move_left", "move_right", "move_forward", "move_back");
velocity.X = input.X * Speed;
velocity.Z = input.Y * Speed;
Velocity = velocity;
MoveAndSlide();
}
}Save the files, build C# with Alt + B and run World.rscene using F6. Click the game window to focus keyboard input.
Why movement works this way
Velocity is speed in world units per second. MoveAndSlide() moves the body, accounts for the physics interval and slides along obstacles. Therefore do not multiply Speed by delta before assigning Velocity here.
Gravity changes speed over time, so its calculation does use delta. IsOnFloor() reports floor contact from the previous movement. IsActionJustPressed() responds to a new press: holding Space does not trigger a jump every frame.
Input.GetVector() combines four actions into a 2D direction and limits its length. Diagonal movement should not be faster. This example moves along world axes rather than relative to camera rotation; it is deliberately a simple first controller.
If the character falls through, check both bodies’ Shapes and collision layers/masks. For missing input, check action names and game-window focus. Troubleshooting.