D5/Autoload/SaveLoadManager.gd
2025-05-09 15:36:04 +08:00

148 lines
5.7 KiB
GDScript

# 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