Skip to content

Instantly share code, notes, and snippets.

@N-Carter
Created January 5, 2024 19:34
Show Gist options
  • Save N-Carter/64b3f20b80eba78d3d34c2713efd3bcf to your computer and use it in GitHub Desktop.
Save N-Carter/64b3f20b80eba78d3d34c2713efd3bcf to your computer and use it in GitHub Desktop.
A simple state machine
@icon("res://editor/icons/state_machine.svg")
class_name StateMachine
extends Node
@export var _host_path : NodePath # If unset, the host is self
var _host : Node
enum Mode {
MANUAL,
PROCESS,
PHYSICS_PROCESS
}
@export var _mode : Mode = Mode.MANUAL
@export var _is_logging := false
var _current_state := _no_op # Use set_state() to choose the first state before advancing
var _is_new_state := 0 # If greater than 0, this is still a new state
var _state_time : float # How many seconds this state has been current
var _delta : float # Records whatever delta time interval applies for this step
var _exit_function := _no_op # If not "", called when set_state() changes state
var _logging_name : String
func _ready() -> void:
# FIXME: Now we're using Callable, don't really need _host except for displaying the right name while logging.
_host = get_node_or_null(_host_path)
if not is_instance_valid(_host):
_host = self
_logging_name = owner.name if owner else _host.name
# You automatically get process notifications if a class in the hierarchy implements the relevant
# function, but if that's not the case, you have to manually activate them:
match _mode:
Mode.PROCESS:
set_process(true)
Mode.PHYSICS_PROCESS:
set_physics_process(true)
func _notification(what: int) -> void:
match what:
NOTIFICATION_PROCESS:
if _mode == Mode.PROCESS:
# print("Advancing %s in process mode" % get_path())
advance(get_process_delta_time())
NOTIFICATION_PHYSICS_PROCESS:
if _mode == Mode.PHYSICS_PROCESS:
# print("Advancing %s in physics process mode" % get_path())
advance(get_physics_process_delta_time())
func advance(delta : float) -> void:
_delta = delta
_call_state()
_state_time += delta
func set_state(new_state : Callable) -> void:
if _current_state != new_state:
_current_state = new_state
_is_new_state = 2
_state_time = 0.0
if _exit_function != _no_op:
_exit_function.call()
_exit_function = _no_op
log_message("New state: %s", _current_state)
else:
log_message("Tried to switch to this state while already in it: %s", new_state.get_method())
func is_state(state : Callable) -> bool:
return _current_state == state
func get_state_name() -> String:
return _current_state.get_method()
func _call_state() -> void:
_current_state.call()
if _is_new_state > 0:
_is_new_state -= 1
# Used instead of null:
func _no_op() -> void:
pass
func log_message(format : String, substitutions = null) -> void:
if _is_logging:
if substitutions:
print("[%s] %s" % [_logging_name, format % substitutions])
else:
print("[%s] %s" % [_logging_name, format])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment