Skip to content

A save system for Godot C#. It uses Newtonsoft.Json for serialization and deserialization. It supports both encrypted, compressed and regular mode.

License

Notifications You must be signed in to change notification settings

MrRobinOfficial/Godot-Saveable

Repository files navigation

Icon

A save system for Godot C#

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.

βš™οΈ 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 library. Or you can install from the release section.

Alternatively, you can install this plugin via terminal with git. Here is the command for installing it.

cd addons
git clone git@github.com:MrRobinOfficial/Godot-Saveable.git Saveable

πŸ“¦ 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 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
        );
    }
}

πŸ†˜ Support

If you have any questions or issue, just write either to my YouTube channel, Email or Twitter DM.

πŸ”— References

About

A save system for Godot C#. It uses Newtonsoft.Json for serialization and deserialization. It supports both encrypted, compressed and regular mode.

Topics

Resources

License

Stars

Watchers

Forks

Languages