Files
Workspace/Assets/Modules/ShrinkDataSaver/Editor/ShrinkDataSaverEditorWindow.cs
T
cneicy d74c2f08ca feat(packages): 内置 SDK 包并完善 ContextLoader 集成
- 将 ShrinkEventBus、ShrinkDataSaver 及其 EventBus 集成从 gitlink 转为仓库直接维护的完整 UPM 包,补齐运行时、编辑器工具、测试与文档
- 新增 Command 和 Network 的 App 集成组件,支持 ContextLoader 服务发布、可逆注销及 Network Loopback 生命周期管理
- 更新 Starter 与演示组合逻辑,缺失模块时可注册、已有兼容安装器时可覆盖,并补充宿主启动断言
- 升级内部包依赖与 Shared CodeGen 包定义,放宽 Integration.App 包的 Git 忽略规则
- 将独立服务器生成器改为基于已编译程序集的语义扫描,支持 partial、复杂泛型、命名冲突检测及模板 SHA-256 覆写保护
- 新增 Network 语义扫描、模板保护和 App 组件生命周期测试
- 新增真实 UPM 消费工程验证脚本,校验内部版本一致性、程序集加载及 EditMode 测试
- 重构当前架构文档并归档已完成的 Cordis 迁移与旧代码地图
2026-08-18 18:06:34 +08:00

379 lines
14 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.
#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<ShrinkDataSaverEditorWindow>("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<bool>() ? "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