diff --git a/Assets/Scripts/SaveSystem/Providers/FixSnapshotProvider.cs b/Assets/Scripts/SaveSystem/Providers/FixSnapshotProvider.cs new file mode 100644 index 000000000..163a655e5 --- /dev/null +++ b/Assets/Scripts/SaveSystem/Providers/FixSnapshotProvider.cs @@ -0,0 +1,42 @@ +using System.Collections; +using UnityEngine; + +namespace AibisDream.SaveSystem +{ + /// + /// sections["fix"]:Fix 场景宏观状态(FixStateMachine)。 + /// RestoreOrder=65,在 timeline(60) 之后、screen(70) 之前。 + /// + public class FixSnapshotProvider : ISnapshotProvider + { + public string SaveId => SnapshotProviderIds.Fix; + public int RestoreOrder => 65; + + public object Capture() + { + if (FixSystem.FixSystemCenter.Instance == null) + { + Debug.LogWarning("[FixSnapshotProvider] FixSystemCenter 未初始化,跳过捕获。"); + return null; + } + + return FixSystem.FixSystemCenter.Instance.CaptureSnapshot(); + } + + public IEnumerator Restore(object dto) + { + if (dto is not FixSnapshotDto fix) + { + yield break; + } + + if (FixSystem.FixSystemCenter.Instance == null) + { + Debug.LogWarning("[FixSnapshotProvider] FixSystemCenter 未初始化,跳过还原。"); + yield break; + } + + yield return FixSystem.FixSystemCenter.Instance.RestoreSnapshot(fix); + } + } +} diff --git a/Assets/Scripts/SaveSystem/Providers/MacroSnapshotProvider.cs.meta b/Assets/Scripts/SaveSystem/Providers/FixSnapshotProvider.cs.meta similarity index 83% rename from Assets/Scripts/SaveSystem/Providers/MacroSnapshotProvider.cs.meta rename to Assets/Scripts/SaveSystem/Providers/FixSnapshotProvider.cs.meta index d2883a8a3..c196d08f4 100644 --- a/Assets/Scripts/SaveSystem/Providers/MacroSnapshotProvider.cs.meta +++ b/Assets/Scripts/SaveSystem/Providers/FixSnapshotProvider.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: af663fafb5b2a9748902e2e34e1f6be3 +guid: bda963cd11c77cf4bb8d669b0ef6d5f9 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/SaveSystem/Providers/MacroSnapshotProvider.cs b/Assets/Scripts/SaveSystem/Providers/MacroSnapshotProvider.cs deleted file mode 100644 index 1a20cb8a2..000000000 --- a/Assets/Scripts/SaveSystem/Providers/MacroSnapshotProvider.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Collections; -using UnityEngine; - -namespace AibisDream.SaveSystem -{ - /// - /// sections["macro"]:章节 TalkSceneSO 与 YarnProject 绑定。 - /// RestoreOrder=30,在场景与环境就绪后设置 GameManager 并 LoadDialog(不 Start 节点,锚点由 RestoreAnchor 负责)。 - /// - public class MacroSnapshotProvider : ISnapshotProvider - { - public string SaveId => SnapshotProviderIds.Macro; - public int RestoreOrder => 30; - - public object Capture() - { - var gameManager = GameManager.Instance; - var dialog = DialogController.Instance; - var yarnProject = dialog?.DialogueRunner?.YarnProject; - - return new MacroSnapshotDto - { - sceneSoName = gameManager != null ? gameManager.GetSceneSoName() : null, - yarnProjectId = yarnProject != null ? yarnProject.name : null - }; - } - - public IEnumerator Restore(object dto) - { - if (dto is not MacroSnapshotDto macro) - { - yield break; - } - - if (!string.IsNullOrEmpty(macro.sceneSoName)) - { - GameManager.Instance.SetSceneSoByName(macro.sceneSoName); - } - - if (!string.IsNullOrEmpty(macro.yarnProjectId)) - { - var sceneSo = GameManager.Instance.GetCurrentTalkSceneSo(); - if (sceneSo?.yarnProject != null && sceneSo.yarnProject.name == macro.yarnProjectId) - { - DialogController.Instance.LoadDialog(sceneSo.yarnProject); - } - else - { - Debug.LogWarning($"[MacroSnapshotProvider] 无法匹配 YarnProject: {macro.yarnProjectId}"); - } - } - - yield break; - } - } -} diff --git a/Assets/Scripts/SaveSystem/Providers/SceneSnapshotProvider.cs b/Assets/Scripts/SaveSystem/Providers/SceneSnapshotProvider.cs deleted file mode 100644 index ef56fd906..000000000 --- a/Assets/Scripts/SaveSystem/Providers/SceneSnapshotProvider.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections; - -namespace AibisDream.SaveSystem -{ - /// - /// sections["scene"]:当前 Addressable 场景。 - /// RestoreOrder=10,读档时最先加载场景,供后续 env/actor 等依赖。 - /// - public class SceneSnapshotProvider : ISnapshotProvider - { - public string SaveId => SnapshotProviderIds.Scene; - public int RestoreOrder => 10; - - public object Capture() - { - var sceneLoader = SceneLoader.Instance; - return new SceneSnapshotDto - { - sceneName = sceneLoader != null ? sceneLoader.CurrentSceneName : null - }; - } - - public IEnumerator Restore(object dto) - { - if (dto is not SceneSnapshotDto scene || string.IsNullOrEmpty(scene.sceneName)) - { - yield break; - } - - yield return SceneLoader.Instance.LoadSceneAsync(scene.sceneName); - } - } -} diff --git a/Assets/Scripts/SaveSystem/Providers/SceneSnapshotProvider.cs.meta b/Assets/Scripts/SaveSystem/Providers/SceneSnapshotProvider.cs.meta deleted file mode 100644 index 7c5c2fa6d..000000000 --- a/Assets/Scripts/SaveSystem/Providers/SceneSnapshotProvider.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d814b6c56374b4446baa86e09f510098 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/SaveSystem/SaveSnapshot.cs b/Assets/Scripts/SaveSystem/SaveSnapshot.cs index 181859ec3..af381c5e4 100644 --- a/Assets/Scripts/SaveSystem/SaveSnapshot.cs +++ b/Assets/Scripts/SaveSystem/SaveSnapshot.cs @@ -34,7 +34,10 @@ namespace AibisDream.SaveSystem /// 保存时间(本地),格式 yyyy-MM-dd HH:mm:ss public string savedAt; - /// 恢复锚点:读档后从此 Yarn 节点重新进入(所见即所存)。 + /// 当前场景(恢复的第一步)。 + public SceneSnapshotDto scene = new(); + + /// 恢复锚点:章节、YarnProject 与节点,读档最后阶段重新进入。 public AnchorSnapshot anchor = new(); /// 全部 Yarn 运行时变量(来自 )。 @@ -52,11 +55,13 @@ namespace AibisDream.SaveSystem } /// - /// 恢复锚点。读档时在场景与状态还原完成后,从 重新 StartDialogue。 + /// 恢复锚点:包含宏观章节信息,读档最后阶段加载对话工程并重新 StartDialogue。 /// [Serializable] public class AnchorSnapshot { + /// 当前章节 TalkSceneSO 名称,用于 + public string sceneSoName; public string yarnProjectId; public string nodeName; } @@ -79,14 +84,6 @@ namespace AibisDream.SaveSystem public string sceneName; } - /// sections["macro"]:章节与 Yarn 工程绑定。 - [Serializable] - public class MacroSnapshotDto - { - public string sceneSoName; - public string yarnProjectId; - } - /// sections["env"]:环境时间/天气。 [Serializable] public class EnvironmentSnapshotDto @@ -177,5 +174,28 @@ namespace AibisDream.SaveSystem public bool isFadeScreenCovered; } + /// sections["fix"]:Fix 场景宏观状态(FixStateMachine)。 + [Serializable] + public class FixSnapshotDto + { + /// 枚举名。 + public string state; + + /// 状态参数(如 BodyModule 的 ModuleState 字符串)。 + public string args; + + /// BodyModule 当前分组(Body/Head)。 + public string moduleState; + + /// EyeSystem 当前颜色阶段。 + public string eyeColorState; + + /// FixPanelSystem 是否已启动。 + public bool isSystemOn; + + /// RepairSystemManager 当前激活的系统类型名。 + public string currentRepairSystemType; + } + #endregion } diff --git a/Assets/Scripts/SaveSystem/SaveSnapshotSummary.cs b/Assets/Scripts/SaveSystem/SaveSnapshotSummary.cs index 15f1d487e..203dc1961 100644 --- a/Assets/Scripts/SaveSystem/SaveSnapshotSummary.cs +++ b/Assets/Scripts/SaveSystem/SaveSnapshotSummary.cs @@ -1,21 +1,21 @@ namespace AibisDream.SaveSystem { /// - /// 从 现算的宏观摘要(场景 / 章节 SO / YarnProject)。 + /// 从 根对象直接读取的宏观摘要(场景 / 章节 SO / YarnProject)。 /// - /// 替代已移除的顶层 macroStage 冗余字段:sections["scene"] / sections["macro"] 为唯一来源, - /// 槽位 / 「继续游戏」等 UI 在需要展示时调用 派生,避免重复字段与读写漂移。 + /// scene 与 anchor 已提升到根对象,无需再从 sections 字典派生。 + /// 槽位 / 「继续游戏」等 UI 在需要展示时调用 即可。 /// /// public readonly struct SaveSnapshotSummary { - /// Addressable 场景 key,如 Scene/ClinicOut(来自 sections["scene"])。 + /// Addressable 场景 key(来自 )。 public readonly string SceneName; - /// 当前 TalkSceneSO 的 asset 名称(来自 sections["macro"])。 + /// 当前 TalkSceneSO 的 asset 名称(来自 )。 public readonly string SceneSoName; - /// 当前 YarnProject 的 name(来自 sections["macro"],缺省回退到 anchor)。 + /// 当前 YarnProject 的 name(来自 )。 public readonly string YarnProjectId; public SaveSnapshotSummary(string sceneName, string sceneSoName, string yarnProjectId) @@ -25,34 +25,17 @@ namespace AibisDream.SaveSystem YarnProjectId = yarnProjectId; } - /// 从快照派生宏观摘要;兼容 sections 为强类型 DTO 或读盘后的 JObject。 + /// 从快照根对象直接读取宏观摘要。 public static SaveSnapshotSummary From(SaveSnapshot snapshot) { - if (snapshot?.sections == null) + if (snapshot == null) { - return new SaveSnapshotSummary(null, null, snapshot?.anchor?.yarnProjectId); + return new SaveSnapshotSummary(null, null, null); } - string sceneName = null; - if (snapshot.sections.TryGetValue(SnapshotProviderIds.Scene, out var sceneRaw) - && SnapshotSectionDeserializer.Coerce(SnapshotProviderIds.Scene, sceneRaw) is SceneSnapshotDto scene) - { - sceneName = scene.sceneName; - } - - string sceneSoName = null; - string yarnProjectId = null; - if (snapshot.sections.TryGetValue(SnapshotProviderIds.Macro, out var macroRaw) - && SnapshotSectionDeserializer.Coerce(SnapshotProviderIds.Macro, macroRaw) is MacroSnapshotDto macro) - { - sceneSoName = macro.sceneSoName; - yarnProjectId = macro.yarnProjectId; - } - - if (string.IsNullOrEmpty(yarnProjectId)) - { - yarnProjectId = snapshot.anchor?.yarnProjectId; - } + var sceneName = snapshot.scene?.sceneName; + var sceneSoName = snapshot.anchor?.sceneSoName; + var yarnProjectId = snapshot.anchor?.yarnProjectId; return new SaveSnapshotSummary(sceneName, sceneSoName, yarnProjectId); } diff --git a/Assets/Scripts/SaveSystem/SnapshotBootstrap.cs b/Assets/Scripts/SaveSystem/SnapshotBootstrap.cs index 7b337e755..1662efd48 100644 --- a/Assets/Scripts/SaveSystem/SnapshotBootstrap.cs +++ b/Assets/Scripts/SaveSystem/SnapshotBootstrap.cs @@ -14,13 +14,13 @@ if (_initialized) return; _initialized = true; - SnapshotRegistry.Register(new SceneSnapshotProvider()); - SnapshotRegistry.Register(new MacroSnapshotProvider()); SnapshotRegistry.Register(new EnvironmentSnapshotProvider()); SnapshotRegistry.Register(new ActorSnapshotProvider()); SnapshotRegistry.Register(new AudioSnapshotProvider()); SnapshotRegistry.Register(new TimelineSnapshotProvider()); + SnapshotRegistry.Register(new FixSnapshotProvider()); SnapshotRegistry.Register(new ScreenSnapshotProvider()); } + } } diff --git a/Assets/Scripts/SaveSystem/SnapshotCapture.cs b/Assets/Scripts/SaveSystem/SnapshotCapture.cs index 44246a85d..44dd55d4c 100644 --- a/Assets/Scripts/SaveSystem/SnapshotCapture.cs +++ b/Assets/Scripts/SaveSystem/SnapshotCapture.cs @@ -7,8 +7,8 @@ namespace AibisDream.SaveSystem /// /// 从运行时组装 (纯内存,不写盘)。 /// - /// 流程:元数据 → 锚点 → Yarn 变量 → 各 Provider.Capture。 - /// 宏观信息(scene/macro)由对应 Provider 写入 sections,不再额外汇总到顶层。 + /// 流程:元数据 → 场景 → 锚点(含宏观章节)→ Yarn 变量 → 各 Provider.Capture。 + /// scene / anchor 由框架直管;其余子系统通过 Provider 写入 sections。 /// /// public static class SnapshotCapture @@ -24,6 +24,7 @@ namespace AibisDream.SaveSystem savedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }; + CaptureScene(snapshot); CaptureAnchor(snapshot); CaptureYarnVariables(storage, snapshot); @@ -37,6 +38,15 @@ namespace AibisDream.SaveSystem return snapshot; } + private static void CaptureScene(SaveSnapshot snapshot) + { + var sceneLoader = SceneLoader.Instance; + snapshot.scene = new SceneSnapshotDto + { + sceneName = sceneLoader != null ? sceneLoader.CurrentSceneName : null + }; + } + private static void CaptureAnchor(SaveSnapshot snapshot) { var dialog = DialogController.Instance; @@ -46,8 +56,12 @@ namespace AibisDream.SaveSystem var yarnProject = dialog.DialogueRunner?.YarnProject; var projectId = yarnProject != null ? yarnProject.name : string.Empty; + var gameManager = GameManager.Instance; + var sceneSoName = gameManager != null ? gameManager.GetSceneSoName() : null; + snapshot.anchor = new AnchorSnapshot { + sceneSoName = sceneSoName, yarnProjectId = projectId, nodeName = nodeName ?? string.Empty }; diff --git a/Assets/Scripts/SaveSystem/SnapshotProviderIds.cs b/Assets/Scripts/SaveSystem/SnapshotProviderIds.cs index 3c902f458..4173e1172 100644 --- a/Assets/Scripts/SaveSystem/SnapshotProviderIds.cs +++ b/Assets/Scripts/SaveSystem/SnapshotProviderIds.cs @@ -6,21 +6,21 @@ namespace AibisDream.SaveSystem /// public static class SnapshotProviderIds { - public const string Scene = "scene"; - public const string Macro = "macro"; public const string Environment = "env"; public const string Actor = "actor"; public const string Audio = "audio"; public const string Timeline = "timeline"; public const string Screen = "screen"; + public const string Fix = "fix"; /// /// P1 应参与 Capture 的 provider 清单。 /// 据此告警缺失项;不含深度维修。 + /// scene 与 macro 已提升到 根对象,由框架直管。 /// public static readonly string[] RequiredForCapture = { - Scene, Macro, Environment, Actor, Audio, Timeline, Screen + Environment, Actor, Audio, Timeline, Fix, Screen }; } } diff --git a/Assets/Scripts/SaveSystem/SnapshotRestore.cs b/Assets/Scripts/SaveSystem/SnapshotRestore.cs index 89ae1ddea..3eba9c504 100644 --- a/Assets/Scripts/SaveSystem/SnapshotRestore.cs +++ b/Assets/Scripts/SaveSystem/SnapshotRestore.cs @@ -6,13 +6,15 @@ namespace AibisDream.SaveSystem /// /// 将 逐项写回运行时(纯内存,不读盘)。 /// - /// 顺序:Yarn 变量 → 各 Provider(按 RestoreOrder)→(可选)锚点重进节点。 + /// 顺序:Yarn 变量 → 场景加载 → 设置章节 → 各 Provider(按 RestoreOrder)→ 锚点加载对话并重进节点。 /// 完整淡入淡出时序由 P4 在 扩展。 /// /// public static class SnapshotRestore { - /// 还原 Yarn 变量与各 section;不包含锚点重进(由 或上层编排)。 + /// + /// 还原 Yarn 变量、场景、章节与各 section;不包含锚点重进(由 或上层编排)。 + /// public static IEnumerator Restore(YarnVariableStorage storage, SaveSnapshot snapshot) { if (snapshot == null) @@ -22,6 +24,8 @@ namespace AibisDream.SaveSystem } RestoreYarnVariables(storage, snapshot); + yield return RestoreScene(snapshot); + RestoreSceneSo(snapshot); foreach (var provider in SnapshotRegistry.GetOrderedProviders()) { @@ -42,7 +46,7 @@ namespace AibisDream.SaveSystem } /// - /// 按 重新进入 Yarn 节点,实现「所见即所存」。 + /// 按 加载对话工程并重新进入 Yarn 节点,实现「所见即所存」。 /// 调用前应已完成场景加载与各 provider 还原。 /// public static IEnumerator RestoreAnchor(SaveSnapshot snapshot) @@ -58,12 +62,22 @@ namespace AibisDream.SaveSystem var runner = dialog.DialogueRunner; if (runner == null) yield break; - if (!string.IsNullOrEmpty(snapshot.anchor.yarnProjectId) - && runner.YarnProject != null - && runner.YarnProject.name != snapshot.anchor.yarnProjectId) + // 若当前 Runner 未加载对应 YarnProject,则先加载 + if (!string.IsNullOrEmpty(snapshot.anchor.yarnProjectId)) { - Debug.LogWarning( - $"[SnapshotRestore] YarnProject 不匹配:当前 {runner.YarnProject.name},存档 {snapshot.anchor.yarnProjectId}"); + var sceneSo = GameManager.Instance?.GetCurrentTalkSceneSo(); + if (sceneSo?.yarnProject != null && sceneSo.yarnProject.name == snapshot.anchor.yarnProjectId) + { + if (runner.YarnProject == null || runner.YarnProject.name != snapshot.anchor.yarnProjectId) + { + dialog.LoadDialog(sceneSo.yarnProject); + } + } + else if (runner.YarnProject != null && runner.YarnProject.name != snapshot.anchor.yarnProjectId) + { + Debug.LogWarning( + $"[SnapshotRestore] YarnProject 不匹配:当前 {runner.YarnProject.name},存档 {snapshot.anchor.yarnProjectId}"); + } } if (runner.IsDialogueRunning) @@ -81,5 +95,39 @@ namespace AibisDream.SaveSystem storage.SetAllVariables(vars.floats, vars.strings, vars.bools); } + + private static IEnumerator RestoreScene(SaveSnapshot snapshot) + { + if (snapshot.scene == null || string.IsNullOrEmpty(snapshot.scene.sceneName)) + { + yield break; + } + + var sceneLoader = SceneLoader.Instance; + if (sceneLoader == null) + { + Debug.LogError("[SnapshotRestore] SceneLoader 未初始化,无法加载场景"); + yield break; + } + + yield return sceneLoader.LoadSceneAsync(snapshot.scene.sceneName); + } + + private static void RestoreSceneSo(SaveSnapshot snapshot) + { + if (snapshot.anchor == null || string.IsNullOrEmpty(snapshot.anchor.sceneSoName)) + { + return; + } + + var gameManager = GameManager.Instance; + if (gameManager == null) + { + Debug.LogWarning("[SnapshotRestore] GameManager 未初始化,无法设置章节 SO"); + return; + } + + gameManager.SetSceneSoByName(snapshot.anchor.sceneSoName); + } } } diff --git a/Assets/Scripts/SaveSystem/SnapshotSectionDeserializer.cs b/Assets/Scripts/SaveSystem/SnapshotSectionDeserializer.cs index f8f2b76fc..6352d80ae 100644 --- a/Assets/Scripts/SaveSystem/SnapshotSectionDeserializer.cs +++ b/Assets/Scripts/SaveSystem/SnapshotSectionDeserializer.cs @@ -18,12 +18,11 @@ namespace AibisDream.SaveSystem return saveId switch { - SnapshotProviderIds.Scene => jobj.ToObject(), - SnapshotProviderIds.Macro => jobj.ToObject(), SnapshotProviderIds.Environment => jobj.ToObject(), SnapshotProviderIds.Actor => jobj.ToObject(), SnapshotProviderIds.Audio => jobj.ToObject(), SnapshotProviderIds.Timeline => jobj.ToObject(), + SnapshotProviderIds.Fix => jobj.ToObject(), SnapshotProviderIds.Screen => jobj.ToObject(), _ => jobj };