Godot Version
4.2.1
Question
`My game’s save system has issues: Changes (like Dialogic signals) don’t persist after loading. How do I keep these changes? Also, for area 2ds that get queue_free, should the save function instead record the scene tree state and delete anything missing during load?
this is the save function:
@tool
class_name SaveGame
extends Node
const SAVE_PATH := "user://savegame.save"
var ENCRYPTION_PASS: String:
#not important for forum
const MAX_PLAYER_ATTEMPTS := 20
const PLAYER_WAIT_TIME := 0.2
# Signals
signal save_completed(success: bool, source: String, error: String)
signal load_completed(success: bool, error: String)
# Default save data
var content_to_save: Dictionary = {
"progress": 0.0,
"current_map": "res://Scenes/gendermaria.tscn",
"character_position": Vector2.ZERO,
"character_direction": 1.0,
"inventory": [],
"collected_items": {} # NEW: Track collected items per scene
}
func save_game(source: String = "manual") -> bool:
_update_save_data()
print("Saving game...")
var error := ""
var success := false
var file := FileAccess.open_encrypted_with_pass(SAVE_PATH, FileAccess.WRITE, ENCRYPTION_PASS)
if file:
file.store_var(content_to_save)
file.close()
print("Save successful to: ", SAVE_PATH)
success = true
else:
error = str(FileAccess.get_open_error())
push_error("Save failed: ", error)
emit_signal("save_completed", success, source, error)
return success
func load_game() -> bool:
if not FileAccess.file_exists(SAVE_PATH):
push_warning("No save file found")
emit_signal("load_completed", false, "No save file found")
return false
var file := FileAccess.open_encrypted_with_pass(SAVE_PATH, FileAccess.READ, ENCRYPTION_PASS)
if not file:
var error := str(FileAccess.get_open_error())
push_error("Load failed: ", error)
emit_signal("load_completed", false, error)
return false
var data = file.get_var()
file.close()
if not data is Dictionary or not data.has("character_position") or data["character_position"] == Vector2.ZERO:
var error := "Invalid save data format or missing/invalid position"
push_error(error)
emit_signal("load_completed", false, error)
return false
content_to_save = data
print("Loaded save data: ", content_to_save) # Debug
emit_signal("load_completed", true, "")
return true
func apply_loaded_data() -> void:
if content_to_save.has("current_map"):
await _load_map(content_to_save["current_map"])
var player = await _get_player()
if player:
_restore_player_state(player)
else:
push_error("Failed to restore player state: Player not found")
await _restore_inventory()
await _restore_collected_items()
func _restore_collected_items() -> void:
if content_to_save.has("collected_items"):
var global_state = get_node_or_null("/root/GlobalState")
if global_state and global_state.has_method("set_collected_items"):
global_state.set_collected_items(content_to_save["collected_items"])
print("Restored collected items: ", content_to_save["collected_items"]) # Debug
else:
push_warning("GlobalState not found or lacks set_collected_items method")
func _update_save_data() -> void:
var player = get_tree().get_first_node_in_group("player")
if player:
content_to_save["character_position"] = player.position
print("Saving player position: ", player.position) # Debug
# Assume scale is available for Node2D-derived nodes
if player is Node2D:
content_to_save["character_direction"] = player.scale.x
print("Saving player direction: ", player.scale.x) # Debug
else:
push_warning("Player is not a Node2D, skipping direction save")
else:
push_warning("Player node not found during save")
if get_tree().current_scene and get_tree().current_scene.scene_file_path:
content_to_save["current_map"] = get_tree().current_scene.scene_file_path
print("Saving current map: ", content_to_save["current_map"]) # Debug
else:
push_warning("No current scene found during save")
if has_node("/root/GlobalState"):
var global_state = get_node("/root/GlobalState")
if global_state.has_method("get_collected_items"):
content_to_save["collected_items"] = global_state.get_collected_items()
print("Saving collected items: ", content_to_save["collected_items"]) # Debug
else:
push_warning("GlobalState lacks get_collected_items method")
else:
push_warning("GlobalState singleton not found during save")
if has_node("/root/ProgressManager"):
var progress_manager = get_node("/root/ProgressManager")
if progress_manager.has_method("get_progress"):
content_to_save["progress"] = progress_manager.get_progress()
print("Saving progress: ", content_to_save["progress"]) # Debug
else:
push_warning("ProgressManager lacks get_progress method")
else:
push_warning("ProgressManager singleton not found")
if has_node("/root/GlobalState"):
var global_state = get_node("/root/GlobalState")
if global_state.has_method("get_collected_items"):
content_to_save["collected_items"] = global_state.get_collected_items()
func _load_map(map_path: String) -> void:
if not ResourceLoader.exists(map_path):
var error := "Invalid map path: " + map_path
push_error(error)
return
if get_tree().current_scene and get_tree().current_scene.scene_file_path != map_path:
var result = get_tree().change_scene_to_file(map_path)
if result != OK:
var error := "Failed to load map: " + str(result)
push_error(error)
return
await get_tree().create_timer(0.1).timeout
print("Loaded map: ", map_path) # Debug
func _get_player() -> Node:
var player: Node
var attempts := 0
while attempts < MAX_PLAYER_ATTEMPTS:
player = get_tree().get_first_node_in_group("player")
if player:
print("Player found after ", attempts, " attempts") # Debug
return player
attempts += 1
await get_tree().create_timer(PLAYER_WAIT_TIME).timeout
var error := "Player node not found after " + str(attempts) + " attempts"
push_error(error)
return null
func _restore_player_state(player: Node) -> void:
if content_to_save.has("character_position"):
player.position = content_to_save["character_position"]
print("Restoring player position to: ", player.position) # Debug
else:
push_warning("No character_position in save data")
if player is Node2D and content_to_save.has("character_direction"):
player.scale.x = content_to_save["character_direction"]
print("Restoring player direction to: ", player.scale.x) # Debug
else:
push_warning("Player is not a Node2D or no character_direction in save data")
func _restore_inventory() -> void:
var global_inv = get_node_or_null("/root/GlobalInventory")
if not global_inv:
await get_tree().process_frame # Yield for one frame
global_inv = get_node_or_null("/root/GlobalInventory")
if not global_inv:
push_error("GlobalInventory singleton not found after retry")
return
var inventory = get_node("/root/GlobalInventory")
if inventory and inventory.has_method("set_items"):
inventory.set_items(content_to_save["inventory"])
if inventory.has_signal("inventory_updated"):
inventory.inventory_updated.emit()
else:
push_warning("GlobalInventory lacks inventory_updated signal")
else:
var error := "GlobalInventory lacks set_items method"
push_warning(error)
this is the load function which is inside pause script. also shouldnt load, exits scene and then enter it? am I wrong?:
func _on_load_pressed():
print(“Attempting to load game…”)
get_tree().paused = true
if not Saving.load_game():
print("Error: No save file found or corrupted save!")
get_tree().paused = false
return
$load.disabled = true
await Saving.apply_loaded_data()
var saved_position = Saving.content_to_save["character_position"]
var saved_direction = Saving.content_to_save["character_direction"]
var saved_map = Saving.content_to_save["current_map"]
if get_tree().current_scene.scene_file_path != saved_map:
print("Changing to saved map: ", saved_map)
get_tree().change_scene_to_file(saved_map)
# Wait for the scene to load
await get_tree().create_timer(0.5).timeout
# NEW: Refresh items after scene reload
_refresh_items_in_current_scene()
# Restore player position after scene reload
var player = get_tree().get_first_node_in_group("player")
if player:
player.position = saved_position
if player is Node2D:
player.scale.x = saved_direction
print("Player position restored to: ", player.position)
else:
push_error("Player node not found after scene reload")
# Attempt to restore through SaveGame
await Saving._restore_player_state(await Saving._get_player())
# Refresh skill menu if open
if get_tree().get_nodes_in_group("skill_menu").size() > 0:
var skill_menu = get_tree().get_first_node_in_group("skill_menu")
if skill_menu.has_method("update_all_skill_labels"):
skill_menu.update_all_skill_labels()
skill_menu.update_level_up_button()
$load.disabled = false
print("Game loaded successfully!")
get_tree().paused = false
hide()
inside the load I have made 1 more function for the items that are inside the level scene that which if a player picks up an item, they get queue free when loaded:
func _refresh_items_in_current_scene():
var current_scene = get_tree().current_scene
if not current_scene:
return
var scene_path = current_scene.scene_file_path
for item in get_tree().get_nodes_in_group("items"):
if GlobalState.is_item_collected(scene_path, item.item_id):
item.queue_free()
