Files
aibis-dream/Assets/Scripts/SaveSystem/SaveSnapshot.cs
T

289 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using AibisDream.FixSystem;
namespace AibisDream.SaveSystem
{
/// <summary>
/// 当前快照 schema 的版本号。结构变更时递增。
/// </summary>
public static class SaveSnapshotSchema
{
public const int CurrentVersion = 1;
}
/// <summary>
/// 存档快照根对象:描述「进入可存节点那一刻」的完整游戏状态。
/// <para>
/// 与槽位/文件路径解耦;序列化后写入 JSON。对应需求§3 四分类:
/// anchor(恢复锚点)、sections(表现类 + 维修子模块)、yarnVariables。
/// </para>
/// <para>
/// 宏观阶段(场景 / 章节 SO / YarnProject)不单独冗余存储,统一以 sections["scene"] / sections["macro"]
/// 为唯一来源;槽位 UI 等需要摘要时通过 <see cref="SaveSnapshotSummary"/> 现算派生,避免重复字段漂移。
/// </para>
/// </summary>
[Serializable]
public class SaveSnapshot
{
/// <summary>快照结构版本,对应 <see cref="SaveSnapshotSchema.CurrentVersion"/>。</summary>
public int schemaVersion = SaveSnapshotSchema.CurrentVersion;
/// <summary>保存时的 <c>Application.version</c>。</summary>
public string gameVersion;
/// <summary>保存时间(本地),格式 <c>yyyy-MM-dd HH:mm:ss</c>。</summary>
public string savedAt;
/// <summary>当前场景(恢复的第一步)。</summary>
public SceneSnapshotDto scene = new();
/// <summary>恢复锚点:章节、YarnProject 与节点,读档最后阶段重新进入。</summary>
public AnchorSnapshot anchor = new();
/// <summary>全部 Yarn 运行时变量(来自 <see cref="YarnVariableStorage"/>)。</summary>
public YarnVariablesSnapshot yarnVariables = new();
/// <summary>
/// 各子系统快照,key 为 <see cref="SnapshotProviderIds"/> 中的稳定 id。
/// 表现层与维修模块(含 BlockPuzzle / Memory 等)均在此注册,按 RestoreOrder 还原。
/// </summary>
public Dictionary<string, object> sections = new();
}
/// <summary>
/// 恢复锚点:包含宏观章节信息,读档最后阶段加载对话工程并重新 <c>StartDialogue</c>。
/// </summary>
[Serializable]
public class AnchorSnapshot
{
/// <summary>当前章节 TalkSceneSO 名称,用于 <see cref="GameManager.SetSceneSoByName"/>。</summary>
public string sceneSoName;
public string yarnProjectId;
public string nodeName;
}
/// <summary>Yarn 变量三分组,与 Yarn Spinner 支持的类型一致。</summary>
[Serializable]
public class YarnVariablesSnapshot
{
public Dictionary<string, float> floats = new();
public Dictionary<string, string> strings = new();
public Dictionary<string, bool> bools = new();
}
#region Provider DTOs
/// <summary>sections["scene"]:当前加载的场景。</summary>
[Serializable]
public class SceneSnapshotDto
{
public string sceneName;
}
/// <summary>sections["env"]:环境时间/天气。</summary>
[Serializable]
public class EnvironmentSnapshotDto
{
/// <summary><see cref="TimesOfDay"/> 枚举名。</summary>
public string curTime;
/// <summary><see cref="Weather"/> 枚举名。</summary>
public string curWeather;
public bool isInitialized;
}
/// <summary>sections["actor"]:场景中所有角色的逐项状态。</summary>
[Serializable]
public class ActorSnapshotDto
{
public List<ActorEntrySnapshotDto> actors = new();
}
/// <summary>单个角色的快照条目。</summary>
[Serializable]
public class ActorEntrySnapshotDto
{
public string actorName;
public string slotName;
public string actorType;
public string stateName;
/// <summary>AnimaAnimator layer 0 当前状态的 normalizedTime。</summary>
public float stateNormalizedTime;
public float alpha;
/// <summary>AnimaEx:最后一次 set_actor_face 的表情名。</summary>
public string faceName;
/// <summary>AnimaExAnimator "Is Talking" 参数。</summary>
public bool isTalking;
}
/// <summary>sections["audio"]FMOD 音乐/SFX 终态与 AMB 状态(不记播放进度)。</summary>
[Serializable]
public class AudioSnapshotDto
{
/// <summary>key → FMOD event path。</summary>
public Dictionary<string, string> musicPaths = new();
public Dictionary<string, string> sfxPaths = new();
/// <summary><see cref="AmbState"/> 枚举名。</summary>
public string ambState;
}
/// <summary>
/// Timeline 终/初态标记(决策 D1.1:不按时间轴逐帧还原)。
/// 序列化进 JSON 时使用字符串形式存入 <see cref="TimelineEntrySnapshotDto.phase"/>。
/// </summary>
public enum TimelinePlaybackPhase
{
AtStart,
AtEnd,
Stopped
}
/// <summary>sections["timeline"]:各 PlayableDirector 的终/初态。</summary>
[Serializable]
public class TimelineSnapshotDto
{
public List<TimelineEntrySnapshotDto> entries = new();
}
[Serializable]
public class TimelineEntrySnapshotDto
{
public string directorName;
public bool isActive;
/// <summary><see cref="TimelinePlaybackPhase"/> 的字符串名。</summary>
public string phase;
/// <summary>若由 Addressables 加载 Timeline,记录 key;否则为空。</summary>
public string loadedAddressableKey;
}
/// <summary>sections["screen"]:屏幕饱和度与演出淡入淡出遮罩。</summary>
[Serializable]
public class ScreenSnapshotDto
{
/// <summary>Post-process Volume weight0=正常,1=完全褪色)。</summary>
public float saturationWeight;
/// <summary>
/// <see cref="PlayToolPanel"/> 演出遮罩终态:<c>true</c> = 黑屏盖住(<c>FadeIn</c> 完成);
/// <c>false</c> = 画面可见(<c>FadeOut</c> 完成或遮罩未启用)。不记录转场中间 alpha。
/// </summary>
public bool isFadeScreenCovered;
}
/// <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
{
/// <summary><see cref="FixSceneMode"/> 枚举名。</summary>
public string state;
/// <summary>状态参数(如 BodyModule 的 ModuleState 字符串)。</summary>
public string args;
}
/// <summary>sections["fixPanel"]FixPanel 壳层 + 线缆/插头状态。</summary>
[Serializable]
public class FixPanelSnapshotDto
{
public bool isSystemOn;
/// <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
/// <summary>
/// 从 <see cref="SaveSnapshot"/> 根对象直接读取的宏观摘要(场景 / 章节 SO / YarnProject)。
/// <para>
/// scene 与 anchor 已提升到根对象,无需再从 sections 字典派生。
/// 槽位 / 「继续游戏」等 UI 在需要展示时调用 <see cref="SaveSnapshotSummary.From"/> 即可。
/// </para>
/// </summary>
public readonly struct SaveSnapshotSummary
{
/// <summary>Addressable 场景 key(来自 <see cref="SaveSnapshot.scene"/>)。</summary>
public readonly string SceneName;
/// <summary>当前 <c>TalkSceneSO</c> 的 asset 名称(来自 <see cref="AnchorSnapshot.sceneSoName"/>)。</summary>
public readonly string SceneSoName;
/// <summary>当前 <c>YarnProject</c> 的 name(来自 <see cref="AnchorSnapshot.yarnProjectId"/>)。</summary>
public readonly string YarnProjectId;
public SaveSnapshotSummary(string sceneName, string sceneSoName, string yarnProjectId)
{
SceneName = sceneName;
SceneSoName = sceneSoName;
YarnProjectId = yarnProjectId;
}
/// <summary>从快照根对象直接读取宏观摘要。</summary>
public static SaveSnapshotSummary From(SaveSnapshot snapshot)
{
if (snapshot == null)
{
return new SaveSnapshotSummary(null, null, null);
}
var sceneName = snapshot.scene?.sceneName;
var sceneSoName = snapshot.anchor?.sceneSoName;
var yarnProjectId = snapshot.anchor?.yarnProjectId;
return new SaveSnapshotSummary(sceneName, sceneSoName, yarnProjectId);
}
}
}