A more scalable animation solution

Godot Version

Godot 4.3

Question

The game is a top down 2D game.

Hello, so I’m trying to create something similar to what I had in a previous project in another engine. I wanted to create a Finite State Machine to handle animations that would use generic names for animations every character would use. For example “run_left,” “run_up,” “reload,” etc. for animation names and then check if these animations are to be played. This is easy enough if I wanted to create one bloated character script that handles movement, attacks, and anything else that would require an animation, but in the spirit of decoupling I’d like to have them all be different scripts or scenes.

The issue I’m running into is just simply not knowing how to organize this or if its just easier to have the one character script that handles everything involving input and the animations that accompany the input.

Any suggestions are appreciated! Thanks in advance.

Here’s a simple scalable state machine, that i always use.

state machine script:

class_name StateMachine
extends Node

@export var use_anim_player:bool = false
@export var anim_player:AnimationPlayer
@export var subject:CharacterBody2D
@export var _initial_state:State

var current_state:State:set = _set_current_state


func _enter_tree() -> void:
	current_state = _initial_state
	for child:State in get_children():
		child.subject = subject
		child.new_state_request.connect(on_new_state_requested)


func on_new_state_requested(state:State) -> void:
	current_state = state
	current_state._enter()


func _set_current_state(new_state: State) -> void:
	if current_state:
		current_state._leave()
	current_state = new_state
	if use_anim_player:
		set_animation()


func set_animation() -> void:
	var anim_name:StringName = current_state.name.to_lower()
	if anim_player.has_animation(anim_name):
		anim_player.play(anim_name)


func _physics_process(delta:float) -> void:
	current_state._state_process(delta)

state class script:

class_name State extends Node

var subject:CharacterBody2D
var direction:Vector2

signal new_state_request(state:State)


func _enter() -> void:
	push_error("_enter function not implemented in state %s" % self.name)


func _leave() -> void:
	push_error("_leave function not implemented in state %s" % self.name)


func _state_process(_delta:float) -> void:
	push_error("state_process function not implemented in state %s" % self.name)


func _is_available() -> bool:
	return true

Add a node to your character, name it StateMachine and attach the script.

Then for each state you want add a node as child, and attach a script which extends the state class of my second script.
Override the _enter, _leave and _state_process functions.

If you want to change states from “state A” to “state B”, put an export state in A and assign B.
When changing state use new_state_request.emit(“variable of B”) inside A.

Make sure to set all export variables!

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.