# SaveLoadManager.gd # Autoload Singleton # 负责处理游戏的保存和加载流程。 # 与 GameState 交互以获取/恢复状态数据。 # 将游戏状态以 JSON 格式存储在 user:// 目录下。 extends Node # --- 常量 --- const SAVE_FILENAME_PREFIX = "save_" const SAVE_FILENAME_EXTENSION = ".json" # --- 核心方法 --- # 保存当前游戏状态到指定的槽位 # slot_id: 整数,用于区分不同的存档文件 (例如 1, 2, 3) # 返回: bool - 保存是否成功 func save_game(slot_id: int) -> bool: print("SaveLoadManager: Attempting to save game to slot", slot_id) # 1. 获取需要保存的数据 if not GameState: printerr("SaveLoadManager: GameState Autoload not found!") return false var game_state_data: Dictionary = GameState.get_savable_state() if game_state_data.is_empty() and OS.is_debug_build(): # 在调试模式下,如果保存的数据为空,可能是一个警告信号 print("SaveLoadManager: Warning - Game state data to save is empty.") # 根据游戏逻辑,这里可能允许空状态保存,也可能返回 false # return false # 2. 准备完整的存档结构 (包含元数据) var save_data = { "metadata": { # 使用 ISO 8601 标准格式记录时间,更通用 "save_time_iso": Time.get_datetime_string_from_system(true, true), "game_version": ProjectSettings.get_setting("application/config/version", "unknown") # 获取项目版本 # 可以添加其他元数据,如游戏内日期、截图路径等 }, "game_state": game_state_data } # 3. 序列化为 JSON 字符串 # 使用制表符缩进,提高可读性,发布版可考虑移除缩进减小体积 var json_string = JSON.stringify(save_data, "\t") if json_string.is_empty() and not save_data.is_empty(): printerr("SaveLoadManager: Failed to serialize game data to JSON.") return false # 4. 获取存档文件路径 var save_path = _get_save_path(slot_id) if save_path.is_empty(): printerr("SaveLoadManager: Invalid slot_id provided:", slot_id) return false # 5. 写入文件 # 使用 FileAccess.open 进行文件操作 var file = FileAccess.open(save_path, FileAccess.WRITE) if file == null: # 获取文件操作错误信息 var error_code = FileAccess.get_open_error() printerr("SaveLoadManager: Failed to open save file for writing at '", save_path, "'. Error code:", error_code) return false # 写入 JSON 字符串 file.store_string(json_string) # file.close() # FileAccess 会在变量超出作用域时自动关闭,但显式关闭是好习惯 print("SaveLoadManager: Game successfully saved to '", save_path, "'") return true # 从指定的槽位加载游戏状态 # slot_id: 整数,要加载的存档文件槽位 # 返回: Variant - 成功时返回 game_state 字典,失败时返回 null func load_game(slot_id: int) -> Variant: # <--- 修改返回类型为 Variant print("SaveLoadManager: Attempting to load game data from slot", slot_id) # 1. 获取存档文件路径 (不变) var save_path = _get_save_path(slot_id) if save_path.is_empty(): printerr("SaveLoadManager: Invalid slot_id provided:", slot_id) return null # <--- 失败返回 null # 2. 检查文件是否存在 (不变) if not FileAccess.file_exists(save_path): printerr("SaveLoadManager: Save file not found at '", save_path, "'") return null # <--- 失败返回 null # 3. 读取文件内容 (不变) var file = FileAccess.open(save_path, FileAccess.READ) if file == null: var error_code = FileAccess.get_open_error() printerr("SaveLoadManager: Failed to open save file for reading at '", save_path, "'. Error code:", error_code) return null # <--- 失败返回 null var json_string = file.get_as_text() if json_string.is_empty(): printerr("SaveLoadManager: Save file is empty or failed to read content from '", save_path, "'") return null # <--- 失败返回 null # 4. 解析 JSON 字符串 (不变) var parse_result = JSON.parse_string(json_string) if parse_result == null: printerr("SaveLoadManager: Failed to parse JSON from save file '", save_path, "'. File might be corrupted.") return null # <--- 失败返回 null if not parse_result is Dictionary: printerr("SaveLoadManager: Parsed JSON data is not a Dictionary in '", save_path, "'. Invalid format.") return null # <--- 失败返回 null var loaded_data: Dictionary = parse_result # 5. 验证存档结构并提取游戏状态 (不变) if not loaded_data.has("game_state"): printerr("SaveLoadManager: Save file '", save_path, "' is missing the 'game_state' key. Invalid format.") return null # <--- 失败返回 null var game_state_data = loaded_data["game_state"] if not game_state_data is Dictionary: printerr("SaveLoadManager: 'game_state' data in '", save_path, "' is not a Dictionary. Invalid format.") return null # <--- 失败返回 null print("SaveLoadManager: Game data successfully loaded from '", save_path, "'") # 7. 返回提取到的 game_state 数据字典 return game_state_data # <--- 成功时返回字典 # 检查指定槽位的存档文件是否存在 func does_save_exist(slot_id: int) -> bool: var save_path = _get_save_path(slot_id) if save_path.is_empty(): return false return FileAccess.file_exists(save_path) # --- 辅助方法 --- # 根据槽位 ID 构建完整的存档文件路径 # 返回: String - 完整的 user:// 路径,如果 slot_id 无效则返回空字符串 func _get_save_path(slot_id: int) -> String: # 基础验证,确保 slot_id 是正整数 if slot_id <= 0: printerr("SaveLoadManager: Invalid slot_id:", slot_id, ". Must be a positive integer.") return "" var filename = SAVE_FILENAME_PREFIX + str(slot_id) + SAVE_FILENAME_EXTENSION # "user://" 是 Godot 指向用户数据目录的特殊路径 return "user://" + filename