(client) feat:健康给予,路径优化,结算界面,商店界面 (#60)

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
This commit is contained in:
2025-10-10 14:08:23 +08:00
parent 9a797479ff
commit 16b49f3d3a
1900 changed files with 114053 additions and 34157 deletions

View File

@@ -3,56 +3,89 @@ 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初始化后注册自身。
/// 用于标记那些包含可存储数据的单例类。
/// 实现此接口的单例应在SaveManager初始化后注册自身。
/// </summary>
public interface ISavableSingleton
{
}
/// <summary>
/// 用于标记在单例类中需要被SaveManager存储的属性。
/// 用于标记在单例类中需要被SaveManager存储的属性。
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SavableAttribute : Attribute
{
/// <summary>
/// 获取或设置用于存储的唯一键。如果为空,则使用属性名称。
/// </summary>
public string Key { get; }
public SavableAttribute(string key = null)
{
Key = key;
}
/// <summary>
/// 获取或设置用于存储的唯一键。如果为空,则使用属性名称。
/// </summary>
public string Key { get; }
}
public class SaveManager : Utils.Singleton<SaveManager>, ILaunchManager
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 => "加载存档中";
// 存储所有需要保存状态的单例实例
private readonly Dictionary<Type, ISavableSingleton> _savableSingletons = new();
/// <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)
@@ -69,7 +102,7 @@ namespace Managers
}
/// <summary>
/// 移除一个已注册的单例实例。
/// 移除一个已注册的单例实例。
/// </summary>
/// <param name="singleton">要移除的单例实例。</param>
public void UnregisterSavable(ISavableSingleton singleton)
@@ -82,22 +115,7 @@ namespace Managers
}
/// <summary>
/// 实现ILaunchManager接口的Init方法在启动时加载存档
/// </summary>
public void Init()
{
LoadGame(); // 启动时加载存档
}
/// <summary>
/// 实现ILaunchManager接口的Clear方法Clear是重载不做任何事。
/// </summary>
public void Clear()
{
}
/// <summary>
/// 保存游戏数据到文件。
/// 保存游戏数据到文件
/// </summary>
public void SaveGame()
{
@@ -127,10 +145,8 @@ namespace Managers
{
var directoryPath = Path.GetDirectoryName(SaveFilePath);
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
// DEBUG: Debug.Log($"SaveManager: 文件夹 '{directoryPath}' 已创建。");
}
// DEBUG: Debug.Log($"SaveManager: 文件夹 '{directoryPath}' 已创建。");
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
@@ -147,15 +163,13 @@ namespace Managers
}
/// <summary>
/// 从文件加载游戏数据。
/// 从文件加载游戏数据。
/// </summary>
public void LoadGame()
{
if (!File.Exists(SaveFilePath))
{
// DEBUG: Debug.Log($"SaveManager: 未找到存档文件 '{SaveFilePath}'。将使用默认值启动。");
return;
}
try
{
@@ -180,7 +194,6 @@ namespace Managers
{
var key = GetSavableKey(prop, singletonType);
if (savedData.TryGetValue(key, out var jToken))
{
try
{
var value = jToken.ToObject(prop.PropertyType, JsonSerializer.Create(settings));
@@ -191,12 +204,9 @@ namespace Managers
Debug.LogError(
$"SaveManager: 无法反序列化类型 '{singletonType.Name}' 的属性 '{key}' (类型: {prop.PropertyType.Name}): {innerEx.Message}");
}
}
else
{
Debug.LogWarning(
$"SaveManager: 存档文件中未找到类型 '{singletonType.Name}' 的属性 '{key}'。将保留当前值。");
}
}
}
}
@@ -214,7 +224,7 @@ namespace Managers
}
/// <summary>
/// 获取一个类型中所有带有SavableAttribute的公共实例属性。
/// 获取一个类型中所有带有SavableAttribute的公共实例属性。
/// </summary>
private IEnumerable<PropertyInfo> GetSavableProperties(Type type)
{
@@ -223,7 +233,7 @@ namespace Managers
}
/// <summary>
/// 根据属性和其所属类型生成一个唯一的存储键。
/// 根据属性和其所属类型生成一个唯一的存储键。
/// </summary>
private string GetSavableKey(PropertyInfo prop, Type singletonType)
{
@@ -232,14 +242,11 @@ namespace Managers
}
/// <summary>
/// 获取给定类型的默认值。
/// 获取给定类型的默认值。
/// </summary>
private object GetDefaultValue(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
if (type.IsValueType) return Activator.CreateInstance(type);
return null;
}