2D FSM Alternatives

Godot Version

4.4.1 Stable

Question

Is there any other way to do 2D animation state management? Or is creating an FSM really the best way to do it? It is powerful, but I personally find it really time consuming, especially on characters with a lot of states.

Personally, I’ve just been hand-coding stuff like that in regular gdscript.

1 Like

I use FSM, i find it clearer for the state management as not all states leads to all states. Once the state machine is in place I use 3 functions in each state, enter, exit ant physics_statey_process, called by _physic_process in the state machine. The animation is handled in the state machine

1 Like

Are you talking about hand-coding an FSM or using an AnimationTree? Because I often use an AnimationTree to track which states can move where..

As far as coding my own states, I’ve moved to using nodes.


I created a StateMachine project and work to keep it modular for my games. Then I make more specific states and copy those from game to game.

It makes things more modular, and I have gotten to the point where I am able to start re-using them between games. I call the Character object and tell it what animation I need, but the Character object actually implements that with an AnimationTree, AnimationPlayer, AnimatedSprite2D - whatever.

If you’re getting tired of writing the same code, it’s worthwhile to start making your code reusable.

2 Likes

I’m using nodes for managing state transitions and ensuring the correct animations play on the AnimatedSprite2D. My state machine usually looks like this:


The codes I write for the state machine are reusable and the state nodes inherit a base State class like so:

extends Node
class_name State

var object : CharacterBody2D
var fsm : FSM

func enter():
	pass

func update(_delta):
	pass

func physics_update(_delta):
	pass

func exit():
	pass

func change_state(next_state):
	fsm.change_state(next_state)

It’s just that setting them up the first time is extremely time consuming, but they are reusable. I guess I’ll try learning how to create a modular one where I could just import it to projects and have the default state transitions already like idle, walk, jump, etc.

1 Like

Yeah yours is similar to mine. I turn the _physics_process() and _process() functions off by default, and then turn then on if I want to use them instead of creating differently named functions. Your states clearly know a lot more about each other too.

I went with a pull structure. A State only knows about itself. It can only tell the StateMachine that it wants to enter itself. The StateMachine tells it when it’s time to exit. Likewise, a StateMachine doesn’t decide when a new state should be entered. It is just a clearinghouse for the States it contains asking for a change. It determines whether or not a change can be made, by checking to make sure the current State hasn’t said it can’t be exited (plus a few other conditions); and then if everything is ok, it says “Go!” to the two States involved.

So if I drop an IdleState on an Enemy and nothing else, there are no errors. It doesn’t complain. It just idles - because that’s the only State that exists. As I drop more on, more things can happen.

class_name CharacterStateIdle extends CharacterState


func _activate_state() -> void:
	super()
	set_physics_process(true)


func _enter_state() -> void:
	super()
	character.play_animation("idle")


func _deactivate_state() -> void:
	set_physics_process(false)
	super()


func _physics_process(_delta: float) -> void:
	if character.velocity.is_zero_approx() and not is_current_state():
		# If we are in a looped animation break in.
		if character.is_current_animation_looped():
			switch_state()
		# Otherwise wait until the end of the animation.
		else:
			if not character.animation_finished.is_connected(_on_animation_complete):
				character.animation_finished.connect(_on_animation_complete)


func _on_animation_complete(_animation_name: String) -> void:
	character.animation_finished.disconnect(_on_animation_complete)
	switch_state()

Here the state is Activated when the StateMachine initializes itself. At that point the _physics_process() function starts running and it looks for the Character’s velocity to be zero. If the velocity is zero and we aren’t idling, we switch into it. If the current animation is a looped animation, we break in immediately. If it’s not, we wait for it to finish. This prevents the attack animation from getting cut short for example. Finally, if this State gets disconnected from the tree or deleted, it pauses _physics_process() from running.

My StateMachine also isn’t specifically looking for a Character2D as its owner. I can also use it for my game itself, handling whether the game is paused, playing, etc.

My approach is just one approach. You could have the FSM do all the changes. You could have the states push the changes like yours. I just wanted to help explain how I use mine.

1 Like

That looks like a very well-structured FSM. I like how you toggle the _physics_process only when needed, which I think improves game performance and reduces bugs especially when trying to free a node with an active state machine. I learned mine from a youtube video so I haven’t really customized it that much. I only added a bunch of states and tried to implement a better transition from one state to another by considering the previous state the FSM was in. I think I’ll try experimenting with it a bit more and see if I can make it more modular and easy to implement. Thanks!

1 Like

Feel free to steal mine and work from it if it helps. That’s why I made it open source.

1 Like