Alright, somewhat work in progress.
The anim_tree statemachine is coded so there is no spaghetti connections.


Created animation_slaver.gd
extends RefCounted
class_name AnimationSlaver
const ANIMATION_STATE_MACHINE_NAME : StringName = "StateMachineMovement"
const ANIMATION_XFADE_TIME := 0.1
const ANIMATION_STATES : Dictionary = {
"idle": {
"animation": "Idle_90",
"one_shot": false,
},
"walk": {
"animation": "Walk_32",
"one_shot": false,
},
"Jump_ascend_9": {
"animation": "Jump_ascend_9",
"one_shot": true,
},
"Jump_landed_9": {
"animation": "Jump_landed_9",
"one_shot": true,
},
"Jump_midair_90": {
"animation": "Jump_midair_90",
"one_shot": false,
},
"Run_16": {
"animation": "Run_16",
"one_shot": false,
},
"Crouch_90": {
"animation": "Crouch_90",
"one_shot": false,
},
"Crouch_walk_32": {
"animation": "Crouch_walk_32",
"one_shot": false,
},
"walk_stop": {
"animation": "Walk_stop",
"one_shot": true,
},
"walk_start": {
"animation": "Walk_start",
"one_shot": true,
},
}
const EXCLUDED_TRANSITIONS : Array = [
# "Idle_90->Walk_stop",
# "Walk_stop->Jump_ascend_9",
]
func setup_animation_tree(animation_tree) -> void:
var blend_tree : AnimationNodeBlendTree = animation_tree.tree_root
if blend_tree == null:
push_error("AnimationTree root is not an AnimationNodeBlendTree.")
return
var anim_state_machine : AnimationNodeStateMachine = blend_tree.get_node(
ANIMATION_STATE_MACHINE_NAME
)
if anim_state_machine == null:
push_error(
"Could not find AnimationNodeStateMachine: "
+ str(ANIMATION_STATE_MACHINE_NAME)
)
return
animation_tree.active = true
#animation_tree.advance_expression_base_node = advance_expression_base_node
anim_state_machine.allow_transition_to_self = false
for state in ANIMATION_STATES:
var animation_name: String = (
ANIMATION_STATES[state]["animation"]
)
if anim_state_machine.has_node(animation_name):
continue
var animation_node : AnimationNodeAnimation = AnimationNodeAnimation.new()
animation_node.animation = animation_name
anim_state_machine.add_node(
animation_name,
animation_node,
Vector2.ZERO
)
var playback : AnimationNodeStateMachinePlayback = animation_tree.get(
"parameters/%s/playback" % ANIMATION_STATE_MACHINE_NAME
)
if playback == null:
push_error("Could not get AnimationNodeStateMachinePlayback.")
return
playback.start("Idle_90")
for from_state in ANIMATION_STATES:
var from_animation: String = ANIMATION_STATES[from_state]["animation"]
var from_one_shot: bool = bool(
ANIMATION_STATES[from_state]["one_shot"]
)
for to_state in ANIMATION_STATES:
var to_animation: String = ANIMATION_STATES[to_state]["animation"]
if from_animation == to_animation:
continue
var transition_name := "%s->%s" % [
from_animation,
to_animation
]
if transition_name in EXCLUDED_TRANSITIONS:
continue
var transition :AnimationNodeStateMachineTransition = AnimationNodeStateMachineTransition.new()
transition.xfade_time = ANIMATION_XFADE_TIME
transition.advance_mode = (
AnimationNodeStateMachineTransition.ADVANCE_MODE_AUTO
)
transition.advance_expression = (
'state_machine.current_state_name == "%s"'
% String(to_state).to_lower()
)
if from_one_shot:
transition.switch_mode = (
AnimationNodeStateMachineTransition.SWITCH_MODE_AT_END
)
else:
transition.switch_mode = (
AnimationNodeStateMachineTransition.SWITCH_MODE_IMMEDIATE
)
transition.reset = true
anim_state_machine.add_transition(
from_animation,
to_animation,
transition
)
print(
"TRANSITION: ",
from_animation,
" -> ",
to_animation,
" Adv: ",
transition.advance_expression,
" Mode: ",
transition.switch_mode
)
Here you define your animations and how they connect. In my case i connect everything with everything.
This creates this output:
TRANSITION: Idle_90 -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 0
TRANSITION: Idle_90 -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 0
TRANSITION: Idle_90 -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 0
TRANSITION: Idle_90 -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 0
TRANSITION: Idle_90 -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 0
TRANSITION: Idle_90 -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 0
TRANSITION: Idle_90 -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 0
TRANSITION: Idle_90 -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 0
TRANSITION: Idle_90 -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 0
TRANSITION: Walk_32 -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 0
TRANSITION: Walk_32 -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 0
TRANSITION: Walk_32 -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 0
TRANSITION: Walk_32 -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 0
TRANSITION: Walk_32 -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 0
TRANSITION: Walk_32 -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 0
TRANSITION: Walk_32 -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 0
TRANSITION: Walk_32 -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 0
TRANSITION: Walk_32 -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 0
TRANSITION: Jump_ascend_9 -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 2
TRANSITION: Jump_ascend_9 -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 2
TRANSITION: Jump_ascend_9 -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 2
TRANSITION: Jump_ascend_9 -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 2
TRANSITION: Jump_ascend_9 -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 2
TRANSITION: Jump_ascend_9 -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 2
TRANSITION: Jump_ascend_9 -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 2
TRANSITION: Jump_ascend_9 -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 2
TRANSITION: Jump_ascend_9 -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 2
TRANSITION: Jump_landed_9 -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 2
TRANSITION: Jump_landed_9 -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 2
TRANSITION: Jump_landed_9 -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 2
TRANSITION: Jump_landed_9 -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 2
TRANSITION: Jump_landed_9 -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 2
TRANSITION: Jump_landed_9 -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 2
TRANSITION: Jump_landed_9 -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 2
TRANSITION: Jump_landed_9 -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 2
TRANSITION: Jump_landed_9 -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 2
TRANSITION: Jump_midair_90 -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 0
TRANSITION: Jump_midair_90 -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 0
TRANSITION: Jump_midair_90 -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 0
TRANSITION: Jump_midair_90 -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 0
TRANSITION: Jump_midair_90 -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 0
TRANSITION: Jump_midair_90 -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 0
TRANSITION: Jump_midair_90 -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 0
TRANSITION: Jump_midair_90 -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 0
TRANSITION: Jump_midair_90 -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 0
TRANSITION: Run_16 -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 0
TRANSITION: Run_16 -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 0
TRANSITION: Run_16 -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 0
TRANSITION: Run_16 -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 0
TRANSITION: Run_16 -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 0
TRANSITION: Run_16 -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 0
TRANSITION: Run_16 -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 0
TRANSITION: Run_16 -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 0
TRANSITION: Run_16 -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 0
TRANSITION: Crouch_90 -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 0
TRANSITION: Crouch_90 -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 0
TRANSITION: Crouch_90 -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 0
TRANSITION: Crouch_90 -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 0
TRANSITION: Crouch_90 -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 0
TRANSITION: Crouch_90 -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 0
TRANSITION: Crouch_90 -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 0
TRANSITION: Crouch_90 -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 0
TRANSITION: Crouch_90 -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 0
TRANSITION: Crouch_walk_32 -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 0
TRANSITION: Crouch_walk_32 -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 0
TRANSITION: Crouch_walk_32 -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 0
TRANSITION: Crouch_walk_32 -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 0
TRANSITION: Crouch_walk_32 -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 0
TRANSITION: Crouch_walk_32 -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 0
TRANSITION: Crouch_walk_32 -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 0
TRANSITION: Crouch_walk_32 -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 0
TRANSITION: Crouch_walk_32 -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 0
TRANSITION: Walk_stop -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 2
TRANSITION: Walk_stop -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 2
TRANSITION: Walk_stop -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 2
TRANSITION: Walk_stop -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 2
TRANSITION: Walk_stop -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 2
TRANSITION: Walk_stop -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 2
TRANSITION: Walk_stop -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 2
TRANSITION: Walk_stop -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 2
TRANSITION: Walk_stop -> Walk_start Adv: state_machine.current_state_name == "walk_start" Mode: 2
TRANSITION: Walk_start -> Idle_90 Adv: state_machine.current_state_name == "idle" Mode: 2
TRANSITION: Walk_start -> Walk_32 Adv: state_machine.current_state_name == "walk" Mode: 2
TRANSITION: Walk_start -> Jump_ascend_9 Adv: state_machine.current_state_name == "jump_ascend_9" Mode: 2
TRANSITION: Walk_start -> Jump_landed_9 Adv: state_machine.current_state_name == "jump_landed_9" Mode: 2
TRANSITION: Walk_start -> Jump_midair_90 Adv: state_machine.current_state_name == "jump_midair_90" Mode: 2
TRANSITION: Walk_start -> Run_16 Adv: state_machine.current_state_name == "run_16" Mode: 2
TRANSITION: Walk_start -> Crouch_90 Adv: state_machine.current_state_name == "crouch_90" Mode: 2
TRANSITION: Walk_start -> Crouch_walk_32 Adv: state_machine.current_state_name == "crouch_walk_32" Mode: 2
TRANSITION: Walk_start -> Walk_stop Adv: state_machine.current_state_name == "walk_stop" Mode: 2
In player class i define:


Oneshot animations have this code in their respecive statemachine code:
extends StateInterface
class_name JumpAscendState
var character: Player
func enter(_prev_state: String = "")-> void:
character = state_machine.owner
character.animation_play_timer.one_shot = true
character.animation_play_timer.wait_time = character.animation_player.get_animation("Jump_ascend_9").length
character.animation_play_timer.timeout.connect(on_timer_finished)
character.animation_play_timer.start()
character.is_root_motion = false
func physics_update(_delta: float) -> void:
if character.direction:
var up := Vector3.UP
# For root motion
var forward : Vector3 = character.direction # Check this value if its in correct way
var right := up.cross(forward).normalized()
var corrected_up := forward.cross(right).normalized()
var target_basis := Basis(right, corrected_up, forward).orthonormalized()
var a = character.transform.basis.get_rotation_quaternion()
var b = target_basis.get_rotation_quaternion()
var c = a.slerp(b,0.3)
character.transform.basis = Basis(c).orthonormalized()
character.velocity.x = character.current_root_motion_velocity.x
character.velocity.z = character.current_root_motion_velocity.z
character.move_and_slide()
func on_timer_finished():
print("Ascend state finished: timer")
character.animation_play_timer.timeout.disconnect(on_timer_finished)
state_machine.change_state("Jump_midair_90")
Some states have decider functions:
extends StateInterface
class_name JumpLandedState
var character: Player
var is_blocking:bool = false
func enter(_prev_state: String = "")-> void:
character = state_machine.owner
character.animation_play_timer.one_shot = true
character.animation_play_timer.wait_time = character.animation_player.get_animation("Jump_landed_9").length
character.animation_play_timer.timeout.connect(on_timer_finished)
character.animation_play_timer.start()
character.is_root_motion = false
is_blocking = true
func physics_update(_delta: float) -> void:
if not character.is_on_floor():
state_machine.change_state("Jump_midair_90")
return
character.velocity = (character.animation_tree.get_root_motion_rotation_accumulator().inverse() * character.get_quaternion()) * character.animation_tree.get_root_motion_position() / _delta
character.move_and_slide()
if not is_blocking:
_decide_next_state()
func on_timer_finished():
print("Landed state finished: timer")
character.animation_play_timer.timeout.disconnect(on_timer_finished)
is_blocking = false
func _decide_next_state():
if Input.get_vector("k_left", "k_right", "k_forward", "k_backward"):
if Input.is_action_pressed("k_sprint"):
state_machine.change_state("Run_16")
return
if Input.is_action_pressed("k_crouch"):
state_machine.change_state("Crouch_walk_32")
return
state_machine.change_state("walk")
return
if Input.is_action_just_pressed("k_jump") and character.is_on_floor():
state_machine.change_state("Jump_ascend_9")
return
if Input.is_action_pressed("k_crouch"):
state_machine.change_state("Crouch_90")
return
state_machine.change_state("idle")
return
Need to iron out some quirks, but so far this looks usable in my case. I can always specify special cases for animation connections..
Making the "upper body" animation states and then using filters and blending will be another headache for another time..
EDIT:
Ehh.. need to investigate why the velocity drops upon switch.. 🙁((
Walk stop state finished: timer
Changing state to: idle
State: idle Velocity: 1.179 RM: (0.0, 0.0, 0.016301) | CA: Walk_stop
State: idle Velocity: 0.000 RM: (0.0, 0.0, 0.016301) | CA: Idle_90
State: idle Velocity: 0.000 RM: (0.0, 0.0, 0.0) | CA: Idle_90
Changing state to: walk_start
State: walk_start Velocity: 0.000 RM: (0.0, 0.0, 0.0) | CA: Idle_90
State: walk_start Velocity: 0.000 RM: (0.0, 0.0, 0.0) | CA: Walk_start
State: walk_start Velocity: 0.376 RM: (0.0, 0.0, 0.006264) | CA: Walk_start
State: walk_start Velocity: 0.564 RM: (0.0, 0.0, 0.009396) | CA: Walk_start
State: walk_start Velocity: 0.752 RM: (0.0, 0.0, 0.012528) | CA: Walk_start
State: walk_start Velocity: 0.940 RM: (0.0, 0.0, 0.015661) | CA: Walk_start
State: walk_start Velocity: 1.128 RM: (0.0, 0.0, 0.018793) | CA: Walk_start
Walk start state finished: timer
Changing state to: walk
State: walk Velocity: 1.128 RM: (0.0, 0.0, 0.018793) | CA: Walk_start
State: walk Velocity: 1.128 RM: (0.0, 0.0, 0.018793) | CA: Walk_32
State: walk Velocity: 1.128 RM: (0.0, 0.0, 0.0) | CA: Walk_32
State: walk Velocity: 0.393 RM: (0.0, 0.0, 0.006551) | CA: Walk_32
State: walk Velocity: 0.590 RM: (0.0, 0.0, 0.009827) | CA: Walk_32
State: walk Velocity: 0.786 RM: (0.0, 0.0, 0.013103) | CA: Walk_32
State: walk Velocity: 0.983 RM: (0.0, 0.0, 0.016379) | CA: Walk_32
State: walk Velocity: 1.179 RM: (0.0, 0.0, 0.019654) | CA: Walk_32
State: walk Velocity: 1.179 RM: (0.0, 0.0, 0.019655) | CA: Walk_32
State: walk Velocity: 1.179 RM: (0.0, 0.0, 0.019654) | CA: Walk_32
State: walk Velocity: 1.179 RM: (0.0, 0.0, 0.019655) | CA: Walk_32
State: walk Velocity: 1.179 RM: (0.0, 0.0, 0.019654) | CA: Walk_32
State: walk Velocity: 1.179 RM: (0.0, 0.0, 0.019655) | CA: Walk_32
State: walk Velocity: 1.179 RM: (0.0, 0.0, 0.019654) | CA: Walk_32