Description
Changelog
Reviews (0)

A save system for Godot C#

Forked from Mr. Robin.

Updated and maintained by Phlegmlee.

license

plugin-status

maintenance-status

🔑 Features

  • Saving entire node trees.
  • Uses Newtonsoft.Json for serialization and deserialization.
  • Supports both encrypted, compressed and regular mode.
  • ISaveable interface to allow modular save/load structures.
  • Preset save slot files and support for saving and loading to/from slots.

⚙️ Requirements

You can install Newtonsoft.Json via this command:

dotnet add package Newtonsoft.Json

Or, you can install via Nuget package manager.

⚒️ Installation

You can install from the Godot Asset Store.

Or you can install from the release section.

📦 Converters

This plugin is using Newtonsoft.Json for serialization and deserialization. By using JsonConverter, it allows you to convert your custom classes to and from JSON. This plugin includes custom converters for Godot specific types.

Here's an list of available converters:

📝 Quick guide

This plugin uses JSON as a serialization format. You can read more about JSON file format.

  • SaveSystem is a static class that contains functions to load/save a file.
  • TreeSave is a class that contains NodeSave for each node in the tree.
  • NodeSave is class that contains a dictionary of key-value pairs.
  • ISaveable is an interface that defines a function Load(NodeSave save) and Save(NodeSave save).

Use the ISaveable interface for callbacks related to save system:

public partial class App : Node, ISaveable
{
    StringName ISaveable.UniqueID => "app";

    private float _globalVolume = 1.0f;
    private float _musicVolume = 0.5f;
    private float _effectsVolume = 0.8f;
    private float _motionBlurEffect = 0.0f;
    private float _postProcessingEffect = 1.0f;

    void ISaveable.Load(NodeSave save)
    {
        _globalVolume = save.GetProperty<float>("globalVolume");
        _musicVolume = save.GetProperty<float>("musicVolume");
        _effectsVolume = save.GetProperty<float>("effectsVolume");
        _motionBlurEffect = save.GetProperty<float>("motionBlurEffect");
        _postProcessingEffect = save.GetProperty<float>("postProcessingEffect");
    }

    void ISaveable.Save(NodeSave save)
    {
        save.AddProperty("globalVolume", _globalVolume);
        save.AddProperty("musicVolume", _musicVolume);
        save.AddProperty("effectsVolume", _effectsVolume);
        save.AddProperty("motionBlurEffect", _motionBlurEffect);
        save.AddProperty("postProcessingEffect", _postProcessingEffect);
    }
}

Output:

{
  "app": {
    "globalVolume": 1.0,
    "musicVolume": 0.5,
    "effectsVolume": 0.8,
    "motionBlurEffect": 0.0,
    "postProcessingEffect": 1.0
  }
}

If you want to add JSON property without messing with Newtonsoft.Json and internal systems, you can instead use System.Dynamic.ExpandoObject and dynamic keyword.

Here's the modified version:

public partial class App : Node, ISaveable
{
    StringName ISaveable.UniqueID => "app";

    private float _globalVolume = 1.0f;
    private float _musicVolume = 0.5f;
    private float _effectsVolume = 0.8f;
    private float _motionBlurEffect = 0.0f;
    private float _postProcessingEffect = 1.0f;

    void ISaveable.Load(NodeSave save)
    {
        dynamic? volumes = save.GetProperty<dynamic>("volumes");
        _globalVolume = volumes?.global;
        _musicVolume = volumes?.music;
        _effectsVolume = volumes?.effects;

        dynamic? graphics = save.GetProperty<dynamic>("graphics");
        _motionBlurEffect = graphics?.motionBlur;
        _postProcessingEffect = graphics?.postProcessing;
    }

    void ISaveable.Save(NodeSave save)
    {
        dynamic volumes = new System.Dynamic.ExpandoObject();
        volumes.global = _globalVolume;
        volumes.music = _musicVolume;
        volumes.effects = _effectsVolume;

        dynamic graphics = new System.Dynamic.ExpandoObject();
        graphics.motionBlur = _motionBlurEffect;
        graphics.postProcessing = _postProcessingEffect;

        save.AddProperty("volumes", volumes);
        save.AddProperty("graphics", graphics);
    }
}

Output:

{
  "app": {
    "volumes": {
      "global": 1.0,
      "music": 0.5,
      "effects": 0.8
    },
    "graphics": {
      "motionBlur": 0.0,
      "postProcessing": 1.0
    },
  }
}

Load and Save methods have signatures to use SaveSlotEnum or a string directly.

All examples below will show only FILE_PATH usage, but, this is interchangable with SaveSlotEnum.

// Loads file using slots.
SaveSystem.LoadFile(
    SaveSlotEnum.SlotOne,
    GetTree().Root
);

// Or load a file using string directly.

private string FILE_PATH = "user://saves/profile_01/save.dat";

SaveSystem.LoadFile(
    FILE_PATH,
    GetTree().Root
);

Load a file:

private string FILE_PATH = "user://saves/profile_01/save.dat";

// Loads the file.
SaveSystem.LoadFile(
    FILE_PATH,
    GetTree().Root
);
private string FILE_PATH = "user://saves/profile_01/save.dat";
private FileAccess.CompressionMode COMPRESSION = FileAccess.CompressionMode.Fastlz;

// Loads the file with a compression method.
SaveSystem.LoadFile(
    FILE_PATH,
    GetTree().Root,
    COMPRESSION
);
private string FILE_PATH = "user://saves/profile_01/save.dat";
private string PASSWORD = "abc123";

// Loads the file with a protected password.
SaveSystem.LoadFile(
    FILE_PATH,
    GetTree().Root,
    PASSWORD
);

Save a file:

private string FILE_PATH = "user://saves/profile_01/save.dat";

// Saves the file.
SaveSystem.SaveFile(
    FILE_PATH,
    GetTree().Root
);
private string FILE_PATH = "user://saves/profile_01/save.dat";
private FileAccess.CompressionMode COMPRESSION = FileAccess.CompressionMode.Fastlz;

// Saves the file with a compression method.
SaveSystem.SaveFile(
    FILE_PATH,
    GetTree().Root,
    COMPRESSION
);
private string FILE_PATH = "user://saves/profile_01/save.dat";
private string PASSWORD = "abc123";

// Saves the file with a protected password.
SaveSystem.SaveFile(
    FILE_PATH,
    GetTree().Root,
    PASSWORD
);

[!NOTE] If you wish to load a save file before saving it, you must specify to disable the auto load on LoadFile() method.

Here's an example:

private string FILE_PATH = "user://saves/profile_01/save.dat";

// Create a TreeSave without loading it into memory.
// Very useful when using SaveFile(), as it enables applying only the modifications made.
TreeSave? save = SaveSystem.LoadFile(
    FILE_PATH,
    GetTree().Root,
    loadTree: false
);

// Saves the TreeSave to a file.
// By specifying a 'save' parameter, it appends changes to an existing save.
SaveSystem.SaveFile(
    FILE_PATH,
    GetTree().Root,
    save // Optional parameter to append changes to an existing save
);

Here's a practical example:

public partial class SaveMenu : CanvasLayer
{
    [Export] public Button? LoadBtn { get; private set; }
    [Export] public Button? SaveBtn { get; private set; }

    public override void _EnterTree()
    {
        LoadBtn!.Pressed += OnPressed_Load;
        SaveBtn!.Pressed += OnPressed_Save;
    }

    public override void _ExitTree()
    {
        LoadBtn!.Pressed -= OnPressed_Load;
        SaveBtn!.Pressed -= OnPressed_Save;
    }

    private string FILE_PATH = "user://saves/profile_01/save.dat";

    private void OnPressed_Load()
    {
        SaveSystem.LoadFile(
            FILE_PATH,
            GetTree().Root
        );
    }

    private void OnPressed_Save()
    {
        TreeSave? save = SaveSystem.LoadFile(
            FILE_PATH,
            GetTree().Root,
            loadTree: false
        );

        SaveSystem.SaveFile(
            FILE_PATH,
            GetTree().Root,
            save
        );
    }
}

🔗 References

NOTE:

This repository was originally created by Mr. Robin in 2024.

It has been forked for fixes, updates, and, to be re-added to the asset store.

This project will be actively maintained and features added, save slot support for example.

Still MIT, still free, still open to contributions!

Changelog for version v1.3.3

No changelog provided for this version.

Reviews

Saveable has no reviews yet.

Login to write a review.

Consider supporting the creators!

If you enjoyed this asset consider supporting its creator. Follow the link below.