@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.