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)
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")
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)
Top comments (0)