this post was submitted on 20 Jun 2026
18 points (90.9% liked)

Godot

7689 readers
18 users here now

Welcome to the programming.dev Godot community!

This is a place where you can discuss about anything relating to the Godot game engine. Feel free to ask questions, post tutorials, show off your godot game, etc.

Make sure to follow the Godot CoC while chatting

We have a matrix room that can be used for chatting with other members of the community here

Links

Other Communities

Rules

We have a four strike system in this community where you get warned the first time you break a rule, then given a week ban, then given a year ban, then a permanent ban. Certain actions may bypass this and go straight to permanent ban if severe enough and done with malicious intent

Wormhole

!roguelikedev@programming.dev

Credits

founded 3 years ago
MODERATORS
top 4 comments
sorted by: hot top controversial new old
[–] petrol_sniff_king@lemmy.blahaj.zone 10 points 1 day ago* (last edited 1 day ago) (1 children)

There is a trick I learned from Firebelley Games (a youtube channel) that is just as simple to spin up and use as the Enum + match strategy but without sacrificing any versatility.

I actually like it better than the Node-based pattern because you don't have to set up much boilerplate, and you really don't need to think about how different state classes might share data. Plus, none of it will clog up your scene tree or need to be pointlessly instantiated by the engine.

Tap for code

If you're on mobile, I would recommend reading this in horizontal view.

This is all it takes to spin one up:

class_name Player2D extends Node2D

var _state_machine := CallableStateMachine.new();

func _ready() -> void:
  _state_machine.add_state(
    _state_idle_update,
    Callable(),
    Callable()
  );
  _state_machine.add_state(
    _state_jump_update,
    _state_jump_enter,
    Callable()
  );
  # Set first state
  _state_machine.switch_to(_state_idle_update);

func _process(_delta: float) -> void:
  _state_machine.update();

# These are your state functions.
func _state_idle_update() -> void;
func _state_jump_update() -> void;
func _state_jump_enter() -> void;

The only thing your state machine actually needs to know is which functions are paired together. You can use Callable() to fill in any steps you're not actually using.

func _ready() -> void:
  _state_machine.add_state(
    _state_idle_update, # update
    _state_idle_enter,  # enter
    Callable(),         # exit
  );

You call update() yourself, so its timing is completely under your control.

func _process(delta: float) -> void:
  velocity.y += 9.8 * delta;
  _state_machine.update();
  move_and_slide();

States are keyed by their own update step, so there's no extra overhead for string names or Enums or the like, and you still get your IDE's tab autocomplete to help you with 'em.

func _state_idle_update() -> void:
  if Input.is_action_pressed('jump'):
    _state_machine.switch_to(_state_jump_update);

All state functions exist within the Player2D script, so you have complete access to any shared data or component that Player2D does.

var _anim: AnimatedSprite2D = $An...;
var _jump_times := 0;

func _state_idle_enter() -> void:
  _anim.play('idle');
  _jump_times = 0;

func _state_jump_enter() -> void:
  _anim.play('jump');
  _jump_times += 1;

A basic implementation of CallableStateMachine is none too complicated, and you can reuse it anywhere.

class_name CallableStateMachine extends RefCounted

var _states_map := {} as Dictionary[Callable, CallableState];
var _current_state: CallableState = null;

func add_state(update: Callable, enter: Callable, exit: Callable) -> void:
  _states_map.set(update, CallableState.new(update, enter, exit));

func switch_to(update: Callable) -> void:
  if not _states_map.has(update):
    return;
  exit();
  _current_state = _states_map.get(update);
  enter();

func update() -> void:
  if _current_state:
    _current_state.update.call();

func enter() -> void:
  if _current_state:
    _current_state.enter.call();

func exit() -> void:
  if _current_state:
    _current_state.exit.call();

# This is just a struct to package the set of functions.
class CallableState extends RefCounted:
  var update: Callable;
  var enter: Callable;
  var exit: Callable;
  func _init(update: Callable, enter: Callable, exit: Callable) -> void:
    self.update = update;
    self.enter = enter;
    self.exit = exit;

You can do a lot from this base setup, too. I have mine setup such that if I name my functions like this:

func _state_idle() -> void;
func _state_idle__update(delta: float) -> void;
func _state_idle__unhandled_input(event: InputEvent) -> void;
func _state_idle__exit() -> void;

My state machine automatically knows which step each function is for by the keyword after the double-unders (e.g. '__update'), as well as that the nameless _state_idle() is the enter step and the key that I use to switch_to().

[–] monica_b1998@lemmy.world 4 points 1 day ago

The best code is always in the comments

[–] refalo@programming.dev 2 points 1 day ago
[–] Eibriel@sopuli.xyz 1 points 1 day ago

AI slop spam