[DEV] Add Auto Save / Load System


This Game is made on Godot, 

In this engine I never made a custom Save System so I did one that is global and don't need much to add more to the save file.
I made a global Save System for Nodes and Resources files.


I choose tu use the Built in Godot Resources and made the base save file code:

extends Resource
class_name SaveRes
@export var saved_nodes: Dictionary[NodePath, Variant] = {}
@export var time: Dictionary


For saving any resource in game I created a function using get_property_list():

static func save_obj(in_object: Object) -> Dictionary[String, Variant]:
    var save_data: Dictionary[String, Variant] = {}
    for i: int in range(0, in_object.get_property_list().size(), 1):
        var var_data: Dictionary = in_object.get_property_list()[i]
        #Skip the resource base variable
        if var_data["name"].is_empty():
            continue
        #Skip the @export_group
        if var_data["type"] == 0:
            continue
        #Save the resources by calling the same method recurcively
        if var_data["type"] == 24:
            var sub_object: Object = in_object.get(var_data["name"])
            if sub_object == null:
                save_data[var_data["name"]] = null
            elif sub_object is Resource:
                save_data[var_data["name"]] = save_obj(sub_object)
        else:
            # Save
            save_data[var_data["name"]] = in_object.get(var_data["name"])
    return save_data


To load the resources: 

static func load_data_obj(in_object: Object, in_data: Dictionary[String, Variant]) -> void:
    for i: int in range(0, in_object.get_property_list().size(), 1):
        var var_data: Dictionary = in_object.get_property_list()[i]
        if var_data["name"].is_empty():
            continue
        if not in_data.has(var_data["name"]):
            continue
        if var_data["type"] == 24:
            if in_data[var_data["name"]].has("resource_path") and not in_data[var_data["name"]]["resource_path"].is_empty():
                if in_object.get(var_data["name"]) != null and in_object.get(var_data["name"]).resource_path == in_data[var_data["name"]]["resource_path"]:
                    continue
                print(var_data["name"], " -> load(", in_data[var_data["name"]]["resource_path"], ")")
                in_object.set(var_data["name"], load(in_data[var_data["name"]]["resource_path"]))
            elif in_object.get(var_data["name"]) != null:
                load_data_obj(in_object.get(var_data["name"]), in_data[var_data["name"]])
        in_object.set(var_data["name"], in_data[var_data["name"]])


And then a Save System for the nodes in the scene using the get_script().get_script_property_list()

I decide to create a custom function get_all_children_whith_variable() instead of using Godot Built in groups. it's easier for me and i can use the saveable variable in my scripts if i need to customise my save calls

func call_save() -> void:
    var saved_nodes: Dictionary[NodePath, Variant] = {}
    var to_save_nodes: Array[Node] = Utils.get_all_children_whith_variable(get_tree().root, "saveable")
    for node: Node in to_save_nodes:
        print("Saved: ", node.name)
        var path: NodePath = node.get_path()
        saved_nodes[path] = {}
        var list: Array[Dictionary] = node.get_script().get_script_property_list()
        for dict: Dictionary in list:
            # Skip saveable var
            if dict["name"] == "saveable": continue
            # Skip the @export_group()
            if dict["type"] == 0: continue
            # Skip Node Refs
            if dict["type"] == 24 && (node.get(dict["name"]) == null or node.get(dict["name"]) is Node): continue
            if dict["type"] != 24:
                saved_nodes[path][dict["name"]] = node.get(dict["name"])
            else:
                saved_nodes[path][dict["name"]] = AutoSaveRes.save_obj(node.get(dict["name"]))
    var save: SaveRes = SaveRes.new()
    save.saved_nodes = saved_nodes
    save.time = Time.get_datetime_dict_from_system()
    ResourceSaver.save(save, "user://savegame.res")

In the Node Scripts I want to save i just need to put a const or var named saveable:

const saveable: bool = true

then the Loading System use the set_deffered from godot:

func call_load() -> void:
    if not FileAccess.file_exists("user://savegame.res"):
        return
    var save = ResourceLoader.load("user://savegame.res")
    if save == null:
        printerr("Failed to load user://savegame.res")
        return
    for node_path: NodePath in save.saved_nodes:
        var node: Node = get_node(node_path)
        if node == null:
            printerr("Load Failed for ", node_path.get_name(node_path.get_name_count() - 1))
            continue
        for var_name: String in save.saved_nodes[node_path]:
            if not var_name in node:
                printerr("Load Failed for ", node.get_name(), ": The node dosen't has a property named: ", var_name)
                continue
            node.set_deferred(var_name, save.saved_nodes[node_path][var_name])

Thanks for Reading !

Files

25-05-20_idler_game_project.zip Play in browser
2 days ago

Get Idle Game Project

Leave a comment

Log in with itch.io to leave a comment.