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
Btw, also found out that owner.ready
also works in this case.
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.
My game Scene tree:
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.