Quick answer: The most common cause is transition conditions never being met. In GDScript state machines, the condition check may run before the state variable is updated, or transitions are split across _process() and _physics_process().
Here is how to fix Godot state machine stuck wrong state. Your character should transition from idle to run when they move, but they are stuck in idle forever. Or your AnimationTree state machine refuses to leave its starting state no matter what parameters you set. State machines in Godot 4 — whether hand-rolled in GDScript or built with AnimationTree — can silently refuse to transition for several fixable reasons.
The Symptom
Your state machine does not change states. In a custom GDScript implementation, the current state variable never updates even though your transition function is called. In an AnimationTree, travel() calls have no effect and the playback stays on its initial node. The game runs without errors but the character plays the wrong animation indefinitely.
What Causes This
1. Transition conditions never met. If you set velocity in _physics_process() but check it in _process(), the condition sees the previous frame’s value. This timing mismatch means the transition condition is never true at the moment it is checked.
2. States not connected in AnimationTree. The state machine editor requires explicit transitions between states. If two states are visually close but have no transition arrow, travel() cannot pathfind between them and silently fails.
3. Wrong condition evaluation order. In custom state machines with multiple if/elif checks, a higher-priority condition that is always true blocks lower-priority transitions. Checking is_on_floor() before jump input can make the jump state unreachable.
4. AnimationTree not active. The active property must be true for state transitions to process. It defaults to false in the editor, which catches many developers off guard.
The Fix
Keep all state logic in _physics_process() and evaluate transitions after movement. Check fall states before movement states so airborne characters do not enter ground states:
extends CharacterBody2D
enum State { IDLE, RUN, JUMP, FALL }
var current_state: State = State.IDLE
func _physics_process(delta):
_update_velocity(delta)
move_and_slide()
var new_state = _get_next_state()
if new_state != current_state:
print("Transition: ", State.keys()[current_state],
" -> ", State.keys()[new_state])
current_state = new_state
func _get_next_state() -> State:
match current_state:
State.IDLE:
if not is_on_floor(): return State.FALL
if abs(velocity.x) > 10.0: return State.RUN
State.RUN:
if not is_on_floor(): return State.FALL
if abs(velocity.x) < 10.0: return State.IDLE
State.FALL:
if is_on_floor(): return State.IDLE
return current_state
For AnimationTree state machines, ensure the tree is active and use travel() with correctly connected states:
@onready var anim_tree = $AnimationTree
@onready var playback: AnimationNodeStateMachinePlayback = \
anim_tree["parameters/playback"]
func _ready():
anim_tree.active = true
print("Current state: ", playback.get_current_node())
Related Issues
If your state machine transitions work but animations do not play, see AnimationPlayer not playing on scene load. If your state machine depends on overlap detection for transitions, check get_overlapping_bodies() returning empty to ensure physics queries are not giving stale data.
Put all state checks in _physics_process, after move_and_slide. Never split transitions across _process and _physics_process.