🔁Finite State Machine

A finite state machine designed to cover 95% of use cases, providing essential functionality and a basic state node that can be extended.

Getting started

Using this node is as simple as adding it to the target node where you want to implement state machine logic. To make states available, you just need to create a new script that inherits from the 'GodotEssentialsState' class and add to child inside the GodotEssentialsFiniteStateMachine

When to use it?

There is nothing wrong using the same process on the CharacterBody2D to handle all the movement but when the things start to grow and the player can perform a wide number of moves it is better to start thinking about a modular way to build this movement.

In this case, a state machine is a design pattern widely employed in the video game industry to manage this complexity.

GodotEssentialState

All the functions here are virtual, which means they can be overridden with the desired functionality in each case:

class_name GodotEssentialsState extends Node

signal state_entered
signal state_finished(next_state, params: Dictionary)

var previous_states: Array[GodotEssentialsState] = []
var params: Dictionary = {}

func _enter() -> void:
	pass
	

func _exit() -> void:
	pass
	

func handle_input(_event):
	pass	


func physics_update(_delta):
	pass
	
	
func update(_delta):
	pass
	

func _on_animation_player_finished(name: String):
	pass


func _on_animation_finished():
	pass

Accessible normal variables

  • previous_states: Array[GodotEssentialsState] = []

  • params: Dictionary = {}


_enter()

This function executes when the state enters for the first time as the current state.

_exit()

This function executes when the state exits from being the current state and transitions to the next one.

_handle_input(event)

In case you want to customize how this state handle the inputs in your game this is the place to do that. The event type is InputEvent

physics_update(delta)

This function executes on each frame of the finite state machine's physic process

update(delta)

This function executes on each frame of the finite state machine's process

_on_animation_player_finished(name: String)

You can use this function generically to execute custom logic when an AnimationPlayer finishes any animation. This receive the animation name as parameter to avoid errors and be consistent with the original signal.

_on_animation_finished()

You can use this function generically to execute custom logic when an AnimatedSprite2D finishes any animation. This does not receive any params to avoid errors and be consistent with the original signal.

Signals

  • state_entered

  • state_finished(next_state)


The finite state machine

This node enables the handling of state logic for any node by simply nesting it as a child. It is highly recommended to set a current state before initializing the state machine in the scene tree. While nothing will break without it, having a defined initial state is good practice to start from.

Exported parameters

  • current_state: GodotEssentialState = null

  • stack_capacity: int = 3

  • flush_stack_when_reach_capacity: bool = false

  • enable_stack: bool = true

Accessible parameters

  • states: Dictionary

  • states_stack: Array[GodotEssentialsState]

  • locked: bool

When this node is ready in the scene tree, all the states detected as children at any nesting level are saved in a dictionary for easy access by their node names.

The finite state machine connects to all the state_finished signals from the nested existing states.

When a change state happens and the stack is enabled, the previous state is appended to the states_stack. You can define a stack_capacity to define the number of previous states you want to save. This stack is accessible on each state to handle conditions in which we need to know which states have been previously transitioned.

The locked value enables the state machine to be locked or unlocked for state execution. It can be resumed by resetting it to false. When it's locked the stack is also disabled.

How to change the state

This is an example of code that changes state from Idle to Run:

if not horizontal_direction.is_zero_approx() and owner.is_on_floor():
	state_finished.emit("Run", {})
	return

As you can see, within each individual state, you have the option to emit the state_finished signal, which will be monitored by the parent state machine.

Functions

Usually you don't really want to call this functions manually, it is preferable to emit signals from the states themselves and let the finite state machine react to these signals in order to execute actions such as changing the state. By the way, nothing stops you yo do that and may be needed in your use case.

change_state(state: GodotEssentialsState, params: Dictionary = {}, force: bool = false)

Changes the current state to the next state passed as parameter if they are not the same. This action can be forced with the third parameter force.

If the state can be transitioned, the _exit() function from the current state and the _enter() function of the next state will be executed.

In this transition the new state can receive external parameters.

Emits the signal state_changed

change_state_by_name(name: String, params: Dictionary = {}, force: bool = false)

Perform the same action as the change_state function but by receiving the state with the name it has in the states dictionary. For example, if we have a state named 'Idle' in the scene, it can be changed using change_state_by_name("Idle").

enter_state(state: GodotEssentialsState, previous_state: GodotEssentialsState)

This function is called when a new state becomes the current state. During this process, the state_entered signal is emitted.

exit_state(state: GodotEssentialsState)

Exit the state passed as parameter, execute the _exit() function on this state.

get_state(name: String)

Returns the state node using the dictionary key from the states variable if it exists, or null if it does not.

has_state(name: String) -> bool

Check if the state with that name exists on the states dictionary

current_state_is(state: GodotEssentialsState) -> bool

Check if the current state is the one passed as parameter

current_state_name_is(name: String) -> bool

Same as above but using the dictionary key from states

lock_state_machine()

Lock the state machine, all the process are set to false and the stack is disabled. This function is called automatically when locked changes to false

unlock_state_machine()

Unlock the machine, all the process are set to true and stack is enabled again (if it was enabled). This function is called automatically when locked changes to true

Signals

  • state_changed(from_state: GodotEssentialsState, state: GodotEssentialsState)

  • stack_pushed(new_state: GodotEssentialsState, stack:Array[GodotEssentialsState])

  • stack_flushed(flushed_states: Array[GodotEssentialsState])

Examples (coming soon)

Last updated