Godot Version
Godot 4.2.1
Question
Hi. I am new to Godot, and I am having troubles with creating a working save/load system with 5 slots. I am not even sure this is the best method for saving. In any case, with my work there are two problems: 1) whenever I load, all of the previous savings titles disappear, substituted by “Write the saving name here.” which is the default visible name for all empty slots. 2) I do not know why, but whatever number of savings have been made, and whatever of them I load, I only load the last saving.
Can anybody tell me the cause of my error?
Here my actual code in the main scene (delo.tscn).
extends Node2D
func save() → Dictionary:
return {
“filename”: “res://delo.tscn”,
“parent”: get_parent().get_path(),
“slot_name”: “”,
“mese”: global.mese,
“giornomese”: global.giornomese
}
var message_queue =
var current_popup = null
@onready var popup_menu = $CanvasLayer/Panel_menu/MenuButton.get_popup()
@onready var status_label = $CanvasLayer/Label
@onready var save_load_panel = $CanvasLayer/SaveLoadPanel
@onready var slot_names = [“Write here the name of the new save”, “Write here the name of the new save”, “Write here the name of the new save”, “Write here the name of the new save”, “Write here the name of the new save”]
@onready var pannello_informazioni = $CanvasLayer/Panello_informazioni_economiche
@onready var pannello_quests= $CanvasLayer/Pannello_quests
Called when the node enters the scene tree for the first time.
func _ready():
print_all_save_slots()
#menu
popup_menu.add_item("Save", 0)
popup_menu.add_item("Load", 1)
popup_menu.add_item("Exit", 2)
popup_menu.connect("id_pressed", _on_menu_option_selected)
_init_save_load_panel()
save_load_panel.visible = false
global.init_popup()
$CanvasLayer/PopupTemplate/Button.connect("pressed", Callable(self, "_on_popup_close"))
func _on_menu_option_selected(id):
match id:
0:
_show_save_load_panel(“save”)
1:
_show_save_load_panel(“load”)
2:
_on_exit_pressed()
func _show_save_load_panel(mode: String):
save_load_panel.visible = true
$CanvasLayer/SaveLoadPanel/Label.text = “Choose a slot to %s” % mode
var vbox = save_load_panel.get_node("VBoxContainer")
for i in range(vbox.get_child_count()):
var hbox = vbox.get_child(i)
if hbox == null:
print("Warning: HBoxContainer node not found at index %d" % i)
continue
var button = hbox.get_node("Button(save_load)")
var line_edit = hbox.get_node("LineEdit")
var delete_button = hbox.get_node("Button(delete)") # Nuovo pulsante di cancellazione
if button != null:
button.text = mode.capitalize()
# Create a Callable object for the method with binding parameters
var callable = Callable(self, "_on_slot_button_pressed").bind(mode, i, line_edit)
# Verifica e scollega il segnale se già connesso
if button.is_connected("pressed", callable):
button.disconnect("pressed", callable)
# Connette il segnale al pulsante con i parametri
button.connect("pressed", Callable(self, "_on_slot_button_pressed").bind(mode, i, line_edit))
print("Connected button for slot %d with mode %s" % [i, mode])
else:
print("Warning: Button node not found in HBox %d" % i)
if line_edit != null:
line_edit.custom_minimum_size = Vector2(300, 30) # Imposta larghezza a 200 e altezza a 30
if i < slot_names.size():
if slot_names[i] == "":
line_edit.placeholder_text = "..."
line_edit.text = ""
else:
line_edit.text = slot_names[i]
line_edit.placeholder_text = "" # Nascondi il testo segnaposto
else:
line_edit.placeholder_text = "..."
line_edit.text = ""
else:
print("Warning: LineEdit node not found in HBox %d" % i)
if delete_button == null:
print("Warning: Delete Button node not found in HBox %d" % i)
continue
# Connect the delete button
delete_button.connect("pressed", Callable(self, "_on_delete_slot_button_pressed").bind(i, line_edit))
delete_button.text = "Delete"
func _on_slot_button_pressed(mode: String, slot_index: int, line_edit: LineEdit):
if mode == “save”:
global._save_game(slot_index, line_edit.text)
print(“Saved game in slot: %d with name: %s” % [slot_index, line_edit.text])
# Aggiorna il nome dello slot nella UI
if slot_index < slot_names.size():
slot_names[slot_index] = line_edit.text
else:
slot_names.append(line_edit.text)
# Rinfresca i nomi degli slot nel pannello di salvataggio/caricamento
_refresh_save_load_panel()
elif mode == "load":
global._load_game(slot_index, line_edit.text)
print("Loaded game from slot: %d with name: %s" % [slot_index, line_edit.text])
save_load_panel.visible = false
$CanvasLayer/PopupTemplate.visible = false
func _init_save_load_panel():
var vbox = save_load_panel.get_node(“VBoxContainer”)
# Assicurati che ci siano abbastanza nomi di slot per coprire gli HBox
var hbox_count = vbox.get_child_count()
if slot_names.size() < hbox_count:
print("Errore: slot_names non ha abbastanza elementi! Aggiungendo elementi vuoti.")
# Aggiungi elementi vuoti per riempire il gap
while slot_names.size() < hbox_count:
slot_names.append("")
for i in range(hbox_count):
var hbox = vbox.get_child(i)
# Controlla se hbox è effettivamente un HBoxContainer
if not hbox is HBoxContainer:
print("Warning: il nodo %d non è un HBoxContainer!" % i)
continue
var label = hbox.get_node("Label")
var button = hbox.get_node("Button(save_load)")
var line_edit = hbox.get_node("LineEdit")
# Controlla se i nodi sono stati trovati
if label == null:
print("Warning: Label node not found in HBox %d" % i)
continue
if line_edit == null:
print("Warning: LineEdit node not found in HBox %d" % i)
continue
if button == null:
print("Warning: Button node not found in HBox %d" % i)
continue
# Imposta il nome dello slot iniziale
label.text = "Slot %d" % (i + 1)
# Imposta il testo del LineEdit
if i < slot_names.size():
line_edit.text = slot_names[i]
else:
line_edit.text = "Empty Slot"
# Connette il pulsante per il salvataggio/caricamento
button.connect("pressed", Callable(self, "_on_slot_button_pressed").bind("save", i, line_edit))
func _refresh_save_load_panel():
var vbox = save_load_panel.get_node(“VBoxContainer”)
for i in range(vbox.get_child_count()):
var hbox = vbox.get_child(i)
if hbox == null:
continue
var line_edit = hbox.get_node("LineEdit")
if line_edit != null:
if i < slot_names.size():
line_edit.text = slot_names[i]
line_edit.placeholder_text = "" # Nascondi il testo segnaposto
else:
line_edit.text = ""
line_edit.placeholder_text = "..."
func _on_save_pressed():
ricovero.save_game(ricovero.slot_name)
_show_status(“Game Saved”)
func _on_load_pressed():
ricovero.load_game(ricovero.slot_name)
_show_status(“Game Loaded”)
func _on_exit_pressed():
get_tree().quit()
func _show_status(message):
status_label.text = message
status_label.show()
await get_tree().create_timer(2).timeout
status_label.hide()
#mi sa devo creare pure un pannello salvataggi e caricamenti
func _on_button_close_saveload_pressed():
$CanvasLayer/SaveLoadPanel.visible = false
pass # Replace with function body.
And this here is my saving and loading functions, written in a singleton called global.
func _save_game(slot_index: int, slot_name: String):
global._save_global_state()
# Save Dialogic data to the slot
var dialogic_extra_info = {
"slot_name": slot_name,
"timestamp": Time.get_datetime_string_from_system()
}
Dialogic.Save.save(slot_name, false, Dialogic.Save.ThumbnailMode.STORE_ONLY, dialogic_extra_info)
var save_file_path = "user://save_slot_%d.save" % slot_index
var file = FileAccess.open(save_file_path, FileAccess.WRITE)
# Save the slot name
file.store_line(slot_name)
#
var save_nodes = get_tree().get_nodes_in_group("Persist")
for node in save_nodes:
if node.scene_file_path.is_empty():
print("Persistent node '%s' is not an instanced scene, skipped" % node.name)
continue
if !node.has_method("save"):
print("Persistent node '%s' is missing a save() function, skipped" % node.name)
continue
var node_data = node.call("save")
node_data["slot_name"] = slot_name # Save the slot name in the data
var json_string = JSON.stringify(node_data)
print("Saving node data for '%s': %s" % [node.name, json_string]) # Print node data being saved
file.store_line(json_string)
file.close()
print("Game saved to slot %d!" % slot_index)
func _load_game(slot_index: int, slot_name: String):
global._load_global_state()
# Check if Dialogic save slot exists and load Dialogic data
if Dialogic.Save.has_slot(slot_name):
Dialogic.Save.load(slot_name)
else:
print("No Dialogic save file found for slot %d!" % slot_index)
var save_file_path = "user://save_slot_%d.save" % slot_index
if FileAccess.file_exists(save_file_path):
var file = FileAccess.open(save_file_path, FileAccess.READ)
while not file.eof_reached():
var line = file.get_line()
print("Save file content line: ", line)
file.close()
else:
print("Save file not found for slot %d" % slot_index)
# Revert the game state before loading
var save_nodes = get_tree().get_nodes_in_group("Persist")
for node in save_nodes:
node.queue_free()
var file = FileAccess.open(save_file_path, FileAccess.READ)
while file.get_position() < file.get_length():
var json_string = file.get_line()
var json = JSON.new()
var parse_result = json.parse(json_string)
if parse_result != OK:
print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line())
continue
var node_data = json.get_data()
var new_object = load(node_data["filename"]).instantiate()
get_node(node_data["parent"]).add_child(new_object)
#new_object.position = Vector2(node_data["pos_x"], node_data["pos_y"])
for key in node_data.keys():
if key == "filename" or key == "parent" or key == "pos_x" or key == "pos_y":
continue
new_object.set(key, node_data[key])
file.close()
print("Game loaded from slot %d!" % slot_index)
func _save_global_state():
var lista_quest_dict =
for oggetto_quest in lista_quest:
lista_quest_dict.append(quest_to_dict(oggetto_quest))
var global_data = {
“ermaisti”: global.ermaisti, …
}
Save global data using Dialogic’s global save method
for key in global_data.keys():
Dialogic.Save.set_global_info(key, global_data[key])
var file = FileAccess.open("user://global_state.save", FileAccess.WRITE)
var json_string = JSON.stringify(global_data)
file.store_line(json_string)
file.close()
func _load_global_state():
if not FileAccess.file_exists(“user://global_state.save”):
print(“No global state file found!”)
return
var file = FileAccess.open("user://global_state.save", FileAccess.READ)
var json_string = file.get_line()
var json = JSON.new()
var parse_result = json.parse(json_string)
if parse_result != OK:
print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line())
return
var global_data = json.get_data()
# Carica la lista di quest
global.lista_quest.clear()
for quest_dict in global_data.get("lista_quest", []):
var oggetto_quest = dict_to_quest(quest_dict)
global.lista_quest.append(oggetto_quest)
global.ermaisti = global_data["ermaisti"]
…#many others like this one
Load global data using Dialogic’s global load method
for key in global_data.keys():
if Dialogic.Save.get_global_info(key, global_data[key]):
global_data[key] = Dialogic.Save.get_global_info(key, global_data[key])
else:
print("Global data not found for key: ", key)
file.close()
Funzione per convertire un oggetto quest in un dizionario
func quest_to_dict(oggetto_quest):
var quest_dict = {
“nome_quest”: oggetto_quest.nome_quest,
“descrizione”: oggetto_quest.descrizione,
“destinazione”: oggetto_quest.destinazione,
“ricompensa”: oggetto_quest.ricompensa,
“completata”: oggetto_quest.completata,
}
if oggetto_quest is quest.quest_libro:
quest_dict["quest_type"] = "quest_libro"
quest_dict["nome_libro"] = oggetto_quest.nome_libro
quest_dict["nome_autore"] = oggetto_quest.nome_autore
quest_dict["iniziata"] = oggetto_quest.iniziata
quest_dict["libro_trovato"] = oggetto_quest.libro_trovato
elif oggetto_quest is quest.quest_trasporto:
quest_dict["quest_type"] = "quest_trasporto"
quest_dict["nome_oggetto"] = oggetto_quest.nome_oggetto
quest_dict["disponibile"] = oggetto_quest.disponibile
quest_dict["attivata"] = oggetto_quest.attivata
quest_dict["cancellata"] = oggetto_quest.cancellata
return quest_dict
Funzione per convertire un dizionario in un oggetto quest
func dict_to_quest(dict: Dictionary):
var oggetto_quest
if dict.get("quest_type") == "quest_libro":
oggetto_quest = quest.quest_libro.new(
dict.get("nome_quest", ""),
dict.get("descrizione", ""),
dict.get("nome_libro", ""),
dict.get("nome_autore", ""),
dict.get("ricompensa", 0),
dict.get("iniziata", false),
dict.get("libro_trovato", false),
dict.get("completata", false),
dict.get("destinazione", "")
)
elif dict.get("quest_type") == "quest_trasporto":
oggetto_quest = quest.quest_trasporto.new(
dict.get("nome_quest", ""),
dict.get("descrizione", ""),
dict.get("nome_oggetto", ""),
dict.get("destinazione", ""),
dict.get("ricompensa", 0),
dict.get("disponibile", false),
dict.get("attivata", false),
dict.get("completata", false),
dict.get("cancellata", false)
)
return oggetto_quest
Dialogic is just a plugin, and as of now it works well.