Hello,
I am just starting out with Godot, coming from GMS2. I have been reading some tutorials on how to make state machines as I am a big fan of them. I followed a tutorial at this link and the game executes without errors, but nothing happens. The little player block just floats in the air. Since I am new, I am guessing I have done something wrong in getting the state machine to execute the _physics_process code for the state. If anyone knows how to fix this (or even a better way to go about state machines), let me know!
player.gd:
class_name Player
extends CharacterBody2D
@onready var sprite : Sprite2D = $Sprite2D
@onready var state_machine : StateMachine = $StateMachine
####################################################################################################
# Player Physics
const MOVE_SPEED = 100.0
const JUMP_VELOCITY = -225.0
var direction : Vector2 = Vector2.ZERO
var input_direction = 0
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
####################################################################################################
####################################################################################################
# READY
func _ready():
state_machine._ready()
####################################################################################################
# UNHANDLED INPUT
func _unhandled_input(event):
state_machine._unhandled_input(event)
####################################################################################################
# UNHANDLED INPUT
func _process(event):
state_machine._process(event)
####################################################################################################
# PHYSICS PROCESS
func _physics_process(delta):
state_machine._physics_process(delta)
####################################################################################################
StateMachine:
# 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:
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)
print(state)
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)
State:
# 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
PlayerState:
# Boilerplate class to get full autocompletion and type checks for the `player` when coding the player's states.
# Without this, we have to run the game to see typos and other errors the compiler could otherwise catch while scripting.
class_name PlayerState
extends State
# Typed reference to the player node.
var player: Player
func _ready() -> void:
# The states are children of the `Player` node so their `_ready()` callback will execute first.
# That's why we wait for the `owner` to be ready first.
await owner.ready
# The `as` keyword casts the `owner` variable to the `Player` type.
# If the `owner` is not a `Player`, we'll get `null`.
player = owner as Player
# This check will tell us if we inadvertently assign a derived state script
# in a scene other than `Player.tscn`, which would be unintended. This can
# help prevent some bugs that are difficult to understand.
assert(player != null)`
And finally the Air state:
`class_name PlayerAir
extends PlayerState
func _physics_update(delta) -> void:
# Add the gravity.
print("PHYSICS UPDATE FOR AIR STATE")
if not player.is_on_floor():
player.velocity.y += player.gravity * delta
Sorry for the code dump and thank you in advance