feat(save-system): 新增维修与场景子模块快照 DTO 与 Provider

This commit is contained in:
2026-06-24 22:42:56 +08:00
parent 5cae227fec
commit 3746f4aeb6
16 changed files with 425 additions and 24 deletions
@@ -0,0 +1,13 @@
namespace AibisDream.SaveSystem
{
/// <summary>
/// Fix 维修场景门控:fix / punchTape / bodyModule / eye 等 section 仅在维修场景 Capture / Restore。
/// </summary>
public static class FixSceneSnapshotHelper
{
public static bool IsFixSceneActive()
{
return FixSystem.FixSystemCenter.Instance != null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76df7c860ec032240a7278a7767154b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,54 @@
using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// sections["bodyModule"]:插线模块持久物理状态。RestoreOrder=66,在 fix 之后。
/// </summary>
public class BodyModuleSnapshotProvider : ISyncSnapshotProvider
{
public string SaveId => SnapshotProviderIds.BodyModule;
public int RestoreOrder => 67;
public object Capture()
{
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
return null;
}
var bodyModule = FixSystem.FixSystemCenter.SystemDic.Get<FixSystem.BodyModuleSystem>();
if (bodyModule == null)
{
Debug.LogWarning("[BodyModuleSnapshotProvider] BodyModuleSystem 未初始化,跳过捕获。");
return null;
}
return bodyModule.CaptureBodyModuleSnapshot();
}
public void Restore(object dto, SnapshotRestoreContext context)
{
if (dto is not BodyModuleSnapshotDto bodyModuleDto)
{
context.Warn("[BodyModuleSnapshotProvider] DTO 类型不匹配,跳过还原。");
return;
}
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
context.Log("[BodyModuleSnapshotProvider] 当前非维修场景,跳过还原。");
return;
}
var bodyModule = FixSystem.FixSystemCenter.SystemDic.Get<FixSystem.BodyModuleSystem>();
if (bodyModule == null)
{
context.Warn("[BodyModuleSnapshotProvider] BodyModuleSystem 未初始化,跳过还原。");
return;
}
bodyModule.ApplyBodyModuleSnapshot(bodyModuleDto);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9b525cdc20474840b754d942b7fb1cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,55 @@
using AibisDream;
using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// sections["eye"]Eye 维修叙事阶段。RestoreOrder=67,在 bodyModule 之后。
/// </summary>
public class EyeSnapshotProvider : ISyncSnapshotProvider
{
public string SaveId => SnapshotProviderIds.Eye;
public int RestoreOrder => 68;
public object Capture()
{
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
return null;
}
var eyeSystem = FixSystem.FixSystemCenter.SystemDic.Get<EyeSystem>();
if (eyeSystem == null)
{
Debug.LogWarning("[EyeSnapshotProvider] EyeSystem 未初始化,跳过捕获。");
return null;
}
return eyeSystem.CaptureEyeSnapshot();
}
public void Restore(object dto, SnapshotRestoreContext context)
{
if (dto is not EyeSnapshotDto eyeDto)
{
context.Warn("[EyeSnapshotProvider] DTO 类型不匹配,跳过还原。");
return;
}
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
context.Log("[EyeSnapshotProvider] 当前非维修场景,跳过还原。");
return;
}
var eyeSystem = FixSystem.FixSystemCenter.SystemDic.Get<EyeSystem>();
if (eyeSystem == null)
{
context.Warn("[EyeSnapshotProvider] EyeSystem 未初始化,跳过还原。");
return;
}
eyeSystem.ApplyEyeSnapshot(eyeDto);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3207faffc75fae44d802cb9af0d73dcf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,52 @@
using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// sections["fixPanel"]FixPanel 壳层 + 线缆/插头。RestoreOrder=66,在 fix 之后、bodyModule 之前。
/// </summary>
public class FixPanelSnapshotProvider : ISyncSnapshotProvider
{
public string SaveId => SnapshotProviderIds.FixPanel;
public int RestoreOrder => 66;
public object Capture()
{
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
return null;
}
if (FixSystem.FixPanelSystem.Instance == null)
{
Debug.LogWarning("[FixPanelSnapshotProvider] FixPanelSystem 未初始化,跳过捕获。");
return null;
}
return FixSystem.FixPanelSystem.Instance.CapturePanelSnapshot();
}
public void Restore(object dto, SnapshotRestoreContext context)
{
if (dto is not FixPanelSnapshotDto panelDto)
{
context.Warn("[FixPanelSnapshotProvider] DTO 类型不匹配,跳过还原。");
return;
}
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
context.Log("[FixPanelSnapshotProvider] 当前非维修场景,跳过还原。");
return;
}
if (FixSystem.FixPanelSystem.Instance == null)
{
context.Warn("[FixPanelSnapshotProvider] FixPanelSystem 未初始化,跳过还原。");
return;
}
FixSystem.FixPanelSystem.Instance.ApplyPanelSnapshot(panelDto);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 479311b005ae10a45a963b7826cf2da7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -14,6 +14,11 @@ namespace AibisDream.SaveSystem
public object Capture()
{
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
return null;
}
if (FixSystem.FixSystemCenter.Instance == null)
{
Debug.LogWarning("[FixSnapshotProvider] FixSystemCenter 未初始化,跳过捕获。");
@@ -31,6 +36,12 @@ namespace AibisDream.SaveSystem
yield break;
}
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
context.Log("[FixSnapshotProvider] 当前非维修场景,跳过还原。");
yield break;
}
if (FixSystem.FixSystemCenter.Instance == null)
{
context.Warn("[FixSnapshotProvider] FixSystemCenter 未初始化,跳过还原。");
@@ -0,0 +1,52 @@
using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// sections["punchTape"]:打孔带收藏列表。RestoreOrder=63,在 fix 之前。
/// </summary>
public class PunchTapeSnapshotProvider : ISyncSnapshotProvider
{
public string SaveId => SnapshotProviderIds.PunchTape;
public int RestoreOrder => 63;
public object Capture()
{
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
return null;
}
if (FixSystem.PunchTapeManager.Instance == null)
{
Debug.LogWarning("[PunchTapeSnapshotProvider] PunchTapeManager 未初始化,跳过捕获。");
return null;
}
return FixSystem.PunchTapeManager.Instance.CapturePunchTapeSnapshot();
}
public void Restore(object dto, SnapshotRestoreContext context)
{
if (dto is not PunchTapeSnapshotDto punchTape)
{
context.Warn("[PunchTapeSnapshotProvider] DTO 类型不匹配,跳过还原。");
return;
}
if (!FixSceneSnapshotHelper.IsFixSceneActive())
{
context.Log("[PunchTapeSnapshotProvider] 当前非维修场景,跳过还原。");
return;
}
if (FixSystem.PunchTapeManager.Instance == null)
{
context.Warn("[PunchTapeSnapshotProvider] PunchTapeManager 未初始化,跳过还原。");
return;
}
FixSystem.PunchTapeManager.Instance.ApplyPunchTapeSnapshot(punchTape);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b81daacbefa4094b85fa03aaf2202c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,43 @@
using System.Collections;
using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// sections["subway"]:地铁场景宏观状态(SubwayCenter)。
/// RestoreOrder=64,在 timeline(60) 之后、fix(65) 之前。
/// </summary>
public class SubwaySnapshotProvider : IAsyncSnapshotProvider
{
public string SaveId => SnapshotProviderIds.Subway;
public int RestoreOrder => 64;
public object Capture()
{
if (SubwayCenter.Instance == null)
{
Debug.LogWarning("[SubwaySnapshotProvider] SubwayCenter 未初始化,跳过捕获。");
return null;
}
return SubwayCenter.Instance.CaptureSnapshot();
}
public IEnumerator RestoreAsync(object dto, SnapshotRestoreContext context)
{
if (dto is not SubwaySnapshotDto subway)
{
context.Warn("[SubwaySnapshotProvider] DTO 类型不匹配,跳过还原。");
yield break;
}
if (SubwayCenter.Instance == null)
{
context.Warn("[SubwaySnapshotProvider] SubwayCenter 未初始化,跳过还原。");
yield break;
}
yield return SubwayCenter.Instance.RestoreSnapshot(subway);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4e8f1a23b5d6e7094ac8210d3f7b2e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+63 -17
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using AibisDream.FixSystem;
namespace AibisDream.SaveSystem
{
@@ -15,7 +16,7 @@ namespace AibisDream.SaveSystem
/// 存档快照根对象:描述「进入可存节点那一刻」的完整游戏状态。
/// <para>
/// 与槽位/文件路径解耦;序列化后写入 JSON。对应需求§3 四分类:
/// anchor(恢复锚点)、sections宏观 scene/macro + 表现类)、yarnVariables、deepRepair(深度维修,P5
/// anchor(恢复锚点)、sections表现类 + 维修子模块)、yarnVariables
/// </para>
/// <para>
/// 宏观阶段(场景 / 章节 SO / YarnProject)不单独冗余存储,统一以 sections["scene"] / sections["macro"]
@@ -44,14 +45,10 @@ namespace AibisDream.SaveSystem
public YarnVariablesSnapshot yarnVariables = new();
/// <summary>
/// 各子系统表现类快照,key 为 <see cref="SnapshotProviderIds"/> 中的稳定 id。
/// 各子系统快照,key 为 <see cref="SnapshotProviderIds"/> 中的稳定 id。
/// 表现层与维修模块(含 BlockPuzzle / Memory 等)均在此注册,按 RestoreOrder 还原。
/// </summary>
public Dictionary<string, object> sections = new();
/// <summary>
/// 深度维修段。P1 捕获时不写入(null);P5 按模块策略填充。
/// </summary>
public DeepRepairSnapshot deepRepair;
}
/// <summary>
@@ -112,8 +109,13 @@ namespace AibisDream.SaveSystem
public string slotName;
public string actorType;
public string stateName;
/// <summary>AnimaAnimator layer 0 当前状态的 normalizedTime。</summary>
public float stateNormalizedTime;
public float alpha;
public float faceParam;
/// <summary>AnimaEx:最后一次 set_actor_face 的表情名。</summary>
public string faceName;
/// <summary>AnimaExAnimator "Is Talking" 参数。</summary>
public bool isTalking;
}
/// <summary>sections["audio"]FMOD 音乐/SFX 终态与 AMB 状态(不记播放进度)。</summary>
@@ -174,7 +176,21 @@ namespace AibisDream.SaveSystem
public bool isFadeScreenCovered;
}
/// <summary>sections["fix"]Fix 场景宏观模式(FixSceneDirector)。</summary>
/// <summary>sections["subway"]:地铁场景宏观状态(SubwayCenter / OutSideState)。</summary>
[Serializable]
public class SubwaySnapshotDto
{
/// <summary><see cref="OutSideState"/> 枚举名。</summary>
public string state;
/// <summary>状态参数(预留,当前各 State 未使用)。</summary>
public string args;
/// <summary>当前新闻图片 Addressable id<c>Sprite/News[{id}]</c> 中的 id)。</summary>
public string newsSpriteId;
}
/// <summary>sections["fix"]Fix 场景宏观 CueFixSceneDirector),不含面板壳层。</summary>
[Serializable]
public class FixSnapshotDto
{
@@ -183,18 +199,48 @@ namespace AibisDream.SaveSystem
/// <summary>状态参数(如 BodyModule 的 ModuleState 字符串)。</summary>
public string args;
}
/// <summary>BodyModule 当前分组(Body/Head。</summary>
public string moduleState;
/// <summary>EyeSystem 当前颜色阶段。</summary>
public string eyeColorState;
/// <summary>FixPanelSystem 是否已启动。</summary>
/// <summary>sections["fixPanel"]FixPanel 壳层 + 线缆/插头状态。</summary>
[Serializable]
public class FixPanelSnapshotDto
{
public bool isSystemOn;
/// <summary>RepairSystemManager 当前激活系统类型名。</summary>
/// <summary>RepairSystemManager 当前激活系统类型名;空则默认 BodyModuleSystem。</summary>
public string currentRepairSystemType;
public bool isCableRetracted;
/// <summary>插头插入的 BodyModule 名(data.moduleName);null/空 = 未插入。</summary>
public string pluggedModuleName;
}
/// <summary>sections["bodyModule"]:插线模块持久物理状态。</summary>
[Serializable]
public class BodyModuleSnapshotDto
{
/// <summary><see cref="ModuleState"/> 枚举名(Body / Head)。</summary>
public string moduleState;
public bool isUFCoverRemoved;
public bool isUFRemoved;
public bool isColorRemoved;
public bool isChipRemoved;
}
/// <summary>sections["eye"]Eye 维修叙事阶段。</summary>
[Serializable]
public class EyeSnapshotDto
{
/// <summary>Eye 颜色阶段枚举名。</summary>
public string eyeColorState;
}
/// <summary>sections["punchTape"]:打孔带收藏列表。</summary>
[Serializable]
public class PunchTapeSnapshotDto
{
public List<PunchTape> favorites = new();
}
#endregion
+11 -1
View File
@@ -15,7 +15,12 @@ namespace AibisDream.SaveSystem
public const string Audio = "audio";
public const string Timeline = "timeline";
public const string Screen = "screen";
public const string Subway = "subway";
public const string PunchTape = "punchTape";
public const string Fix = "fix";
public const string FixPanel = "fixPanel";
public const string BodyModule = "bodyModule";
public const string Eye = "eye";
/// <summary>
/// P1 应参与 Capture 的 provider 清单。
@@ -24,7 +29,7 @@ namespace AibisDream.SaveSystem
/// </summary>
public static readonly string[] RequiredForCapture =
{
Environment, Actor, Audio, Timeline, Fix, Screen
Environment, Actor, Audio, Timeline, Subway, Fix, Screen
};
}
@@ -48,7 +53,12 @@ namespace AibisDream.SaveSystem
Register(new ActorSnapshotProvider());
Register(new AudioSnapshotProvider());
Register(new TimelineSnapshotProvider());
Register(new SubwaySnapshotProvider());
Register(new PunchTapeSnapshotProvider());
Register(new FixSnapshotProvider());
Register(new FixPanelSnapshotProvider());
Register(new BodyModuleSnapshotProvider());
Register(new EyeSnapshotProvider());
Register(new ScreenSnapshotProvider());
}
+5 -6
View File
@@ -36,12 +36,6 @@ namespace AibisDream.SaveSystem
context.Log("Phase 2: Restore providers (ordered by RestoreOrder)");
yield return RestoreProvidersInOrder(snapshot, context);
if (snapshot.deepRepair != null && snapshot.deepRepair.sections != null
&& snapshot.deepRepair.sections.Count > 0)
{
context.Warn("DeepRepair 段尚未实现,已跳过(P5)。");
}
}
/// <summary>
@@ -209,7 +203,12 @@ namespace AibisDream.SaveSystem
SnapshotProviderIds.Actor => jobj.ToObject<ActorSnapshotDto>(),
SnapshotProviderIds.Audio => jobj.ToObject<AudioSnapshotDto>(),
SnapshotProviderIds.Timeline => jobj.ToObject<TimelineSnapshotDto>(),
SnapshotProviderIds.Subway => jobj.ToObject<SubwaySnapshotDto>(),
SnapshotProviderIds.PunchTape => jobj.ToObject<PunchTapeSnapshotDto>(),
SnapshotProviderIds.Fix => jobj.ToObject<FixSnapshotDto>(),
SnapshotProviderIds.FixPanel => jobj.ToObject<FixPanelSnapshotDto>(),
SnapshotProviderIds.BodyModule => jobj.ToObject<BodyModuleSnapshotDto>(),
SnapshotProviderIds.Eye => jobj.ToObject<EyeSnapshotDto>(),
SnapshotProviderIds.Screen => jobj.ToObject<ScreenSnapshotDto>(),
_ => jobj
};