Making deferred call in script

Godot Version

4.7.1

Question

Trying to make GeneralSavingSystem to have player keep level and position for next gameplay.

Couple things going wrong, once I get Nil for Player, Level or call deferred child if I do direct change of scene,

This time I went with built it scene_change via UI, but problem is the ColorRectFader is not ready at the time of load_game_state() call in ready method inside GeneralSaveSystem.

current error is

E 0:00:00:533   UI.fade_out: Invalid assignment of property or key 'visible' with value of type 'bool' on a base object of type 'Nil'.
  <GDScript Source>UserInterface.gd:66 @ UI.fade_out()
  <Stack Trace> UserInterface.gd:66 @ fade_out()
                UserInterface.gd:82 @ change_scene()
                general_save.gd:19 @ load_game_state()
                general_save.gd:13 @ _ready()


The code starts to be little obscure and not sure where to go next

class_name GeneralSaveSystemClass
extends Node
@export var levels : Array[String] = []
@onready var player : CharacterBody3D = get_tree().get_first_node_in_group("Player")
@onready var level_node : Node3D =  get_tree().get_first_node_in_group("Level")
var current_level_index: int = 0
var level : String
var player_position : Transform3D
const SETTINGS_PATH := "user://attributes.cfg"
var config = ConfigFile.new()

func _ready() -> void:
	load_game_state()

func load_game_state() -> void:
	load_data()
	get_setting("Player", "position", player_position)
	get_setting("Level", "index", current_level_index)
	UserInterface.change_scene(levels[current_level_index], player_position)
func find_level_in_array() -> void:
	for level_index in range(levels.size()):
		if levels[level_index] == level:
			current_level_index = level_index
			break

func update_data() -> void:
	get_tree_reference()
	player_position = player.transform
	level = level_node.scene_file_path
	find_level_in_array()

func get_tree_reference() -> void:
	player = get_tree().get_first_node_in_group("Player")
	level_node = get_tree().get_first_node_in_group("Level")

func save_player_position() -> void:
	update_data()
	set_setting("Player", "position", player_position)
	
func save_level_index() -> void:
	update_data()
	set_setting("Level", "index", current_level_index)

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("debug_key"):
		update_data()
		print(current_level_index, " ", level, " ", player_position)
	if event.is_action_pressed("save_key"):
		save_player_position()
		save_level_index()

	
# recycled from saving_system:
func save_data() -> void:
	var err = config.save(SETTINGS_PATH)
	if err != OK:
		push_warning("Failed to save: %s" % err)

func load_data() -> void:
	var err = config.load(SETTINGS_PATH)
	if err != OK:
		return
		
func set_setting(section: String, key: String, value: Variant) -> void:
	config.set_value(section, key, value)
	save_data()
	
func get_setting(section: String, key: String, value: Variant) -> Variant:
	return config.get_value(section, key, value)

data in attribute.cfg

[Player]

position=Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.0679, 0.9248332, 13.692233)

[Level]

index=1


The error is relevant to UI script

func fade_out(tween_in: Tween):
	color_rect_fader.visible = true
	tween_in.tween_property(color_rect_fader, "color:a", 1.0, 0.25).from(0.0)
	

func change_scene(next_scene: String, player_transform: Transform3D) -> void:
	# Store a reference to the player to pass its settings onto the next player.
	var player = get_tree().get_first_node_in_group("Player")
	# Stop movement and cache settings.
	player.set_physics_process(false)
	var zoom = player.zoom
	var view = player.view
	var tween = create_tween()
	fade_out(tween)
	tween.tween_callback(func(): get_tree().change_scene_to_file(next_scene))
	# Wait at least one frame for the scene to update and ready.
	tween.tween_interval(0.1)
	tween.tween_callback(func():
		# Apply the cached variable to the new player.
		var new_player = get_tree().get_first_node_in_group("Player")
		# Set the player's position in the new level.
		new_player.global_transform = player_transform
		new_player.view = view
		new_player.zoom = zoom
		)
	fade_in(tween)

# Fade the screen out, reload the level and fade back in.

OK there is small fix but I believe there could be a better one

I changed order in Globals - and moved UserInterface above GeneralSavingSystem, so it got time to get ready.

still have error

E 0:00:00:696 _render_reflection_probe_step: Parameter “scenario” is null.
<C++ Source> servers/rendering/renderer_scene_cull.cpp:3780 @ _render_reflection_probe_step()

I thought to make separate scene and load from there but again it will wait for reference of player which wouldn’t exist at the time.

What would be the bullet proof way to jumping back to correct scene?

This start to reminds me when you have to rewrite good part of project to make it works.

core logic → using signal, and check for existing index and position solved the issue

signal game_state_loaded(level_index: int, position: Transform3D)

func _ready() → void:
load_data()

func request_load() -> void:
	var index: int = get_setting("Level", "index", 0)
	var pos: Transform3D = get_setting("Player", "position", Transform3D.IDENTITY)
	if index < 0 or index >= levels.size():
		index = 0
	current_level_index = index
	player_position = pos
	game_state_loaded.emit(current_level_index, player_position)

logic for main_scene_manager

extends Node

func _ready() -> void:
	SaveSystem.game_state_loaded.connect(_on_game_state_loaded)
	SaveSystem.request_load()

func _on_game_state_loaded(level_index: int, position: Transform3D) -> void:
	UserInterface.change_scene(SaveSystem.levels[level_index], position)

another guard in UserInterface applied, this making sure we can find Player in chage_scene() before it change a scene, so I guess this is in sense deferred call but a bit manual with await

func _wait_for_player(old_scene: Node) -> CharacterBody3D:
	while get_tree().current_scene == null or get_tree().current_scene == old_scene:
		await get_tree().process_frame
	await get_tree().process_frame
	return get_tree().get_first_node_in_group("Player") as CharacterBody3D

Short demo →