2237 lines
82 KiB
C#
2237 lines
82 KiB
C#
using UnityEngine;
|
||
using UnityEditor;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.IO;
|
||
using ArtResource;
|
||
using Spine.Unity;
|
||
using UnityEngine.U2D;
|
||
using Thrift;
|
||
using Thrift.Protocol;
|
||
using Thrift.Transport;
|
||
using Thrift.Transport.Client;
|
||
|
||
namespace EditorArt_Tools
|
||
{
|
||
/// <summary>
|
||
/// 美术资源配置编辑器
|
||
/// 提供三栏布局:配置表预览、Item导航、详细编辑
|
||
/// </summary>
|
||
public class ArtResourceConfigEditor : EditorWindow
|
||
{
|
||
private const string SO_ROOT_PATH = "Assets/Art_SubModule/Art_SO";
|
||
private const string JSON_ROOT_PATH = "Assets/Art_SubModule/Art_Json";
|
||
private const string BYTES_ROOT_PATH = "Assets/Art_SubModule/Art_Bytes";
|
||
|
||
// ===== 数据 =====
|
||
private List<ArtTableSO> allTables = new List<ArtTableSO>();
|
||
private ArtTableSO selectedTable;
|
||
private ArtItemData selectedItem;
|
||
private Dictionary<string, bool> folderFoldouts = new Dictionary<string, bool>();
|
||
private Dictionary<int, bool> itemFoldouts = new Dictionary<int, bool>();
|
||
|
||
// 暂存区(用于编辑但未保存的数据)- 完全独立的数据副本
|
||
private List<ArtItemData> tempItemsList = new List<ArtItemData>();
|
||
private new bool hasUnsavedChanges = false;
|
||
|
||
// ID编辑临时缓存
|
||
private Dictionary<int, string> editingIdStrings = new Dictionary<int, string>();
|
||
private Dictionary<int, int> originalIds = new Dictionary<int, int>();
|
||
|
||
// 用于第二栏跳转到第三栏
|
||
private int scrollToItemId = -1;
|
||
|
||
// 当前正在播放Spine动画的Item ID
|
||
private int currentPlayingSpineItemId = -1;
|
||
|
||
// ===== UI滚动 =====
|
||
private Vector2 scrollTableList;
|
||
private Vector2 scrollItemNav;
|
||
private Vector2 scrollEditArea;
|
||
|
||
// ===== Spine预览 =====
|
||
private object spinePreviewInstance;
|
||
private System.Type spinePreviewType;
|
||
|
||
// ===== Sprite预览缓存 =====
|
||
private Dictionary<Sprite, SpriteAtlasInfo?> spriteAtlasCache = new Dictionary<Sprite, SpriteAtlasInfo?>();
|
||
|
||
private struct SpriteAtlasInfo
|
||
{
|
||
public SpriteAtlas atlas;
|
||
public string atlasPath;
|
||
}
|
||
|
||
[MenuItem("美术工具/美术资源配置")]
|
||
public static void ShowWindow()
|
||
{
|
||
var window = GetWindow<ArtResourceConfigEditor>("美术资源配置");
|
||
window.minSize = new Vector2(1600, 800);
|
||
window.Show();
|
||
}
|
||
|
||
[MenuItem("美术工具/路径管理/修复丢失的引用")]
|
||
public static void FixMissingReferences()
|
||
{
|
||
FixAllMissingReferences();
|
||
}
|
||
|
||
[MenuItem("美术工具/路径管理/清理SO丢失文件")]
|
||
public static void CleanupMissingItems()
|
||
{
|
||
CleanupAllMissingItems();
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
RefreshTableList();
|
||
InitializeSpinePreview();
|
||
|
||
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload;
|
||
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
CleanupSpinePreview();
|
||
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload;
|
||
|
||
// 检查是否有未保存的更改
|
||
if (hasUnsavedChanges)
|
||
{
|
||
if (EditorUtility.DisplayDialog("未保存的更改",
|
||
"您有未保存的更改,是否保存?",
|
||
"保存", "放弃"))
|
||
{
|
||
SaveCurrentTable();
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
CleanupSpinePreview();
|
||
}
|
||
|
||
#region Spine预览初始化和清理
|
||
|
||
private void InitializeSpinePreview()
|
||
{
|
||
try
|
||
{
|
||
var assembly = typeof(Spine.Unity.Editor.SkeletonDataAssetInspector).Assembly;
|
||
spinePreviewType = assembly.GetType("Spine.Unity.Editor.SkeletonInspectorPreview");
|
||
|
||
if (spinePreviewType != null)
|
||
{
|
||
spinePreviewInstance = System.Activator.CreateInstance(spinePreviewType);
|
||
EditorApplication.update -= HandleSpinePreviewUpdate;
|
||
EditorApplication.update += HandleSpinePreviewUpdate;
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogWarning($"初始化Spine预览失败: {e.Message}");
|
||
}
|
||
}
|
||
|
||
private void CleanupSpinePreview()
|
||
{
|
||
EditorApplication.update -= HandleSpinePreviewUpdate;
|
||
|
||
if (spinePreviewInstance != null && spinePreviewType != null)
|
||
{
|
||
try
|
||
{
|
||
var clearMethod = spinePreviewType.GetMethod("Clear");
|
||
if (clearMethod != null)
|
||
{
|
||
clearMethod.Invoke(spinePreviewInstance, null);
|
||
}
|
||
|
||
var cleanupMethod = spinePreviewType.GetMethod("Cleanup");
|
||
if (cleanupMethod != null)
|
||
{
|
||
cleanupMethod.Invoke(spinePreviewInstance, null);
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogWarning($"清理Spine预览时出错: {e.Message}");
|
||
}
|
||
|
||
spinePreviewInstance = null;
|
||
}
|
||
}
|
||
|
||
private void HandleSpinePreviewUpdate()
|
||
{
|
||
if (spinePreviewInstance != null && spinePreviewType != null)
|
||
{
|
||
var updateMethod = spinePreviewType.GetMethod("HandleEditorUpdate");
|
||
if (updateMethod != null)
|
||
{
|
||
updateMethod.Invoke(spinePreviewInstance, null);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnBeforeAssemblyReload()
|
||
{
|
||
CleanupSpinePreview();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 主界面绘制
|
||
|
||
private void OnGUI()
|
||
{
|
||
DrawToolbar();
|
||
EditorGUILayout.Space(5);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
// 第一栏:配置表预览(分级显示)
|
||
DrawTableListPanel();
|
||
|
||
// 第二栏:Item导航
|
||
DrawItemNavigatorPanel();
|
||
|
||
// 第三栏:详细编辑
|
||
DrawEditPanel();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void DrawToolbar()
|
||
{
|
||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||
|
||
if (GUILayout.Button("刷新", EditorStyles.toolbarButton, GUILayout.Width(60)))
|
||
{
|
||
RefreshTableList();
|
||
}
|
||
|
||
if (GUILayout.Button("新建配置表", EditorStyles.toolbarButton, GUILayout.Width(100)))
|
||
{
|
||
CreateNewTable();
|
||
}
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
// 未保存提示
|
||
if (hasUnsavedChanges)
|
||
{
|
||
GUI.color = Color.yellow;
|
||
GUILayout.Label("● 有未保存的更改", EditorStyles.boldLabel);
|
||
GUI.color = Color.white;
|
||
}
|
||
|
||
if (selectedTable != null)
|
||
{
|
||
EditorGUILayout.LabelField($"当前表: {selectedTable.TableName}", EditorStyles.boldLabel);
|
||
|
||
if (GUILayout.Button("保存", EditorStyles.toolbarButton, GUILayout.Width(60)))
|
||
{
|
||
SaveCurrentTable();
|
||
}
|
||
|
||
GUI.backgroundColor = Color.red;
|
||
if (GUILayout.Button("删除配置表", EditorStyles.toolbarButton, GUILayout.Width(80)))
|
||
{
|
||
DeleteCurrentTable();
|
||
}
|
||
GUI.backgroundColor = Color.white;
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 第一栏:配置表预览(分级显示)
|
||
|
||
private void DrawTableListPanel()
|
||
{
|
||
EditorGUILayout.BeginVertical("box", GUILayout.Width(320));
|
||
EditorGUILayout.LabelField("配置表列表", EditorStyles.boldLabel);
|
||
|
||
scrollTableList = EditorGUILayout.BeginScrollView(scrollTableList);
|
||
|
||
if (allTables != null && allTables.Count > 0)
|
||
{
|
||
DrawFolderHierarchy();
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.HelpBox("暂无配置表\n点击上方「新建配置表」创建", MessageType.Info);
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawFolderHierarchy()
|
||
{
|
||
// 按文件夹分组
|
||
var tablesByFolder = allTables
|
||
.Select(table => new
|
||
{
|
||
Table = table,
|
||
Path = AssetDatabase.GetAssetPath(table),
|
||
Folder = GetRelativeFolder(AssetDatabase.GetAssetPath(table))
|
||
})
|
||
.GroupBy(x => x.Folder)
|
||
.OrderBy(g => g.Key);
|
||
|
||
foreach (var folderGroup in tablesByFolder)
|
||
{
|
||
string folderName = folderGroup.Key;
|
||
if (!folderFoldouts.ContainsKey(folderName))
|
||
{
|
||
folderFoldouts[folderName] = true;
|
||
}
|
||
|
||
// 绘制文件夹
|
||
EditorGUILayout.BeginHorizontal();
|
||
folderFoldouts[folderName] = EditorGUILayout.Foldout(
|
||
folderFoldouts[folderName],
|
||
$"📁 {folderName} ({folderGroup.Count()})",
|
||
true,
|
||
EditorStyles.foldoutHeader);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// 绘制该文件夹下的配置表
|
||
if (folderFoldouts[folderName])
|
||
{
|
||
EditorGUI.indentLevel++;
|
||
foreach (var item in folderGroup)
|
||
{
|
||
DrawTableButton(item.Table);
|
||
}
|
||
EditorGUI.indentLevel--;
|
||
}
|
||
|
||
EditorGUILayout.Space(3);
|
||
}
|
||
}
|
||
|
||
private void DrawTableButton(ArtTableSO table)
|
||
{
|
||
bool isSelected = table == selectedTable;
|
||
|
||
Color originalBg = GUI.backgroundColor;
|
||
if (isSelected)
|
||
{
|
||
GUI.backgroundColor = new Color(0.3f, 0.6f, 1f);
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
if (GUILayout.Button($"📄 {table.TableName}", GUILayout.Height(30)))
|
||
{
|
||
SelectTable(table);
|
||
}
|
||
|
||
GUI.backgroundColor = originalBg;
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField($"ID: {table.TableId}", EditorStyles.miniLabel, GUILayout.Width(80));
|
||
EditorGUILayout.LabelField($"资源数: {table.Items.Count}", EditorStyles.miniLabel);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private string GetRelativeFolder(string assetPath)
|
||
{
|
||
string relativePath = assetPath.Replace(SO_ROOT_PATH + "/", "");
|
||
int lastSlash = relativePath.LastIndexOf('/');
|
||
if (lastSlash >= 0)
|
||
{
|
||
return relativePath.Substring(0, lastSlash);
|
||
}
|
||
return "根目录";
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 第二栏:Item导航
|
||
|
||
private void DrawItemNavigatorPanel()
|
||
{
|
||
EditorGUILayout.BeginVertical("box", GUILayout.Width(280));
|
||
|
||
if (selectedTable == null)
|
||
{
|
||
EditorGUILayout.HelpBox("请从左侧选择一个配置表", MessageType.Info);
|
||
EditorGUILayout.EndVertical();
|
||
return;
|
||
}
|
||
|
||
EditorGUILayout.LabelField($"{selectedTable.TableName} - 资源项", EditorStyles.boldLabel);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
if (GUILayout.Button("+ 添加新项", GUILayout.Height(25)))
|
||
{
|
||
AddNewItem();
|
||
}
|
||
if (GUILayout.Button("📁 批量导入Sprite", GUILayout.Height(25)))
|
||
{
|
||
BatchImportSprites();
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(5);
|
||
|
||
scrollItemNav = EditorGUILayout.BeginScrollView(scrollItemNav);
|
||
|
||
if (tempItemsList.Count == 0)
|
||
{
|
||
EditorGUILayout.HelpBox("暂无资源项\n点击上方「+ 添加新项」创建", MessageType.Info);
|
||
}
|
||
else
|
||
{
|
||
for (int i = 0; i < tempItemsList.Count; i++)
|
||
{
|
||
var item = tempItemsList[i];
|
||
DrawItemNavigatorButton(item, i);
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawItemNavigatorButton(ArtItemData item, int index)
|
||
{
|
||
bool isSelected = item == selectedItem;
|
||
|
||
Color originalBg = GUI.backgroundColor;
|
||
if (isSelected)
|
||
{
|
||
GUI.backgroundColor = new Color(0.3f, 0.8f, 0.3f);
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
// 小缩略图
|
||
if (item.Sprite != null)
|
||
{
|
||
Rect previewRect = GUILayoutUtility.GetRect(40, 40, GUILayout.ExpandWidth(false));
|
||
EditorGUI.DrawRect(previewRect, new Color(0.2f, 0.2f, 0.2f));
|
||
DrawSpritePreview(previewRect, item.Sprite);
|
||
}
|
||
else if (item.SpineAsset != null)
|
||
{
|
||
Rect previewRect = GUILayoutUtility.GetRect(40, 40, GUILayout.ExpandWidth(false));
|
||
EditorGUI.DrawRect(previewRect, new Color(0.2f, 0.2f, 0.2f));
|
||
GUI.Label(previewRect, "🦴", new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 24 });
|
||
}
|
||
else
|
||
{
|
||
GUILayout.Space(40);
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical();
|
||
if (GUILayout.Button($"{index + 1}. {item.Name}", GUILayout.Height(40)))
|
||
{
|
||
SelectItem(item);
|
||
}
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// 显示基本信息
|
||
EditorGUILayout.LabelField($"ID: {item.Id}", EditorStyles.miniLabel);
|
||
if (!string.IsNullOrEmpty(item.Desc))
|
||
{
|
||
EditorGUILayout.LabelField($"描述: {item.Desc}", EditorStyles.miniLabel);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
|
||
GUI.backgroundColor = originalBg;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 第三栏:详细编辑(显示所有Item)
|
||
|
||
private void DrawEditPanel()
|
||
{
|
||
EditorGUILayout.BeginVertical("box", GUILayout.ExpandWidth(true));
|
||
|
||
if (selectedTable == null)
|
||
{
|
||
EditorGUILayout.HelpBox("请从左侧选择一个配置表", MessageType.Info);
|
||
EditorGUILayout.EndVertical();
|
||
return;
|
||
}
|
||
|
||
// 表格信息编辑区域
|
||
EditorGUILayout.BeginVertical("box");
|
||
EditorGUILayout.LabelField("配置表信息", EditorStyles.boldLabel);
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("表格ID", GUILayout.Width(80));
|
||
selectedTable.TableId = EditorGUILayout.IntField(selectedTable.TableId);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("表格名称", GUILayout.Width(80));
|
||
selectedTable.TableName = EditorGUILayout.TextField(selectedTable.TableName);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
MarkAsChanged();
|
||
}
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.Space(5);
|
||
|
||
scrollEditArea = EditorGUILayout.BeginScrollView(scrollEditArea);
|
||
|
||
if (tempItemsList.Count == 0)
|
||
{
|
||
EditorGUILayout.HelpBox("暂无资源项\n点击中间栏上方「+ 添加新项」创建", MessageType.Info);
|
||
}
|
||
else
|
||
{
|
||
// 显示所有Item
|
||
for (int i = 0; i < tempItemsList.Count; i++)
|
||
{
|
||
var item = tempItemsList[i];
|
||
|
||
// 如果需要跳转到这个Item
|
||
if (scrollToItemId == item.Id)
|
||
{
|
||
// 展开这个Item
|
||
itemFoldouts[item.Id] = true;
|
||
|
||
// 计算滚动位置:前面的item都是折叠状态,高度约60像素
|
||
float collapsedItemHeight = 44f; // 折叠状态的实际高度(只有标题行)
|
||
scrollEditArea.y = Mathf.Max(0, i * collapsedItemHeight);
|
||
|
||
// 重置跳转标记
|
||
scrollToItemId = -1;
|
||
|
||
// 让GUI重绘以应用折叠状态
|
||
Repaint();
|
||
}
|
||
|
||
DrawItemEditSection(item, i);
|
||
EditorGUILayout.Space(10);
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawItemEditSection(ArtItemData item, int index)
|
||
{
|
||
// 整个Item的大面板
|
||
bool isSelected = item == selectedItem;
|
||
Color originalBg = GUI.backgroundColor;
|
||
|
||
if (isSelected)
|
||
{
|
||
GUI.backgroundColor = new Color(0.4f, 0.7f, 1f, 0.3f);
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical("box");
|
||
GUI.backgroundColor = originalBg;
|
||
|
||
// 标题行
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
string title = $"{index + 1}. {item.Name} (ID: {item.Id})";
|
||
if (!itemFoldouts.ContainsKey(item.Id))
|
||
{
|
||
itemFoldouts[item.Id] = false;
|
||
}
|
||
|
||
itemFoldouts[item.Id] = EditorGUILayout.Foldout(
|
||
itemFoldouts[item.Id],
|
||
title,
|
||
true,
|
||
EditorStyles.foldoutHeader);
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
GUI.backgroundColor = Color.red;
|
||
if (GUILayout.Button("删除", GUILayout.Width(60), GUILayout.Height(20)))
|
||
{
|
||
if (EditorUtility.DisplayDialog("确认删除",
|
||
$"确定要删除资源项「{item.Name}」吗?\n此操作不可恢复!",
|
||
"删除", "取消"))
|
||
{
|
||
DeleteItem(item);
|
||
}
|
||
}
|
||
GUI.backgroundColor = Color.white;
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
// 展开内容
|
||
if (itemFoldouts[item.Id])
|
||
{
|
||
EditorGUI.indentLevel++;
|
||
|
||
// 基本信息
|
||
DrawBasicInfoSection(item);
|
||
|
||
EditorGUILayout.Space(5);
|
||
|
||
// Sprite资源
|
||
DrawSpriteSection(item);
|
||
|
||
EditorGUILayout.Space(5);
|
||
|
||
// Spine资源
|
||
DrawSpineSection(item);
|
||
|
||
EditorGUI.indentLevel--;
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawBasicInfoSection(ArtItemData editingData)
|
||
{
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
EditorGUILayout.LabelField("资源ID", EditorStyles.boldLabel);
|
||
|
||
// 初始化编辑字符串
|
||
int itemHashCode = editingData.GetHashCode();
|
||
if (!editingIdStrings.ContainsKey(itemHashCode))
|
||
{
|
||
editingIdStrings[itemHashCode] = editingData.Id.ToString();
|
||
originalIds[itemHashCode] = editingData.Id;
|
||
}
|
||
|
||
// 使用TextField进行编辑
|
||
GUI.SetNextControlName($"IdField_{itemHashCode}");
|
||
string newIdString = EditorGUILayout.TextField(editingIdStrings[itemHashCode]);
|
||
|
||
// 如果字符串改变,更新缓存
|
||
if (newIdString != editingIdStrings[itemHashCode])
|
||
{
|
||
editingIdStrings[itemHashCode] = newIdString;
|
||
}
|
||
|
||
// 检查是否失去焦点
|
||
string currentFocus = GUI.GetNameOfFocusedControl();
|
||
if (currentFocus != $"IdField_{itemHashCode}" && editingIdStrings.ContainsKey(itemHashCode))
|
||
{
|
||
// 尝试解析ID
|
||
if (int.TryParse(editingIdStrings[itemHashCode], out int newId))
|
||
{
|
||
// 检查是否有重复ID(排除自己)
|
||
bool hasDuplicate = tempItemsList.Any(item => item != editingData && item.Id == newId);
|
||
|
||
if (hasDuplicate)
|
||
{
|
||
// 有重复,恢复原值并提示
|
||
EditorUtility.DisplayDialog("ID重复",
|
||
$"ID {newId} 已被其他资源项使用,请使用不同的ID。",
|
||
"确定");
|
||
editingIdStrings[itemHashCode] = originalIds[itemHashCode].ToString();
|
||
editingData.Id = originalIds[itemHashCode];
|
||
}
|
||
else if (newId != editingData.Id)
|
||
{
|
||
// 没有重复,更新ID
|
||
int oldId = editingData.Id;
|
||
editingData.Id = newId;
|
||
originalIds[itemHashCode] = newId;
|
||
|
||
// 更新itemFoldouts字典的key
|
||
if (itemFoldouts.ContainsKey(oldId))
|
||
{
|
||
bool wasFoldout = itemFoldouts[oldId];
|
||
itemFoldouts.Remove(oldId);
|
||
itemFoldouts[newId] = wasFoldout;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 解析失败,恢复原值
|
||
editingIdStrings[itemHashCode] = originalIds[itemHashCode].ToString();
|
||
editingData.Id = originalIds[itemHashCode];
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.Space(5);
|
||
|
||
EditorGUILayout.LabelField("资源名称", EditorStyles.boldLabel);
|
||
editingData.Name = EditorGUILayout.TextField(editingData.Name);
|
||
|
||
EditorGUILayout.Space(5);
|
||
|
||
EditorGUILayout.LabelField("资源描述", EditorStyles.boldLabel);
|
||
editingData.Desc = EditorGUILayout.TextArea(editingData.Desc, GUILayout.Height(60));
|
||
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
MarkAsChanged();
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawSpriteSection(ArtItemData editingData)
|
||
{
|
||
EditorGUILayout.LabelField("Sprite 图片资源", EditorStyles.boldLabel);
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("Sprite引用", GUILayout.Width(80));
|
||
var newSprite = (Sprite)EditorGUILayout.ObjectField(
|
||
editingData.Sprite,
|
||
typeof(Sprite),
|
||
false);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
editingData.Sprite = newSprite;
|
||
MarkAsChanged();
|
||
}
|
||
|
||
// 预览
|
||
if (editingData.Sprite != null)
|
||
{
|
||
EditorGUILayout.Space(10);
|
||
EditorGUILayout.LabelField("预览", EditorStyles.boldLabel);
|
||
|
||
Rect previewRect = GUILayoutUtility.GetRect(150, 150, GUILayout.Width(150), GUILayout.Height(150));
|
||
EditorGUI.DrawRect(previewRect, new Color(0.2f, 0.2f, 0.2f));
|
||
DrawSpritePreview(previewRect, editingData.Sprite);
|
||
|
||
// 显示信息
|
||
var atlas = GetSpriteAtlas(editingData.Sprite);
|
||
if (atlas.HasValue)
|
||
{
|
||
EditorGUILayout.LabelField($"图集: {atlas.Value.atlas.name}");
|
||
}
|
||
|
||
EditorGUILayout.LabelField($"尺寸: {editingData.Sprite.rect.width} x {editingData.Sprite.rect.height}");
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawSpineSection(ArtItemData editingData)
|
||
{
|
||
EditorGUILayout.LabelField("Spine 骨骼资源", EditorStyles.boldLabel);
|
||
EditorGUILayout.BeginVertical("box");
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("SkeletonDataAsset引用", GUILayout.Width(150));
|
||
var newSpineAsset = (SkeletonDataAsset)EditorGUILayout.ObjectField(
|
||
editingData.SpineAsset,
|
||
typeof(SkeletonDataAsset),
|
||
false);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
bool spineAssetChanged = EditorGUI.EndChangeCheck();
|
||
|
||
if (spineAssetChanged)
|
||
{
|
||
editingData.SpineAsset = newSpineAsset;
|
||
|
||
// 自动选择第一个动画
|
||
if (newSpineAsset != null)
|
||
{
|
||
var animations = GetSpineAnimations(newSpineAsset);
|
||
if (animations != null && animations.Length > 0)
|
||
{
|
||
editingData.SpineAnimName = animations[0];
|
||
// 暂停其他正在播放的动画
|
||
StopAllSpineAnimations();
|
||
currentPlayingSpineItemId = editingData.Id;
|
||
}
|
||
}
|
||
|
||
MarkAsChanged();
|
||
}
|
||
|
||
if (editingData.SpineAsset != null)
|
||
{
|
||
EditorGUILayout.Space(5);
|
||
|
||
// 获取所有动画名称
|
||
var animations = GetSpineAnimations(editingData.SpineAsset);
|
||
if (animations != null && animations.Length > 0)
|
||
{
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("动画名称", GUILayout.Width(80));
|
||
|
||
int currentIndex = System.Array.IndexOf(animations, editingData.SpineAnimName);
|
||
if (currentIndex < 0) currentIndex = 0;
|
||
|
||
int newIndex = EditorGUILayout.Popup(currentIndex, animations);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
if (EditorGUI.EndChangeCheck())
|
||
{
|
||
editingData.SpineAnimName = animations[newIndex];
|
||
MarkAsChanged();
|
||
|
||
// 切换动画时自动播放
|
||
StopAllSpineAnimations();
|
||
currentPlayingSpineItemId = editingData.Id;
|
||
PlaySpineAnimation(editingData.SpineAsset, editingData.SpineAnimName);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.HelpBox("该Spine资源没有动画", MessageType.Warning);
|
||
}
|
||
|
||
// 预览
|
||
EditorGUILayout.Space(10);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("预览", EditorStyles.boldLabel);
|
||
|
||
bool isPlaying = (currentPlayingSpineItemId == editingData.Id);
|
||
GUI.backgroundColor = isPlaying ? Color.green : Color.white;
|
||
if (GUILayout.Button(isPlaying ? "⏸ 暂停" : "▶ 播放", GUILayout.Width(80)))
|
||
{
|
||
if (isPlaying)
|
||
{
|
||
currentPlayingSpineItemId = -1;
|
||
StopSpineAnimation();
|
||
}
|
||
else
|
||
{
|
||
StopAllSpineAnimations();
|
||
currentPlayingSpineItemId = editingData.Id;
|
||
PlaySpineAnimation(editingData.SpineAsset, editingData.SpineAnimName);
|
||
}
|
||
}
|
||
GUI.backgroundColor = Color.white;
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
Rect previewRect = GUILayoutUtility.GetRect(150, 150, GUILayout.Width(150), GUILayout.Height(150));
|
||
DrawSpinePreview(previewRect, editingData.SpineAsset, editingData.SpineAnimName, isPlaying);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 数据操作
|
||
|
||
// 找到所有的SO文件展示在列表中
|
||
private void RefreshTableList()
|
||
{
|
||
allTables.Clear();
|
||
|
||
// 搜索所有ScriptableObject,然后手动过滤ArtTableSO类型
|
||
string[] allSOGuids = AssetDatabase.FindAssets("t:ScriptableObject");
|
||
int checkedCount = 0;
|
||
|
||
foreach (string guid in allSOGuids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
|
||
// 只检查在SO_ROOT_PATH路径下的文件
|
||
if (path.StartsWith(SO_ROOT_PATH))
|
||
{
|
||
checkedCount++;
|
||
var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
|
||
|
||
// 检查是否是ArtTableSO类型
|
||
if (asset is ArtTableSO table)
|
||
{
|
||
allTables.Add(table);
|
||
// Debug.Log($"[ArtResourceConfig] 找到配置表: {table.TableName} - {path}");
|
||
}
|
||
}
|
||
}
|
||
|
||
allTables = allTables.OrderBy(t => t.TableName).ToList();
|
||
// Debug.Log($"[ArtResourceConfig] 在 {SO_ROOT_PATH} 下检查了 {checkedCount} 个SO文件,找到 {allTables.Count} 个配置表");
|
||
|
||
// 清除缓存
|
||
tempItemsList.Clear();
|
||
hasUnsavedChanges = false;
|
||
|
||
Repaint();
|
||
}
|
||
|
||
private void SelectTable(ArtTableSO table)
|
||
{
|
||
// 如果有未保存的更改,提示用户
|
||
if (hasUnsavedChanges && selectedTable != null && selectedTable != table)
|
||
{
|
||
if (!EditorUtility.DisplayDialog("切换配置表",
|
||
"当前有未保存的更改,切换将丢失这些更改。\n确定要切换吗?",
|
||
"切换", "取消"))
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
selectedTable = table;
|
||
selectedItem = null;
|
||
hasUnsavedChanges = false;
|
||
itemFoldouts.Clear();
|
||
|
||
// 加载数据到暂存区
|
||
LoadToTempData();
|
||
}
|
||
|
||
private void LoadToTempData()
|
||
{
|
||
tempItemsList.Clear();
|
||
|
||
if (selectedTable == null) return;
|
||
|
||
// 创建完全独立的副本
|
||
foreach (var item in selectedTable.Items)
|
||
{
|
||
var copy = new ArtItemData
|
||
{
|
||
Id = item.Id,
|
||
Name = item.Name,
|
||
Desc = item.Desc,
|
||
Sprite = item.Sprite,
|
||
SpineAsset = item.SpineAsset,
|
||
SpineAnimName = item.SpineAnimName
|
||
};
|
||
tempItemsList.Add(copy);
|
||
}
|
||
}
|
||
|
||
private void SelectItem(ArtItemData item)
|
||
{
|
||
selectedItem = item;
|
||
|
||
// 清除当前焦点,避免显示旧的编辑内容
|
||
GUI.FocusControl(null);
|
||
GUIUtility.keyboardControl = 0;
|
||
EditorGUIUtility.editingTextField = false;
|
||
|
||
// 折叠所有其他项,只展开当前项
|
||
foreach (var tempItem in tempItemsList)
|
||
{
|
||
itemFoldouts[tempItem.Id] = (tempItem == item);
|
||
}
|
||
|
||
// 设置跳转标记,让第三栏滚动到这个Item
|
||
scrollToItemId = item.Id;
|
||
|
||
// 如果有Spine资源,自动播放动画
|
||
if (item.SpineAsset != null && !string.IsNullOrEmpty(item.SpineAnimName))
|
||
{
|
||
StopAllSpineAnimations();
|
||
currentPlayingSpineItemId = item.Id;
|
||
PlaySpineAnimation(item.SpineAsset, item.SpineAnimName);
|
||
}
|
||
else
|
||
{
|
||
// 如果没有Spine资源,停止所有播放
|
||
StopAllSpineAnimations();
|
||
}
|
||
|
||
Repaint();
|
||
}
|
||
|
||
private void MarkAsChanged()
|
||
{
|
||
hasUnsavedChanges = true;
|
||
}
|
||
|
||
private void CreateNewTable()
|
||
{
|
||
string path = EditorUtility.SaveFilePanelInProject(
|
||
"创建新配置表",
|
||
"NewArtTable",
|
||
"asset",
|
||
"请选择配置表保存位置",
|
||
SO_ROOT_PATH);
|
||
|
||
if (string.IsNullOrEmpty(path))
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 检查是否直接放在Art_SO根目录
|
||
string relativePath = path.Replace(SO_ROOT_PATH + "/", "");
|
||
if (!relativePath.Contains("/"))
|
||
{
|
||
EditorUtility.DisplayDialog("路径错误",
|
||
"配置表不能直接放在 Art_SO 根目录下!\n请在 Art_SO 下创建子文件夹,然后将配置表放在子文件夹中。",
|
||
"确定");
|
||
return;
|
||
}
|
||
|
||
var newTable = ScriptableObject.CreateInstance<ArtTableSO>();
|
||
newTable.TableName = Path.GetFileNameWithoutExtension(path);
|
||
newTable.TableId = GenerateUniqueTableId();
|
||
newTable.Items = new List<ArtItemData>();
|
||
|
||
AssetDatabase.CreateAsset(newTable, path);
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
|
||
// 同步创建JSON文件
|
||
SyncToJson(newTable);
|
||
|
||
// 更新manifest文件
|
||
UpdateManifest();
|
||
|
||
RefreshTableList();
|
||
SelectTable(newTable);
|
||
|
||
Debug.Log($"创建配置表成功: {path}");
|
||
}
|
||
|
||
private int GenerateUniqueTableId()
|
||
{
|
||
if (allTables.Count == 0) return 1;
|
||
return allTables.Max(t => t.TableId) + 1;
|
||
}
|
||
|
||
private void AddNewItem()
|
||
{
|
||
if (selectedTable == null) return;
|
||
|
||
int newId = tempItemsList.Count > 0
|
||
? tempItemsList.Max(i => i.Id) + 1
|
||
: 1;
|
||
|
||
var newItem = new ArtItemData
|
||
{
|
||
Id = newId,
|
||
Name = $"新资源项_{newId}",
|
||
Desc = ""
|
||
};
|
||
|
||
tempItemsList.Add(newItem);
|
||
hasUnsavedChanges = true;
|
||
|
||
SelectItem(newItem);
|
||
}
|
||
|
||
private void DeleteItem(ArtItemData item)
|
||
{
|
||
if (item == null) return;
|
||
|
||
tempItemsList.Remove(item);
|
||
hasUnsavedChanges = true;
|
||
|
||
if (selectedItem == item)
|
||
{
|
||
selectedItem = tempItemsList.FirstOrDefault();
|
||
}
|
||
|
||
Repaint();
|
||
}
|
||
|
||
private void DeleteCurrentTable()
|
||
{
|
||
if (selectedTable == null) return;
|
||
|
||
if (!EditorUtility.DisplayDialog("确认删除",
|
||
$"确定要删除配置表「{selectedTable.TableName}」吗?\n这将同时删除SO文件和对应的JSON文件!\n此操作不可恢复!",
|
||
"删除", "取消"))
|
||
{
|
||
return;
|
||
}
|
||
|
||
string tableName = selectedTable.TableName;
|
||
string soPath = AssetDatabase.GetAssetPath(selectedTable);
|
||
|
||
// 计算JSON路径
|
||
string relativePath = soPath.Replace(SO_ROOT_PATH, "").Replace(".asset", ".json");
|
||
string jsonPath = JSON_ROOT_PATH + relativePath;
|
||
|
||
// 删除SO文件
|
||
AssetDatabase.DeleteAsset(soPath);
|
||
|
||
// 删除JSON文件(如果存在)
|
||
if (File.Exists(jsonPath))
|
||
{
|
||
File.Delete(jsonPath);
|
||
// 同时删除.meta文件
|
||
if (File.Exists(jsonPath + ".meta"))
|
||
{
|
||
File.Delete(jsonPath + ".meta");
|
||
}
|
||
}
|
||
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
|
||
// 更新manifest文件
|
||
UpdateManifest();
|
||
|
||
Debug.Log($"已删除配置表: {tableName}");
|
||
Debug.Log($"SO路径: {soPath}");
|
||
if (File.Exists(jsonPath))
|
||
{
|
||
Debug.Log($"JSON路径: {jsonPath}");
|
||
}
|
||
|
||
// 清空选择
|
||
selectedTable = null;
|
||
selectedItem = null;
|
||
tempItemsList.Clear();
|
||
hasUnsavedChanges = false;
|
||
|
||
// 刷新列表
|
||
RefreshTableList();
|
||
|
||
EditorUtility.DisplayDialog("删除成功", $"配置表「{tableName}」已删除", "确定");
|
||
}
|
||
|
||
private void BatchImportSprites()
|
||
{
|
||
if (selectedTable == null)
|
||
{
|
||
EditorUtility.DisplayDialog("错误", "请先选择一个配置表", "确定");
|
||
return;
|
||
}
|
||
|
||
string folderPath = EditorUtility.OpenFolderPanel("选择Sprite文件夹", "Assets", "");
|
||
if (string.IsNullOrEmpty(folderPath))
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 转换为相对路径
|
||
if (!folderPath.StartsWith(Application.dataPath))
|
||
{
|
||
EditorUtility.DisplayDialog("错误", "请选择项目内的文件夹", "确定");
|
||
return;
|
||
}
|
||
|
||
string relativePath = "Assets" + folderPath.Substring(Application.dataPath.Length);
|
||
|
||
// 查找所有Sprite
|
||
string[] guids = AssetDatabase.FindAssets("t:Sprite", new[] { relativePath });
|
||
|
||
if (guids.Length == 0)
|
||
{
|
||
EditorUtility.DisplayDialog("提示", "该文件夹中没有找到Sprite资源", "确定");
|
||
return;
|
||
}
|
||
|
||
int startId = tempItemsList.Count > 0 ? tempItemsList.Max(i => i.Id) + 1 : 1;
|
||
int importCount = 0;
|
||
|
||
foreach (string guid in guids)
|
||
{
|
||
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
||
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
|
||
|
||
if (sprite != null)
|
||
{
|
||
var newItem = new ArtItemData
|
||
{
|
||
Id = startId + importCount,
|
||
Name = sprite.name,
|
||
Desc = "",
|
||
Sprite = sprite
|
||
};
|
||
|
||
tempItemsList.Add(newItem);
|
||
importCount++;
|
||
}
|
||
}
|
||
|
||
if (importCount > 0)
|
||
{
|
||
hasUnsavedChanges = true;
|
||
EditorUtility.DisplayDialog("导入成功",
|
||
$"成功导入 {importCount} 个Sprite资源\n记得点击保存按钮!",
|
||
"确定");
|
||
}
|
||
}
|
||
|
||
private void SaveCurrentTable()
|
||
{
|
||
if (selectedTable == null)
|
||
{
|
||
EditorUtility.DisplayDialog("保存失败", "没有选中的配置表", "确定");
|
||
return;
|
||
}
|
||
|
||
// 检查表格ID和名称的唯一性
|
||
foreach (var table in allTables)
|
||
{
|
||
if (table == selectedTable) continue;
|
||
|
||
if (table.TableId == selectedTable.TableId)
|
||
{
|
||
EditorUtility.DisplayDialog("保存失败",
|
||
$"表格ID {selectedTable.TableId} 已被表格「{table.TableName}」使用!\n请使用不同的ID。",
|
||
"确定");
|
||
return;
|
||
}
|
||
|
||
if (table.TableName == selectedTable.TableName)
|
||
{
|
||
EditorUtility.DisplayDialog("保存失败",
|
||
$"表格名称「{selectedTable.TableName}」已被使用!\n请使用不同的名称。",
|
||
"确定");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 检查资源项名称的唯一性
|
||
var nameGroups = tempItemsList.GroupBy(item => item.Name).Where(g => g.Count() > 1).ToList();
|
||
if (nameGroups.Any())
|
||
{
|
||
string duplicateNames = string.Join(", ", nameGroups.Select(g => $"'{g.Key}'"));
|
||
EditorUtility.DisplayDialog("保存失败",
|
||
$"资源项名称重复!\n以下名称出现了多次: {duplicateNames}\n请确保每个资源项的名称唯一。",
|
||
"确定");
|
||
return;
|
||
}
|
||
|
||
// 检查资源项ID的唯一性
|
||
var idGroups = tempItemsList.GroupBy(item => item.Id).Where(g => g.Count() > 1).ToList();
|
||
if (idGroups.Any())
|
||
{
|
||
string duplicateIds = string.Join(", ", idGroups.Select(g => g.Key.ToString()));
|
||
EditorUtility.DisplayDialog("保存失败",
|
||
$"资源项ID重复!\n以下ID出现了多次: {duplicateIds}\n请确保每个资源项的ID唯一。",
|
||
"确定");
|
||
return;
|
||
}
|
||
|
||
// 检查是否有资源项既没有Sprite又没有Spine
|
||
var emptyItems = tempItemsList.Where(item => item.Sprite == null && item.SpineAsset == null).ToList();
|
||
if (emptyItems.Any())
|
||
{
|
||
string emptyItemNames = string.Join(", ", emptyItems.Select(item => $"'{item.Name}' (ID: {item.Id})"));
|
||
EditorUtility.DisplayDialog("保存失败",
|
||
$"以下资源项既没有Sprite也没有Spine资源!\n{emptyItemNames}\n请至少为每个资源项配置一种资源。",
|
||
"确定");
|
||
return;
|
||
}
|
||
|
||
// 清空原始列表
|
||
selectedTable.Items.Clear();
|
||
|
||
// 将暂存区的数据复制到原始数据,同时更新路径
|
||
foreach (var tempItem in tempItemsList)
|
||
{
|
||
var newItem = new ArtItemData
|
||
{
|
||
Id = tempItem.Id,
|
||
Name = tempItem.Name,
|
||
Desc = tempItem.Desc,
|
||
Sprite = tempItem.Sprite,
|
||
SpritePath = tempItem.Sprite != null ? AssetDatabase.GetAssetPath(tempItem.Sprite) : "",
|
||
SpineAsset = tempItem.SpineAsset,
|
||
SpineAssetPath = tempItem.SpineAsset != null ? AssetDatabase.GetAssetPath(tempItem.SpineAsset) : "",
|
||
SpineAnimName = tempItem.SpineAnimName
|
||
};
|
||
selectedTable.Items.Add(newItem);
|
||
}
|
||
|
||
// 保存SO
|
||
EditorUtility.SetDirty(selectedTable);
|
||
AssetDatabase.SaveAssets();
|
||
|
||
// 同步JSON
|
||
SyncToJson(selectedTable);
|
||
|
||
// 同步Thrift Bytes(新增)
|
||
SyncToThriftBytes(selectedTable);
|
||
|
||
// 更新manifest文件
|
||
UpdateManifest();
|
||
|
||
// 生成合并的Bytes文件
|
||
GenerateMergedThriftBytes();
|
||
|
||
hasUnsavedChanges = false;
|
||
|
||
Debug.Log($"配置表保存成功: {selectedTable.TableName}");
|
||
EditorUtility.DisplayDialog("保存成功", $"配置表「{selectedTable.TableName}」已保存\n共 {selectedTable.Items.Count} 项", "确定");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region JSON同步
|
||
|
||
private void SyncToJson(ArtTableSO table)
|
||
{
|
||
string soPath = AssetDatabase.GetAssetPath(table);
|
||
string relativePath = soPath.Replace(SO_ROOT_PATH, "").Replace(".asset", ".json");
|
||
string jsonPath = JSON_ROOT_PATH + relativePath;
|
||
|
||
// 确保目录存在
|
||
string directory = Path.GetDirectoryName(jsonPath);
|
||
if (!Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
|
||
// 创建JSON数据
|
||
var jsonData = new ArtTableJsonData
|
||
{
|
||
TableId = table.TableId,
|
||
TableName = table.TableName,
|
||
Items = table.Items.Select(item => new ArtItemJsonData
|
||
{
|
||
Id = item.Id,
|
||
Name = item.Name,
|
||
Desc = item.Desc,
|
||
SpritePath = item.Sprite != null ? AssetDatabase.GetAssetPath(item.Sprite) : "",
|
||
SpineAssetPath = item.SpineAsset != null ? AssetDatabase.GetAssetPath(item.SpineAsset) : "",
|
||
SpineAnimName = item.SpineAnimName
|
||
}).ToList()
|
||
};
|
||
|
||
string json = JsonUtility.ToJson(jsonData, true);
|
||
File.WriteAllText(jsonPath, json);
|
||
|
||
AssetDatabase.Refresh();
|
||
|
||
Debug.Log($"JSON同步成功: {jsonPath}");
|
||
}
|
||
|
||
private void UpdateManifest()
|
||
{
|
||
const string MANIFEST_PATH = "Assets/Art_SubModule/Art_Json/art_table_manifest.json";
|
||
|
||
try
|
||
{
|
||
// 查找所有JSON文件(从JSON目录扫描)
|
||
string[] jsonFiles = Directory.GetFiles(JSON_ROOT_PATH, "*.json", SearchOption.AllDirectories);
|
||
List<string> tablePaths = new List<string>();
|
||
|
||
foreach (string fullPath in jsonFiles)
|
||
{
|
||
string relativePath = fullPath.Replace("\\", "/").Replace(Application.dataPath.Replace("/Assets", "").Replace("\\", "/") + "/", "");
|
||
|
||
// 排除manifest文件本身
|
||
if (relativePath.EndsWith("art_table_manifest.json"))
|
||
continue;
|
||
|
||
tablePaths.Add(relativePath);
|
||
}
|
||
|
||
// 排序路径
|
||
tablePaths.Sort();
|
||
|
||
// 创建或更新manifest
|
||
ArtTableManifest manifest;
|
||
|
||
// 如果文件存在,读取现有的预加载配置
|
||
if (File.Exists(MANIFEST_PATH))
|
||
{
|
||
string existingJson = File.ReadAllText(MANIFEST_PATH);
|
||
manifest = JsonUtility.FromJson<ArtTableManifest>(existingJson);
|
||
|
||
// 确保字段不为null
|
||
if (manifest == null)
|
||
{
|
||
manifest = new ArtTableManifest();
|
||
}
|
||
if (manifest.preloadTableIds == null)
|
||
{
|
||
manifest.preloadTableIds = new int[0];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 创建新的manifest
|
||
manifest = new ArtTableManifest
|
||
{
|
||
preloadTableIds = new int[0]
|
||
};
|
||
}
|
||
|
||
// 更新路径列表
|
||
manifest.tablePaths = tablePaths.ToArray();
|
||
|
||
// 清理预加载配置:移除已删除表的ID
|
||
// 获取所有当前存在的表的ID
|
||
List<int> validTableIds = new List<int>();
|
||
foreach (string jsonPath in tablePaths)
|
||
{
|
||
try
|
||
{
|
||
string jsonContent = File.ReadAllText(jsonPath);
|
||
var jsonData = JsonUtility.FromJson<ArtTableJsonData>(jsonContent);
|
||
if (jsonData != null)
|
||
{
|
||
validTableIds.Add(jsonData.TableId);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略无法解析的文件
|
||
}
|
||
}
|
||
|
||
// 过滤掉已删除表的ID
|
||
if (manifest.preloadTableIds != null && manifest.preloadTableIds.Length > 0)
|
||
{
|
||
var cleanedPreloadIds = manifest.preloadTableIds.Where(id => validTableIds.Contains(id)).ToArray();
|
||
int removedCount = manifest.preloadTableIds.Length - cleanedPreloadIds.Length;
|
||
|
||
manifest.preloadTableIds = cleanedPreloadIds;
|
||
|
||
if (removedCount > 0)
|
||
{
|
||
Debug.Log($"[ArtResourceConfigEditor] 从预加载配置中移除了 {removedCount} 个已删除表的ID");
|
||
}
|
||
}
|
||
|
||
// 序列化为JSON
|
||
string json = JsonUtility.ToJson(manifest, true);
|
||
|
||
// 确保目录存在
|
||
string directory = Path.GetDirectoryName(MANIFEST_PATH);
|
||
if (!Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
|
||
// 写入文件
|
||
File.WriteAllText(MANIFEST_PATH, json);
|
||
AssetDatabase.ImportAsset(MANIFEST_PATH);
|
||
|
||
Debug.Log($"[ArtResourceConfigEditor] Manifest文件已更新: {MANIFEST_PATH}, 共 {tablePaths.Count} 个JSON表");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"[ArtResourceConfigEditor] 更新Manifest文件失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将单个表同步到Thrift Bytes格式(单表文件,暂不使用)
|
||
/// </summary>
|
||
private void SyncToThriftBytes(ArtTableSO table)
|
||
{
|
||
// 注意:这个方法生成单表bytes,但我们实际使用的是GenerateMergedThriftBytes()
|
||
// 保留此方法以防将来需要单表加载
|
||
Debug.Log($"[ArtResourceConfigEditor] 跳过单表Bytes生成: {table.TableName}(使用合并模式)");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成合并的Thrift Bytes文件(所有表合并)
|
||
/// 参考 ConfigManager 的加载方式
|
||
/// </summary>
|
||
private void GenerateMergedThriftBytes()
|
||
{
|
||
try
|
||
{
|
||
var startTime = System.Diagnostics.Stopwatch.StartNew();
|
||
|
||
// 确保输出目录存在
|
||
if (!Directory.Exists(BYTES_ROOT_PATH))
|
||
{
|
||
Directory.CreateDirectory(BYTES_ROOT_PATH);
|
||
}
|
||
|
||
string outputPath = Path.Combine(BYTES_ROOT_PATH, "ArtResourceConfig.bytes");
|
||
|
||
// 加载所有SO表
|
||
string[] allSOGuids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { SO_ROOT_PATH });
|
||
List<ArtTableSO> allSOTables = new List<ArtTableSO>();
|
||
|
||
foreach (string guid in allSOGuids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
ArtTableSO table = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
|
||
if (table != null)
|
||
{
|
||
allSOTables.Add(table);
|
||
}
|
||
}
|
||
|
||
// 创建Thrift数据结构
|
||
var thriftConfig = new Byway.Thrift.Data.ArtResourceConfig
|
||
{
|
||
Tables = new List<Byway.Thrift.Data.ArtTable>(),
|
||
Version = System.DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||
GenerateTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||
};
|
||
|
||
// 转换所有表
|
||
foreach (var soTable in allSOTables)
|
||
{
|
||
var thriftTable = new Byway.Thrift.Data.ArtTable
|
||
{
|
||
TableId = soTable.TableId,
|
||
TableName = soTable.TableName,
|
||
Items = new List<Byway.Thrift.Data.ArtItem>()
|
||
};
|
||
|
||
foreach (var soItem in soTable.Items)
|
||
{
|
||
var thriftItem = new Byway.Thrift.Data.ArtItem
|
||
{
|
||
Id = soItem.Id,
|
||
Name = soItem.Name ?? "",
|
||
Desc = soItem.Desc ?? "",
|
||
SpritePath = soItem.Sprite != null ? AssetDatabase.GetAssetPath(soItem.Sprite) : "",
|
||
SpineAssetPath = soItem.SpineAsset != null ? AssetDatabase.GetAssetPath(soItem.SpineAsset) : "",
|
||
SpineAnimName = soItem.SpineAnimName ?? ""
|
||
};
|
||
|
||
thriftTable.Items.Add(thriftItem);
|
||
}
|
||
|
||
thriftConfig.Tables.Add(thriftTable);
|
||
}
|
||
|
||
// 加载预加载配置(从manifest读取)
|
||
const string MANIFEST_PATH = "Assets/Art_SubModule/Art_Json/art_table_manifest.json";
|
||
if (File.Exists(MANIFEST_PATH))
|
||
{
|
||
string manifestJson = File.ReadAllText(MANIFEST_PATH);
|
||
var manifest = JsonUtility.FromJson<ArtTableManifest>(manifestJson);
|
||
if (manifest != null && manifest.preloadTableIds != null)
|
||
{
|
||
thriftConfig.PreloadTableIds = manifest.preloadTableIds.ToList();
|
||
}
|
||
}
|
||
|
||
// 序列化到bytes
|
||
byte[] bytesData;
|
||
using (var memoryStream = new MemoryStream())
|
||
{
|
||
using (var transport = new TStreamTransport(null, memoryStream, new TConfiguration()))
|
||
{
|
||
using (var protocol = new TBinaryProtocol(transport))
|
||
{
|
||
thriftConfig.WriteAsync(protocol, System.Threading.CancellationToken.None).GetAwaiter().GetResult();
|
||
}
|
||
}
|
||
bytesData = memoryStream.ToArray();
|
||
}
|
||
|
||
// 写入文件
|
||
File.WriteAllBytes(outputPath, bytesData);
|
||
AssetDatabase.ImportAsset(outputPath);
|
||
|
||
startTime.Stop();
|
||
|
||
Debug.Log($"[ArtResourceConfigEditor] ✅ Thrift Bytes生成成功!\n" +
|
||
$" 路径: {outputPath}\n" +
|
||
$" 表数量: {thriftConfig.Tables.Count}\n" +
|
||
$" 文件大小: {bytesData.Length / 1024f:F2} KB\n" +
|
||
$" 耗时: {startTime.ElapsedMilliseconds} ms");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"[ArtResourceConfigEditor] ❌ 生成Thrift Bytes失败: {ex.Message}\n{ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 静态版本:生成合并的Thrift Bytes文件(供外部工具调用)
|
||
/// </summary>
|
||
public static void GenerateMergedThriftBytesStatic()
|
||
{
|
||
try
|
||
{
|
||
var startTime = System.Diagnostics.Stopwatch.StartNew();
|
||
|
||
// 确保输出目录存在
|
||
const string BYTES_ROOT_PATH = "Assets/Art_SubModule/Art_Bytes";
|
||
if (!Directory.Exists(BYTES_ROOT_PATH))
|
||
{
|
||
Directory.CreateDirectory(BYTES_ROOT_PATH);
|
||
}
|
||
|
||
string outputPath = Path.Combine(BYTES_ROOT_PATH, "ArtResourceConfig.bytes");
|
||
|
||
// 加载所有SO表
|
||
const string SO_ROOT_PATH = "Assets/Art_SubModule/Art_SO";
|
||
string[] allSOGuids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { SO_ROOT_PATH });
|
||
List<ArtTableSO> allSOTables = new List<ArtTableSO>();
|
||
|
||
foreach (string guid in allSOGuids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
ArtTableSO table = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
|
||
if (table != null)
|
||
{
|
||
allSOTables.Add(table);
|
||
}
|
||
}
|
||
|
||
// 创建Thrift数据结构
|
||
var thriftConfig = new Byway.Thrift.Data.ArtResourceConfig
|
||
{
|
||
Tables = new List<Byway.Thrift.Data.ArtTable>(),
|
||
Version = System.DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||
GenerateTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||
};
|
||
|
||
// 转换所有表
|
||
foreach (var soTable in allSOTables)
|
||
{
|
||
var thriftTable = new Byway.Thrift.Data.ArtTable
|
||
{
|
||
TableId = soTable.TableId,
|
||
TableName = soTable.TableName,
|
||
Items = new List<Byway.Thrift.Data.ArtItem>()
|
||
};
|
||
|
||
foreach (var soItem in soTable.Items)
|
||
{
|
||
var thriftItem = new Byway.Thrift.Data.ArtItem
|
||
{
|
||
Id = soItem.Id,
|
||
Name = soItem.Name ?? "",
|
||
Desc = soItem.Desc ?? "",
|
||
SpritePath = soItem.Sprite != null ? AssetDatabase.GetAssetPath(soItem.Sprite) : "",
|
||
SpineAssetPath = soItem.SpineAsset != null ? AssetDatabase.GetAssetPath(soItem.SpineAsset) : "",
|
||
SpineAnimName = soItem.SpineAnimName ?? ""
|
||
};
|
||
|
||
thriftTable.Items.Add(thriftItem);
|
||
}
|
||
|
||
thriftConfig.Tables.Add(thriftTable);
|
||
}
|
||
|
||
// 加载预加载配置(从manifest读取)
|
||
const string MANIFEST_PATH = "Assets/Art_SubModule/Art_Json/art_table_manifest.json";
|
||
if (File.Exists(MANIFEST_PATH))
|
||
{
|
||
string manifestJson = File.ReadAllText(MANIFEST_PATH);
|
||
var manifest = JsonUtility.FromJson<ArtTableManifest>(manifestJson);
|
||
if (manifest != null && manifest.preloadTableIds != null)
|
||
{
|
||
thriftConfig.PreloadTableIds = manifest.preloadTableIds.ToList();
|
||
}
|
||
}
|
||
|
||
// 序列化到bytes
|
||
byte[] bytesData;
|
||
using (var memoryStream = new MemoryStream())
|
||
{
|
||
using (var transport = new TStreamTransport(null, memoryStream, new TConfiguration()))
|
||
{
|
||
using (var protocol = new TBinaryProtocol(transport))
|
||
{
|
||
thriftConfig.WriteAsync(protocol, System.Threading.CancellationToken.None).GetAwaiter().GetResult();
|
||
}
|
||
}
|
||
bytesData = memoryStream.ToArray();
|
||
}
|
||
|
||
// 写入文件
|
||
File.WriteAllBytes(outputPath, bytesData);
|
||
AssetDatabase.ImportAsset(outputPath);
|
||
|
||
startTime.Stop();
|
||
|
||
Debug.Log($"[ArtResourceConfigEditor] ✅ Thrift Bytes生成成功!\n" +
|
||
$" 路径: {outputPath}\n" +
|
||
$" 表数量: {thriftConfig.Tables.Count}\n" +
|
||
$" 文件大小: {bytesData.Length / 1024f:F2} KB\n" +
|
||
$" 耗时: {startTime.ElapsedMilliseconds} ms");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"[ArtResourceConfigEditor] ❌ 生成Thrift Bytes失败: {ex.Message}\n{ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
[System.Serializable]
|
||
public class ArtTableManifest
|
||
{
|
||
public string[] tablePaths;
|
||
public int[] preloadTableIds;
|
||
}
|
||
|
||
[System.Serializable]
|
||
public class ArtTableJsonData
|
||
{
|
||
public int TableId;
|
||
public string TableName;
|
||
public List<ArtItemJsonData> Items;
|
||
}
|
||
|
||
[System.Serializable]
|
||
public class ArtItemJsonData
|
||
{
|
||
public int Id;
|
||
public string Name;
|
||
public string Desc;
|
||
public string SpritePath;
|
||
public string SpineAssetPath;
|
||
public string SpineAnimName;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 预览功能
|
||
|
||
private void DrawSpritePreview(Rect rect, Sprite sprite)
|
||
{
|
||
if (sprite == null || sprite.texture == null) return;
|
||
|
||
Rect texCoords = sprite.textureRect;
|
||
Texture2D tex = sprite.texture;
|
||
|
||
Rect normalizedCoords = new Rect(
|
||
texCoords.x / tex.width,
|
||
texCoords.y / tex.height,
|
||
texCoords.width / tex.width,
|
||
texCoords.height / tex.height
|
||
);
|
||
|
||
// 计算适配矩形(保持长宽比)
|
||
float aspect = texCoords.width / texCoords.height;
|
||
Rect drawRect = rect;
|
||
if (aspect > 1f)
|
||
{
|
||
float height = drawRect.width / aspect;
|
||
drawRect.y += (drawRect.height - height) * 0.5f;
|
||
drawRect.height = height;
|
||
}
|
||
else
|
||
{
|
||
float width = drawRect.height * aspect;
|
||
drawRect.x += (drawRect.width - width) * 0.5f;
|
||
drawRect.width = width;
|
||
}
|
||
|
||
GUI.DrawTextureWithTexCoords(drawRect, tex, normalizedCoords, true);
|
||
}
|
||
|
||
private string[] GetSpineAnimations(SkeletonDataAsset spineAsset)
|
||
{
|
||
if (spineAsset == null || spineAsset.GetSkeletonData(false) == null)
|
||
return new string[0];
|
||
|
||
var skeletonData = spineAsset.GetSkeletonData(false);
|
||
var animations = skeletonData.Animations;
|
||
|
||
if (animations == null || animations.Count == 0)
|
||
return new string[0];
|
||
|
||
string[] animNames = new string[animations.Count];
|
||
for (int i = 0; i < animations.Count; i++)
|
||
{
|
||
animNames[i] = animations.Items[i].Name;
|
||
}
|
||
|
||
return animNames;
|
||
}
|
||
|
||
private void PlaySpineAnimation(SkeletonDataAsset spineAsset, string animName)
|
||
{
|
||
if (spineAsset == null || spinePreviewInstance == null || spinePreviewType == null)
|
||
return;
|
||
|
||
try
|
||
{
|
||
// 先清理旧的预览
|
||
var clearMethod = spinePreviewType.GetMethod("Clear");
|
||
if (clearMethod != null)
|
||
{
|
||
clearMethod.Invoke(spinePreviewInstance, null);
|
||
}
|
||
|
||
// 初始化预览(传入Repaint回调、SkeletonDataAsset和空字符串)
|
||
var initMethod = spinePreviewType.GetMethod("Initialize", new System.Type[] { typeof(System.Action), typeof(SkeletonDataAsset), typeof(string) });
|
||
if (initMethod != null)
|
||
{
|
||
initMethod.Invoke(spinePreviewInstance, new object[] { new System.Action(Repaint), spineAsset, "" });
|
||
|
||
// 延迟设置动画,确保skeleton已完全初始化
|
||
if (!string.IsNullOrEmpty(animName))
|
||
{
|
||
EditorApplication.delayCall += () =>
|
||
{
|
||
try
|
||
{
|
||
if (spinePreviewInstance != null && spinePreviewType != null)
|
||
{
|
||
var skeletonAnimField = spinePreviewType.GetField("skeletonAnimation", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
if (skeletonAnimField != null)
|
||
{
|
||
var skeletonAnim = skeletonAnimField.GetValue(spinePreviewInstance) as SkeletonAnimation;
|
||
if (skeletonAnim != null && skeletonAnim.valid && skeletonAnim.AnimationState != null)
|
||
{
|
||
skeletonAnim.AnimationState.SetAnimation(0, animName, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
};
|
||
}
|
||
}
|
||
|
||
Repaint();
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogWarning($"播放Spine动画失败: {e.Message}");
|
||
}
|
||
}
|
||
|
||
private void StopSpineAnimation()
|
||
{
|
||
if (spinePreviewInstance == null || spinePreviewType == null)
|
||
return;
|
||
|
||
try
|
||
{
|
||
var clearMethod = spinePreviewType.GetMethod("Clear");
|
||
if (clearMethod != null)
|
||
{
|
||
clearMethod.Invoke(spinePreviewInstance, null);
|
||
}
|
||
|
||
Repaint();
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogWarning($"停止Spine动画失败: {e.Message}");
|
||
}
|
||
}
|
||
|
||
private void StopAllSpineAnimations()
|
||
{
|
||
currentPlayingSpineItemId = -1;
|
||
StopSpineAnimation();
|
||
}
|
||
|
||
private void DrawSpinePreview(Rect rect, SkeletonDataAsset spineAsset, string animName, bool isPlaying)
|
||
{
|
||
if (spineAsset == null || spinePreviewInstance == null || spinePreviewType == null)
|
||
{
|
||
EditorGUI.HelpBox(rect, "Spine预览不可用", MessageType.Info);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 只有在播放状态时才绘制预览
|
||
if (isPlaying)
|
||
{
|
||
// 检查预览是否有效
|
||
var isValidProperty = spinePreviewType.GetProperty("IsValid");
|
||
bool isValid = isValidProperty != null && (bool)isValidProperty.GetValue(spinePreviewInstance, null);
|
||
|
||
if (isValid)
|
||
{
|
||
// 绘制预览 - 使用HandleInteractivePreviewGUI方法
|
||
var handleMethod = spinePreviewType.GetMethod("HandleInteractivePreviewGUI");
|
||
if (handleMethod != null)
|
||
{
|
||
handleMethod.Invoke(spinePreviewInstance, new object[] { rect, EditorStyles.helpBox });
|
||
}
|
||
}
|
||
else
|
||
{
|
||
EditorGUI.DrawRect(rect, new Color(0.2f, 0.2f, 0.2f));
|
||
GUI.Label(rect, "Spine预览初始化中...", new GUIStyle(GUI.skin.label)
|
||
{
|
||
alignment = TextAnchor.MiddleCenter,
|
||
normal = new GUIStyleState { textColor = Color.gray }
|
||
});
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 不播放时显示静态提示
|
||
EditorGUI.DrawRect(rect, new Color(0.2f, 0.2f, 0.2f));
|
||
GUI.Label(rect, "点击播放按钮查看动画", new GUIStyle(GUI.skin.label)
|
||
{
|
||
alignment = TextAnchor.MiddleCenter,
|
||
normal = new GUIStyleState { textColor = Color.gray }
|
||
});
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
EditorGUI.HelpBox(rect, $"Spine预览错误: {e.Message}", MessageType.Warning);
|
||
}
|
||
}
|
||
|
||
private SpriteAtlasInfo? GetSpriteAtlas(Sprite sprite)
|
||
{
|
||
if (sprite == null) return null;
|
||
|
||
if (spriteAtlasCache.TryGetValue(sprite, out var cached))
|
||
{
|
||
return cached;
|
||
}
|
||
|
||
string[] atlasGuids = AssetDatabase.FindAssets("t:SpriteAtlas");
|
||
foreach (string guid in atlasGuids)
|
||
{
|
||
string atlasPath = AssetDatabase.GUIDToAssetPath(guid);
|
||
SpriteAtlas atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasPath);
|
||
|
||
if (atlas != null && atlas.CanBindTo(sprite))
|
||
{
|
||
var info = new SpriteAtlasInfo
|
||
{
|
||
atlas = atlas,
|
||
atlasPath = atlasPath
|
||
};
|
||
spriteAtlasCache[sprite] = info;
|
||
return info;
|
||
}
|
||
}
|
||
|
||
spriteAtlasCache[sprite] = null;
|
||
return null;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 修复丢失引用和清理功能
|
||
|
||
/// <summary>
|
||
/// 修复所有配置表中丢失的引用
|
||
/// 按照路径来找到引用(与"更新所有美术资源路径"相反)
|
||
/// </summary>
|
||
private static void FixAllMissingReferences()
|
||
{
|
||
if (!EditorUtility.DisplayDialog("修复丢失的引用",
|
||
"此操作将遍历所有配置表,根据路径信息修复丢失的引用。\n" +
|
||
"适用于跨项目拉取代码后引用丢失但路径正确的情况。\n\n" +
|
||
"确定要继续吗?",
|
||
"确定", "取消"))
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
EditorUtility.DisplayProgressBar("修复丢失的引用", "正在查找配置表...", 0f);
|
||
|
||
// 查找所有ArtTableSO文件
|
||
string[] guids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { SO_ROOT_PATH });
|
||
|
||
if (guids.Length == 0)
|
||
{
|
||
EditorUtility.ClearProgressBar();
|
||
EditorUtility.DisplayDialog("提示", "未找到任何配置表。", "确定");
|
||
return;
|
||
}
|
||
|
||
int totalFixed = 0;
|
||
int totalTables = 0;
|
||
List<string> errorMessages = new List<string>();
|
||
List<string> processedTables = new List<string>();
|
||
|
||
for (int i = 0; i < guids.Length; i++)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
|
||
ArtTableSO table = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
|
||
|
||
if (table == null) continue;
|
||
|
||
EditorUtility.DisplayProgressBar("修复丢失的引用",
|
||
$"正在处理: {table.TableName} ({i + 1}/{guids.Length})",
|
||
(float)i / guids.Length);
|
||
|
||
int fixedInTable = FixTableReferences(table, errorMessages);
|
||
|
||
if (fixedInTable > 0)
|
||
{
|
||
totalFixed += fixedInTable;
|
||
totalTables++;
|
||
processedTables.Add($"{table.TableName}: {fixedInTable}个");
|
||
|
||
// 保存修改
|
||
EditorUtility.SetDirty(table);
|
||
}
|
||
}
|
||
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
EditorUtility.ClearProgressBar();
|
||
|
||
// 显示结果
|
||
string message = $"修复完成!\n\n" +
|
||
$"处理的配置表: {guids.Length}个\n" +
|
||
$"修复引用的配置表: {totalTables}个\n" +
|
||
$"总共修复的引用: {totalFixed}个";
|
||
|
||
if (processedTables.Count > 0)
|
||
{
|
||
message += "\n\n修复详情:\n" + string.Join("\n", processedTables);
|
||
}
|
||
|
||
if (errorMessages.Count > 0)
|
||
{
|
||
message += "\n\n⚠️ 以下问题需要注意:\n" + string.Join("\n", errorMessages);
|
||
}
|
||
|
||
EditorUtility.DisplayDialog(totalFixed > 0 ? "修复成功" : "无需修复", message, "确定");
|
||
|
||
Debug.Log($"[修复丢失引用] {message}");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
EditorUtility.ClearProgressBar();
|
||
EditorUtility.DisplayDialog("错误", $"修复过程中出现错误:\n{ex.Message}", "确定");
|
||
Debug.LogError($"[修复丢失引用] 错误: {ex.Message}\n{ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 修复单个配置表的引用
|
||
/// </summary>
|
||
private static int FixTableReferences(ArtTableSO table, List<string> errorMessages)
|
||
{
|
||
int fixedCount = 0;
|
||
|
||
foreach (var item in table.Items)
|
||
{
|
||
// 修复Sprite引用
|
||
if (item.Sprite == null && !string.IsNullOrEmpty(item.SpritePath))
|
||
{
|
||
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(item.SpritePath);
|
||
if (sprite != null)
|
||
{
|
||
item.Sprite = sprite;
|
||
fixedCount++;
|
||
Debug.Log($"[修复引用] {table.TableName}/{item.Name}: 修复了Sprite引用 ({item.SpritePath})");
|
||
}
|
||
else
|
||
{
|
||
string error = $"[{table.TableName}/{item.Name}] 找不到Sprite: {item.SpritePath}";
|
||
errorMessages.Add(error);
|
||
Debug.LogWarning(error);
|
||
}
|
||
}
|
||
|
||
// 修复SpineAsset引用
|
||
if (item.SpineAsset == null && !string.IsNullOrEmpty(item.SpineAssetPath))
|
||
{
|
||
SkeletonDataAsset spineAsset = AssetDatabase.LoadAssetAtPath<SkeletonDataAsset>(item.SpineAssetPath);
|
||
if (spineAsset != null)
|
||
{
|
||
item.SpineAsset = spineAsset;
|
||
fixedCount++;
|
||
Debug.Log($"[修复引用] {table.TableName}/{item.Name}: 修复了SpineAsset引用 ({item.SpineAssetPath})");
|
||
}
|
||
else
|
||
{
|
||
string error = $"[{table.TableName}/{item.Name}] 找不到SpineAsset: {item.SpineAssetPath}";
|
||
errorMessages.Add(error);
|
||
Debug.LogWarning(error);
|
||
}
|
||
}
|
||
|
||
// 检查既没有引用又没有路径的情况
|
||
if (item.Sprite == null && string.IsNullOrEmpty(item.SpritePath) &&
|
||
item.SpineAsset == null && string.IsNullOrEmpty(item.SpineAssetPath))
|
||
{
|
||
string error = $"[{table.TableName}/{item.Name}] 既没有引用也没有路径,需要手动配置";
|
||
errorMessages.Add(error);
|
||
Debug.LogWarning(error);
|
||
}
|
||
}
|
||
|
||
return fixedCount;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理所有配置表中引用丢失的Item
|
||
/// </summary>
|
||
private static void CleanupAllMissingItems()
|
||
{
|
||
if (!EditorUtility.DisplayDialog("清理丢失文件",
|
||
"此操作将删除所有配置表中引用丢失的资源项。\n" +
|
||
"同时会同步更新对应的JSON文件。\n\n" +
|
||
"⚠️ 此操作不可恢复!\n\n" +
|
||
"确定要继续吗?",
|
||
"确定", "取消"))
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
EditorUtility.DisplayProgressBar("清理丢失文件", "正在查找配置表...", 0f);
|
||
|
||
// 查找所有ArtTableSO文件
|
||
string[] guids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { SO_ROOT_PATH });
|
||
|
||
if (guids.Length == 0)
|
||
{
|
||
EditorUtility.ClearProgressBar();
|
||
EditorUtility.DisplayDialog("提示", "未找到任何配置表。", "确定");
|
||
return;
|
||
}
|
||
|
||
int totalRemoved = 0;
|
||
int totalTables = 0;
|
||
List<string> cleanedTables = new List<string>();
|
||
|
||
for (int i = 0; i < guids.Length; i++)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
|
||
ArtTableSO table = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
|
||
|
||
if (table == null) continue;
|
||
|
||
EditorUtility.DisplayProgressBar("清理丢失文件",
|
||
$"正在处理: {table.TableName} ({i + 1}/{guids.Length})",
|
||
(float)i / guids.Length);
|
||
|
||
int removedInTable = CleanupTableMissingItems(table);
|
||
|
||
if (removedInTable > 0)
|
||
{
|
||
totalRemoved += removedInTable;
|
||
totalTables++;
|
||
cleanedTables.Add($"{table.TableName}: 移除{removedInTable}个");
|
||
|
||
// 保存修改
|
||
EditorUtility.SetDirty(table);
|
||
|
||
// 同步到JSON
|
||
SyncTableToJson(table);
|
||
}
|
||
}
|
||
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
EditorUtility.ClearProgressBar();
|
||
|
||
// 显示结果
|
||
string message = $"清理完成!\n\n" +
|
||
$"处理的配置表: {guids.Length}个\n" +
|
||
$"清理的配置表: {totalTables}个\n" +
|
||
$"总共移除的资源项: {totalRemoved}个";
|
||
|
||
if (cleanedTables.Count > 0)
|
||
{
|
||
message += "\n\n清理详情:\n" + string.Join("\n", cleanedTables);
|
||
}
|
||
|
||
EditorUtility.DisplayDialog(totalRemoved > 0 ? "清理成功" : "无需清理", message, "确定");
|
||
|
||
Debug.Log($"[清理丢失文件] {message}");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
EditorUtility.ClearProgressBar();
|
||
EditorUtility.DisplayDialog("错误", $"清理过程中出现错误:\n{ex.Message}", "确定");
|
||
Debug.LogError($"[清理丢失文件] 错误: {ex.Message}\n{ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理单个配置表中引用丢失的Item
|
||
/// </summary>
|
||
private static int CleanupTableMissingItems(ArtTableSO table)
|
||
{
|
||
List<ArtItemData> itemsToRemove = new List<ArtItemData>();
|
||
|
||
foreach (var item in table.Items)
|
||
{
|
||
bool hasMissingReference = false;
|
||
string missingInfo = "";
|
||
|
||
// 检查Sprite引用是否丢失
|
||
if (!string.IsNullOrEmpty(item.SpritePath) && item.Sprite == null)
|
||
{
|
||
hasMissingReference = true;
|
||
missingInfo += $" Sprite({item.SpritePath})";
|
||
}
|
||
|
||
// 检查SpineAsset引用是否丢失
|
||
if (!string.IsNullOrEmpty(item.SpineAssetPath) && item.SpineAsset == null)
|
||
{
|
||
hasMissingReference = true;
|
||
missingInfo += $" SpineAsset({item.SpineAssetPath})";
|
||
}
|
||
|
||
if (hasMissingReference)
|
||
{
|
||
itemsToRemove.Add(item);
|
||
Debug.Log($"[清理丢失文件] {table.TableName}/{item.Name}: 标记为删除 - 丢失:{missingInfo}");
|
||
}
|
||
}
|
||
|
||
// 移除标记的Item
|
||
foreach (var item in itemsToRemove)
|
||
{
|
||
table.Items.Remove(item);
|
||
}
|
||
|
||
return itemsToRemove.Count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步单个配置表到JSON(静态版本)
|
||
/// </summary>
|
||
private static void SyncTableToJson(ArtTableSO table)
|
||
{
|
||
string soPath = AssetDatabase.GetAssetPath(table);
|
||
string relativePath = soPath.Replace(SO_ROOT_PATH, "").Replace(".asset", ".json");
|
||
string jsonPath = JSON_ROOT_PATH + relativePath;
|
||
|
||
// 确保目录存在
|
||
string directory = Path.GetDirectoryName(jsonPath);
|
||
if (!Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
|
||
// 创建JSON数据
|
||
var jsonData = new ArtTableJsonData
|
||
{
|
||
TableId = table.TableId,
|
||
TableName = table.TableName,
|
||
Items = table.Items.Select(item => new ArtItemJsonData
|
||
{
|
||
Id = item.Id,
|
||
Name = item.Name,
|
||
Desc = item.Desc,
|
||
SpritePath = item.Sprite != null ? AssetDatabase.GetAssetPath(item.Sprite) : "",
|
||
SpineAssetPath = item.SpineAsset != null ? AssetDatabase.GetAssetPath(item.SpineAsset) : "",
|
||
SpineAnimName = item.SpineAnimName
|
||
}).ToList()
|
||
};
|
||
|
||
string json = JsonUtility.ToJson(jsonData, true);
|
||
File.WriteAllText(jsonPath, json);
|
||
|
||
AssetDatabase.Refresh();
|
||
|
||
Debug.Log($"[同步JSON] {jsonPath}");
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|