Blog

How to use a rigged GLB character in Godot 4

Import a rigged, animated .glb into Godot 4, loop its clips, play them from code and walk it around with a CharacterBody3D.

You have a rigged character as one .glb file, and you want it walking around your Godot scene. This guide takes you from the file to a character you steer with the arrow keys. We tested every step in Godot 4.7.2 with a character from Hatchery3D, but they work for any glTF character with a skeleton and named clips.

What's in the file

A Hatchery3D character is a single glTF 2.0 binary file. Godot reads it natively, with no plugin. Inside it you get:

  • A textured mesh, already at its real height in metres, with its feet on the floor at the origin.
  • A humanoid skeleton of 77 bones, with Hips at the root.
  • Nine animation clips: idle, walk, run, jump, punch, slash, hit, death and wave.
  • Two empty sockets, HandSocket_L and HandSocket_R, in the palms, for the things your character holds.

Import it

Drag the .glb into the FileSystem dock. Godot imports it as a scene. Open it, and you see this tree:

character (Node3D)
├── Rig (Node3D)
│   └── Skeleton3D
│       ├── LeftHand (BoneAttachment3D)
│       │   └── HandSocket_L
│       ├── RightHand (BoneAttachment3D)
│       │   └── HandSocket_R
│       └── RETOPO (MeshInstance3D)
└── AnimationPlayer

Every clip is an animation in the AnimationPlayer, under its own name. The hand sockets become BoneAttachment3D nodes, so anything you put under them follows the hand.

Loop idle, walk and run

Play walk now and it stops after one step. The file marks idle, walk and run as loops, but Godot's importer doesn't read that mark, so every clip comes in set to play once. Fix it once, in the import settings:

  1. Double-click the .glb in the FileSystem dock. The Advanced Import Settings window opens.
  2. In the tree on the left, open the AnimationPlayer and select idle.
  3. On the right, set Loop Mode to Linear.
  4. Do the same for walk and run.
  5. Click Reimport.

The setting lives in the .import file next to the .glb, so it survives when you replace the file with a new take of the same name. Leave jump, punch, slash, hit, death and wave on None: they play once and stop.

Play a clip

Make a new 3D scene and drag the .glb from the FileSystem dock into it. Rename the new node to character, so the paths below match. Then play a clip from any script:

$character/AnimationPlayer.play("wave")

To switch between clips without a pop, pass a blend time in seconds as the second argument: play("walk", 0.2) fades from the current clip into walk over a fifth of a second.

Walk it around

The walk and run clips play on the spot, like a treadmill. That's what you want in a game: your code moves the body, and the clip moves the legs. Here's a minimal player.

  1. Make a new scene with a CharacterBody3D as its root, and call it Player.
  2. Drag the character's .glb under it, and rename the node to character.
  3. Add a CollisionShape3D under Player. Give it a CapsuleShape3D with the character's height (1.8 for a 1.8 m character) and a radius of about 0.3. Move it up by half the height, so it stands on the floor like the mesh.
  4. Attach this script to Player:
extends CharacterBody3D

# The speeds at which the feet don't slide, in metres per second.
const WALK_SPEED := 1.3
const RUN_SPEED := 2.6

@onready var model: Node3D = $character
@onready var animation: AnimationPlayer = $character/AnimationPlayer


func _physics_process(delta: float) -> void:
	if not is_on_floor():
		velocity += get_gravity() * delta

	var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	var direction := Vector3(input.x, 0.0, input.y)
	var running := Input.is_key_pressed(KEY_SHIFT)
	var speed := RUN_SPEED if running else WALK_SPEED

	velocity.x = direction.x * speed
	velocity.z = direction.z * speed
	move_and_slide()

	if direction.length() > 0.1:
		# The model faces +Z, so turn it toward the direction of travel.
		model.rotation.y = atan2(direction.x, direction.z)
		animation.play("run" if running else "walk", 0.2)
	else:
		animation.play("idle", 0.2)

Put the Player in a level with a floor, press F6, and steer with the arrow keys. Hold Shift to run.

Match the speed to the feet

If the feet slide, the body moves faster or slower than the legs. Each Hatchery3D character comes with its own measured speeds: the file stores them on the walk and run clips as speed_mps, in the glTF extras. Godot doesn't import extras on animations, so copy the numbers into the two constants. For a 1.8 m character, they are about 1.3 m/s for the walk and 2.6 m/s for the run. A shorter character takes shorter steps, so its numbers are lower.

Put a sword in its hand

Characters come with empty hands, because a weapon rigged into the hand swings through the body. Make the sword as a separate prop instead, and parent it to a socket:

  1. Open the character's scene with New Inherited Scene (right-click the .glb), or work in the scene where you placed it. In a placed instance, right-click it and turn on Editable Children first.
  2. Drag the sword's .glb under Skeleton3D/RightHand/HandSocket_R.
  3. Rotate and move the sword until the grip sits in the palm.

A Hatchery3D prop comes with a collision box. In a hand, that box pushes against the character's own body, so delete the prop's prop_collision node when you use it as a held item.

Props: colliders and LODs

Props import the same way. Their pivot sits at the base, so a prop stands on the floor where you drop it. The file carries a box collider that Godot turns into a StaticBody3D with a CollisionShape3D, so walls, crates and barrels block the player right away.

The file also holds two lower levels of detail. Godot skips those and builds its own LODs for every imported mesh, so you don't need to do anything: distant props draw fewer triangles automatically.

When something looks wrong

You seeThe causeThe fix
The walk stops after one stepThe loop mode is NoneSet Loop Mode to Linear in the import settings, then reimport
The character walks backwardsIt faces +Z and your code expects −ZTurn the model with atan2(direction.x, direction.z), as in the script
The feet slideThe body speed doesn't match the clipUse the clip's speed_mps for the constants
A held prop shoves the characterThe prop's collider touches the bodyDelete the prop's prop_collision node

For bigger setups, put the clips in an AnimationTree with a blend space for idle, walk and run. The clips and their names stay the same.