Quick answer: Set xfade_time on the transition to a non-zero value (0.15–0.3s) for cross-fading. Use travel() instead of start() to follow transition paths. Verify advance conditions are set correctly via animation_tree.set() with matching parameter names.
Here is how to fix Godot AnimationTree state machine transitions skipping frames. You set up an AnimationNodeStateMachine with Idle, Run, and Jump states. Transitions between them pop instantly — your character snaps from idle to run with no blending. Or worse, pressing jump triggers the jump animation but it skips the first few frames and starts mid-air. The AnimationTree state machine has several settings that control transition timing, and the defaults produce jarring results without tuning.
The Symptom
Animations snap from one to another without blending. The first few frames of the target animation appear skipped. Or a transition that should take a defined path through intermediate states jumps directly to the destination, bypassing required animations.
Variant: transitions work visually but the advance condition never triggers, leaving the character stuck in one state. Or the transition fires repeatedly, causing a flicker between two states.
What Causes This
xfade_time is zero. Each transition in the state machine graph has an xfade_time property. When set to 0 (the default), the switch is instantaneous with no blending. The target animation starts at frame 0 but the visual pop is jarring because there is no interpolation between the old and new poses.
Using start() instead of travel(). AnimationNodeStateMachinePlayback has two methods for changing state: travel(state_name) follows the transition graph (respecting paths, conditions, and xfade), while start(state_name) jumps immediately to the target with no transition at all. Using start() when you mean travel() produces instant snaps.
Advance condition typo or wrong parameter path. Transitions with advance conditions only fire when the condition is true. The condition name must exactly match the string in the AnimationTree’s parameters. A typo means the transition never triggers, and the state machine appears stuck.
Switch mode set to Immediate instead of AtEnd. Each transition has a Switch Mode: Immediate (switch as soon as condition is met) or AtEnd (wait for current animation to finish). If set to Immediate and the condition is toggling rapidly, you get flicker. If set to AtEnd but the current animation loops, the transition never fires because the animation never “ends.”
Reset on transition re-entering same state. If a transition targets the same state it left, or if the animation’s reset behavior is wrong, the animation restarts from frame 0 instead of continuing. This causes the visible “skipping” artifact.
The Fix
Step 1: Set xfade_time on transitions. Select a transition arrow in the AnimationTree editor. In the Inspector, set Xfade Time to a non-zero value:
# Typical cross-fade durations:
# Idle ↔ Run: 0.15 - 0.2 seconds
# Run → Jump: 0.1 seconds (quick, responsive)
# Jump → Fall: 0.1 seconds
# Fall → Land: 0.05 seconds (snappy landing)
# Any → Death: 0.2 seconds
The xfade_time is per-transition, so you can tune each one independently. Shorter values feel more responsive but may pop; longer values feel smoother but add input lag.
Step 2: Use travel() for gameplay transitions.
extends CharacterBody2D
@onready var anim_tree = $AnimationTree
@onready var state_machine = anim_tree["parameters/playback"]
func _physics_process(delta):
if is_on_floor() and Input.is_action_pressed("move_right"):
state_machine.travel("Run")
elif is_on_floor():
state_machine.travel("Idle")
elif velocity.y > 0:
state_machine.travel("Fall")
Reserve start() for hard resets only — respawning, teleporting to a cutscene state, or debugging. For all normal gameplay, travel() is correct.
Step 3: Set advance conditions correctly. In the transition Inspector, type a condition name (e.g. is_jumping). Then set it from code:
# Correct: matches the advance condition name exactly
anim_tree.set("parameters/conditions/is_jumping", true)
# After transition completes, reset it
anim_tree.set("parameters/conditions/is_jumping", false)
The parameter path must be parameters/conditions/<name>. Case matters. A common bug is setting parameters/conditions/IsJumping when the condition field says is_jumping.
Step 4: Choose the right Switch Mode. For each transition, set Switch Mode based on the use case:
- Immediate: Transition starts as soon as the condition is true. Use for responsive actions like jumping or attacking.
- AtEnd: Wait for the current animation to finish first. Use for non-looping sequences like attack combos where you want the full wind-up to play.
If you use AtEnd on a looping animation (like Idle or Run), the transition never fires because a looping animation has no “end.” Switch to Immediate for transitions out of looping states.
Preventing Flicker Between States
If your character rapidly toggles between Idle and Run (e.g. near a velocity threshold), add hysteresis:
const RUN_THRESHOLD = 20.0
const IDLE_THRESHOLD = 10.0 # lower than RUN to prevent toggling
func _physics_process(delta):
var speed = velocity.length()
var current = state_machine.get_current_node()
if current == "Idle" and speed > RUN_THRESHOLD:
state_machine.travel("Run")
elif current == "Run" and speed < IDLE_THRESHOLD:
state_machine.travel("Idle")
The gap between the two thresholds prevents oscillation. Without it, speeds near the boundary cause rapid Idle–Run–Idle–Run transitions that visually stutter.
Checking Current State
Use get_current_node() to query what the state machine is doing. This is helpful for guarding transitions:
func try_attack():
var current = state_machine.get_current_node()
if current in ["Idle", "Run"]:
state_machine.travel("Attack")
“travel() follows the graph. start() teleports. xfade_time blends. Get all three right and transitions feel polished.”
Related Issues
For signal timing issues with animations, see Await Signal Never Completing. For physics-based movement that interacts with animation states, CharacterBody2D Floor Snap covers related patterns.
xfade_time for blending, travel() for graph paths, Immediate mode for looping states. Smooth transitions every time.