#if UNITY_EDITOR using System; using System.IO; using System.Linq; using Cysharp.Threading.Tasks; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; namespace ShrinkDataSaver.Editor { public class ShrinkDataSaverEditorWindow : EditorWindow { private int _selectedTab; private readonly string[] _tabs = { "设置", "存档槽", "工具" }; private Vector2 _settingsScroll; private Vector2 _slotsScroll; private string _setKey = ""; private string _setValue = ""; private int _setType; // 0=string, 1=int, 2=float, 3=bool private readonly string[] _typeNames = { "字符串", "整数", "浮点数", "布尔值" }; private int _saveSlot; private string _slotName = "新存档"; private bool _encrypt; private string _encryptKey = ""; private SaveMeta[] _metaCache; [MenuItem("ShrinkSDK/存档/数据查看器")] public static void ShowWindow() { var w = GetWindow("ShrinkDataSaver"); w.minSize = new Vector2(460, 500); } private void OnGUI() { DrawHeader(); _selectedTab = GUILayout.Toolbar(_selectedTab, _tabs, EditorStyles.toolbarButton); EditorGUILayout.Space(4); switch (_selectedTab) { case 0: DrawSettingsTab(); break; case 1: DrawSaveSlotsTab(); break; case 2: DrawToolsTab(); break; } } private void OnInspectorUpdate() => Repaint(); private void DrawHeader() { EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); GUILayout.Label("ShrinkDataSaver", EditorStyles.boldLabel); GUILayout.FlexibleSpace(); var color = GUI.color; GUI.color = Application.isPlaying ? Color.green : Color.gray; GUILayout.Label(Application.isPlaying ? "● 运行中" : "○ 未运行", EditorStyles.miniLabel); GUI.color = color; EditorGUILayout.EndHorizontal(); } private void DrawSettingsTab() { if (!Application.isPlaying) { EditorGUILayout.HelpBox("设置查看器仅在运行模式下可用。", MessageType.Info); DrawSettingsFilePath(); return; } EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("重新加载", EditorStyles.miniButton)) ShrinkSettings.LoadAsync().Forget(); if (GUILayout.Button("立即保存", EditorStyles.miniButton)) ShrinkSettings.SaveAsync().Forget(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(6); var allSettings = ShrinkSettings.GetAllRaw(); EditorGUILayout.LabelField($"当前设置项({allSettings.Count} 项)", EditorStyles.boldLabel); if (allSettings.Count == 0) { EditorGUILayout.HelpBox("暂无设置项。", MessageType.None); } else { _settingsScroll = EditorGUILayout.BeginScrollView(_settingsScroll, GUILayout.MaxHeight(240)); string keyToRemove = null; foreach (var kvp in allSettings.OrderBy(k => k.Key)) { EditorGUILayout.BeginHorizontal("box"); EditorGUILayout.LabelField(kvp.Key, EditorStyles.boldLabel, GUILayout.Width(140)); var valueStr = FormatJToken(kvp.Value); EditorGUILayout.LabelField(valueStr, EditorStyles.wordWrappedMiniLabel); var prevColor = GUI.backgroundColor; GUI.backgroundColor = new Color(1f, 0.6f, 0.6f); if (GUILayout.Button("删除", EditorStyles.miniButton, GUILayout.Width(40))) keyToRemove = kvp.Key; GUI.backgroundColor = prevColor; EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); if (keyToRemove != null) ShrinkSettings.Remove(keyToRemove); } EditorGUILayout.Space(8); EditorGUILayout.LabelField("新增 / 修改", EditorStyles.boldLabel); _setKey = EditorGUILayout.TextField("键", _setKey); _setType = EditorGUILayout.Popup("类型", _setType, _typeNames); _setValue = EditorGUILayout.TextField("值", _setValue); EditorGUI.BeginDisabledGroup(string.IsNullOrWhiteSpace(_setKey)); if (GUILayout.Button("设置", EditorStyles.miniButton)) SetTypedValue(_setKey, _setValue, _setType); EditorGUI.EndDisabledGroup(); } private static void SetTypedValue(string key, string value, int type) { switch (type) { case 0: // string ShrinkSettings.Set(key, value); break; case 1: // int if (int.TryParse(value, out var intVal)) ShrinkSettings.Set(key, intVal); else Debug.LogWarning($"[ShrinkDataSaver] 无法将 \"{value}\" 解析为整数。"); break; case 2: // float if (float.TryParse(value, out var floatVal)) ShrinkSettings.Set(key, floatVal); else Debug.LogWarning($"[ShrinkDataSaver] 无法将 \"{value}\" 解析为浮点数。"); break; case 3: // bool if (bool.TryParse(value, out var boolVal)) ShrinkSettings.Set(key, boolVal); else ShrinkSettings.Set(key, value is "1" or "true" or "True"); break; } } private static string FormatJToken(JToken token) { if (token == null) return "null"; return token.Type switch { JTokenType.String => $"\"{token}\"", JTokenType.Boolean => token.Value() ? "true" : "false", JTokenType.Array or JTokenType.Object => token.ToString(Newtonsoft.Json.Formatting.None), _ => token.ToString() }; } private void DrawSettingsFilePath() { var cfg = ShrinkDataSaverSettings.Instance; var root = string.IsNullOrEmpty(cfg.customSavePath) ? Application.persistentDataPath : cfg.customSavePath; EditorGUILayout.LabelField("设置文件路径:", Path.Combine(root, cfg.settingsFileName), EditorStyles.wordWrappedMiniLabel); if (GUILayout.Button("打开目录", EditorStyles.miniButton)) EditorUtility.RevealInFinder(root); } private void DrawSaveSlotsTab() { if (!Application.isPlaying) { EditorGUILayout.HelpBox("存档操作仅在运行模式下可用。", MessageType.Info); DrawSavesFolderPath(); return; } EditorGUILayout.LabelField("快速操作", EditorStyles.boldLabel); _saveSlot = EditorGUILayout.IntField("槽位索引", _saveSlot); _slotName = EditorGUILayout.TextField("存档名称", _slotName); _encrypt = EditorGUILayout.Toggle("加密", _encrypt); if (_encrypt) _encryptKey = EditorGUILayout.TextField("密钥", _encryptKey); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("保存", EditorStyles.miniButton)) { ShrinkSave.SaveSlotAsync(_saveSlot, new SaveOptions { SlotName = _slotName, Encrypt = _encrypt, EncryptionKey = _encryptKey }).Forget(); RefreshMetaDelayed().Forget(); } if (GUILayout.Button("加载", EditorStyles.miniButton)) { ShrinkSave.LoadSlotAsync(_saveSlot, _encrypt ? _encryptKey : null).Forget(); } if (GUILayout.Button("删除", EditorStyles.miniButton)) { if (EditorUtility.DisplayDialog("删除存档", $"确认删除槽位 {_saveSlot}?", "删除", "取消")) { ShrinkSave.DeleteSlotAsync(_saveSlot).Forget(); RefreshMetaDelayed().Forget(); } } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(8); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("所有存档", EditorStyles.boldLabel); if (GUILayout.Button("刷新", EditorStyles.miniButton, GUILayout.Width(50))) RefreshMeta().Forget(); EditorGUILayout.EndHorizontal(); if (_metaCache == null) { EditorGUILayout.HelpBox("点击「刷新」加载存档列表。", MessageType.None); } else if (_metaCache.Length == 0) { EditorGUILayout.HelpBox("暂无存档。", MessageType.None); } else { DrawMetaList(); } } private async UniTask RefreshMeta() { _metaCache = await ShrinkSave.GetAllMetaAsync(); Repaint(); } private async UniTaskVoid RefreshMetaDelayed() { await UniTask.Delay(300); await RefreshMeta(); } private void DrawMetaList() { _slotsScroll = EditorGUILayout.BeginScrollView(_slotsScroll, GUILayout.MaxHeight(360)); foreach (var meta in _metaCache) { EditorGUILayout.BeginVertical("box"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"槽位 {meta.SlotIndex} — {meta.SlotName}", EditorStyles.boldLabel); GUILayout.FlexibleSpace(); if (GUILayout.Button("加载", EditorStyles.miniButton, GUILayout.Width(40))) { var key = meta.IsEncrypted ? _encryptKey : null; ShrinkSave.LoadSlotAsync(meta.SlotIndex, key).Forget(); } var prevColor = GUI.backgroundColor; GUI.backgroundColor = new Color(1f, 0.6f, 0.6f); if (GUILayout.Button("删除", EditorStyles.miniButton, GUILayout.Width(40))) { if (EditorUtility.DisplayDialog("删除存档", $"确认删除槽位 {meta.SlotIndex}({meta.SlotName})?", "删除", "取消")) { ShrinkSave.DeleteSlotAsync(meta.SlotIndex).Forget(); RefreshMetaDelayed().Forget(); } } GUI.backgroundColor = prevColor; EditorGUILayout.EndHorizontal(); EditorGUILayout.LabelField("最后修改", meta.LastModifiedTime.ToString("yyyy-MM-dd HH:mm:ss")); EditorGUILayout.LabelField("游戏时长", TimeSpan.FromSeconds(meta.PlaytimeSeconds).ToString(@"hh\:mm\:ss")); EditorGUILayout.LabelField("存档版本", meta.SaveVersion.ToString()); EditorGUILayout.LabelField("已加密", meta.IsEncrypted ? "是" : "否"); EditorGUILayout.LabelField("截图", meta.ScreenshotBase64 != null ? "有" : "无"); EditorGUILayout.EndVertical(); EditorGUILayout.Space(2); } EditorGUILayout.EndScrollView(); } private void DrawSavesFolderPath() { var cfg = ShrinkDataSaverSettings.Instance; var root = string.IsNullOrEmpty(cfg.customSavePath) ? Application.persistentDataPath : cfg.customSavePath; var saves = Path.Combine(root, "saves"); EditorGUILayout.LabelField("存档目录:", saves, EditorStyles.wordWrappedMiniLabel); if (GUILayout.Button("打开目录", EditorStyles.miniButton)) EditorUtility.RevealInFinder(saves); } private void DrawToolsTab() { EditorGUILayout.LabelField("实用工具", EditorStyles.boldLabel); if (GUILayout.Button("打开持久化数据目录")) EditorUtility.RevealInFinder(Application.persistentDataPath); EditorGUILayout.Space(8); EditorGUILayout.LabelField("配置资源", EditorStyles.boldLabel); var settings = ShrinkDataSaverSettings.Instance; if (settings) { EditorGUILayout.ObjectField("资源文件", settings, typeof(ShrinkDataSaverSettings), false); } else { EditorGUILayout.HelpBox( "未找到 ShrinkDataSaverSettings 资源文件。\n" + "请通过菜单 Assets → Create → ShrinkDataSaver → Settings 创建。", MessageType.Warning); } EditorGUILayout.Space(8); EditorGUILayout.LabelField("危险操作", EditorStyles.boldLabel); GUI.backgroundColor = new Color(1f, 0.4f, 0.4f); if (GUILayout.Button("删除所有存档文件")) { var cfg = ShrinkDataSaverSettings.Instance; var root = string.IsNullOrEmpty(cfg?.customSavePath) ? Application.persistentDataPath : cfg.customSavePath; var saves = Path.Combine(root, "saves"); if (EditorUtility.DisplayDialog("删除所有存档", $"将删除以下目录中的所有内容:\n{saves}\n\n此操作不可撤销!", "全部删除", "取消")) { if (Directory.Exists(saves)) Directory.Delete(saves, true); _metaCache = null; Debug.Log("[ShrinkDataSaver] 所有存档文件已删除。"); } } GUI.backgroundColor = Color.white; } } } #endif