Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/Managers/SaveManager.cs

261 lines
9.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace Managers
{
/// <summary>
/// 用于标记那些包含可存储数据的单例类。
/// 实现此接口的单例应在SaveManager初始化后注册自身。
/// </summary>
public interface ISavableSingleton
{
}
/// <summary>
/// 用于标记在单例类中需要被SaveManager存储的属性。
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class SavableAttribute : Attribute
{
/// <summary>
/// 获取或设置用于存储的唯一键。如果为空,则使用属性名称。
/// </summary>
public string Key { get; }
public SavableAttribute(string key = null)
{
Key = key;
}
}
public class SaveManager : Utils.Singleton<SaveManager>, ILaunchManager
{
// 存档文件名
private const string SaveFileName = "game_save.json";
// 存档路径
private static string SaveFilePath => Path.Combine(Application.persistentDataPath, SaveFileName);
public string StepDescription => "加载存档中";
// 存储所有需要保存状态的单例实例
private readonly Dictionary<Type, ISavableSingleton> _savableSingletons = new();
/// <summary>
/// 注册一个需要保存状态的单例实例。
/// </summary>
/// <param name="singleton">实现ISavableSingleton接口的单例实例。</param>
public void RegisterSavable(ISavableSingleton singleton)
{
var singletonType = singleton.GetType();
if (_savableSingletons.TryAdd(singletonType, singleton))
{
// DEBUG: Debug.Log($"SaveManager: 已注册可保存单例: {singletonType.Name}");
}
else
{
Debug.LogWarning($"SaveManager: 单例 {singletonType.Name} 已注册。");
}
}
/// <summary>
/// 移除一个已注册的单例实例。
/// </summary>
/// <param name="singleton">要移除的单例实例。</param>
public void UnregisterSavable(ISavableSingleton singleton)
{
var singletonType = singleton.GetType();
if (_savableSingletons.Remove(singletonType))
{
// DEBUG: Debug.Log($"SaveManager: 已移除可保存单例: {singletonType.Name}");
}
}
/// <summary>
/// 实现ILaunchManager接口的Init方法在启动时加载存档。
/// </summary>
public void Init()
{
LoadGame(); // 启动时加载存档
}
/// <summary>
/// 实现ILaunchManager接口的Clear方法清空存档数据。
/// </summary>
public void Clear()
{
try
{
if (File.Exists(SaveFilePath))
{
File.Delete(SaveFilePath);
Debug.Log($"SaveManager: 存档文件 '{SaveFileName}' 已删除。");
}
}
catch (Exception ex)
{
Debug.LogError($"SaveManager: 清空存档数据失败: {ex.Message}");
}
// 清空所有注册单例的Savable属性设置为默认值
foreach (var singletonEntry in _savableSingletons)
{
var singletonType = singletonEntry.Key;
var singletonInstance = singletonEntry.Value;
foreach (var prop in GetSavableProperties(singletonType))
{
prop.SetValue(singletonInstance, GetDefaultValue(prop.PropertyType));
}
}
}
/// <summary>
/// 保存游戏数据到文件。
/// </summary>
public void SaveGame()
{
var dataToSave = new Dictionary<string, JToken>();
foreach (var singletonEntry in _savableSingletons)
{
var singletonType = singletonEntry.Key;
var singletonInstance = singletonEntry.Value;
foreach (var prop in GetSavableProperties(singletonType))
{
var key = GetSavableKey(prop, singletonType);
var value = prop.GetValue(singletonInstance);
try
{
dataToSave[key] = JToken.FromObject(value);
}
catch (Exception ex)
{
Debug.LogError(
$"SaveManager: 无法将属性 '{key}' (类型: {prop.PropertyType.Name}) 转换为 JToken: {ex.Message}");
}
}
}
try
{
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Auto
};
var jsonString = JsonConvert.SerializeObject(dataToSave, settings);
File.WriteAllText(SaveFilePath, jsonString);
// DEBUG: Debug.Log($"SaveManager: 游戏数据已保存到 '{SaveFilePath}'。");
}
catch (Exception ex)
{
Debug.LogError($"SaveManager: 保存游戏数据到 '{SaveFilePath}' 失败: {ex.Message}");
}
}
/// <summary>
/// 从文件加载游戏数据。
/// </summary>
public void LoadGame()
{
if (!File.Exists(SaveFilePath))
{
// DEBUG: Debug.Log($"SaveManager: 未找到存档文件 '{SaveFilePath}'。将使用默认值启动。");
return;
}
try
{
var jsonString = File.ReadAllText(SaveFilePath);
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
};
var savedData = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(jsonString, settings);
if (savedData == null)
{
Debug.LogError("SaveManager: 存档文件为空或已损坏。将使用默认值启动。");
return;
}
foreach (var singletonEntry in _savableSingletons)
{
var singletonType = singletonEntry.Key;
var singletonInstance = singletonEntry.Value;
foreach (var prop in GetSavableProperties(singletonType))
{
var key = GetSavableKey(prop, singletonType);
if (savedData.TryGetValue(key, out var jToken))
{
try
{
var value = jToken.ToObject(prop.PropertyType, JsonSerializer.Create(settings));
prop.SetValue(singletonInstance, value);
}
catch (Exception innerEx)
{
Debug.LogError(
$"SaveManager: 无法反序列化类型 '{singletonType.Name}' 的属性 '{key}' (类型: {prop.PropertyType.Name}): {innerEx.Message}");
}
}
else
{
Debug.LogWarning(
$"SaveManager: 存档文件中未找到类型 '{singletonType.Name}' 的属性 '{key}'。将保留当前值。");
}
}
}
}
catch (JsonException ex)
{
Debug.LogError($"SaveManager: 解析存档文件失败 (JSON 错误): {ex.Message}");
File.Delete(SaveFilePath); // 建议删除损坏的存档文件
Debug.LogWarning("SaveManager: 已删除损坏的存档文件以防止进一步问题。");
}
catch (Exception ex)
{
Debug.LogError(
$"SaveManager: 从 '{SaveFilePath}' 加载游戏数据失败 (文件 I/O 或其他错误): {ex.Message}");
}
}
/// <summary>
/// 获取一个类型中所有带有SavableAttribute的公共实例属性。
/// </summary>
private IEnumerable<PropertyInfo> GetSavableProperties(Type type)
{
return type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.CanRead && prop.CanWrite && prop.IsDefined(typeof(SavableAttribute), true));
}
/// <summary>
/// 根据属性和其所属类型生成一个唯一的存储键。
/// </summary>
private string GetSavableKey(PropertyInfo prop, Type singletonType)
{
var savableAttr = prop.GetCustomAttribute<SavableAttribute>();
return savableAttr?.Key ?? $"{singletonType.Name}.{prop.Name}";
}
/// <summary>
/// 获取给定类型的默认值。
/// </summary>
private object GetDefaultValue(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
}
}