How to Make a 3D Platformer in Godot (With GDScript Code)

By Arron R.11 min read
How to make a 3D platformer in Godot: build a CharacterBody3D player with camera-relative GDScript movement, add a SpringArm3D camera, block out levels with CSG

If you want to learn how to make a 3D platformer in Godot, the shortest honest path is this: one player scene built on CharacterBody3D, one movement script that feels good to jump with, a camera that follows without fighting you, and a level made of simple blocks you can replace with real art later. This guide walks through each piece with complete GDScript for Godot 4, explains the beginner terms as they come up, and covers the mistakes that make first platformers feel floaty or broken.

Godot is a free, open-source engine released under the MIT License, and as of October 2026 its latest stable release is 4.7.2. Everything below uses standard Godot 4 nodes and APIs, so it works across recent 4.x versions.

What you need before you start

You need the Godot 4 editor and a basic grasp of two ideas Godot is built around:

  • Nodes: single building blocks, such as a camera, a light, a mesh or a physics body. Each node does one job.
  • Scenes: a saved tree of nodes. Your player is a scene, a coin is a scene, and your level is a scene that contains instances of the others.

Scripts are written in GDScript, Godot’s Python-like language. If you have never touched Godot, the official “Your first 3D game” tutorial covers 3D coordinates, a jumping mechanic, kinematic movement and collision detection, and the docs suggest beginners start with the 2D series first. That advice is sound: 3D adds a camera problem that 2D does not have.

If you would rather study a finished controller before writing your own, the 3D Platformer Starter Kit on GitHub is a free template tested in Godot 4.1 and above, with idle, run, jump and flip animations and a heavily commented player script. Reading it alongside this guide is a good way to see the same ideas solved slightly differently.

How to make a 3D platformer in Godot: the core scene setup

Create a new project, then set up input before any code. Open Project Settings, go to the Input Map tab and add these actions, binding each to a key and, if you like, a gamepad input:

  • move_left, move_right, move_forward, move_back (WASD or a stick)
  • jump (Space or the bottom face button)
  • cam_left, cam_right (Q and E, or the right stick, for keyboard camera turning)

Naming actions instead of reading raw keys means you can rebind controls later without touching the scripts.

The player scene

Make a new scene with a CharacterBody3D as the root. This is Godot’s node for characters you move with code rather than with physics forces; it gives you velocity, move_and_slide() and is_on_floor() for free. Build this tree:

Player (CharacterBody3D)        player.gd
├── CollisionShape3D            CapsuleShape3D, height 1.8
├── Model (Node3D)              your character mesh goes in here
└── CameraPivot (Node3D)        camera_pivot.gd, position y = 1.5
    └── SpringArm3D             spring_length = 5
        └── Camera3D

A collision shape is the invisible volume the physics engine actually tests against; a capsule is the standard choice because it slides over small bumps instead of catching on them. The Model node is separate so you can rotate the visible character to face its movement without rotating the body or the camera. Add the player to a group called player (Node tab, Groups) so pickups can recognise it.

The level scene

Make a second scene with a Node3D root, add a DirectionalLight3D, a WorldEnvironment for sky and ambient light, and an instance of your player. For platforms, use CSGBox3D nodes with Use Collision turned on. CSG (constructive solid geometry) boxes are quick to resize in the editor, which makes them ideal for blocking out a level. Swap them for proper StaticBody3D meshes once the layout is fun.

Scene tree for a Godot 3D platformer player with collision shape, model, camera pivot, spring arm and camera
The player scene: the body moves, the model turns, the pivot owns the camera.

Writing the player movement script

This is where most of the “feel” of a platformer lives. The script below does five things: gravity, a heavier fall for a snappier arc, coyote time, jump buffering and variable jump height. Two of those need a definition:

  • Coyote time: a short window after walking off a ledge where a jump still counts. Players perceive it as fairness, not as a cheat.
  • Jump buffering: remembering a jump press made just before landing, so the jump fires the moment the player touches the ground.

Attach this as player.gd on the Player node:

extends CharacterBody3D

@export var speed := 7.0
@export var acceleration := 40.0
@export var jump_velocity := 9.0
@export var fall_multiplier := 1.8
@export var coyote_time := 0.12
@export var jump_buffer_time := 0.12

@onready var camera_pivot: Node3D = $CameraPivot
@onready var model: Node3D = $Model

var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
var coyote_timer := 0.0
var jump_buffer_timer := 0.0
var spawn_point := Vector3.ZERO

func _ready() -> void:
	spawn_point = global_position

func _physics_process(delta: float) -> void:
	# Gravity, stronger while falling for a less floaty arc
	if is_on_floor():
		coyote_timer = coyote_time
	else:
		var mult: float = fall_multiplier if velocity.y < 0.0 else 1.0
		velocity.y -= gravity * mult * delta
		coyote_timer -= delta

	# Jump buffering
	if Input.is_action_just_pressed("jump"):
		jump_buffer_timer = jump_buffer_time
	else:
		jump_buffer_timer -= delta

	if jump_buffer_timer > 0.0 and coyote_timer > 0.0:
		velocity.y = jump_velocity
		jump_buffer_timer = 0.0
		coyote_timer = 0.0

	# Variable jump height: release early for a short hop
	if Input.is_action_just_released("jump") and velocity.y > 0.0:
		velocity.y *= 0.5

	# Camera-relative movement
	var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
	var cam_basis := camera_pivot.global_transform.basis
	var direction := cam_basis.x * input_dir.x + cam_basis.z * input_dir.y
	direction.y = 0.0
	direction = direction.normalized()

	var target := direction * speed
	velocity.x = move_toward(velocity.x, target.x, acceleration * delta)
	velocity.z = move_toward(velocity.z, target.z, acceleration * delta)

	# Turn the visible model toward the movement direction
	if direction.length() > 0.1:
		var target_angle := atan2(direction.x, direction.z)
		model.rotation.y = lerp_angle(model.rotation.y, target_angle, 12.0 * delta)

	move_and_slide()

	if global_position.y < -20.0:
		respawn()

func respawn() -> void:
	global_position = spawn_point
	velocity = Vector3.ZERO

A few notes on why it is written this way. Movement is built from the camera pivot’s basis (its local axes), so “forward” always means “away from the camera”, which is what players expect in a third-person platformer. move_toward gives acceleration and deceleration instead of instant starts and stops; raise acceleration for tighter control, lower it for a slippery feel. If your imported character faces backwards while running, add PI to target_angle, because models from different tools disagree about which way is forward.

The exported variables show up in the Inspector, so you can tune jump height and speed while the game runs. That tuning loop, not the code itself, is most of the work when you figure out how to make a 3D platformer in Godot feel good.

Building a third-person camera that works

The camera is the part beginners underestimate. A camera stuck rigidly behind the player clips through walls, and a free mouse camera with no limits lets players look straight up through the floor. The fix is a spring arm: a SpringArm3D casts a shape from the pivot toward the camera and pulls the camera in when something is in the way.

The scene tree already splits rotation across two nodes on purpose. The pivot handles yaw (turning left and right), and the spring arm handles pitch (looking up and down). Movement reads only the pivot’s basis, so tilting the camera never makes the player walk into the ground. Attach this as camera_pivot.gd:

extends Node3D

@export var mouse_sensitivity := 0.003
@export var key_turn_speed := 2.5
@export var min_pitch_deg := -60.0
@export var max_pitch_deg := 20.0

@onready var spring_arm: SpringArm3D = $SpringArm3D

func _ready() -> void:
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
	# Stop the spring arm colliding with the player's own capsule
	spring_arm.add_excluded_object(get_parent().get_rid())

func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		rotation.y -= event.relative.x * mouse_sensitivity
		spring_arm.rotation.x -= event.relative.y * mouse_sensitivity
		spring_arm.rotation.x = clampf(spring_arm.rotation.x,
			deg_to_rad(min_pitch_deg), deg_to_rad(max_pitch_deg))
	if event.is_action_pressed("ui_cancel"):
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE

func _process(delta: float) -> void:
	# Keyboard or right-stick camera turning
	var turn := Input.get_axis("cam_right", "cam_left")
	rotation.y += turn * key_turn_speed * delta

Both mouse and keyboard turning feed the same rotation.y, so you can support either control scheme, or both, without changing the movement code. Start the pitch limits narrow; a camera that cannot look too far down is rarely a complaint, while one that shows the underside of the level always is.

Levels, respawning and collectibles

With movement and camera in place, you have a toy. Four small pieces turn it into a game: a kill zone, checkpoints, collectibles and moving platforms. All of them rely on Area3D, a node that detects overlaps without blocking movement, or on AnimatableBody3D, a solid body you move with code or animation that carries the player along.

Kill zones and checkpoints

The height check in the player script already catches long falls. For pits inside the level, place a wide Area3D with a box collision shape below the danger area. For checkpoints, use another Area3D that overwrites the player’s spawn point:

# kill_zone.gd (on an Area3D)
extends Area3D

func _ready() -> void:
	body_entered.connect(func(body: Node3D) -> void:
		if body.has_method("respawn"):
			body.respawn())
# checkpoint.gd (on an Area3D)
extends Area3D

func _ready() -> void:
	body_entered.connect(func(body: Node3D) -> void:
		if body.is_in_group("player"):
			body.spawn_point = global_position + Vector3.UP)

Coins

A coin is an Area3D with a small sphere shape and a mesh. It spins, emits a signal when collected and removes itself. The signal lets a separate score label listen without the coin needing to know the UI exists.

# coin.gd (on an Area3D)
extends Area3D

signal collected

func _ready() -> void:
	body_entered.connect(_on_body_entered)

func _process(delta: float) -> void:
	rotate_y(2.0 * delta)

func _on_body_entered(body: Node3D) -> void:
	if body.is_in_group("player"):
		collected.emit()
		queue_free()

Moving platforms

Use AnimatableBody3D with a box collision and mesh, and drive it with a tween that runs in the physics step so the player rides it smoothly:

# moving_platform.gd (on an AnimatableBody3D)
extends AnimatableBody3D

@export var offset := Vector3(0, 0, 6)
@export var duration := 2.0

func _ready() -> void:
	var start := position
	var tween := create_tween().set_loops().set_process_mode(Tween.TWEEN_PROCESS_PHYSICS)
	tween.tween_property(self, "position", start + offset, duration).set_trans(Tween.TRANS_SINE)
	tween.tween_property(self, "position", start, duration).set_trans(Tween.TRANS_SINE)

Because move_and_slide() tracks the floor the character stands on, the player inherits the platform’s motion without extra code.

Isometric platformer level showing a kill zone, checkpoint, coin and moving platform
Four Area3D and AnimatableBody3D pieces turn a movement test into a level.

Building a 3D platformer faster with WizardGenie

Everything above is the right approach if your goal is to learn Godot. If your goal is a playable 3D platformer and you would rather describe it than wire nodes by hand, WizardGenie is the cleaner route. It is the AI game engine inside Sorceress: you describe the game you want, and WizardGenie writes it, runs it and iterates on it in real time.

Here is how that maps onto the work in this guide:

  1. Describe the core loop in plain language. For example: “A third-person 3D platformer. The player runs and jumps across floating platforms, collects coins, and respawns at the last checkpoint after falling. The camera follows from behind and turns with the mouse or Q and E.”
  2. Tune the feel with the same vocabulary. The concepts from the movement section transfer directly. Ask for coyote time, jump buffering, a heavier fall than rise, and a short hop when jump is released early. Because WizardGenie runs the game as it iterates, you can play each version and ask for changes such as “jump 20% higher, less air control”.
  3. Pick the model that suits the task. WizardGenie drives the leading AI coding models, and you can switch between them freely with your own API key.
  4. Choose where to run it. It works on the web with nothing to install, and as a Windows desktop app with an auto-updater that adds native file access and longer agent sessions, which helps on bigger projects.

A practical split many developers use: prototype the feel in WizardGenie, where changes are a sentence away, and if you are committed to shipping in Godot, carry the numbers that felt right (jump velocity, fall multiplier, acceleration) into the exported variables above. If prompting a game into existence is new to you, the guide to vibe coding and how to do it explains the workflow. The WizardGenie overview covers the engine itself.

Characters and materials for either route

Art is the other slow part of making a platformer, and it is the same problem whether you use Godot or WizardGenie. 3D Studio chains the full character pipeline: type an idea and get a rigged, animated, game-ready 3D character. A rig is the internal skeleton that lets a mesh bend and animate, which is what your run and jump cycles need. For a blocky look, Voxel Studio turns images, text prompts or 3D models into voxel creations and automatically rigs humanoids.

For level surfaces, Material Forge (uses AI credits) turns a single image into a full PBR material: albedo, roughness, metallic, normal and AO maps. PBR (physically based rendering) means the surface reacts to light realistically; in Godot those maps slot straight into the matching slots of a StandardMaterial3D. When bringing models into Godot, prefer glTF, the open standard format for 3D scenes and models, which Godot imports with meshes, materials and animations intact.

If you work in Godot with a coding agent instead, the Sorceress API gives WizardGenie, Cursor, Claude Code or Codex one key to generate sprites, 3D models, rigs, animation, music and SFX. The tools guide lists every tool in one place.

Common mistakes when making a 3D platformer in Godot

  • Moving in world space instead of camera space. If “forward” always means negative Z, controls feel inverted the moment the camera turns. Always build direction from the camera pivot’s basis.
  • Putting pitch on the same node as yaw. Movement then tilts into the floor when the camera looks down. Keep yaw on the pivot and pitch on the spring arm.
  • Floaty jumps. Default gravity with a symmetric arc feels like the moon. A fall multiplier and variable jump height fix most of it before you touch anything else.
  • Rotating the CharacterBody3D to face movement. That drags the camera child around with it. Rotate a separate Model node instead.
  • Forgetting the spring arm exclusion. Without excluding the player’s own collision, the spring arm detects the capsule and snaps the camera into the character’s head.
  • Using a box collider for the player. Boxes snag on edges and steps. Capsules slide.
  • Art before fun. Polishing a hero model before the jump feels right wastes time. Block out with CSG, tune movement, then add art.
  • Building a huge first level. A single room with five jumps, a coin and a pit teaches more than a sprawling world you never finish.

A frequent question is how to make a platformer game feel “tight”. The answer is almost always in the list above: camera-relative input, a faster fall than rise, coyote time and buffering. None of them is specific to Godot, so the same checklist applies to any engine.

The workflow at a glance

Here is the whole process of how to make a 3D platformer in Godot, condensed into an order you can follow:

StageWhat you buildKey nodes
1. InputNamed move, jump and camera actionsInput Map
2. PlayerBody, capsule, separate model, camera pivotCharacterBody3D, CollisionShape3D
3. MovementGravity, coyote time, buffering, camera-relative inputmove_and_slide()
4. CameraYaw on pivot, pitch on arm, wall avoidanceSpringArm3D, Camera3D
5. LevelBlockout platforms, light, skyCSGBox3D, WorldEnvironment
6. GameplayKill zones, checkpoints, coins, moving platformsArea3D, AnimatableBody3D
7. Art and audioRigged hero, PBR surfaces, soundImported glTF scenes

Get stages 1 to 4 right before moving on; a good jump and a calm camera carry a plain-looking game further than great art carries a bad jump. Once you know how to make a 3D platformer in Godot at this scale, extending it is mostly more of the same: new hazards are Area3D scenes, new platforms are AnimatableBody3D scenes, and new levels reuse the player you already built. If you would rather skip the wiring and play a prototype the same afternoon, describe it to WizardGenie and iterate from there.

Frequently Asked Questions

Is Godot good for making 3D platformers?

Yes. Godot 4 ships everything a platformer needs: CharacterBody3D for player movement with built-in floor detection, SpringArm3D for a third-person camera that avoids walls, CSG nodes for quick level blockouts, and Area3D for pickups and kill zones. It is open source under the MIT License, so there are no licensing fees, and free starter kits exist if you want a working controller to study before writing your own.

Should I learn 2D in Godot before making a 3D platformer?

If you are completely new to game development, starting with 2D is easier, and the official Godot documentation recommends it before its 3D tutorial. The concepts carry over directly: nodes, scenes, signals, input actions and move_and_slide work the same way. If you already know some programming, you can start in 3D, keep the first level tiny and treat the camera as the hardest part.

How do I respawn the player after falling off a platform in Godot?

Store the player’s starting position in a variable when the scene loads, then reset global_position to it and zero the velocity when the player falls. You can trigger this with a simple height check, such as global_position.y below -20, or with an Area3D kill zone under the level that calls a respawn function on bodies entering it. Update the stored position at checkpoints.

How do I make the camera rotate with the keyboard instead of the mouse?

Add two input actions, such as cam_left and cam_right, in the Input Map. In the camera pivot script, read them with Input.get_axis inside _process and add the result, multiplied by a turn speed and delta, to the pivot’s rotation.y. Because movement is calculated from the pivot’s basis, the player’s controls stay camera-relative whether you turn with keys, mouse or a gamepad stick.

Can I make a 3D platformer without writing code?

In Godot you will need at least some GDScript, though starter templates reduce how much. If you would rather describe the game in plain language, WizardGenie, the AI game engine in Sorceress, writes, runs and iterates on a game from your description in real time. It runs in the browser and as a Windows desktop app, and you can switch between leading AI coding models using your own API key.

Where do I get 3D models for a Godot platformer?

Block out levels with Godot’s CSG boxes first, then replace them with real art. Free asset packs work for prototypes. For a custom hero, Sorceress 3D Studio turns a typed idea into a rigged, animated, game-ready 3D character, and Voxel Studio converts images, prompts or models into voxel art. Godot imports glTF files most smoothly, so prefer that format where your tools offer it.

Sources

  1. Your first 3D game - Godot Docs
  2. A 3D platformer demo for beginners made in godot 4. ...
  3. Godot (game engine) — Wikipedia
  4. GlTF — Wikipedia
Written by Arron R.·2,644 words·11 min read

Related posts