├── images
├── doc1.png
├── doc2.png
├── doc3.png
├── SaveEngine.png
├── doc1.png.import
├── doc2.png.import
├── doc3.png.import
├── SaveEngine.png.import
├── Add.svg.import
├── Load.svg.import
├── Save.svg.import
├── Player.svg.import
├── Remove.svg.import
├── SaveEngine.svg.import
├── Add.svg
├── Save.svg
├── Remove.svg
├── Load.svg
├── Player.svg
└── SaveEngine.svg
├── .gitattributes
├── addons
└── saveEngine
│ ├── scripts
│ ├── SaveFileScene.gd
│ ├── SaveFileGlobal.gd
│ ├── SaveFileNode.gd
│ ├── saveSettings.gd
│ ├── SaveFile.gd
│ ├── SaveElementBase.gd
│ ├── SaveElement3D.gd
│ ├── SaveElement2D.gd
│ ├── SaveAgent.gd
│ ├── SerializationHelper.gd
│ └── SaveService.gd
│ ├── plugin.cfg
│ ├── saveEngine.gd
│ ├── SaveAgent.svg.import
│ ├── SaveElement2D.svg.import
│ ├── SaveElement3D.svg.import
│ ├── SaveAgent.svg
│ ├── SaveElement2D.svg
│ └── SaveElement3D.svg
├── .gitignore
├── example
├── game.gd
├── player.gd
├── example.gd
├── Player.tscn
├── save_slot.gd
├── example.tscn
├── Game.tscn
└── SaveSlot.tscn
├── icon.svg.import
├── LICENSE
├── icon.svg
├── project.godot
└── README.md
/images/doc1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiaNCI-Studio/SaveEngine/HEAD/images/doc1.png
--------------------------------------------------------------------------------
/images/doc2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiaNCI-Studio/SaveEngine/HEAD/images/doc2.png
--------------------------------------------------------------------------------
/images/doc3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiaNCI-Studio/SaveEngine/HEAD/images/doc3.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Normalize EOL for all files that Git considers text files.
2 | * text=auto eol=lf
3 |
--------------------------------------------------------------------------------
/images/SaveEngine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiaNCI-Studio/SaveEngine/HEAD/images/SaveEngine.png
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveFileScene.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name SaveFileScene
3 |
4 | @export var ScenePath : String = ""
5 | @export var SceneNodes : Array[SaveFileNode] = []
6 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveFileGlobal.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name SaveFileGlobal
3 |
4 | @export var GlobalId : String = ""
5 | @export var GlobalClassName : String = ""
6 | @export var GlobalData : Dictionary = {}
7 |
--------------------------------------------------------------------------------
/addons/saveEngine/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="Save Engine"
4 | description="Save and load game mechanics
5 | With autosave and Multiple save slots
6 | See examples and docs on github for usage.
7 | "
8 | author="CiaNCI"
9 | version="1.0"
10 | script="saveEngine.gd"
11 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveFileNode.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name SaveFileNode
3 |
4 | @export var NodeTreePath : String = ""
5 | @export var ParentTreePath : String = ""
6 | @export var NodeFilePath : String = ""
7 | @export var NodeScriptPath : String = ""
8 | @export var NodeProperties : Dictionary = {}
9 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/saveSettings.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name SaveSettigns
3 |
4 | @export var SaveFilesPrefix : String = ""
5 | @export var StaticScriptsToSave : Array[String] = []
6 | @export var SaveFolderPath : String = "user://Saves/"
7 | @export var HashSalt : String = "b3203daa0aeb25a333b3c1d5d2db7f57"
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot 4+ specific ignores
2 | .godot/
3 |
4 | # Godot-specific ignores
5 | .import/
6 | export.cfg
7 | export_presets.cfg
8 |
9 | # Imported translations (automatically generated from CSV files)
10 | *.translation
11 |
12 | # Mono-specific ignores
13 | .mono/
14 | data_*/
15 | mono_crash.*.json
16 |
--------------------------------------------------------------------------------
/example/game.gd:
--------------------------------------------------------------------------------
1 | extends Node2D
2 |
3 | @onready var saving_label: Label = $CanvasLayer/SavingLabel
4 | @onready var save_agent: SaveAgent = $SaveAgent
5 |
6 | func _ready() -> void:
7 | pass # Replace with function body.
8 |
9 | func _process(delta: float) -> void:
10 | pass
11 |
12 | func _on_save_agent_start_auto_saving() -> void:
13 | saving_label.visible = true
14 |
15 | func _on_save_agent_finish_auto_saving() -> void:
16 | await get_tree().create_timer(2).timeout
17 | saving_label.visible = false
18 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveFile.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name SaveFile
3 |
4 | @export var Version : String = "1.0"
5 | @export var SlotId : String = ""
6 | @export var Name : String = ""
7 | @export var DateTime : Dictionary = {}
8 | @export var LastTime : int = 0
9 | @export var GameTime : int = 0
10 | @export var Hash : String = ""
11 | @export var CurrentSceneId : String = ""
12 | @export var MetaData : Dictionary = {}
13 | @export var GlobalData : Array[SaveFileGlobal] = []
14 | @export var ScenesData : Array[SaveFileScene] = []
15 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveElementBase.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name SaveElementBase
3 |
4 | @export var SaveTransform : bool = true
5 | @export var SaveVisibility : bool = true
6 | @export var SaveProperties : Array[String] = []
7 |
8 | func _enter_tree() -> void:
9 | add_to_group(SaveService.PERSISTENCE_GROUP)
10 |
11 | func Load(data : SaveFileNode):
12 | var parent = get_parent()
13 | for property in data.NodeProperties:
14 | if property.contains(":"):
15 | var nodePath = property.split(":")[0]
16 | var propertyPath = ":".join(property.split(":").slice(1))
17 | var node = parent.get_node(nodePath)
18 | node.set(propertyPath, SaveSerializationHelper.DeserializeVariable(data.NodeProperties[property]))
19 | else:
20 | parent.set(property, SaveSerializationHelper.DeserializeVariable(data.NodeProperties[property]))
21 |
22 |
--------------------------------------------------------------------------------
/addons/saveEngine/saveEngine.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorPlugin
3 |
4 | func _enter_tree() -> void:
5 | add_autoload_singleton("SaveService", "res://addons/saveEngine/scripts/SaveService.gd")
6 | add_custom_type("SaveAgent", "Node", preload("res://addons/saveEngine/scripts/SaveAgent.gd"), preload("res://addons/saveEngine/SaveAgent.svg"))
7 | add_custom_type("SaveElement3D", "Node3D", preload("res://addons/saveEngine/scripts/SaveElement3D.gd"), preload("res://addons/saveEngine/SaveElement3D.svg"))
8 | add_custom_type("SaveElement2D", "Node2D", preload("res://addons/saveEngine/scripts/SaveElement2D.gd"), preload("res://addons/saveEngine/SaveElement2D.svg"))
9 |
10 | func _exit_tree() -> void:
11 | remove_autoload_singleton("SaveService")
12 | remove_custom_type("SaveAgent")
13 | remove_custom_type("SaveElement3D")
14 | remove_custom_type("SaveElement2D")
15 |
16 |
--------------------------------------------------------------------------------
/images/doc1.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://7nq8vu0t5alc"
6 | path="res://.godot/imported/doc1.png-8f44361e1b0dd535e64afad5631c0d49.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/doc1.png"
14 | dest_files=["res://.godot/imported/doc1.png-8f44361e1b0dd535e64afad5631c0d49.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/images/doc2.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bncjbwqlymqxg"
6 | path="res://.godot/imported/doc2.png-c4b016087a2134fade7982abcd0fc0be.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/doc2.png"
14 | dest_files=["res://.godot/imported/doc2.png-c4b016087a2134fade7982abcd0fc0be.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/images/doc3.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://i7icamqhq4wg"
6 | path="res://.godot/imported/doc3.png-c1615d96242ad5edc8967a75ac699cf6.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/doc3.png"
14 | dest_files=["res://.godot/imported/doc3.png-c1615d96242ad5edc8967a75ac699cf6.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/images/SaveEngine.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dkcr01oovji8b"
6 | path="res://.godot/imported/SaveEngine.png-6089c14964272ccddfade94ab2035c9a.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/SaveEngine.png"
14 | dest_files=["res://.godot/imported/SaveEngine.png-6089c14964272ccddfade94ab2035c9a.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cw8q6bmk2idax"
6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://icon.svg"
14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/images/Add.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://tgoufab05dru"
6 | path="res://.godot/imported/Add.svg-503dba5da9a4bfa3df52926ee0d1e396.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/Add.svg"
14 | dest_files=["res://.godot/imported/Add.svg-503dba5da9a4bfa3df52926ee0d1e396.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/images/Load.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://crobxyirk3dqt"
6 | path="res://.godot/imported/Load.svg-d825c099a5f750096215e3e117017d3c.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/Load.svg"
14 | dest_files=["res://.godot/imported/Load.svg-d825c099a5f750096215e3e117017d3c.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/images/Save.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://btq06kdtsv3l4"
6 | path="res://.godot/imported/Save.svg-e1ffbbdd75597985253bffc2afbd25df.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/Save.svg"
14 | dest_files=["res://.godot/imported/Save.svg-e1ffbbdd75597985253bffc2afbd25df.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/images/Player.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://sduh5pg5rbab"
6 | path="res://.godot/imported/Player.svg-b3f79b95310d05bbf9b6d15db3289960.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/Player.svg"
14 | dest_files=["res://.godot/imported/Player.svg-b3f79b95310d05bbf9b6d15db3289960.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/images/Remove.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://d0x7lwfg088xo"
6 | path="res://.godot/imported/Remove.svg-d5f75f24e1f623e211f1b9bade42bc59.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/Remove.svg"
14 | dest_files=["res://.godot/imported/Remove.svg-d5f75f24e1f623e211f1b9bade42bc59.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/images/SaveEngine.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bfy8p1fh0oqkv"
6 | path="res://.godot/imported/SaveEngine.svg-6d139854abefd0fb26a7471bdb8bbc22.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://images/SaveEngine.svg"
14 | dest_files=["res://.godot/imported/SaveEngine.svg-6d139854abefd0fb26a7471bdb8bbc22.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/saveEngine/SaveAgent.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dyqomj7kevuxb"
6 | path="res://.godot/imported/SaveAgent.svg-3cc5ce4c56caa4969141a686a81b7915.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/saveEngine/SaveAgent.svg"
14 | dest_files=["res://.godot/imported/SaveAgent.svg-3cc5ce4c56caa4969141a686a81b7915.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/saveEngine/SaveElement2D.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dquajgwn2quu5"
6 | path="res://.godot/imported/SaveElement2D.svg-678cee46d199b92aacdeef575a48b642.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/saveEngine/SaveElement2D.svg"
14 | dest_files=["res://.godot/imported/SaveElement2D.svg-678cee46d199b92aacdeef575a48b642.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/saveEngine/SaveElement3D.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dr7vk6xtly7vv"
6 | path="res://.godot/imported/SaveElement3D.svg-ef99cb334e841d04753b1076b571b69f.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/saveEngine/SaveElement3D.svg"
14 | dest_files=["res://.godot/imported/SaveElement3D.svg-ef99cb334e841d04753b1076b571b69f.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 CiaNCI Studio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/player.gd:
--------------------------------------------------------------------------------
1 | extends CharacterBody2D
2 |
3 | const SPEED = 300.0
4 | const JUMP_VELOCITY = -400.0
5 |
6 | @export var Hp : int = 100
7 | @onready var sprite: Sprite2D = $Sprite
8 | @onready var hp_label: Label = $CanvasLayer/HpLabel
9 |
10 |
11 | func _physics_process(delta: float) -> void:
12 |
13 | hp_label.text = str(Hp)
14 |
15 | # Add the gravity.
16 | if not is_on_floor():
17 | velocity += get_gravity() * delta
18 |
19 | # Handle jump.
20 | if Input.is_action_just_pressed("jump") and is_on_floor():
21 | velocity.y = JUMP_VELOCITY
22 |
23 | # Get the input direction and handle the movement/deceleration.
24 | # As good practice, you should replace UI actions with custom gameplay actions.
25 | var direction := Input.get_axis("left", "right")
26 | if direction:
27 | velocity.x = direction * SPEED
28 | if velocity.x > 0:
29 | sprite.scale.x = -1
30 | else:
31 | sprite.scale.x = 1
32 | else:
33 | velocity.x = move_toward(velocity.x, 0, SPEED)
34 |
35 | move_and_slide()
36 |
37 |
38 | func _on_contact_area_body_entered(body: Node2D) -> void:
39 | if body.is_in_group("enemy"):
40 | Hp = Hp - 10
41 | hp_label.text = str(Hp)
42 |
43 |
--------------------------------------------------------------------------------
/example/example.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | @onready var slots_stack: VBoxContainer = $Scroll/SlotsStack
4 | @onready var new_game_name: TextEdit = $HBoxContainer/NewGameName
5 |
6 | var saveSlots : Array[SaveFile] = []
7 |
8 | func _ready() -> void:
9 | LoadSlots()
10 |
11 | func LoadSlots():
12 | saveSlots = SaveService.GetSlots()
13 | for child in slots_stack.get_children():
14 | child.queue_free()
15 | for slot in saveSlots:
16 | var slotView = load("res://example/SaveSlot.tscn").instantiate() as SaveSlot
17 | slotView.saveSlot = slot
18 | slotView.Load.connect(LoadGame)
19 | slotView.Delete.connect(DeleteSlot)
20 | slots_stack.add_child(slotView)
21 |
22 | func LoadGame(slot : SaveFile):
23 | SaveService.LoadGame(slot.SlotId)
24 | get_tree().change_scene_to_file(SaveService.CurrentLoadedSlot.CurrentSceneId)
25 |
26 | func DeleteSlot(slot : SaveFile):
27 | SaveService.DeleteSlot(slot.SlotId)
28 | LoadSlots()
29 |
30 | func NewGame():
31 | var name = new_game_name.text.strip_edges()
32 | if name.is_empty():
33 | return
34 | if saveSlots.any(func(item : SaveFile): return item.Name == name):
35 | return
36 | var slotId = name.replace(" ", "_")
37 | SaveService.NewSlot(slotId, name, "res://example/Game.tscn")
38 | LoadSlots()
39 |
40 | func _on_new_game_pressed() -> void:
41 | NewGame()
42 |
--------------------------------------------------------------------------------
/images/Add.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/images/Save.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/example/Player.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=6 format=3 uid="uid://bkejndsxx5kat"]
2 |
3 | [ext_resource type="Script" path="res://example/player.gd" id="1_b2lvn"]
4 | [ext_resource type="Texture2D" uid="uid://sduh5pg5rbab" path="res://images/Player.svg" id="1_w5kk0"]
5 | [ext_resource type="Script" path="res://addons/saveEngine/scripts/SaveElement2D.gd" id="3_necci"]
6 |
7 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_1qdt6"]
8 | radius = 29.0
9 | height = 74.0
10 |
11 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_iilbe"]
12 | radius = 32.0
13 | height = 78.0
14 |
15 | [node name="Player" type="CharacterBody2D"]
16 | script = ExtResource("1_b2lvn")
17 |
18 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
19 | shape = SubResource("CapsuleShape2D_1qdt6")
20 |
21 | [node name="Sprite" type="Sprite2D" parent="."]
22 | texture = ExtResource("1_w5kk0")
23 |
24 | [node name="ContactArea" type="Area2D" parent="."]
25 |
26 | [node name="CollisionShape2D" type="CollisionShape2D" parent="ContactArea"]
27 | shape = SubResource("CapsuleShape2D_iilbe")
28 |
29 | [node name="CanvasLayer" type="CanvasLayer" parent="."]
30 |
31 | [node name="HpLabel" type="Label" parent="CanvasLayer"]
32 | offset_left = 13.0
33 | offset_top = 14.0
34 | offset_right = 53.0
35 | offset_bottom = 37.0
36 | theme_override_font_sizes/font_size = 52
37 | text = "100"
38 |
39 | [node name="SaveElement2D" type="Node" parent="."]
40 | script = ExtResource("3_necci")
41 | SaveProperties = Array[String](["Hp"])
42 |
43 | [connection signal="body_entered" from="ContactArea" to="." method="_on_contact_area_body_entered"]
44 |
--------------------------------------------------------------------------------
/images/Remove.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/example/save_slot.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 | class_name SaveSlot
3 |
4 | signal Load(slot : SaveFile)
5 | signal Delete(slot : SaveFile)
6 |
7 | @export var saveSlot : SaveFile
8 | @onready var name_label: Label = $HBoxContainer/VBoxContainer/NameGroup/NameLabel
9 | @onready var date_label: Label = $HBoxContainer/VBoxContainer/DateGroup/DateLabel
10 | @onready var game_time_label: Label = $HBoxContainer/VBoxContainer/GameTimeGroup/GameTimeLabel
11 | @onready var slot_image: TextureRect = $HBoxContainer/ImageMargin/SlotImage
12 |
13 | func _ready() -> void:
14 | LoadData()
15 |
16 | func LoadData():
17 | name_label.text = saveSlot.Name
18 | date_label.text = Time.get_datetime_string_from_datetime_dict(saveSlot.DateTime, true)
19 | game_time_label.text = FormatGameTime()
20 | if saveSlot.MetaData.has("image"):
21 | slot_image.texture = load(saveSlot.MetaData["image"])
22 |
23 | func _on_load_game_pressed() -> void:
24 | Load.emit(saveSlot)
25 |
26 | func _on_delete_slot_pressed() -> void:
27 | Delete.emit(saveSlot)
28 |
29 | func FormatGameTime() -> String:
30 | var result = ""
31 | var fullSeconds = saveSlot.GameTime
32 | var hours = fullSeconds / 3600
33 | fullSeconds = fullSeconds - hours * 3600
34 | var minutes = fullSeconds / 60
35 | fullSeconds = fullSeconds - minutes * 60
36 | var seconds = fullSeconds
37 | if hours < 10:
38 | result = result + "0" + str(hours) + ":"
39 | else:
40 | result = result + str(hours) + ":"
41 | if minutes < 10:
42 | result = result + "0" + str(minutes) + ":"
43 | else:
44 | result = result + str(minutes) + ":"
45 | if seconds < 10:
46 | result = result + "0" + str(seconds)
47 | else:
48 | result = result + str(seconds)
49 | return result
50 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveElement3D.gd:
--------------------------------------------------------------------------------
1 | extends SaveElementBase
2 | class_name SaveElement3D
3 |
4 | func Serialize() -> SaveFileNode:
5 | var parent = get_parent()
6 | var result := SaveFileNode.new()
7 | result.NodeTreePath = parent.get_path()
8 | result.ParentTreePath = parent.get_parent().get_path()
9 | var script = parent.get_script()
10 | if script:
11 | result.NodeScriptPath = script.resource_path
12 | result.NodeFilePath = parent.scene_file_path
13 | if SaveTransform:
14 | result.NodeProperties.get_or_add("position", SaveSerializationHelper.SerializeVariable(parent.position))
15 | result.NodeProperties.get_or_add("rotation", SaveSerializationHelper.SerializeVariable(parent.rotation))
16 | result.NodeProperties.get_or_add("scale", SaveSerializationHelper.SerializeVariable(parent.scale))
17 | result.NodeProperties.get_or_add("top_level", SaveSerializationHelper.SerializeVariable(parent.top_level))
18 | result.NodeProperties.get_or_add("rotation_edit_mode", SaveSerializationHelper.SerializeVariable(parent.rotation_edit_mode))
19 | result.NodeProperties.get_or_add("rotation_order", SaveSerializationHelper.SerializeVariable(parent.rotation_order))
20 | if SaveVisibility:
21 | result.NodeProperties.get_or_add("visible", SaveSerializationHelper.SerializeVariable(parent.visible))
22 | for property in SaveProperties:
23 | if property.contains(":"):
24 | var nodePath = property.split(":")[0]
25 | var propertyPath = property.split(":")[1]
26 | var node = parent.get_node(nodePath)
27 | result.NodeProperties.get_or_add(property, SaveSerializationHelper.SerializeVariable(node.get(propertyPath)))
28 | else:
29 | result.NodeProperties.get_or_add(property, SaveSerializationHelper.SerializeVariable(parent.get(property)))
30 |
31 | return result
32 |
--------------------------------------------------------------------------------
/example/example.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=3 format=3 uid="uid://soopr2v4ywxe"]
2 |
3 | [ext_resource type="Script" path="res://example/example.gd" id="1_efwug"]
4 | [ext_resource type="Texture2D" uid="uid://tgoufab05dru" path="res://images/Add.svg" id="2_7cmod"]
5 |
6 | [node name="Test" type="Control"]
7 | layout_mode = 3
8 | anchors_preset = 15
9 | anchor_right = 1.0
10 | anchor_bottom = 1.0
11 | grow_horizontal = 2
12 | grow_vertical = 2
13 | script = ExtResource("1_efwug")
14 |
15 | [node name="Scroll" type="ScrollContainer" parent="."]
16 | layout_mode = 1
17 | anchors_preset = 8
18 | anchor_left = 0.5
19 | anchor_top = 0.5
20 | anchor_right = 0.5
21 | anchor_bottom = 0.5
22 | offset_left = -314.0
23 | offset_top = -193.0
24 | offset_right = 314.0
25 | offset_bottom = 193.0
26 | grow_horizontal = 2
27 | grow_vertical = 2
28 |
29 | [node name="SlotsStack" type="VBoxContainer" parent="Scroll"]
30 | layout_mode = 2
31 | size_flags_horizontal = 3
32 | size_flags_vertical = 3
33 |
34 | [node name="HBoxContainer" type="HBoxContainer" parent="."]
35 | layout_mode = 1
36 | anchors_preset = 8
37 | anchor_left = 0.5
38 | anchor_top = 0.5
39 | anchor_right = 0.5
40 | anchor_bottom = 0.5
41 | offset_left = -387.0
42 | offset_top = 201.0
43 | offset_right = 305.0
44 | offset_bottom = 241.0
45 | grow_horizontal = 2
46 | grow_vertical = 2
47 | alignment = 2
48 |
49 | [node name="Label" type="Label" parent="HBoxContainer"]
50 | layout_mode = 2
51 | text = "New Game Name:"
52 |
53 | [node name="NewGameName" type="TextEdit" parent="HBoxContainer"]
54 | layout_mode = 2
55 | size_flags_horizontal = 3
56 | scroll_fit_content_height = true
57 |
58 | [node name="NewGame" type="Button" parent="HBoxContainer"]
59 | layout_mode = 2
60 | icon = ExtResource("2_7cmod")
61 | flat = true
62 |
63 | [connection signal="pressed" from="HBoxContainer/NewGame" to="." method="_on_new_game_pressed"]
64 |
--------------------------------------------------------------------------------
/images/Load.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveElement2D.gd:
--------------------------------------------------------------------------------
1 | extends SaveElementBase
2 | class_name SaveElement2D
3 |
4 | func Serialize() -> SaveFileNode:
5 | var parent = get_parent()
6 | var result := SaveFileNode.new()
7 | result.NodeTreePath = parent.get_path()
8 | result.ParentTreePath = parent.get_parent().get_path()
9 | var script = parent.get_script()
10 | if script:
11 | result.NodeScriptPath = script.resource_path
12 | result.NodeFilePath = parent.scene_file_path
13 | if SaveTransform:
14 | result.NodeProperties.get_or_add("position", SaveSerializationHelper.SerializeVariable(parent.position))
15 | result.NodeProperties.get_or_add("rotation", SaveSerializationHelper.SerializeVariable(parent.rotation))
16 | result.NodeProperties.get_or_add("scale", SaveSerializationHelper.SerializeVariable(parent.scale))
17 | result.NodeProperties.get_or_add("skew", SaveSerializationHelper.SerializeVariable(parent.skew))
18 | if SaveVisibility:
19 | result.NodeProperties.get_or_add("visible", SaveSerializationHelper.SerializeVariable(parent.visible))
20 | result.NodeProperties.get_or_add("modulate", SaveSerializationHelper.SerializeVariable(parent.modulate))
21 | result.NodeProperties.get_or_add("self_modulate", SaveSerializationHelper.SerializeVariable(parent.self_modulate))
22 | result.NodeProperties.get_or_add("show_behind_parent", SaveSerializationHelper.SerializeVariable(parent.show_behind_parent))
23 | result.NodeProperties.get_or_add("light_mask", SaveSerializationHelper.SerializeVariable(parent.light_mask))
24 | result.NodeProperties.get_or_add("visibility_layer", SaveSerializationHelper.SerializeVariable(parent.visibility_layer))
25 |
26 | for property in SaveProperties:
27 | if property.contains(":"):
28 | var nodePath = property.split(":")[0]
29 | var propertyPath = property.split(":")[1]
30 | var node = parent.get_node(nodePath)
31 | result.NodeProperties.get_or_add(property, SaveSerializationHelper.SerializeVariable(node.get(propertyPath)))
32 | else:
33 | result.NodeProperties.get_or_add(property, SaveSerializationHelper.SerializeVariable(parent.get(property)))
34 |
35 | return result
36 |
--------------------------------------------------------------------------------
/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/saveEngine/SaveAgent.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/saveEngine/SaveElement2D.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/addons/saveEngine/SaveElement3D.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/images/Player.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/example/Game.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=8 format=3 uid="uid://gl11optf4etu"]
2 |
3 | [ext_resource type="PackedScene" uid="uid://bkejndsxx5kat" path="res://example/Player.tscn" id="1_jpn4t"]
4 | [ext_resource type="Script" path="res://example/game.gd" id="1_oyjxt"]
5 | [ext_resource type="Texture2D" uid="uid://sduh5pg5rbab" path="res://images/Player.svg" id="2_oydj2"]
6 | [ext_resource type="Script" path="res://addons/saveEngine/scripts/SaveAgent.gd" id="3_sq3qb"]
7 |
8 | [sub_resource type="QuadMesh" id="QuadMesh_2hhh8"]
9 |
10 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_3dfmn"]
11 | size = Vector2(1.00043, 0.983957)
12 |
13 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_5bbrv"]
14 | size = Vector2(100, 110)
15 |
16 | [node name="Game" type="Node2D"]
17 | script = ExtResource("1_oyjxt")
18 |
19 | [node name="Floor" type="StaticBody2D" parent="."]
20 | position = Vector2(580, 600)
21 |
22 | [node name="MeshInstance2D" type="MeshInstance2D" parent="Floor"]
23 | position = Vector2(-3, 0)
24 | scale = Vector2(1151.5, 93.5)
25 | mesh = SubResource("QuadMesh_2hhh8")
26 |
27 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Floor"]
28 | position = Vector2(-3, 0)
29 | scale = Vector2(1151.5, 93.5)
30 | shape = SubResource("RectangleShape2D_3dfmn")
31 |
32 | [node name="Player" parent="." instance=ExtResource("1_jpn4t")]
33 | position = Vector2(223, 425)
34 |
35 | [node name="Enemy" type="StaticBody2D" parent="." groups=["enemy"]]
36 | position = Vector2(1096, 502)
37 |
38 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Enemy"]
39 | position = Vector2(4, -5)
40 | shape = SubResource("RectangleShape2D_5bbrv")
41 |
42 | [node name="Sprite2D" type="Sprite2D" parent="Enemy"]
43 | modulate = Color(1, 0, 0, 1)
44 | scale = Vector2(1.48761, 1.58366)
45 | texture = ExtResource("2_oydj2")
46 |
47 | [node name="Enemy2" type="StaticBody2D" parent="." groups=["enemy"]]
48 | position = Vector2(48, 502)
49 |
50 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Enemy2"]
51 | position = Vector2(4, -5)
52 | shape = SubResource("RectangleShape2D_5bbrv")
53 |
54 | [node name="Sprite2D" type="Sprite2D" parent="Enemy2"]
55 | modulate = Color(1, 0, 0, 1)
56 | scale = Vector2(-1.488, 1.584)
57 | texture = ExtResource("2_oydj2")
58 |
59 | [node name="SaveAgent" type="Node" parent="."]
60 | script = ExtResource("3_sq3qb")
61 | AutoSavePeriod = 30
62 |
63 | [node name="CanvasLayer" type="CanvasLayer" parent="."]
64 |
65 | [node name="SavingLabel" type="Label" parent="CanvasLayer"]
66 | visible = false
67 | anchors_preset = 1
68 | anchor_left = 1.0
69 | anchor_right = 1.0
70 | offset_left = -158.0
71 | offset_top = 19.0
72 | offset_right = -31.0
73 | offset_bottom = 64.0
74 | grow_horizontal = 0
75 | theme_override_font_sizes/font_size = 32
76 | text = "Saving..."
77 |
78 | [connection signal="FinishAutoSaving" from="SaveAgent" to="." method="_on_save_agent_finish_auto_saving"]
79 | [connection signal="StartAutoSaving" from="SaveAgent" to="." method="_on_save_agent_start_auto_saving"]
80 |
--------------------------------------------------------------------------------
/project.godot:
--------------------------------------------------------------------------------
1 | ; Engine configuration file.
2 | ; It's best edited using the editor UI and not directly,
3 | ; since the parameters that go here are not all obvious.
4 | ;
5 | ; Format:
6 | ; [section] ; section goes between []
7 | ; param=value ; assign values to parameters
8 |
9 | config_version=5
10 |
11 | [application]
12 |
13 | config/name="Save Engine"
14 | run/main_scene="res://example/example.tscn"
15 | config/features=PackedStringArray("4.3", "Forward Plus")
16 |
17 | [autoload]
18 |
19 | SaveService="*res://addons/saveEngine/scripts/SaveService.gd"
20 |
21 | [editor_plugins]
22 |
23 | enabled=PackedStringArray("res://addons/saveEngine/plugin.cfg")
24 |
25 | [global_group]
26 |
27 | enemy=""
28 |
29 | [input]
30 |
31 | jump={
32 | "deadzone": 0.5,
33 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
34 | , Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
35 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
36 | ]
37 | }
38 | left={
39 | "deadzone": 0.5,
40 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
41 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
42 | ]
43 | }
44 | right={
45 | "deadzone": 0.5,
46 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
47 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveAgent.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name SaveAgent
3 |
4 | signal StartAutoSaving
5 | signal FinishAutoSaving
6 | signal StartSaving
7 | signal FinishSaving
8 | signal StartLoading
9 | signal FinishLoading
10 |
11 | @export var AutoSave : bool = true
12 | @export var AutoSavePeriod : int = 60
13 |
14 | var parent : Node
15 | var autoSaveTimer : Timer
16 |
17 | func _enter_tree() -> void:
18 | parent = get_parent()
19 | autoSaveTimer = Timer.new()
20 | autoSaveTimer.autostart = AutoSave
21 | autoSaveTimer.wait_time = AutoSavePeriod
22 | autoSaveTimer.timeout.connect(__AutoSave)
23 | add_child(autoSaveTimer)
24 | LoadSceneData()
25 |
26 | func LoadSceneData():
27 | StartLoading.emit()
28 | if not SaveService.CurrentLoadedSlot:
29 | FinishLoading.emit()
30 | return
31 | if not SaveService.CurrentLoadedSlot.ScenesData.any(func(item: SaveFileScene) : return item.ScenePath == parent.scene_file_path):
32 | FinishLoading.emit()
33 | return
34 | get_tree().paused = true
35 | var scene : SaveFileScene = SaveService.CurrentLoadedSlot.ScenesData.filter(func(item: SaveFileScene) : return item.ScenePath == parent.scene_file_path)[0]
36 | var persistentNodes = get_tree().get_nodes_in_group(SaveService.PERSISTENCE_GROUP)
37 |
38 | #Remove all persistent nodes:
39 | for persistent in persistentNodes:
40 | persistent.get_parent().queue_free()
41 |
42 | #Re-Add
43 | for persistent in scene.SceneNodes:
44 | var instance = load(persistent.NodeFilePath).instantiate()
45 | parent.get_node(persistent.ParentTreePath).add_child.call_deferred(instance)
46 | instance.request_ready()
47 | await instance.ready
48 | var saveElement = instance.get_children().filter(func(child : Node): return child is SaveElement3D or child is SaveElement2D)
49 | if len(saveElement) > 0:
50 | saveElement[0].Load(persistent)
51 | get_tree().paused = false
52 | FinishLoading.emit()
53 |
54 |
55 | func SaveSceneData(auto = false):
56 | if not SaveService.CurrentLoadedSlot:
57 | SaveService.NewSlot()
58 |
59 | if not auto:
60 | StartSaving.emit()
61 | else:
62 | StartAutoSaving.emit()
63 |
64 | var sceneData = SaveFileScene.new()
65 | sceneData.ScenePath = parent.scene_file_path
66 |
67 | var persistentNodes = get_tree().get_nodes_in_group(SaveService.PERSISTENCE_GROUP)
68 | for persistent in persistentNodes:
69 | sceneData.SceneNodes.append(persistent.Serialize())
70 |
71 | if SaveService.CurrentLoadedSlot.ScenesData.any(func(item: SaveFileScene) : return item.ScenePath == parent.scene_file_path):
72 | var currentIndex = SaveService.CurrentLoadedSlot.ScenesData.find(SaveService.CurrentLoadedSlot.ScenesData.filter(func(item: SaveFileScene) : return item.ScenePath == parent.scene_file_path)[0])
73 | SaveService.CurrentLoadedSlot.ScenesData.remove_at(currentIndex)
74 | SaveService.CurrentLoadedSlot.ScenesData.append(sceneData)
75 | SaveService.SaveGame()
76 |
77 | await SaveService.saveCompleted
78 |
79 | if not auto:
80 | FinishSaving.emit()
81 | else:
82 | FinishAutoSaving.emit()
83 |
84 | func __AutoSave():
85 | SaveSceneData(true)
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SaveEngine
2 |
3 | ### Save and load game mechanics with slots and autosave.
4 |
5 | ## Services:
6 |
7 | ### SaveService
8 |
9 | It's the main singleton service of the plugin, used to save, load, delete and list save slots.
10 |
11 | Save files will be validated by hash and salt.
12 | It will save singleton classes as global data.
13 | Which global classes must be saved can be configured on the auto generated file: Data/Settings/saveSettings.cfg
14 |
15 | **Methods:**
16 |
17 | * **NewSlot(slotId : String = "", name : String = "", currentScene : String = "", metaData : Dictionary = {}) -> bool**
18 | * Create a new save slot
19 |
20 | * **DeleteSlot(slotId : String) -> bool:**
21 | * Delete an saved slot
22 |
23 | * **SaveGame(autoSetCurrentScene = true) -> bool**
24 | * Save a loaded game slot
25 |
26 | * **LoadGame(slotId : String) -> bool**
27 | * Load a game slot
28 |
29 | * **GetSlots() -> Array[SaveFile]**
30 | * Get all saved slots (files)
31 |
32 | ### Configuration File:
33 |
34 | ```json
35 | {
36 | "HashSalt":"HASH salt (used to validate save files, can be any text or hash)",
37 | "SaveFilesPrefix":"prefix to add to every save file, can be empty",
38 | "SaveFolderPath":"default save file folder, default: user://Saves/",
39 | "StaticScriptsToSave":["List of singleton class to be saved as global data, if empty will save all singletons (no recommended)"]
40 | }
41 | ```
42 |
43 | ### SaveAgent
44 |
45 | Must be placed on a scene that pretend to be saved, handles all saving process for the scene, there's no need to call SaveService methods on the scene.
46 | If there’s an loaded slot and in this slot there’s data related to the scene, will automatically apply the saved data to the nodes that contains the SaveElement child.
47 |
48 | 
49 |
50 | **Signals:**
51 |
52 | * **StartAutoSaving**
53 | * **FinishAutoSaving**
54 | * **StartSaving**
55 | * **FinishSaving**
56 | * **StartLoading**
57 | * **FinishLoading**
58 |
59 | **Properties:**
60 |
61 | * **AutoSave : bool = true**
62 | * Enable autosave
63 | * **AutoSavePeriod : int = 60**
64 | * Auto save period in seconds
65 |
66 | **Methods:**
67 |
68 | * **LoadSceneData()**
69 | * Manually trigger the load scene data
70 |
71 | * **SaveSceneData(auto = false)**
72 | * Manually trigger the save scene data
73 |
74 | ### SaveElement2D and SaveElement3D
75 |
76 | Must be placed on a node or scene that pretends to have the properties saved, handles the serialization and load of the object.
77 | Used by the SaveAgent to save and load the scene elements.
78 | Specific properties to be saved can be set on the inspector.
79 |
80 | 
81 | 
82 |
83 | **Properties:**
84 |
85 | * **SaveTransform : bool = true**
86 | * Save the transform properties of the parent node
87 | * **SaveVisibility : bool = true**
88 | * Save the visibility properties of the parent node
89 | * **SaveProperties : Array[String] = []**
90 | * List of properties to be saved.
91 | * Can save child nodes properties, use following pattern: "nodePath:property"
92 | ex:. "geometry:rotation"
93 |
94 | **Methods (used internally be SaveAgent):**
95 |
96 | * **Load(data : SaveFileNode)**
97 | * Load parent properties
98 |
99 | * **Serialize() -> SaveFileNode**
100 | * Serialize parent properties
101 |
102 | ### About
103 |
104 | By Cianci
105 |
106 | KelvysB.
107 |
108 | Check Cianci Tutorials (Brazilian Portuguese):
109 |
110 | https://www.youtube.com/@CiaNCIStudio
111 |
112 |
--------------------------------------------------------------------------------
/example/SaveSlot.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=5 format=3 uid="uid://1nfjxoy27gmu"]
2 |
3 | [ext_resource type="Script" path="res://example/save_slot.gd" id="1_ou5vv"]
4 | [ext_resource type="Texture2D" uid="uid://btq06kdtsv3l4" path="res://images/Save.svg" id="2_adn1g"]
5 | [ext_resource type="Texture2D" uid="uid://crobxyirk3dqt" path="res://images/Load.svg" id="5_6lqsp"]
6 | [ext_resource type="Texture2D" uid="uid://d0x7lwfg088xo" path="res://images/Remove.svg" id="6_45lry"]
7 |
8 | [node name="SaveSlot" type="Control"]
9 | custom_minimum_size = Vector2(100, 80)
10 | layout_mode = 3
11 | anchors_preset = 0
12 | offset_right = 1153.0
13 | offset_bottom = 80.0
14 | size_flags_horizontal = 3
15 | script = ExtResource("1_ou5vv")
16 |
17 | [node name="Background" type="ColorRect" parent="."]
18 | layout_mode = 1
19 | anchors_preset = 15
20 | anchor_right = 1.0
21 | anchor_bottom = 1.0
22 | grow_horizontal = 2
23 | grow_vertical = 2
24 | color = Color(0.223103, 0.223103, 0.223103, 1)
25 |
26 | [node name="HBoxContainer" type="HBoxContainer" parent="."]
27 | layout_mode = 2
28 | offset_left = 20.0
29 | offset_top = 3.0
30 | offset_right = 1132.0
31 | offset_bottom = 75.0
32 | grow_horizontal = 2
33 | grow_vertical = 2
34 | theme_override_constants/separation = 21
35 |
36 | [node name="ImageMargin" type="MarginContainer" parent="HBoxContainer"]
37 | layout_mode = 2
38 |
39 | [node name="SlotImage" type="TextureRect" parent="HBoxContainer/ImageMargin"]
40 | layout_mode = 2
41 | texture = ExtResource("2_adn1g")
42 | expand_mode = 2
43 | stretch_mode = 4
44 |
45 | [node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
46 | layout_mode = 2
47 | alignment = 1
48 |
49 | [node name="NameGroup" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"]
50 | layout_mode = 2
51 |
52 | [node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/NameGroup"]
53 | layout_mode = 2
54 | text = "Name: "
55 |
56 | [node name="NameLabel" type="Label" parent="HBoxContainer/VBoxContainer/NameGroup"]
57 | layout_mode = 2
58 | text = "Save Name"
59 |
60 | [node name="DateGroup" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"]
61 | layout_mode = 2
62 |
63 | [node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/DateGroup"]
64 | layout_mode = 2
65 | text = "Date: "
66 |
67 | [node name="DateLabel" type="Label" parent="HBoxContainer/VBoxContainer/DateGroup"]
68 | layout_mode = 2
69 | text = "Dat Time"
70 |
71 | [node name="GameTimeGroup" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"]
72 | layout_mode = 2
73 |
74 | [node name="Label" type="Label" parent="HBoxContainer/VBoxContainer/GameTimeGroup"]
75 | layout_mode = 2
76 | text = "Game Time: "
77 |
78 | [node name="GameTimeLabel" type="Label" parent="HBoxContainer/VBoxContainer/GameTimeGroup"]
79 | layout_mode = 2
80 | text = "Game Time"
81 |
82 | [node name="ButtonsMargin" type="MarginContainer" parent="HBoxContainer"]
83 | layout_mode = 2
84 |
85 | [node name="ButtonsGroup" type="HBoxContainer" parent="HBoxContainer/ButtonsMargin"]
86 | layout_mode = 2
87 | theme_override_constants/separation = 17
88 |
89 | [node name="LoadGame" type="Button" parent="HBoxContainer/ButtonsMargin/ButtonsGroup"]
90 | layout_mode = 2
91 | icon = ExtResource("5_6lqsp")
92 | icon_alignment = 1
93 |
94 | [node name="DeleteSlot" type="Button" parent="HBoxContainer/ButtonsMargin/ButtonsGroup"]
95 | layout_mode = 2
96 | icon = ExtResource("6_45lry")
97 | icon_alignment = 1
98 |
99 | [connection signal="pressed" from="HBoxContainer/ButtonsMargin/ButtonsGroup/LoadGame" to="." method="_on_load_game_pressed"]
100 | [connection signal="pressed" from="HBoxContainer/ButtonsMargin/ButtonsGroup/DeleteSlot" to="." method="_on_delete_slot_pressed"]
101 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SerializationHelper.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name SaveSerializationHelper
3 |
4 | static func SerializeVariable(variable):
5 | match typeof(variable):
6 | TYPE_NIL:
7 | return null
8 | TYPE_VECTOR2:
9 | return __SerializeVector2(variable)
10 | TYPE_VECTOR3:
11 | return __SerializeVector3(variable)
12 | TYPE_COLOR:
13 | return __SerializeColor(variable)
14 | TYPE_BASIS:
15 | return __SerializeBasis(variable)
16 | TYPE_TRANSFORM3D:
17 | return __SerializeTransform(variable)
18 | TYPE_TRANSFORM2D:
19 | return __SerializeTransform2d(variable)
20 | TYPE_ARRAY:
21 | return __SerializeArray(variable)
22 | TYPE_DICTIONARY:
23 | return __SerializeDictionary(variable)
24 | return variable
25 |
26 | static func DeserializeVariable(input):
27 | if typeof(input) == TYPE_NIL:
28 | return null
29 |
30 | if typeof(input) != TYPE_DICTIONARY:
31 | return input
32 |
33 | match input.type:
34 | "vector2":
35 | return __DeserializeVector2(input)
36 | "vector3":
37 | return __DeserializeVector3(input)
38 | "color":
39 | return __DeserializeColor(input)
40 | "basis":
41 | return __DeserializeBasis(input)
42 | "transform":
43 | return __DeserializeTransform(input)
44 | "transform2d":
45 | return __DeserializeTransform2d(input)
46 | "array":
47 | return __DeserializeArray(input)
48 | "dictionary":
49 | return __DeserializeDictionary(input)
50 | return input
51 |
52 | static func __SerializeVector2(input):
53 | return {
54 | "type": "vector2",
55 | "x" : input.x,
56 | "y" : input.y
57 | }
58 |
59 | static func __SerializeVector3(input):
60 | return {
61 | "type": "vector3",
62 | "x" : input.x,
63 | "y" : input.y,
64 | "z" : input.z
65 | }
66 |
67 | static func __SerializeColor(input):
68 | return {
69 | "type": "color",
70 | "r" : input.r,
71 | "g" : input.g,
72 | "b" : input.b,
73 | "a" : input.a
74 | }
75 |
76 | static func __SerializeBasis(input):
77 | return {
78 | "type": "basis",
79 | "x": SerializeVariable(input.x),
80 | "y": SerializeVariable(input.y),
81 | "z": SerializeVariable(input.z)
82 | }
83 |
84 | static func __SerializeTransform(input):
85 | return {
86 | "type": "transform",
87 | "basis": SerializeVariable(input.basis),
88 | "origin": SerializeVariable(input.origin)
89 | }
90 |
91 | static func __SerializeTransform2d(input):
92 | return {
93 | "type": "transform2d",
94 | "x": SerializeVariable(input.x),
95 | "y": SerializeVariable(input.y),
96 | "origin": SerializeVariable(input.origin)
97 | }
98 |
99 | static func __SerializeArray(input):
100 | var array = []
101 | for entry in input:
102 | array.push_back(SerializeVariable(entry))
103 | return {
104 | "type": "array",
105 | "data": array
106 | }
107 |
108 | static func __SerializeDictionary(input):
109 | var dict = {}
110 | for entry in input:
111 | dict[entry] = SerializeVariable(input[entry])
112 | return {
113 | "type": "dictionary",
114 | "data": dict
115 | }
116 |
117 | static func __DeserializeVector2(input):
118 | return Vector2(
119 | input.x,
120 | input.y
121 | )
122 |
123 | static func __DeserializeVector3(input):
124 | return Vector3(
125 | input.x,
126 | input.y,
127 | input.z
128 | )
129 |
130 | static func __DeserializeColor(input):
131 | return Color(
132 | input.r,
133 | input.g,
134 | input.b,
135 | input.a
136 | )
137 |
138 | static func __DeserializeBasis(input):
139 | return Basis(
140 | __DeserializeVector3(input.x),
141 | __DeserializeVector3(input.y),
142 | __DeserializeVector3(input.z)
143 | )
144 |
145 | static func __DeserializeTransform(input):
146 | return Transform3D(
147 | __DeserializeBasis(input.basis),
148 | __DeserializeVector3(input.origin)
149 | )
150 |
151 | static func __DeserializeTransform2d(input):
152 | return Transform2D(
153 | __DeserializeVector2(input.x),
154 | __DeserializeVector2(input.y),
155 | __DeserializeVector2(input.origin)
156 | )
157 |
158 | static func __DeserializeArray(input):
159 | var array = []
160 | for entry in input.data:
161 | array.push_back(DeserializeVariable(entry))
162 | return array
163 |
164 | static func __DeserializeDictionary(input):
165 | var dict = {}
166 | for entry in input.data:
167 | dict[entry] = DeserializeVariable(input.data[entry])
168 | return dict
169 |
--------------------------------------------------------------------------------
/addons/saveEngine/scripts/SaveService.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | signal loadCompleted
4 | signal loadStarted
5 | signal saveCompleted
6 | signal saveStarted
7 |
8 | const PERSISTENCE_GROUP : String = "persist"
9 | const SETTINGS_FOLDER_PATH : String = "res://Data/Settings/"
10 | const SETTINGS_FILE_NAME : String = "saveSettings.cfg"
11 | const IGNORE_GLOBAL_PROPERTIES : Array[String] = ["Auto Translate",
12 | "Editor Description",
13 | "GameState.gd",
14 | "Node",
15 | "Physics Interpolation",
16 | "Process",
17 | "Thread Group",
18 | "_import_path",
19 | "auto_translate_mode",
20 | "editor_description",
21 | "multiplayer",
22 | "name",
23 | "owner",
24 | "physics_interpolation_mode",
25 | "process_mode",
26 | "process_physics_priority",
27 | "process_priority",
28 | "process_thread_group",
29 | "process_thread_group_order",
30 | "process_thread_messages",
31 | "scene_file_path",
32 | "script",
33 | "unique_name_in_owner"]
34 |
35 | @export var CurrentLoadedSlot : SaveFile
36 | @export var Settings : SaveSettigns
37 |
38 | func _ready() -> void:
39 | __EnsureSettingsFolder()
40 | __LoadSettings()
41 | __EnsureSaveFolder()
42 |
43 | func NewSlot(slotId : String = "", name : String = "", currentScene : String = "", metaData : Dictionary = {}) -> bool:
44 | var currentSlots = GetSlots()
45 | if currentSlots.any(func(slot:SaveFile): return slot.SlotId == slotId):
46 | return false
47 | CurrentLoadedSlot = SaveFile.new()
48 | if not slotId.is_empty():
49 | CurrentLoadedSlot.SlotId = slotId
50 | else:
51 | CurrentLoadedSlot.SlotId = __GetDateTime()
52 | if not name.is_empty():
53 | CurrentLoadedSlot.Name = name
54 | else:
55 | CurrentLoadedSlot.Name = "SaveSlot"
56 |
57 | if not currentScene.is_empty():
58 | CurrentLoadedSlot.CurrentSceneId = currentScene
59 | else:
60 | CurrentLoadedSlot.CurrentSceneId = get_tree().current_scene.scene_file_path
61 |
62 | CurrentLoadedSlot.MetaData = metaData
63 | CurrentLoadedSlot.LastTime = Time.get_unix_time_from_system()
64 | CurrentLoadedSlot.GameTime = 0
65 |
66 | return SaveGame(false)
67 |
68 | func DeleteSlot(slotId : String) -> bool:
69 | var filePath = Settings.SaveFilesPrefix + Settings.SaveFolderPath + slotId + ".sav"
70 | var fileMetaPath = Settings.SaveFilesPrefix + Settings.SaveFolderPath + slotId + ".meta.sav"
71 | if FileAccess.file_exists(filePath) and FileAccess.file_exists(fileMetaPath):
72 | DirAccess.remove_absolute(filePath)
73 | DirAccess.remove_absolute(fileMetaPath)
74 | else:
75 | return false
76 | return true
77 |
78 | func SaveGame(autoSetCurrentScene = true) -> bool:
79 | if not CurrentLoadedSlot:
80 | return false
81 | emit_signal("saveStarted")
82 | CurrentLoadedSlot.GlobalData = __GetGlobalData()
83 | CurrentLoadedSlot.DateTime = Time.get_datetime_dict_from_system()
84 | CurrentLoadedSlot.GameTime = CurrentLoadedSlot.GameTime + (Time.get_unix_time_from_system() - CurrentLoadedSlot.LastTime)
85 | CurrentLoadedSlot.LastTime = Time.get_unix_time_from_system()
86 | if autoSetCurrentScene:
87 | CurrentLoadedSlot.CurrentSceneId = get_tree().current_scene.scene_file_path
88 | CurrentLoadedSlot.Hash = ""
89 | var serialized = __SerializeSaveFile(CurrentLoadedSlot)
90 | var filePath = Settings.SaveFilesPrefix + Settings.SaveFolderPath + CurrentLoadedSlot.SlotId + ".sav"
91 | var file = FileAccess.open(filePath, FileAccess.WRITE)
92 | file.store_string(serialized)
93 | file.close()
94 | __SaveMetaFile(CurrentLoadedSlot, filePath)
95 | emit_signal("saveCompleted")
96 | return true
97 |
98 | func LoadGame(slotId : String) -> bool:
99 | emit_signal("loadStarted")
100 | if not __ValidateMetaFile(slotId):
101 | emit_signal("loadCompleted")
102 | return false
103 |
104 | var filePath = Settings.SaveFilesPrefix + Settings.SaveFolderPath + slotId + ".sav"
105 | var file = FileAccess.open(filePath, FileAccess.READ)
106 | var content = file.get_as_text()
107 | file.close()
108 | CurrentLoadedSlot = __DeserializeSaveFile(content)
109 | CurrentLoadedSlot.LastTime = Time.get_unix_time_from_system()
110 | __ApplyGlobalData()
111 | emit_signal("loadCompleted")
112 | return true
113 |
114 | func GetSlots() -> Array[SaveFile]:
115 | var result : Array[SaveFile] = []
116 | var files : Array[String] = []
117 | files.append_array(DirAccess.get_files_at(Settings.SaveFolderPath))
118 | files = files.filter(func(item : String) : return item.ends_with(".meta.sav"))
119 | for filePath in files:
120 | var file = FileAccess.open(Settings.SaveFolderPath + filePath, FileAccess.READ)
121 | var fileContent = file.get_as_text()
122 | file.close()
123 | result.append(__DeserializeSaveFile(fileContent))
124 | return result
125 |
126 | func __ApplyGlobalData():
127 | if not CurrentLoadedSlot:
128 | return
129 | for autoload in get_tree().root.get_children():
130 | if CurrentLoadedSlot.GlobalData.any(func(item : SaveFileGlobal) : return item.GlobalClassName == autoload.name):
131 | var stored : SaveFileGlobal = CurrentLoadedSlot.GlobalData.filter(func(item : SaveFileGlobal) : return item.GlobalClassName == autoload.name)[0]
132 | for property in stored.GlobalData:
133 | autoload.set(property, SaveSerializationHelper.DeserializeVariable(stored.GlobalData[property]))
134 |
135 |
136 | func __GetGlobalData() -> Array[SaveFileGlobal]:
137 | var result : Array[SaveFileGlobal] = []
138 |
139 | for autoload in get_tree().root.get_children():
140 | if __ValidateAutoload(autoload):
141 | result.append(__SerializeGlobal(autoload))
142 |
143 | return result
144 |
145 | func __ValidateAutoload(autoload) -> bool:
146 | var result = autoload != get_tree().get_current_scene() and autoload != self
147 | if len(Settings.StaticScriptsToSave) > 0:
148 | result = result and Settings.StaticScriptsToSave.any(func(item : String): return autoload.script.resource_path.ends_with(item))
149 |
150 | return result
151 |
152 | func __SerializeGlobal(autoload : Node) -> SaveFileGlobal:
153 | var result = SaveFileGlobal.new()
154 | result.GlobalClassName = autoload.name
155 | result.GlobalId = autoload.get_script().resource_path
156 | result.GlobalData = {}
157 | for property in autoload.get_property_list():
158 | if __ValidateGlobalProperty(property["name"]):
159 | result.GlobalData.get_or_add(property["name"], SaveSerializationHelper.SerializeVariable(autoload.get(property["name"])))
160 | return result
161 |
162 | func __ValidateGlobalProperty(propertyName) -> bool:
163 | return not IGNORE_GLOBAL_PROPERTIES.any(func(item): return propertyName == item)
164 |
165 | func __SerializeSaveFile(saveFile : SaveFile) -> String:
166 | var result : Dictionary = {
167 | "Version" : saveFile.Version,
168 | "SlotId" : saveFile.SlotId,
169 | "Name" : saveFile.Name,
170 | "DateTime" : saveFile.DateTime,
171 | "LastTime" : saveFile.LastTime,
172 | "GameTime" : saveFile.GameTime,
173 | "Hash" : saveFile.Hash,
174 | "CurrentSceneId" : saveFile.CurrentSceneId,
175 | "MetaData" : saveFile.MetaData,
176 | "GlobalData" : saveFile.GlobalData.map(func(item: SaveFileGlobal): return {"GlobalId": item.GlobalId, "GlobalClassName": item.GlobalClassName, "GlobalData" : item.GlobalData }),
177 | "ScenesData" : saveFile.ScenesData.map(__GetSceneDictionary)
178 | }
179 | return JSON.stringify(result)
180 |
181 | func __DeserializeSaveFile(saveContent : String) -> SaveFile:
182 | var result = SaveFile.new()
183 | var json = JSON.parse_string(saveContent)
184 | result.Version = json["Version"]
185 | result.SlotId = json["SlotId"]
186 | result.Name = json["Name"]
187 | result.DateTime = json["DateTime"]
188 | result.LastTime = json["LastTime"]
189 | result.GameTime = json["GameTime"]
190 | result.Hash = json["Hash"]
191 | result.CurrentSceneId = json["CurrentSceneId"]
192 | result.MetaData = json["MetaData"]
193 | result.GlobalData.append_array(json["GlobalData"].map(
194 | func(item):
195 | var globalResult = SaveFileGlobal.new()
196 | globalResult.GlobalId = item["GlobalId"]
197 | globalResult.GlobalClassName = item["GlobalClassName"]
198 | globalResult.GlobalData = item["GlobalData"]
199 | return globalResult))
200 | result.ScenesData.append_array(__GetScenesData(json["ScenesData"]))
201 | return result
202 |
203 | func __ValidateMetaFile(slotId : String) -> bool:
204 | var metaFilePath = Settings.SaveFilesPrefix + Settings.SaveFolderPath + slotId + ".meta.sav"
205 | var file = FileAccess.open(metaFilePath, FileAccess.READ)
206 | var content = file.get_as_text()
207 | file.close()
208 | var metaFile = __DeserializeSaveFile(content)
209 | var filePath = Settings.SaveFilesPrefix + Settings.SaveFolderPath + slotId + ".sav"
210 | var hash = __GetFileHash(filePath)
211 | return hash == metaFile.Hash
212 |
213 | func __SaveMetaFile(saveFile : SaveFile, filePath : String):
214 | var metaFile = SaveFile.new()
215 | metaFile.SlotId = saveFile.SlotId
216 | metaFile.Name = saveFile.Name
217 | metaFile.DateTime = Time.get_datetime_dict_from_system()
218 | metaFile.LastTime = saveFile.LastTime
219 | metaFile.GameTime = saveFile.GameTime
220 | metaFile.Version = saveFile.Version
221 | metaFile.MetaData = saveFile.MetaData
222 | metaFile.CurrentSceneId = saveFile.CurrentSceneId
223 | var hash = __GetFileHash(filePath)
224 | if hash.is_empty():
225 | return ""
226 | metaFile.Hash = hash
227 | var metaFilePath = filePath.replace(".sav", ".meta.sav")
228 | var file = FileAccess.open(metaFilePath, FileAccess.WRITE)
229 | file.store_string(__SerializeSaveFile(metaFile))
230 | file.close()
231 |
232 | func __GetFileHash(filePath : String) -> String:
233 | var file = FileAccess.open(filePath, FileAccess.READ)
234 | if file == null or not file.is_open():
235 | return ""
236 | var content = file.get_as_text()
237 | file.close()
238 | content = content + Settings.HashSalt
239 | return content.md5_text()
240 |
241 | func __GetDateTime() -> String:
242 | var dateTime = Time.get_datetime_dict_from_system()
243 | return "%d%02d%02d_%02d%02d%02d" % [dateTime["year"], dateTime["month"], dateTime["day"], dateTime["hour"], dateTime["minute"], dateTime["second"]]
244 |
245 | func __EnsureSaveFolder():
246 | if not DirAccess.dir_exists_absolute(Settings.SaveFolderPath):
247 | DirAccess.make_dir_recursive_absolute(Settings.SaveFolderPath)
248 |
249 | func __EnsureSettingsFolder():
250 | if not DirAccess.dir_exists_absolute(SETTINGS_FOLDER_PATH):
251 | DirAccess.make_dir_recursive_absolute(SETTINGS_FOLDER_PATH)
252 |
253 | func __LoadSettings():
254 | Settings = SaveSettigns.new()
255 | if FileAccess.file_exists(SETTINGS_FOLDER_PATH + SETTINGS_FILE_NAME):
256 | var file = FileAccess.open(SETTINGS_FOLDER_PATH + SETTINGS_FILE_NAME, FileAccess.READ)
257 | var content = file.get_as_text()
258 | file.close()
259 | var json = JSON.parse_string(content)
260 | Settings.SaveFilesPrefix = json["SaveFilesPrefix"]
261 | Settings.StaticScriptsToSave.append_array(json["StaticScriptsToSave"])
262 | else:
263 | __CreateSettingsFile()
264 |
265 | func __CreateSettingsFile():
266 | if not FileAccess.file_exists(SETTINGS_FOLDER_PATH + SETTINGS_FILE_NAME):
267 | var file = FileAccess.open(SETTINGS_FOLDER_PATH + SETTINGS_FILE_NAME, FileAccess.WRITE)
268 | var settings = SaveSettigns.new()
269 | var settingsDict = {
270 | "SaveFilesPrefix": settings.SaveFilesPrefix,
271 | "StaticScriptsToSave": settings.StaticScriptsToSave,
272 | "SaveFolderPath": settings.SaveFolderPath,
273 | "HashSalt": settings.HashSalt
274 | }
275 | file.store_string(JSON.stringify(settingsDict))
276 | file.close()
277 |
278 | func __GetSceneDictionary(sceneData : SaveFileScene) -> Dictionary:
279 | return {
280 | "ScenePath": sceneData.ScenePath,
281 | "SceneNodes" : sceneData.SceneNodes.map(__GetNodeDictionary)
282 | }
283 |
284 | func __GetNodeDictionary(sceneNode : SaveFileNode) -> Dictionary:
285 | return {
286 | "NodeTreePath": sceneNode.NodeTreePath,
287 | "ParentTreePath": sceneNode.ParentTreePath,
288 | "NodeFilePath" : sceneNode.NodeFilePath,
289 | "NodeScriptPath" : sceneNode.NodeScriptPath,
290 | "NodeProperties" : sceneNode.NodeProperties
291 | }
292 |
293 | func __GetScenesData(data) -> Array[SaveFileScene]:
294 | var result : Array[SaveFileScene]
295 | result.append_array(data.map(
296 | func(item):
297 | var sceneResult = SaveFileScene.new()
298 | sceneResult.ScenePath = item["ScenePath"]
299 | sceneResult.SceneNodes.append_array(__GetSceneNodes(item["SceneNodes"]))
300 | return sceneResult
301 | ))
302 | return result
303 |
304 | func __GetSceneNodes(data) -> Array[SaveFileNode]:
305 | var result : Array[SaveFileNode]
306 | result.append_array(data.map(
307 | func(item):
308 | var nodeResult = SaveFileNode.new()
309 | nodeResult.NodeTreePath = item["NodeTreePath"]
310 | nodeResult.ParentTreePath = item["ParentTreePath"]
311 | nodeResult.NodeFilePath = item["NodeFilePath"]
312 | nodeResult.NodeScriptPath = item["NodeScriptPath"]
313 | nodeResult.NodeProperties = item["NodeProperties"]
314 | return nodeResult
315 | ))
316 | return result
317 |
--------------------------------------------------------------------------------
/images/SaveEngine.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
269 |
--------------------------------------------------------------------------------