- 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.
- Godot 4.2.x Mono Version.
- Installed Newtonsoft.Json package.
You can install Newtonsoft.Json via this command:
dotnet add package Newtonsoft.Json
Or, you can install via Nuget package manager.
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 [email protected]:MrRobinOfficial/Godot-Saveable.git Saveable
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:
- Core converters
NodeSaveConverter
TreeSaveConverter
- Godot converters
AabbConverter
- AabbBasisConverter
- BasisCallableConverter
- CallableColorConverter
- ColorGodotObjectConverter
- GodotObjectNodePathConverter
- NodePathPlaneConverter
- PlaneProjectionConverter
- ProjectionQuaternionConverter
- QuaternionRect2Converter
- Rect2Rect2IConverter
- Rect2IRidConverter
- RidSceneTreeConverter
- SceneTreeSignalConverter
- SignalStringNameConverter
- StringNameTransform2DConverter
- Transform2DTransform3DConverter
- Transform3DVector2Converter
- Vector2Vector2IConverter
- Vector2IVector3Converter
- Vector3Vector3IConverter
- Vector3IVector4Converter
- Vector4Vector4IConverter
- Vector4I
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 containsNodeSave
for each node in the tree.NodeSave
is class that contains a dictionary of key-value pairs.ISaveable
is an interface that defines a functionLoad(NodeSave save)
andSave(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
);
}
}
If you have any questions or issue, just write either to my YouTube channel, Email or Twitter DM.