Advice on management of AnimationTrees or alternative strategies.

Godot Version

v4.2.1.stable.official.b09f793f5

Question

Good day everyone! I’ve been having some trouble recently with a project of mine. Mainly, I am having trouble with management of AnimationTrees. It is the first project in which I’m using them, and I’m finding them way more complex than what I expected. So, what is my problem? Well it looks something like this:

Hahaha yeah…
So, my question is; how would you manage big trees like this? The thing is, I’m finding them redundant for my project since I already work with an implementation of a state machine that manages the player through GDScript.

The only reason I started using an AnimationTree is because I am interested in the transitions between animations.

I know you can do something similar (although less customizable) through the AnimationPlayer by editing transitions. But for some reason they don’t seem to work if the transition time is too long (over 0.7s more or less).

Description of my project:
A FP melee combat system. With directional attacks, similar to Mount & Blade or Mordhau. Most resources I’ve read (like this one) handle the trees from a central state that branches to the other ones, and usually said branches don’t extend too far away from the main state. The problem would be that I have several states that need to transition between them. All “Guards” should transition between them, and towards their “Attack”. And the attacks back to any “Guard”, since you should be able to chain attacks (Yeah, that is what the mess from above is supposed to be). That is pretty much it. Everything else (collision, logic, etc.) already works. I’m only missing the animation part.

Why am I trying to solve this if it is already working? Because this just considers behavior for left click. What if I want to do something with the right click? Yeah, everything doubles.

I am open to any suggestion, I am quite sure my current workflow is terrible. Thanks in advance!

I am definitely no expert with animation in Godot, and barely any help, but I have a suggestion. Can you break up the transitions into multiple animation tree nodes? Like idle to run, idle to swing, run to idle, etc. Maybe less broken up than that, but it might help? Then a central animation tree node for the other ones? Again, I don’t know much, and it might not even work that way. Just a thought.

1 Like

So, I’ve found a YouTube channel that addresses this precise issue. I think his approach is golden.

Link to the video

I would highly recommend to anyone worried with the scalability of their projects, specially regarding Animations for player controllers or enemy behavior to check out that channel.

I still have to figure out some things, but now I have some clear understanding on where my approach failed

1 Like

It might work. I could try it in the future. Still, I believe in terms of scalability it might bring some issues since the core problem of branching handling would still exist (I might be wrong, though). Still, thanks for your feedback. I found a YouTube channel that addresses my issue and I will follow a similar approach.

Hey there, could you kindly provide an update on how you solved your issue? I am starting to encounter the same problem. Ive taken a look at the video you have suggested and I was wondering which part of it you have implemented that helped the most with your project.

Thanks!

I also have ran into the exact same issue. I’m already managing my state in each of my composition components, meaning I have individual statemachines for say a movement controller or attack controller.
Same as OP the only reason Im interested in the animtree is to blend and transition between animations easily. I dislike having to keep track of yet another statemachine (the anim tree), but I guess you could argue that animations have their own state, independent of my other statemachines, i.e thr same animation can play for different combination of states of other statemachines which are used to trigger the animation states.
i have honestly no idea how to properly address this issue or what a clean communication between the anim tree and my various statemachines look like. Worst of all, my other statemachines are now starting to depend on the anim tree state (for instance when a anim has finished playing), which smells like thr beginning of absolute chaos to me.

On top of godot does not even provide enter/exit state callbacks/signals for anim tree statemachines :exploding_head:

@Bwompy @Stacklucker
Sorry that my update took so long. I honestly forgot I had my session closed in the forum, so I had no notifications hahaha.
So I kinda gave up in using AnimationTrees. I understood they’re not meant to be used this way, and then came up with my own system. It looks something like this:

I have “StateMachine.gd” a Class script that looks like this:

extends Node
class_name StateMachine

var currentState : State
var transitions : Dictionary = {}

func _physics_process(_delta : float) -> void:
	var transition : Transition = getTransition()
	if(transition):
		#print_debug("ENTERING: " + str(transition.to))
		setStateTo(transition.to)
	if(currentState):
		currentState.physicsUpdate(_delta)
	extendPhysicsProcess(_delta)

func _process(_delta : float) -> void:
	if(currentState):
		currentState.update(_delta)

func setStateTo(state: State) -> void:
	if(currentState):
		currentState.exit()
	currentState = state
	currentState.enter()

func getTransition() -> Transition:
	for stateIdentifier : State in transitions:
		if stateIdentifier == currentState:
			for transition : Transition in transitions[stateIdentifier]:
				## IF YOU CRASHED HERE: check your transition is written correctly // is a valid function
				if transition.condition.call() != transition.isNegating:
					return transition
	return null

func addTransition(from: State, to: State, condition: Callable,
	negateCondition: bool = false) -> void:
	if(transitions.has(from)):
		transitions[from].append(Transition.new(to, condition, negateCondition))
	else:
		transitions.merge({from : [Transition.new(to, condition, negateCondition)]})

func removeTransition(to: State) -> void:
	for stateIdentifier : State in transitions:
		for transition : Transition in transitions[stateIdentifier]:
			if transition.to == to:
				transitions[stateIdentifier].erase(transition)
				transition.queue_free()

func extendPhysicsProcess(_delta: float) -> void:
	pass

And two other Classes that look like this:
“Transition.gd”:

extends Node
class_name Transition

var to : State
var condition : Callable
var isNegating : bool

func _init(stateTo: State, conditionalStatement: Callable, negateCondition: bool) -> void:
	to = stateTo
	condition = conditionalStatement
	isNegating = negateCondition

“State.gd”:

extends Node
class_name State

func enter() -> void:
	pass

func update(_delta: float) -> void:
	pass

func physicsUpdate(_delta: float) -> void:
	pass

func exit() -> void:
	pass

Then, what I’ll do is have my object nodeTree that requires a state machine something like this:

player
| stateManager[.gd]
| | idle[.gd]
| | run[.gd]
| | walk[.gd]

where StateManager.gd extends StateMachine and looks like this:

extends StateMachine

@onready var player: Node = $".."

@onready var idle : State = $idle
@onready var run : State = $run
@onready var walk : State = $walk

func _ready() -> void:
	# Idle
	addTransition(idle, run, Callable(player, "isPlayerRunning"))
	addTransition(idle, walk, Callable(player, "isPlayerWalking"))
	
	# run
	addTransition(run , idle, Callable(player, "isPlayerRunning"), true)
	
	# walk
	addTransition(walk, idle, Callable(player, "isPlayerWalking"), true)
	

	setStateTo(idle)

This script has a couple things that require explaining. Firstly, I use Callables because there are no pointers in GDScript. So, referencing a function itself, you pretty much get a pointer. This callables MUST return a boolean. And the last parameter in addTransition() is optional, what it will do is negate the method’s output. This way, you don’t have to make double the functions for checking the same boolean in opposite cases (like shown in the example). So, with this, you can move through states as long as the conditions of the transitions are met. The only thing missing is the actual functionality of the states themselves.

extends State

@onready var player : CharacterBody3D = $"../.."

func enter() -> void:
	player.colliderStand()

func update(_delta: float) -> void:
	pass

func physicsUpdate(_delta: float) -> void:
	player.doMovement(player.m_sprintSpeed, player.m_playerHeight, _delta)
	player.doHeadRotation()

func exit() -> void:
	pass

This is an example of one of my actual states in my game. Where you override the functions of State. I keep all actions made in the state in a different function. This allows me to modify the function itself and keep functionality in other states to, without having to change everything in each one. But you could perfectly make everything happen in the script itself.

The cool thing, is that this allows you to work alongside AnimationTrees if you want. But I’ve mainly used AnimationPlayers and work with them programmatically. It works amazingly well. Since I have started doing this, I’d have 0 issues regarding states and the management of them.

And that’s it. I think I covered everything I have going on with my current workflow. A couple months late, but maybe you can still find this useful. If not, I hope this helps somebody else. I also would gladly accept corrections or advice.

1 Like