@@ -0,0 +1,378 @@
#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