Getting line is .Tres save resource in script for level save

Godot Version

4.4

Question

I’m currently reworking my level save system so it is more streamlined and works better with my game. It all save always on a level_save.tres and in that it stores all the info I need, there’s only one issue I’m stuck as to how to get the player level information. For my scene manager I need the Level the player is on and the door tag to spawn the player next to it. That info is in the level_save.tres, but after reading the docs for a while I have not figured out how to get that info off the file.

This is the current save file: (I need that level_id and scene_file_path. )

da[gd_resource type="Resource" script_class="LevelSaveData" load_steps=2 format=3]

[ext_resource type="Script" path="res://Scripts/Level_save_data.gd" id="1_ce52g"]

[resource]
script = ExtResource("1_ce52g")
data = {
NodePath("CharacterBody2D/SaverNode"): {
"health_for_save": 2,
"inventory_for_save": {
"Key1": "22",
"Key2": "33",
"Officekey": "42",
"TempKey1": null
},
"xeno_for_save": {
"Xeno-coin 1": true,
"Xeno-coin2": true
}
},
NodePath("SaverNode"): {
"level_id": 5,
"scene_file_path": "res://Levels/level_4.tscn"
}
}

Seems like you have the level information, specifically the scene_file_path. So do you only need to add a node path to the level’s door and/or where you want to spawn the player?

Maybe you are trying to store too much complex data instead of base types? For your data property the dictionary should only store values that do not have a .duplicate() function. Nodes for example will not store exactly right.

But it’s hard to tell, sharing your save/load script would help more.

These are the save and load script:

func get_level_save_file_path()-> String:
	return LEVEL_SAVES_FOLDER + str("level_save") + ".tres"

func save_level_data()-> void:
	var new_save = LevelSaveData.new()
	
	for saver: SaverNode in get_tree().get_nodes_in_group(SaverNode.SAVER_NODE_GROUP):
		var node_path = get_path_to(saver)
		new_save.data[node_path] = saver.get_save_dict()
		
	ResourceSaver.save(new_save, get_level_save_file_path())
	print("Saved Level data to",get_level_save_file_path())
	
func load_level_data()-> void:
	if not FileAccess.file_exists(get_level_save_file_path()):
		return
	
	print("loading map data from", get_level_save_file_path())
	var loaded_save = ResourceLoader.load(get_level_save_file_path()) as LevelSaveData
	
	for path in loaded_save.data:
		var saver_node = get_node_or_null(path)
		if saver_node:
			saver_node.apply_save_dict(loaded_save.data[path])

the savernode script:

@tool
class_name SaverNode extends Node

const SAVER_NODE_GROUP : String = "saver_node"

@export var properties_to_save: Array[String] = []

var suggested_properties : Array[String] = []


func _ready() -> void :
	add_to_group(SAVER_NODE_GROUP)
	if Engine.is_editor_hint():
		_update_property_list()

func _update_property_list()-> void:
	var parent = get_parent()
	if not parent:
		return
		
	suggested_properties.clear()
	var all_props = parent.get_property_list()
	
	for p in all_props:
		var pname = p.name
		suggested_properties.append(pname)
		
	notify_property_list_changed()


func _validate_property(property: Dictionary)-> void:
	if property.name == "properties_to_save":
		var options = ",".join(suggested_properties)
		property.hint = PROPERTY_HINT_TYPE_STRING
		property.hint_string = "%d/%d:%s" % [TYPE_STRING, PROPERTY_HINT_ENUM, options]


func get_save_dict()-> Dictionary:
	var parent = get_parent()
	var node_data = {}
	for prop in properties_to_save:
		print("Saving property:", prop)
		if prop in parent:
			node_data[prop] = parent.get(prop)
	return node_data


func apply_save_dict(node_data: Dictionary)-> void:
	var parent = get_parent()
	for prop in node_data:
		if prop in parent:
			parent.set(prop, node_data[prop])

All I need is to access the property Level_id (in this case the “5” int that connects to my level 5) in my game manager (that is a global script and not connected to any of these scripts) from the save. I’ve tried getting access via the packedstringarray and directly from the file but I cannot find a way.

I need that level id because to transition to the right scene I need the string that connect to the level and the door such as: SceneManager.transiton_to_scene(“Level 1”, “2”) → this would transition me to my level 1 and door 2.

This way if I am able to get the saved level_id of the last level the person was in I am able to transition them into the right level and door (doors are always named 1 less then the level name) they were in upon loading the game in the continue game.

I know it may seem complex, but it is necessary for my game, pls I just need to get that level id.

You’ll have to modify your load function, your current one only sets properties, it never calls transition_to_scene. Not all data can store and load as a base type, some data has consequences that must be replicated.

Assuming your new_save.data is a dictionary you could add it as a special key and read from that special key just as well. If you are using current_scene/change_scene_to_file properly then this may work for you.

func save_level_data()-> void:
	var new_save = LevelSaveData.new()
	
	for saver: SaverNode in get_tree().get_nodes_in_group(SaverNode.SAVER_NODE_GROUP):
		var node_path = get_path_to(saver)
		new_save.data[node_path] = saver.get_save_dict()

	# Store level path, and/or your id
	new_save.data["level_path"] = get_tree().current_scene.scene_file_path
	new_save.data["level_id"] = get_tree().current_scene.level_id

	ResourceSaver.save(new_save, get_level_save_file_path())
	print("Saved Level data to",get_level_save_file_path())
	
func load_level_data()-> void:
	if not FileAccess.file_exists(get_level_save_file_path()):
		return
	
	print("loading map data from", get_level_save_file_path())
	var loaded_save = ResourceLoader.load(get_level_save_file_path()) as LevelSaveData

	# Loading scene first
	var level_path: String = loaded_save.data["level_path"]
	var level_id: int = loaded_save.data["level_path"]
	SceneManager.transition_to_scene(level_path, level_id)

	# then path-based node data
	for path in loaded_save.data:
		var saver_node = get_node_or_null(path)
		if saver_node:
			saver_node.apply_save_dict(loaded_save.data[path])

Hey thank you so much for all the help. I like your method, but how can I call the load function on the game manager to continue game if the load and save functions are on the level scene (not global)?

I’m honestly starting to think I’m gonna need to overhaul all of this save system and alter it to be more global or at least based on a global save manager, because currently it is giving me so many issues. I have made it work in most things, but any time I try to expand it, it bugs and breaks.

Open to all opinions and ideas because creating this save system is honestly breaking me.

Hey same dev, just different account. I like your method I’m just finding one issue: I can’t access the load game function on my game manager to call this. That’s why I was trying to get the info directly from the save file. The save and load function are in the level scene which is not global. How would I do this?

This looks like a consistent path, it seems like you could call this load function from anywhere. Why couldn’t these be static functions? Why not call them from a global/your game manager?

Yes, this is the point where the save coordinator should become an autoload. The level scene should provide state to save, but it should not own the only function capable of loading the game; otherwise the main menu cannot continue into a level that does not exist yet.

Also, the sample in the previous reply has a typo:


var level_id: int = loaded_save.data["level_path"]

That should read loaded_save.data["level_id"].

I would separate metadata from scene-node data:


new_save.data = {
    "meta": {
        "scene_path": get_tree().current_scene.scene_file_path,
        "level_id": current_level_id,
        "door_id": current_door_id,
    },
    "nodes": {},
}

var scene := get_tree().current_scene
for saver: SaverNode in get_tree().get_nodes_in_group(SaverNode.SAVER_NODE_GROUP):
    var path := scene.get_path_to(saver)
    new_save.data["nodes"][path] = saver.get_save_dict()

Then put continue_game() in a SaveManager autoload:


func continue_game() -> void:
    var save := ResourceLoader.load(get_level_save_file_path()) as LevelSaveData
    if save == null:
        return

    var meta: Dictionary = save.data.get("meta", {})
    SceneManager.transition_to_scene(
        meta.get("scene_path", ""),
        meta.get("door_id", "")
    )

    await SceneManager.transition_finished

    var scene := get_tree().current_scene
    var node_data: Dictionary = save.data.get("nodes", {})

    for path in node_data:
        var saver := scene.get_node_or_null(path)
        if saver is SaverNode:
            saver.apply_save_dict(node_data[path])

Have SceneManager emit transition_finished only after the new level is in the tree and ready. That gives you the required order: read metadata → change level → wait for it → restore its nodes. Your Continue button can now call SaveManager.continue_game() from anywhere.

I run CodingQuests. The related Save + Load System develops the same central-manager/persist-group idea; the first four lessons are free and the rest require membership. It is architectural guidance rather than a drop-in replacement for your current Resource format.