mirror of
http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite.git
synced 2025-11-20 00:57:14 +08:00
Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com> Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/60
255 lines
9.9 KiB
C#
255 lines
9.9 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|