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:
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:
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.
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").
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