DEV Community

Umbr4x
Umbr4x

Posted on

Godot 4.3+ Hierachical State Machine

In this post I am assuming you have basic knowledge of how a State Machine Works

This is a Hierarchical Finite State Machine!

This means it is setup in the editor as custom nodes!

The State Class

In this state machine, each state will extend from the State class below. Transitions between states can be called with this nice function I made called transition_to(). When using this state machine treat the update and physics_update like _process and _physics_process respectively.

class_name State
extends Node

signal transitioned(state: State, new_state_name: String)

var player: CharacterBody2D
@onready var sprite: AnimatedSprite2D = %AnimatedSprite2D

func enter() -> void:
    pass

func exit() -> void:
    pass

func update(delta: float) -> void:
    pass

func physics_update(delta: float) -> void:
    pass

func transition_to(new_state_name: String) -> void:
    transitioned.emit(self, new_state_name)

Enter fullscreen mode Exit fullscreen mode

The State Machine

The State Machine node is where we will be adding our State nodes to as children. The state machine mostly takes care of transitions, keeping track of current state, and executes the code of the states.
Must be a child of a CharacterBody2D (Aka your player)


class_name StateMachine
extends Node

@export var initial_state: State
var current_state: State
var states: Dictionary = {}

func _ready() -> void:
    _initialize_states()
    _set_initial_state()

func _initialize_states() -> void:
    for child in get_children():
        if child is State:
            states[child.name.to_lower()] = child
            child.transitioned.connect(_on_state_transition)

func _set_initial_state() -> void:
    if initial_state:
        _assign_player_reference(initial_state)
        initial_state.enter()
        current_state = initial_state
    else:
        push_warning("No initial state set for StateMachine in " + owner.name)

func _process(delta: float) -> void:
    if current_state:
        current_state.update(delta)

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

func _on_state_transition(state: State, new_state_name: String) -> void:
    if state != current_state:
        return

    var new_state = states.get(new_state_name.to_lower())
    if not new_state:
        push_warning("State '" + new_state_name + "' not found in StateMachine")
        return

    _transition_to_new_state(new_state)

func _transition_to_new_state(new_state: State) -> void:
    current_state.exit()
    current_state = new_state
    _assign_player_reference(new_state)
    new_state.enter()

func _assign_player_reference(state: State) -> void:
    var parent = get_parent()
    if parent is CharacterBody2D:
        state.player = parent
    else:
        push_warning("StateMachine's parent must be CharacterBody2D")

Enter fullscreen mode Exit fullscreen mode

Example States and proper usage:

Write code necessary only for that state.


class_name IdleState
extends State

@export_range(120, 420) var friction: float


func enter() -> void:
    print("Entered Idle")
    sprite.play("idle")

func exit() -> void:
    print("Exited Idle")

func update(delta: float) -> void:
    if Input.get_axis("ui_left", "ui_right") != 0:
        transition_to("Walk")
    if not player.is_on_floor():
        transition_to("Fall")


func physics_update(delta: float) -> void:
    if player.velocity.x != 0:
        player.velocity.x = lerp(player.velocity.x, 0, friction * delta)

Enter fullscreen mode Exit fullscreen mode

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay