Skip to content

Instantly share code, notes, and snippets.

@turdfire
Forked from ansimuz/Idle.gd
Created March 2, 2025 16:11
Show Gist options
  • Save turdfire/4fb1a8db218b324500f6842bb5dfdedb to your computer and use it in GitHub Desktop.
Save turdfire/4fb1a8db218b324500f6842bb5dfdedb to your computer and use it in GitHub Desktop.
State Machine example for Godot 4
extends State
# Idle State example for the player
func handle_input(event: InputEvent) -> void:
pass
func update(delta: float) -> void:
owner.apply_gravity(delta)
owner.move_player()
# jump
if Input.is_action_just_pressed("ui_accept"):
state_machine.transition_to("Jump")
# run
if Input.is_action_pressed("ui_right") or Input.is_action_pressed("ui_left"):
state_machine.transition_to("Run")
func physics_update(delta: float) -> void:
if not owner.is_on_floor():
state_machine.transition_to("Fall")
func enter(msg := {}) -> void:
owner.animated_sprite_2d.play("idle")
func exit() -> void:
pass
# Virtual base class for all states.
class_name State
extends Node
# Reference to the state machine, to call its `transition_to()` method directly.
# That's one unorthodox detail of our state implementation, as it adds a dependency between the
# state and the state machine objects, but we found it to be most efficient for our needs.
# The state machine node will set it.
var state_machine = null
# Virtual function. Receives events from the `_unhandled_input()` callback.
func handle_input(_event: InputEvent) -> void:
pass
# Virtual function. Corresponds to the `_process()` callback.
func update(_delta: float) -> void:
pass
# Virtual function. Corresponds to the `_physics_process()` callback.
func physics_update(_delta: float) -> void:
pass
# Virtual function. Called by the state machine upon changing the active state. The `msg` parameter
# is a dictionary with arbitrary data the state can use to initialize itself.
func enter(_msg := {}) -> void:
pass
# Virtual function. Called by the state machine before changing the active state. Use this function
# to clean up the state.
func exit() -> void:
pass
"""
Original code by GDQuest and contributors, CC BY-SA 4.0
link: https://www.gdquest.com/tutorial/godot/design-patterns/finite-state-machine/
"""
# Generic state machine. Initializes states and delegates engine callbacks
# (_physics_process, _unhandled_input) to the active state.
class_name StateMachine
extends Node
# Emitted when transitioning to a new state.
signal transitioned(state_name)
# Path to the initial active state. We export it to be able to pick the initial state in the inspector.
@export var initial_state := NodePath()
# The current active state. At the start of the game, we get the `initial_state`.
@onready var state: State = get_node(initial_state)
func _ready() -> void:
#yield(owner, "ready")
# await till owner is ready
await owner.ready
# The state machine assigns itself to the State objects' state_machine property.
for child in get_children():
child.state_machine = self
state.enter()
# The state machine subscribes to node callbacks and delegates them to the state objects.
func _unhandled_input(event: InputEvent) -> void:
state.handle_input(event)
func _process(delta: float) -> void:
state.update(delta)
func _physics_process(delta: float) -> void:
state.physics_update(delta)
# This function calls the current state's exit() function, then changes the active state,
# and calls its enter function.
# It optionally takes a `msg` dictionary to pass to the next state's enter() function.
func transition_to(target_state_name: String, msg: Dictionary = {}) -> void:
# Safety check, you could use an assert() here to report an error if the state name is incorrect.
# We don't use an assert here to help with code reuse. If you reuse a state in different state machines
# but you don't want them all, they won't be able to transition to states that aren't in the scene tree.
if not has_node(target_state_name):
return
state.exit()
state = get_node(target_state_name)
state.enter(msg)
emit_signal("transitioned", state.name)
# custom. Display state name in label
if owner.has_node("StateLabel"):
owner.state_label.text = state.name
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment