godot-save-load-systems

安装量: 82
排名: #9616

安装

npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-save-load-systems
Save/Load Systems
JSON serialization, version migration, and PERSIST group patterns define robust data persistence.
Available Scripts
save_migration_manager.gd
Expert save file versioning with automatic migration between schema versions.
save_system_encryption.gd
AES-256 encrypted saves with compression to prevent casual save editing.
MANDATORY - For Production
Read save_migration_manager.gd before shipping to handle schema changes. NEVER Do in Save Systems NEVER save without version field — Game updates, old saves break. MUST include "version": "1.0.0" + migration logic for schema changes. NEVER use absolute paths — FileAccess.open("C:/Users/...") breaks on other machines. Use user:// protocol (maps to OS-specific app data folder). NEVER save Node references — save_data["player"] = $Player ? Nodes aren't serializable. Extract data via player.save_data() method instead. NEVER forget to close FileAccess — var file = FileAccess.open(...) without .close() ? File handle leak = save corruption. Use close() OR GDScript auto-close on scope exit. NEVER use JSON for large binary data — 10MB texture as base64 in JSON? Massive file size + slow parse. Use binary format ( store_var ) OR separate asset files. NEVER trust loaded data — Save file edited by user? data.get("health", 100) prevents crash if field missing. VALIDATE all loaded values. NEVER save during physics/animation frames — _physics_process trigger save? File corruption if game crashes mid-write. Save ONLY on explicit events (level complete, menu). Pattern 1: JSON Save System (Recommended for Most Games) Step 1: Create SaveManager AutoLoad

save_manager.gd

extends Node const SAVE_PATH := "user://savegame.save"

Save data to JSON file

func save_game ( data : Dictionary ) -> void : var save_file := FileAccess . open ( SAVE_PATH , FileAccess . WRITE ) if save_file == null : push_error ( "Failed to open save file: " + str ( FileAccess . get_open_error ( ) ) ) return var json_string := JSON . stringify ( data , "\t" )

Pretty print

save_file . store_line ( json_string ) save_file . close ( ) print ( "Game saved successfully" )

Load data from JSON file

func load_game ( ) -> Dictionary : if not FileAccess . file_exists ( SAVE_PATH ) : push_warning ( "Save file does not exist" ) return { } var save_file := FileAccess . open ( SAVE_PATH , FileAccess . READ ) if save_file == null : push_error ( "Failed to open save file: " + str ( FileAccess . get_open_error ( ) ) ) return { } var json_string := save_file . get_as_text ( ) save_file . close ( ) var json := JSON . new ( ) var parse_result := json . parse ( json_string ) if parse_result != OK : push_error ( "JSON Parse Error: " + json . get_error_message ( ) ) return { } return json . data as Dictionary

Delete save file

func delete_save ( ) -> void : if FileAccess . file_exists ( SAVE_PATH ) : DirAccess . remove_absolute ( SAVE_PATH ) print ( "Save file deleted" ) Step 2: Save Player Data

player.gd

extends CharacterBody2D var health : int = 100 var score : int = 0 var level : int = 1 func save_data ( ) -> Dictionary : return { "health" : health , "score" : score , "level" : level , "position" : { "x" : global_position . x , "y" : global_position . y } } func load_data ( data : Dictionary ) -> void : health = data . get ( "health" , 100 ) score = data . get ( "score" , 0 ) level = data . get ( "level" , 1 ) if data . has ( "position" ) : global_position = Vector2 ( data . position . x , data . position . y ) Step 3: Trigger Save/Load

game_manager.gd

extends Node func save_game_state ( ) -> void : var save_data := { "player" : $Player . save_data ( ) , "timestamp" : Time . get_unix_time_from_system ( ) , "version" : "1.0.0" } SaveManager . save_game ( save_data ) func load_game_state ( ) -> void : var data := SaveManager . load_game ( ) if data . is_empty ( ) : print ( "No save data found, starting new game" ) return if data . has ( "player" ) : $Player . load_data ( data . player ) Pattern 2: Binary Save System (Advanced, Faster) For large save files or when human-readability isn't needed: const SAVE_PATH := "user://savegame.dat" func save_game_binary ( data : Dictionary ) -> void : var save_file := FileAccess . open ( SAVE_PATH , FileAccess . WRITE ) if save_file == null : return save_file . store_var ( data , true )

true = full objects

save_file . close ( ) func load_game_binary ( ) -> Dictionary : if not FileAccess . file_exists ( SAVE_PATH ) : return { } var save_file := FileAccess . open ( SAVE_PATH , FileAccess . READ ) if save_file == null : return { } var data : Dictionary = save_file . get_var ( true ) save_file . close ( ) return data Pattern 3: PERSIST Group Pattern For auto-saving nodes with the persist group:

Add nodes to "persist" group in editor or via code:

add_to_group ( "persist" )

Implement save/load in each persistent node:

func save ( ) -> Dictionary : return { "filename" : get_scene_file_path ( ) , "parent" : get_parent ( ) . get_path ( ) , "pos_x" : position . x , "pos_y" : position . y ,

... other data

} func load ( data : Dictionary ) -> void : position = Vector2 ( data . pos_x , data . pos_y )

... load other data

SaveManager collects all persist nodes:

func save_all_persist_nodes ( ) -> void : var save_nodes := get_tree ( ) . get_nodes_in_group ( "persist" ) var save_dict := { } for node in save_nodes : if not node . has_method ( "save" ) : continue save_dict [ node . name ] = node . save ( ) save_game ( save_dict ) Best Practices 1. Use user:// Protocol

✅ Good - platform-independent

const SAVE_PATH := "user://savegame.save"

❌ Bad - hardcoded path

const SAVE_PATH := "C:/Users/Player/savegame.save" user:// paths: Windows : %APPDATA%\Godot\app_userdata[project_name] macOS : ~/Library/Application Support/Godot/app_userdata/[project_name] Linux : ~/.local/share/godot/app_userdata/[project_name] 2. Version Your Save Format const SAVE_VERSION := "1.0.0" func save_game ( data : Dictionary ) -> void : data [ "version" ] = SAVE_VERSION

... save logic

func load_game ( ) -> Dictionary : var data :=

... load logic

if data . get ( "version" ) != SAVE_VERSION : push_warning ( "Save version mismatch, migrating..." ) data = migrate_save_data ( data ) return data 3. Handle Errors Gracefully func save_game ( data : Dictionary ) -> bool : var save_file := FileAccess . open ( SAVE_PATH , FileAccess . WRITE ) if save_file == null : var error := FileAccess . get_open_error ( ) push_error ( "Save failed: " + error_string ( error ) ) return false save_file . store_line ( JSON . stringify ( data ) ) save_file . close ( ) return true 4. Auto-Save Pattern var auto_save_timer : Timer func _ready ( ) -> void :

Auto-save every 5 minutes

auto_save_timer

Timer
.
new
(
)
add_child
(
auto_save_timer
)
auto_save_timer
.
wait_time
=
300.0
auto_save_timer
.
timeout
.
connect
(
_on_auto_save
)
auto_save_timer
.
start
(
)
func
_on_auto_save
(
)
->
void
:
save_game_state
(
)
print
(
"Auto-saved"
)
Testing Save Systems
func
_ready
(
)
->
void
:
if
OS
.
is_debug_build
(
)
:
test_save_load
(
)
func
test_save_load
(
)
->
void
:
var
test_data
:=
{
"test_key"
:
"test_value"
,
"number"
:
42
}
save_game
(
test_data
)
var
loaded
:=
load_game
(
)
assert
(
loaded
.
test_key
==
"test_value"
)
assert
(
loaded
.
number
==
42
)
print
(
"Save/Load test passed"
)
Common Gotchas
Issue
Saved Vector2/Vector3 not loading correctly

✅ Solution: Store as x, y, z components

"position" : { "x" : pos . x , "y" : pos . y }

Then reconstruct:

position

Vector2
(
data
.
position
.
x
,
data
.
position
.
y
)
Issue
Resource paths not resolving

✅ Store resource paths as strings

"texture_path" : texture . resource_path

Then reload:

texture

load ( data . texture_path ) Reference Godot Docs: Saving Games Godot Docs: File System Related Master Skill: godot-master

返回排行榜