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
};