A base state machine class to be used in games.
For use with Godot 4.6.stable and later.
- Copy the
dragonforge_state_machinefolder from theaddonsfolder into your project'saddonsfolder. - In your project go to Project -> Reload Current Project
- Wait for the project to reload.
NOTE: The set_arg(), remove_arg(), and is_arg() functions have been removed as of v1.0. This is a breaking change.
To use the StateMachine and State classes, you add a StateMachine to the Node you want it to control. Then you add a State node to the StateMachine for each state that you want the object to have.
- Add a new node as you would normally. (The Create New Node window will appear.)
- Type
stateinto the Search box. - Select StateMachine.
- Click the Create button.
- Add a new node as you would normally. (The Create New Node window will appear.)
- Type
stateinto the Search box. - Select State.
- Click the Create button.
While there are a number of public functions, this class is not intended to be changed or operated directly. All state switching happens from the State class.
This state machine is intended to be a "pull" machine instead of a "push" machine. Based on the Kanban principle of only pulling work when it's available. This means that instead of the StateMachine telling classes when they take over, the State class is implemented to tell the StateMachine when it wants to start up. This means that all the logic for switching is stored in the State. This keeps states modular, and means you can add or remove them from a StateMachine without breaking anything or having to rewrite code.
As such, even though there are public methods and variables, they are meant to only be accessed by State nodes. If there was a protected scope, StateMachine would fall into that scope - only accessible by State. So there is no documentation on how to use it because you are not supposed to use it. If you are curious how it works, the code is well-documented.
state_changedEmitted when state is changed.
starting_state: StateThe initial State for the StateMachine. This can be left blank, in which case the StateMachine will typically transition when the first State that is triggered calls State.switch_stateautostart: bool = trueBy default, the StateMachine is started automatically, unless this flag is turned off. In such case, to start the StateMachine manually, both initialize and start need to be called.print_state_changes: bool = trueBy default, State status changes are printed to the console, unless this flag is turned off.
add_state(state: State) -> voidAdds state as a child to the StateMachine and immediately activates it.remove_state(state: State) -> voidRemoves state from the StateMachine and immediately deactivates it.get_current_state() -> StateReturns the [StateMachine]'s current [State]. Intended for type checking. E.G. if you wanted to switch the direction a player faces during a custom WallSlideState state, as long as the WallSlideState has class_name WallSlideState at the top of the script, you could write:
@onready var state_machine: StateMachine = $StateMachine
func _process(delta: float) -> void:
if state_machine.get_current_state() is WallSlideState:
new_horizontal_facing *= -1.0
can_transition = trueSet to false if this [State] cannot be transitioned to (or alternately, from). For example when waiting for a cooldown timer to expire, when a character is dead, or when the splash screens have been completed.
Though public, typically these functions are used inside the State itself.
switch_state() -> voidAsks the state machine to switch to this [State]. Should always be used instead of _enter_state() when a [State] wants to switch to itself.is_current_state() -> boolReturns true if this is the current [State].clear_state() -> voidAsks the [StateMachine] to exit this [State] if it is the current [State]. Should always be used instead of calling _exit_state() when a [State] wants to exit. Will fail if the current [State] is not this one.get_current_state() -> StateReturns the [StateMachine]'s current [State]. Intended for type checking. E.G. if you wanted to only switch to a custom JumpState state when the "jump" action is pressed and the player is not in a custom FallState, as long as the FallState had class_name FallState at the top of the script, you could write:
func _process(delta: float) -> void:
if Input.is_action_just_pressed("jump") and not get_current_state() is FallState:
switch_state()
To implement a State, there are four functions you typically override. Sometimes you only need one or two. Sometimes you need all five. Whenever you override any of these functions, the first line of your override function should be to call super(). This ensures that the logging code runs, but forgetting to do it can cause the State to malfunction and weird bugs to occur. Note that none of these functions should be called directly in code. The StateMachine will call them at the appropriate time.
_ready() -> voidTurns off the _process(), _phsyics_process(), _input() and _unhandled_input() functions. If you want to use them for a [State] you can turn them on in the _activate_state() function, or turned on and off in _enter_state() and _exit_state(). NOTE: Although typically you override this function in Godot, that code should be moved to the_activate_state()function for State nodes._activate_state() -> voidCalled when the [State] is added to a [StateMachine]. This should be used for initialization instead of_ready()because it is guaranteed to be run after all of the nodes that are in the owner's tree have been constructed - preventing race conditions. WARNING: When overriding, be sure to callsuper()on the first line of your method. Never call this method directly. It should only be used by the [StateMachine]._deactivate_state() -> voidCalled when a [State] is removed from a [StateMachine]. WARNING: When overriding, be sure to callsuper()on the first line of your method. Never call this method directly. It should only be used by the [StateMachine]._enter_state() -> voidCalled every time the [State] is entered. WARNING: When overriding, be sure to callsuper()on the first line of your method. Never call this method directly. It should only be used by the [StateMachine]._exit_state() -> voidCalled every time the [State] is exited. WARNING: When overriding, be sure to callsuper()on the first line of your method. Never call this method directly. It should only be used by the [StateMachine].