State Machine unable to access global variable

Godot Version

4.3

Question

I’m making a game where the player gets “Dynamically Loaded” into the scene, basically it does:
Saves the player’s last map(scene) and his last position.
Then once opening the game again the game loads the map and instatiates the player on the position and map he was before.

I’m currently implementing a state machine for the player and in order to update the player i’m using the Global variable that i made for him instead of just referencing the node in the code.

But, the Idle state doesn’t seem to be able to access it, because every time i print Global.Player it returns null while The walk state prints the player, and i don’t see anything wrong at all with the code.

So i’m asking help

class_name StateMachine
extends Node

@export var CURRENT_STATE: 	State
var states: Dictionary = {}

func _ready():
   for child in get_children():
   	if child is State:
   		states[child.name] = child
   		child.transition.connect(on_child_transition)
   	else:
   		push_warning("State Machine contains a node that isn't a state.")
   		
   CURRENT_STATE.enter()

func _process(delta: float) -> void:
   CURRENT_STATE.update(delta)
   
func _physics_process(delta: float) -> void:
   pass
   #CURRENT_STATE.physics_update(delta)

func on_child_transition(new_state_name: StringName):
   var new_state = states.get(new_state_name)
   if new_state != null:
   	if new_state != CURRENT_STATE:
   		CURRENT_STATE.exit()
   		new_state.enter()
   		CURRENT_STATE = new_state
   		print(CURRENT_STATE)
   else:
   	push_warning("Invalid state")

Idle state:

class_name PlayerIdleState
extends State

func enter():
	print(Global.Player)

func update(delta):
	if Global.Player.velocity.length() > 0.0:
		transition.emit("Walking")
	else:
		pass

Player:

class_name Player extends CharacterBody2D

@onready var sprite: AnimatedSprite2D = $Sprite
@export var SPEED: float = 100.0

var is_moving: bool = false
var _is_moving: bool = false
var is_on_menu: bool = false
var direction: String = "down"
var can_move: bool = true

func _ready() -> void:
	Global.Player

This statement has no effect, it does not set Global.Player you need an equals sign to assign a value

func _ready() -> void:
	Global.Player = self

Still, if your statemachine is ready before the player is then it will fail to load. You could wait for a frame to help guarentee the player exists

func _ready():
   for child in get_children():
   	if child is State:
   		states[child.name] = child
   		child.transition.connect(on_child_transition)
   	else:
   		push_warning("State Machine contains a node that isn't a state.")
   		
   await get_tree().process_frame # wait for one frame
   CURRENT_STATE.enter()
1 Like

"Global.Player was indeed a mistake I forgot to fix before making this post. It used to be Global.Player = self, as you mentioned.

Regarding waiting for a frame, it worked, thank you! but, in the long term, could this affect the performance of the game/project?"

It will take one frame longer to load. If anything relies on the state machine entering a state then that will have to be two frames delayed, this could spiral out of control if you have highly interlocked/sequential scripts.

1 Like

I see, so simply referencing the player directly in the code is indeed the better approach.

class_name PlayerIdleState
extends State

@onready var player: Player = $"../.."

func enter():
	print("dir: ", player.direction)

func update(delta):
	if Global.Player.velocity.length() > 0.0:
		transition.emit("Walking")
	else:
		pass

Thanks for your help! i appreciate it :slight_smile:

Btw, also found out that owner.ready also works in this case.
image

owner may not be what you are looking for, it’s for packing scenes and the editor. It may work in your case if the owner happens to create the player on it’s ready, or one of it’s children, but that’s not always going to be the case, and the owner can be deceptive, usually it’s the root of a scene.

Could you share your scene tree? Maybe you mean to use get_parent()?

Here’s my Player Scene Tree.
image
My game Scene tree:
image

Seems like you could use await get_parent().ready.

Considering the state machine is a child of the player, why did it need to use a global reference? Do you get errors with that grand-parent path $"../.."? If you do not need any on-ready values from the player then you do not need to wait for the player to be ready either.

Using the grand-parent i wasn’t able to use some functions like “update_animation”, it would return “Invalid update_animation on base Nil”.

Also, await get_parent().ready worked fine.

Using grand-parent, i was indeed able to use/get the variables like player direction, speed, but not the functions.