State Machine Help
Asked Answered
L

8

0

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

Land answered 20/11, 2023 at 23:2 Comment(0)
M
0

Land Your code never calls CharacterBody2D::move_and_slide() so nothing will be moved. This is a 3.x tutorial. To avoid needless confusion, download 3.x and do it there.

Maddock answered 20/11, 2023 at 23:33 Comment(0)
L
0

Maddock I know its a 3.x, I have tried to adapt it to 4.x. I just added "move_and_slide" to the player script under _physics_process" and it still doesnt move

Land answered 20/11, 2023 at 23:41 Comment(0)
M
0

Land You can't realistically expect to be able to adapt something that you yet have to learn. Better to complete it in 3.5 without hassle, and then try to adapt it.

Maddock answered 20/11, 2023 at 23:45 Comment(0)
L
0

Maddock Well the game is running without errors, it just isn't processing the states, so thats all I need help with for now, not a discouraging comment.

Land answered 20/11, 2023 at 23:46 Comment(0)
M
0

Land Who calls _physics_update() in PlayerAir state?

Maddock answered 20/11, 2023 at 23:59 Comment(0)
L
0

Maddock Well physics need to be applied to make the player fall...

Land answered 21/11, 2023 at 0:1 Comment(0)
M
0

Land I'm suggesting you to determine which code part is calling this function. It's the only function that does some actual update. Is it getting called at all?

Maddock answered 21/11, 2023 at 0:5 Comment(0)
M
0

Land Here's a hint.

Base class State implements virtual method physics_update(). This method is expected to be overridden in any of State's child classes so when StateMachine makes a virtual call to physics_update(), it always goes to overridden version in the child class. Sounds good.

However, your PlayerAir class does not override physics_update(). It implements only _physics_update(), which StateMachine knows nothing about.

Maddock answered 21/11, 2023 at 0:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.