A clay knight with one boot lifted
|

Godot 4.x 3D Character Controller with Animation Tree

Add an AnimationTree to your existing Godot 4 character so it blends from idle to walk based on how fast the body is already moving.

This is a different slice from our Godot 4 character controller post: that one wrote the walking, turning, and falling code, while this one leaves that code alone and adds only the animation layer that reads its speed.

If you have not built the controller yet, do that first. This article assumes you have a CharacterBody3D named Player with a Visual child, and a script that sets velocity and calls move_and_slide().

You will write no new movement code. The only new script reads the body’s speed and hands one number to the animation system.

The node names here come from the current Godot 4 documentation page “Using AnimationTree” and its class reference. Godot still shifts details between minor versions. If a property or panel below looks different in your editor, match the docs for your exact version.

What you need before you start

You need a character model with two looping animations: an idle and a walk. Most rigged characters from an asset pack or your own Blender export will have them.

The docs explain that AnimationTree does not hold animations of its own. It controls playback of animations stored in an AnimationPlayer. When you import a 3D model with animations, Godot places them in an AnimationPlayer inside the imported scene.

So the layout is:

  • Player (your existing CharacterBody3D)
  • Visual (your existing node that turns to face movement)
  • Your imported model, instanced under Visual, with its AnimationPlayer inside it

Put the model under Visual, not directly under Player. Your controller already rotates Visual, so the model will turn with it for free.

Delete or hide the placeholder capsule mesh and nose box if the model replaces them. Leave the collision shape exactly as it is.

Check the two animations loop

Open the AnimationPlayer and play idle, then walk. Each should loop without a visible pop at the end.

If either plays once and stops, turn on looping for it. For imported animations, the loop setting usually lives in the import settings for the model. The import dock layout changes between versions, so check the current docs on importing 3D scenes if you cannot find it.

Note the exact names of both animations. You will pick them from a list in a moment.

Add the AnimationTree

Select Player and add an AnimationTree node as a child. Its position in the tree does not affect the blend.

In the Inspector, point the tree at your AnimationPlayer. The docs describe this as pointing the AnimationTree node to the AnimationPlayer that was created in the imported scene. Look for the property that takes an AnimationPlayer path and select the one inside your model.

Then set the tree root. The docs list several root types. For idle and walk driven by one number, the one that fits is AnimationNodeBlendSpace1D. The docs describe it as linear blending between animation nodes, controlled by a blend position on a line.

A state machine would also work, with one transition each way. It adds two transitions, crossfade times, and conditions to manage. For a single number that slides from still to walking, the 1D blend space is the shorter path.

Make sure the tree is active. Without that, nothing plays.

Build the blend space

With the tree selected, the bottom panel shows the blend space editor, a horizontal line. Each point on that line is an animation.

Set the line’s range to run from 0 to 1. Here, 0 means standing still and 1 means full walk speed.

Add two points:

  • At 0, add an animation point and choose your idle.
  • At 1, add an animation point and choose your walk.

Drag the blend position marker along the line in the editor. The model should go from idle at the left end to walk at the right, and blend between them in the middle. If it does not change, recheck that the tree is active and pointed at the right player.

You could put the walk point at your real speed value instead, such as 4. Normalizing to 0 through 1 keeps the blend space the same even if you later change the controller’s speed.

Sync the loops

Blending two loops of different lengths can make the feet look like they skip. The current docs describe a Sync Mode property on BlendSpace1D with four options. One of them, Cyclic Mutable, is described as useful for locomotion loops that share the same logical cycle but differ slightly in length.

Try it and compare against the default. If your version does not show Sync Mode, it may be an older release that used a simpler sync toggle. Check the class reference for your version.

Read the speed

Now connect the blend to the body. Attach a new script to the AnimationTree node, not to Player. This keeps your movement script untouched.

extends AnimationTree

@export var body: CharacterBody3D
@export var walk_speed := 4.0

func _physics_process(_delta: float) -> void:
    var flat := Vector2(body.velocity.x, body.velocity.z)
    var amount := clampf(flat.length() / walk_speed, 0.0, 1.0)
    set("parameters/blend_position", amount)

In the Inspector, drag Player into the body slot. Set walk_speed to the same value as speed in your controller.

Here is what the script does. It takes only the horizontal part of velocity, so falling does not count as walking. It divides by full walking speed to get a value from 0 to 1. It clamps the result so a slide off a slope cannot push past the end of the line. Then it writes that value into the tree’s blend position.

The parameter path matters. The docs explain that you find a parameter’s path by hovering over it in the tree’s Parameters section in the Inspector. For a blend space used as the root, the path is usually parameters/blend_position. Hover over yours and copy the exact string. A typo here fails without moving the blend.

The script runs in _physics_process() so it reads velocity on the same step the controller sets it.

Test the switch

Run your test level. Check each case:

  • Standing still plays idle.
  • Holding a direction plays walk.
  • Releasing input returns to idle.
  • Walking off the floor edge and falling does not trigger walk.
  • Turning keeps the model facing the way it moves.

The controller from the earlier post stops almost instantly when you let go. That means the blend jumps from 1 to 0 in about one frame. If the switch looks abrupt, smooth only the animation value, not the movement. Store the last amount in a variable and use lerpf() toward the new one each frame before you set it. The body still stops at once. The legs just ease out.

If the model walks in place but faces the wrong way, rotate the model inside Visual until its front matches the old nose box. If it slides without stepping, your walk point may be holding the idle animation by mistake.

Stop here

You now have idle and walk following the body’s real speed, with no change to how the body moves. Commit it.

Running, jumping, and landing can come later. A run animation can become a third point on the same line. A jump usually needs a state machine or a one-shot node. Add them one at a time, and keep the current AnimationTree docs open as you do.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *