Files

255 lines
9.9 KiB
C#
Raw Permalink 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 System.Threading.Tasks; // 引入 Task for async/await
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using Utils;
namespace Managers
{
/// <summary>
/// 用于标记那些包含可存储数据的单例类。
/// 实现此接口的单例应在SaveManager初始化后注册自身。
/// </summary>
public interface ISavableSingleton
{
}
/// <summary>
/// 用于标记在单例类中需要被SaveManager存储的属性。
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SavableAttribute : Attribute
{
public SavableAttribute(string key = null)
{
Key = key;
}
/// <summary>
/// 获取或设置用于存储的唯一键。如果为空,则使用属性名称。
/// </summary>
public string Key { get; }
}
public class SaveManager : Singleton<SaveManager>, ILaunchManager
{
// 存档文件名
private const string SaveFileName = "game_save.json";
// 存储所有需要保存状态的单例实例
private readonly Dictionary<Type, ISavableSingleton> _savableSingletons = new();
// 存档路径
private static string SaveFilePath => Path.Combine("Save", SaveFileName);
/// <summary>
/// 指示管理器是否已完成初始化。
/// </summary>
public bool Completed { get; set; } // 新增:实现 ILaunchManager 接口的 Completed 属性
public string StepDescription => "加载存档中";
/// <summary>
/// 实现ILaunchManager接口的Init方法在启动时加载存档。
/// </summary>
/// <returns>一个表示异步操作完成的 Task。</returns>
public async Task Init() // 接口变更:方法签名变为 async Task
{
// 如果已经完成初始化,则直接返回,避免重复执行
if (Completed) return;
LoadGame(); // 启动时加载存档
Completed = true; // 在加载逻辑完成后设置 Completed 为 true
// 由于 Init 方法内部当前没有真正的异步操作,不需要显式 await Task.CompletedTask;
// 编译器会为同步的 async Task 方法自动生成一个已完成的 Task。
}
/// <summary>
/// 实现ILaunchManager接口的Clear方法Clear是重载不做任何事。
/// 根据用户指示,此方法不应改变管理器自身的 Completed 状态,且不执行任何清理操作。
/// </summary>
public void Clear()
{
// 按照用户指示,此方法为空实现,不影响 SaveManager 的完成状态或数据。
}
~SaveManager()
{
SaveGame(); // 在销毁时保存游戏
}
/// <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>
/// 保存游戏数据到文件。
/// </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 directoryPath = Path.GetDirectoryName(SaveFilePath);
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
Directory.CreateDirectory(directoryPath);
// DEBUG: Debug.Log($"SaveManager: 文件夹 '{directoryPath}' 已创建。");
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;
}
}
}