using System; using UnityEngine; using Yarn.Unity; namespace AibisDream.SaveSystem { /// /// 从运行时组装 (纯内存,不写盘)。 /// /// 流程:元数据 → 场景 → 锚点(含宏观章节)→ Yarn 变量 → 各 Provider.Capture。 /// scene / anchor 由框架直管;其余子系统通过 Provider 写入 sections。 /// /// public static class SnapshotCapture { /// 捕获当前完整快照。调用前需已注册全部 provider。 /// /// 触发存档时的 Yarn 节点名(通常为 OnNodeStart 传入值)。 /// 非 null 时作为 anchor,并与 Capture 时刻的当前节点比较;不一致时打 warning,不阻断写盘。 /// /// /// 为 true 时写入空 (显式 <<save>> 默认行为)。 /// public static SaveSnapshot Capture( YarnVariableStorage storage, string triggerNodeName = null, bool omitAnchor = false) { SnapshotRegistry.ValidateRequiredProviders(); var snapshot = new SaveSnapshot { gameVersion = Application.version, savedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }; CaptureScene(snapshot); CaptureAnchor(snapshot, triggerNodeName, omitAnchor); CaptureYarnVariables(storage, snapshot); foreach (var provider in SnapshotRegistry.GetOrderedProviders()) { var dto = provider.Capture(); if (dto == null) continue; snapshot.sections[provider.SaveId] = dto; } 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, string triggerNodeName, bool omitAnchor) { var dialog = DialogController.Instance; if (dialog == null) return; string anchorNodeName; if (omitAnchor) { anchorNodeName = string.Empty; } else { var (currentNodeName, _) = dialog.GetCurrentNodeContext(); anchorNodeName = triggerNodeName ?? currentNodeName ?? string.Empty; if (triggerNodeName != null && !string.Equals( NormalizeNodeName(triggerNodeName), NormalizeNodeName(currentNodeName), StringComparison.Ordinal)) { Debug.LogWarning( $"[SnapshotCapture] 存档锚点漂移:触发时「{FormatNodeName(triggerNodeName)}」," + $"Capture 时「{FormatNodeName(currentNodeName)}」。anchor 使用触发节点。"); } } 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 = anchorNodeName }; } private static string NormalizeNodeName(string nodeName) { return string.IsNullOrEmpty(nodeName) ? string.Empty : nodeName; } private static string FormatNodeName(string nodeName) { return string.IsNullOrEmpty(nodeName) ? "(无节点)" : nodeName; } private static void CaptureYarnVariables(YarnVariableStorage storage, SaveSnapshot snapshot) { var (floats, strings, bools) = storage.GetAllVariables(); snapshot.yarnVariables = new YarnVariablesSnapshot { floats = floats ?? new(), strings = strings ?? new(), bools = bools ?? new() }; } } }