791 lines
30 KiB
C#
791 lines
30 KiB
C#
using UnityEditor;
|
|
using UnityEngine;
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
|
|
public class DependencyOrganizerWindow : EditorWindow
|
|
{
|
|
private List<UnityEngine.Object> draggedAssets = new List<UnityEngine.Object>();
|
|
private Vector2 prefabScroll;
|
|
private Vector2 depScroll;
|
|
|
|
private Dictionary<string, Dictionary<string, bool>> dependenciesByType = new Dictionary<string, Dictionary<string, bool>>();
|
|
private Dictionary<string, bool> typeToggle = new Dictionary<string, bool>();
|
|
private Dictionary<string, bool> typeFoldout = new Dictionary<string, bool>();
|
|
|
|
private Dictionary<string, bool> filterTypeToggle = new Dictionary<string, bool>();
|
|
|
|
// 贴图路径 -> HashSet<材质路径>
|
|
private Dictionary<string, HashSet<string>> textureUsedByMaterials = new Dictionary<string, HashSet<string>>();
|
|
|
|
private string targetRootFolder = string.Empty;
|
|
private string lastOrganizeAbsPath = string.Empty;
|
|
|
|
private SortedDictionary<long, string> undoManifests = new SortedDictionary<long, string>();
|
|
|
|
private const string UndoManifestDir = "Assets/Editor/DependencyOrganizerUndo";
|
|
private const string UndoManifestPrefix = "DependencyOrganizerWindow_undo_";
|
|
|
|
private Dictionary<string, bool> matsFoldout = new Dictionary<string, bool>();
|
|
|
|
[MenuItem("Tools/特效依赖整理工具")]
|
|
public static void ShowWindow()
|
|
{
|
|
var w = GetWindow<DependencyOrganizerWindow>("特效依赖整理工具-by很喜欢乱来");
|
|
w.minSize = new Vector2(700, 700);
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
LoadUndoManifests();
|
|
InitFilterTypeToggle();
|
|
}
|
|
|
|
private void InitFilterTypeToggle()
|
|
{
|
|
string[] filterTypes = new string[] {
|
|
"Textures", "Materials", "Models", "Shaders", "Scripts",
|
|
"Prefabs", "Animations", "Animators", "Audio", "Physics", "Others"
|
|
};
|
|
foreach (var t in filterTypes)
|
|
{
|
|
if (!filterTypeToggle.ContainsKey(t))
|
|
filterTypeToggle[t] = true;
|
|
}
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
GUILayout.Label("依赖整理工具-by很喜欢乱来", EditorStyles.boldLabel);
|
|
EditorGUILayout.HelpBox(
|
|
"1. 拖入Prefab/Scene/ScriptableObject等资源。\n" +
|
|
"2. 点击“收集依赖资源”,按类型和文件复选。\n" +
|
|
"3. 支持折叠、全选/反选。\n" +
|
|
"4. 选择目标根目录(支持拖入文件夹)。\n" +
|
|
"5. 点击“整理”移动资源,自动避免冲突重命名。\n" +
|
|
"6. 支持多版本撤销。\n" +
|
|
"7. 支持导出UnityPackage。\n" +
|
|
"8. 移动时显示进度条。\n" +
|
|
"9. 显示资源大小预估。\n" +
|
|
"10. 资源名点击定位,右键复制资源名。\n" +
|
|
"11. 贴图资源显示被哪些材质引用,材质路径列表可点击定位。\n",
|
|
MessageType.Info);
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
DrawFilterTypeToggle();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
DrawDragArea();
|
|
DrawPrefabList();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
if (GUILayout.Button("收集依赖资源", GUILayout.Height(30)))
|
|
CollectDependencies();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
DrawDependenciesList();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
DrawSizeEstimate();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
DrawTargetFolderSelector();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
DrawActionButtons();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
DrawUndoHistory();
|
|
}
|
|
|
|
private void DrawFilterTypeToggle()
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
foreach (var key in new List<string>(filterTypeToggle.Keys))
|
|
{
|
|
bool val = filterTypeToggle[key];
|
|
bool newVal = GUILayout.Toggle(val, key, "Button", GUILayout.Height(22));
|
|
if (newVal != val)
|
|
{
|
|
filterTypeToggle[key] = newVal;
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawDragArea()
|
|
{
|
|
Rect dropArea = GUILayoutUtility.GetRect(0, 60, GUILayout.ExpandWidth(true));
|
|
GUI.Box(dropArea, "拖入Prefab/Scene/ScriptableObject/Hierarchy中的GameObject");
|
|
|
|
var evt = Event.current;
|
|
if ((evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform) && dropArea.Contains(evt.mousePosition))
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
|
if (evt.type == EventType.DragPerform)
|
|
{
|
|
DragAndDrop.AcceptDrag();
|
|
foreach (var obj in DragAndDrop.objectReferences)
|
|
{
|
|
string path = AssetDatabase.GetAssetPath(obj);
|
|
|
|
if (obj is GameObject go)
|
|
{
|
|
bool isPrefab = !string.IsNullOrEmpty(path) && path.StartsWith("Assets");
|
|
if (isPrefab)
|
|
{
|
|
if (!draggedAssets.Contains(obj))
|
|
draggedAssets.Add(obj);
|
|
}
|
|
else
|
|
{
|
|
GameObject prefabRoot = PrefabUtility.GetCorrespondingObjectFromSource(go);
|
|
if (prefabRoot != null && !draggedAssets.Contains(prefabRoot))
|
|
draggedAssets.Add(prefabRoot);
|
|
else if (!draggedAssets.Contains(obj))
|
|
draggedAssets.Add(obj);
|
|
}
|
|
}
|
|
else if (IsSupportedAsset(obj, path))
|
|
{
|
|
if (!draggedAssets.Contains(obj))
|
|
draggedAssets.Add(obj);
|
|
}
|
|
}
|
|
}
|
|
evt.Use();
|
|
}
|
|
}
|
|
|
|
private bool IsSupportedAsset(UnityEngine.Object obj, string path)
|
|
{
|
|
if (obj == null || string.IsNullOrEmpty(path)) return false;
|
|
return (obj is GameObject && path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
|
|
|| path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase)
|
|
|| obj is ScriptableObject;
|
|
}
|
|
|
|
private void DrawPrefabList()
|
|
{
|
|
EditorGUILayout.LabelField("已拖入资源:");
|
|
if (draggedAssets.Count > 0)
|
|
{
|
|
if (GUILayout.Button("清空列表", GUILayout.Width(80)))
|
|
{
|
|
draggedAssets.Clear();
|
|
dependenciesByType.Clear();
|
|
typeToggle.Clear();
|
|
typeFoldout.Clear();
|
|
textureUsedByMaterials.Clear();
|
|
matsFoldout.Clear();
|
|
}
|
|
}
|
|
prefabScroll = EditorGUILayout.BeginScrollView(prefabScroll, GUILayout.Height(100));
|
|
for (int i = draggedAssets.Count - 1; i >= 0; i--)
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
draggedAssets[i] = EditorGUILayout.ObjectField(draggedAssets[i], typeof(UnityEngine.Object), false);
|
|
if (GUILayout.Button("移除", GUILayout.Width(60)))
|
|
draggedAssets.RemoveAt(i);
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
EditorGUILayout.EndScrollView();
|
|
}
|
|
|
|
private void CollectDependencies()
|
|
{
|
|
dependenciesByType.Clear();
|
|
typeToggle.Clear();
|
|
typeFoldout.Clear();
|
|
textureUsedByMaterials.Clear();
|
|
matsFoldout.Clear();
|
|
|
|
if (draggedAssets.Count == 0)
|
|
{
|
|
EditorUtility.DisplayDialog("提示", "请先拖入至少一个资源。", "确定");
|
|
return;
|
|
}
|
|
|
|
HashSet<string> allDeps = new HashSet<string>();
|
|
|
|
foreach (var obj in draggedAssets)
|
|
{
|
|
string origPath = AssetDatabase.GetAssetPath(obj);
|
|
var deps = AssetDatabase.GetDependencies(origPath, true);
|
|
foreach (var dep in deps)
|
|
{
|
|
if (!dep.StartsWith("Assets/") || dep.StartsWith("Assets/Editor/") || dep.Contains("Packages/"))
|
|
continue;
|
|
|
|
allDeps.Add(dep);
|
|
}
|
|
}
|
|
|
|
// 构建贴图->材质引用映射
|
|
foreach (var dep in allDeps)
|
|
{
|
|
Type type = AssetDatabase.GetMainAssetTypeAtPath(dep);
|
|
if (type == typeof(Material))
|
|
{
|
|
Material mat = AssetDatabase.LoadAssetAtPath<Material>(dep);
|
|
if (mat == null) continue;
|
|
|
|
Shader shader = mat.shader;
|
|
int propertyCount = ShaderUtil.GetPropertyCount(shader);
|
|
for (int i = 0; i < propertyCount; i++)
|
|
{
|
|
if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv)
|
|
{
|
|
string propName = ShaderUtil.GetPropertyName(shader, i);
|
|
Texture tex = mat.GetTexture(propName);
|
|
if (tex != null)
|
|
{
|
|
string texPath = AssetDatabase.GetAssetPath(tex);
|
|
if (!string.IsNullOrEmpty(texPath))
|
|
{
|
|
if (!textureUsedByMaterials.TryGetValue(texPath, out var mats))
|
|
{
|
|
mats = new HashSet<string>();
|
|
textureUsedByMaterials[texPath] = mats;
|
|
}
|
|
mats.Add(dep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var dep in allDeps)
|
|
{
|
|
Type type = AssetDatabase.GetMainAssetTypeAtPath(dep);
|
|
string folder = GetTargetFolder(type, dep);
|
|
|
|
if (!dependenciesByType.ContainsKey(folder))
|
|
dependenciesByType[folder] = new Dictionary<string, bool>();
|
|
|
|
if (!dependenciesByType[folder].ContainsKey(dep))
|
|
dependenciesByType[folder][dep] = true;
|
|
|
|
if (!typeToggle.ContainsKey(folder))
|
|
typeToggle[folder] = true;
|
|
|
|
if (!typeFoldout.ContainsKey(folder))
|
|
typeFoldout[folder] = false;
|
|
}
|
|
}
|
|
|
|
private void DrawDependenciesList()
|
|
{
|
|
if (dependenciesByType.Count == 0) return;
|
|
|
|
float availableHeight = position.height - 550f;
|
|
if (availableHeight < 150f) availableHeight = 150f;
|
|
|
|
depScroll = EditorGUILayout.BeginScrollView(depScroll, GUILayout.Height(availableHeight));
|
|
foreach (var kvp in dependenciesByType)
|
|
{
|
|
string type = kvp.Key;
|
|
var filesDict = kvp.Value;
|
|
|
|
if (!filterTypeToggle.TryGetValue(type, out bool showType) || !showType)
|
|
continue;
|
|
|
|
if (!typeFoldout.ContainsKey(type)) typeFoldout[type] = false;
|
|
if (!typeToggle.ContainsKey(type)) typeToggle[type] = true;
|
|
|
|
EditorGUILayout.BeginVertical("box");
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
typeFoldout[type] = EditorGUILayout.Foldout(typeFoldout[type], $"{type} ({filesDict.Count})", true);
|
|
|
|
if (GUILayout.Button("全选", GUILayout.Width(40)))
|
|
{
|
|
typeToggle[type] = true;
|
|
foreach (var k in filesDict.Keys.ToList())
|
|
filesDict[k] = true;
|
|
}
|
|
if (GUILayout.Button("反选", GUILayout.Width(40)))
|
|
{
|
|
bool allSelected = filesDict.Values.All(v => v);
|
|
foreach (var k in filesDict.Keys.ToList())
|
|
filesDict[k] = !allSelected;
|
|
typeToggle[type] = filesDict.Values.Any(v => v);
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
bool newTypeToggle = EditorGUILayout.ToggleLeft("选择该类型全部文件", typeToggle[type]);
|
|
if (newTypeToggle != typeToggle[type])
|
|
{
|
|
typeToggle[type] = newTypeToggle;
|
|
foreach (var k in filesDict.Keys.ToList())
|
|
filesDict[k] = newTypeToggle;
|
|
}
|
|
|
|
if (typeFoldout[type])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
foreach (var fileKey in filesDict.Keys.ToList())
|
|
{
|
|
bool selected = filesDict[fileKey];
|
|
|
|
EditorGUILayout.BeginHorizontal(GUILayout.Height(22));
|
|
|
|
GUILayout.Space(6 * (EditorGUI.indentLevel - 1));
|
|
|
|
Rect toggleRect = GUILayoutUtility.GetRect(24, 22, GUILayout.Width(24));
|
|
bool newSelected = EditorGUI.Toggle(toggleRect, selected);
|
|
if (newSelected != selected)
|
|
filesDict[fileKey] = newSelected;
|
|
|
|
if (!newSelected)
|
|
GUI.enabled = false;
|
|
|
|
string fileName = Path.GetFileName(fileKey);
|
|
GUIContent fileNameContent = new GUIContent(fileName);
|
|
Vector2 fileNameSize = EditorStyles.boldLabel.CalcSize(fileNameContent);
|
|
float fileNameWidth = Mathf.Min(fileNameSize.x, 200);
|
|
|
|
GUILayout.Space(6);
|
|
|
|
Rect fileNameRect = GUILayoutUtility.GetRect(fileNameWidth, 22, GUILayout.Width(fileNameWidth));
|
|
GUI.Label(fileNameRect, fileName, EditorStyles.boldLabel);
|
|
|
|
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && fileNameRect.Contains(Event.current.mousePosition))
|
|
{
|
|
var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(fileKey);
|
|
if (obj != null)
|
|
{
|
|
EditorGUIUtility.PingObject(obj);
|
|
Event.current.Use();
|
|
}
|
|
}
|
|
else if (Event.current.type == EventType.ContextClick && fileNameRect.Contains(Event.current.mousePosition))
|
|
{
|
|
Event.current.Use();
|
|
GenericMenu menu = new GenericMenu();
|
|
menu.AddItem(new GUIContent("复制资源名称"), false, () =>
|
|
{
|
|
GUIUtility.systemCopyBuffer = fileName;
|
|
});
|
|
menu.ShowAsContext();
|
|
}
|
|
|
|
GUILayout.Label(fileKey, EditorStyles.miniLabel, GUILayout.ExpandWidth(true));
|
|
|
|
if (GUILayout.Button("定位", GUILayout.Width(60)))
|
|
{
|
|
var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(fileKey);
|
|
if (obj != null)
|
|
EditorGUIUtility.PingObject(obj);
|
|
}
|
|
|
|
if (!newSelected)
|
|
GUI.enabled = true;
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if (textureUsedByMaterials.TryGetValue(fileKey, out var mats) && mats.Count > 0)
|
|
{
|
|
if (!matsFoldout.ContainsKey(fileKey)) matsFoldout[fileKey] = false;
|
|
|
|
Rect foldoutRect = GUILayoutUtility.GetRect(130, EditorGUIUtility.singleLineHeight);
|
|
matsFoldout[fileKey] = EditorGUI.Foldout(foldoutRect, matsFoldout[fileKey], $"引用于 {mats.Count} 个材质", true);
|
|
|
|
if (matsFoldout[fileKey])
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
foreach (var matPath in mats)
|
|
{
|
|
Rect matRect = GUILayoutUtility.GetRect(20, 18);
|
|
GUILayout.Space(6 * (EditorGUI.indentLevel - 1));
|
|
if (GUI.Button(matRect, matPath, EditorStyles.linkLabel))
|
|
{
|
|
var matObj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(matPath);
|
|
if (matObj != null)
|
|
EditorGUIUtility.PingObject(matObj);
|
|
}
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
}
|
|
}
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
EditorGUILayout.EndScrollView();
|
|
}
|
|
|
|
private void DrawSizeEstimate()
|
|
{
|
|
long totalBytes = 0;
|
|
foreach (var kvp in dependenciesByType)
|
|
{
|
|
foreach (var path in kvp.Value.Keys)
|
|
{
|
|
string absPath = Path.Combine(Application.dataPath, path.Substring("Assets/".Length));
|
|
if (File.Exists(absPath))
|
|
{
|
|
totalBytes += new FileInfo(absPath).Length;
|
|
}
|
|
}
|
|
}
|
|
EditorGUILayout.LabelField($"预估待移动资源大小: {totalBytes / 1024f / 1024f:F2} MB");
|
|
}
|
|
|
|
private void DrawTargetFolderSelector()
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.LabelField("目标根目录:", GUILayout.Width(80));
|
|
|
|
GUIStyle boxStyle = GUI.skin.box;
|
|
Rect dragRect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth - 100, EditorGUIUtility.singleLineHeight + 6);
|
|
GUI.Box(dragRect, "", boxStyle);
|
|
|
|
GUI.Label(dragRect, string.IsNullOrEmpty(targetRootFolder) ? "<拖入目标根目录文件夹>" : targetRootFolder, EditorStyles.label);
|
|
|
|
Event evt = Event.current;
|
|
if ((evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform) && dragRect.Contains(evt.mousePosition))
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
|
|
|
if (evt.type == EventType.DragPerform)
|
|
{
|
|
DragAndDrop.AcceptDrag();
|
|
|
|
if (DragAndDrop.objectReferences.Length > 0)
|
|
{
|
|
UnityEngine.Object obj = DragAndDrop.objectReferences[0];
|
|
string path = AssetDatabase.GetAssetPath(obj);
|
|
if (AssetDatabase.IsValidFolder(path))
|
|
{
|
|
targetRootFolder = path;
|
|
evt.Use();
|
|
EditorGUIUtility.ExitGUI();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (DragAndDrop.paths != null && DragAndDrop.paths.Length > 0)
|
|
{
|
|
string firstPath = DragAndDrop.paths[0];
|
|
if (Directory.Exists(firstPath))
|
|
{
|
|
string absDataPath = Application.dataPath.Replace("/", "\\");
|
|
string absFirstPath = firstPath.Replace("/", "\\");
|
|
|
|
if (absFirstPath.StartsWith(absDataPath))
|
|
{
|
|
string relativePath = "Assets" + absFirstPath.Substring(absDataPath.Length);
|
|
targetRootFolder = relativePath.Replace("\\", "/");
|
|
evt.Use();
|
|
EditorGUIUtility.ExitGUI();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
evt.Use();
|
|
}
|
|
else
|
|
{
|
|
evt.Use();
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawActionButtons()
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("整理", GUILayout.Height(30))) MoveSelectedDependencies();
|
|
if (GUILayout.Button("导出 UnityPackage", GUILayout.Height(30))) ExportPackage();
|
|
if (GUILayout.Button("撤销", GUILayout.Height(30))) ShowUndoMenu();
|
|
if (!string.IsNullOrEmpty(lastOrganizeAbsPath) && GUILayout.Button("打开生成路径", GUILayout.Height(30)))
|
|
EditorUtility.RevealInFinder(lastOrganizeAbsPath);
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawUndoHistory()
|
|
{
|
|
if (undoManifests.Count == 0)
|
|
{
|
|
EditorGUILayout.LabelField("暂无撤销历史");
|
|
return;
|
|
}
|
|
EditorGUILayout.LabelField("撤销历史记录:");
|
|
foreach (var kvp in undoManifests.Reverse())
|
|
{
|
|
string label = DateTimeOffset.FromUnixTimeMilliseconds(kvp.Key).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
|
EditorGUILayout.LabelField(label);
|
|
}
|
|
}
|
|
|
|
private void ShowUndoMenu()
|
|
{
|
|
if (undoManifests.Count == 0)
|
|
{
|
|
EditorUtility.DisplayDialog("提示", "没有可用的撤销记录。", "确定");
|
|
return;
|
|
}
|
|
GenericMenu menu = new GenericMenu();
|
|
foreach (var kvp in undoManifests.Reverse())
|
|
{
|
|
string label = DateTimeOffset.FromUnixTimeMilliseconds(kvp.Key).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
|
string path = kvp.Value;
|
|
menu.AddItem(new GUIContent(label), false, () =>
|
|
{
|
|
UndoMoveAssets(path);
|
|
});
|
|
}
|
|
menu.ShowAsContext();
|
|
}
|
|
|
|
private void UndoMoveAssets(string manifestPath)
|
|
{
|
|
if (!File.Exists(manifestPath))
|
|
{
|
|
EditorUtility.DisplayDialog("错误", "撤销文件不存在。", "确定");
|
|
RemoveManifestByPath(manifestPath);
|
|
return;
|
|
}
|
|
|
|
string json = File.ReadAllText(manifestPath);
|
|
var manifest = JsonUtility.FromJson<MoveManifest>(json);
|
|
if (manifest == null || manifest.entries == null)
|
|
{
|
|
EditorUtility.DisplayDialog("错误", "撤销文件格式错误。", "确定");
|
|
return;
|
|
}
|
|
|
|
AssetDatabase.StartAssetEditing();
|
|
try
|
|
{
|
|
foreach (var e in manifest.entries.Reverse())
|
|
{
|
|
if (AssetDatabase.MoveAsset(e.destPath, e.originalPath).Length == 0)
|
|
Debug.Log($"已恢复 {e.destPath}");
|
|
else
|
|
Debug.LogWarning($"恢复失败或路径冲突: {e.destPath}");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
AssetDatabase.StopAssetEditing();
|
|
AssetDatabase.Refresh();
|
|
}
|
|
File.Delete(manifestPath);
|
|
RemoveManifestByPath(manifestPath);
|
|
EditorUtility.DisplayDialog("撤销完成", "资源已恢复。", "确定");
|
|
}
|
|
|
|
private void RemoveManifestByPath(string manifestPath)
|
|
{
|
|
var keysToRemove = undoManifests.Where(kvp => kvp.Value == manifestPath).Select(kvp => kvp.Key).ToList();
|
|
foreach (var key in keysToRemove)
|
|
undoManifests.Remove(key);
|
|
}
|
|
|
|
private void LoadUndoManifests()
|
|
{
|
|
undoManifests.Clear();
|
|
if (!AssetDatabase.IsValidFolder(UndoManifestDir))
|
|
return;
|
|
|
|
string fullDir = Path.Combine(Application.dataPath, "Editor/DependencyOrganizerUndo");
|
|
if (!Directory.Exists(fullDir))
|
|
return;
|
|
|
|
foreach (var file in Directory.GetFiles(fullDir, UndoManifestPrefix + "*.json"))
|
|
{
|
|
try
|
|
{
|
|
string fileName = Path.GetFileNameWithoutExtension(file);
|
|
string timestampStr = fileName.Substring(UndoManifestPrefix.Length);
|
|
if (long.TryParse(timestampStr, out long ts))
|
|
undoManifests[ts] = file;
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
private void MoveSelectedDependencies()
|
|
{
|
|
if (draggedAssets.Count == 0)
|
|
{
|
|
EditorUtility.DisplayDialog("提示", "请先拖入至少一个资源。", "确定");
|
|
return;
|
|
}
|
|
if (dependenciesByType.Count == 0)
|
|
{
|
|
EditorUtility.DisplayDialog("提示", "请先收集依赖资源。", "确定");
|
|
return;
|
|
}
|
|
|
|
if (!AssetDatabase.IsValidFolder(UndoManifestDir))
|
|
AssetDatabase.CreateFolder("Assets/Editor", "DependencyOrganizerUndo");
|
|
|
|
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
string manifestFile = $"{UndoManifestDir}/{UndoManifestPrefix}{timestamp}.json";
|
|
|
|
List<MoveEntry> moves = new List<MoveEntry>();
|
|
List<(string originalPath, string destPath)> movesToDo = new List<(string, string)>();
|
|
|
|
AssetDatabase.StartAssetEditing();
|
|
try
|
|
{
|
|
foreach (var obj in draggedAssets)
|
|
{
|
|
string origPath = AssetDatabase.GetAssetPath(obj);
|
|
string root;
|
|
if (!string.IsNullOrEmpty(targetRootFolder) && AssetDatabase.IsValidFolder(targetRootFolder))
|
|
{
|
|
root = targetRootFolder;
|
|
}
|
|
else
|
|
{
|
|
string exportBase = "Assets/export";
|
|
if (!AssetDatabase.IsValidFolder(exportBase))
|
|
AssetDatabase.CreateFolder("Assets", "export");
|
|
string name = Path.GetFileNameWithoutExtension(origPath);
|
|
root = $"{exportBase}/{name}";
|
|
if (!AssetDatabase.IsValidFolder(root))
|
|
AssetDatabase.CreateFolder(exportBase, name);
|
|
}
|
|
lastOrganizeAbsPath = Path.GetFullPath(root);
|
|
|
|
var deps = AssetDatabase.GetDependencies(origPath, true);
|
|
|
|
foreach (var dep in deps)
|
|
{
|
|
if (!dep.StartsWith("Assets/") || dep.StartsWith("Assets/Editor/") || dep.Contains("Packages/"))
|
|
continue;
|
|
if (movesToDo.Any(m => m.destPath == dep) || dep.StartsWith(root + "/"))
|
|
continue;
|
|
|
|
Type type = AssetDatabase.GetMainAssetTypeAtPath(dep);
|
|
string folder = GetTargetFolder(type, dep);
|
|
|
|
if (!dependenciesByType.TryGetValue(folder, out var filesDict))
|
|
continue;
|
|
if (!filesDict.TryGetValue(dep, out bool moveThis) || !moveThis)
|
|
continue;
|
|
|
|
string dstDir = $"{root}/{folder}";
|
|
if (!AssetDatabase.IsValidFolder(dstDir))
|
|
AssetDatabase.CreateFolder(root, folder);
|
|
|
|
string dst = GetUniqueAssetPath($"{dstDir}/{Path.GetFileName(dep)}");
|
|
movesToDo.Add((dep, dst));
|
|
}
|
|
}
|
|
|
|
int total = movesToDo.Count;
|
|
for (int i = 0; i < total; i++)
|
|
{
|
|
var (src, dst) = movesToDo[i];
|
|
EditorUtility.DisplayProgressBar("移动资源", $"正在移动 {Path.GetFileName(src)} ({i + 1}/{total})", (float)i / total);
|
|
string err = AssetDatabase.MoveAsset(src, dst);
|
|
if (string.IsNullOrEmpty(err))
|
|
moves.Add(new MoveEntry(src, dst));
|
|
else
|
|
Debug.LogWarning($"移动失败: {src} -> {dst},错误:{err}");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
EditorUtility.ClearProgressBar();
|
|
AssetDatabase.StopAssetEditing();
|
|
AssetDatabase.Refresh();
|
|
}
|
|
|
|
File.WriteAllText(manifestFile, JsonUtility.ToJson(new MoveManifest { entries = moves.ToArray() }, true));
|
|
AssetDatabase.ImportAsset(manifestFile);
|
|
LoadUndoManifests();
|
|
|
|
EditorUtility.DisplayDialog("整理完成", $"共移动 {moves.Count} 个资源,存放于 {(!string.IsNullOrEmpty(targetRootFolder) ? targetRootFolder : "Assets/export")}", "确定");
|
|
}
|
|
|
|
private string GetTargetFolder(Type type, string path)
|
|
{
|
|
string ext = Path.GetExtension(path).ToLowerInvariant();
|
|
if (new[] { ".cs" }.Contains(ext)) return "Scripts";
|
|
if (new[] { ".fbx", ".obj", ".blend" }.Contains(ext)) return "Models";
|
|
if (new[] { ".anim" }.Contains(ext) || type == typeof(AnimationClip)) return "Animations";
|
|
if (new[] { ".controller" }.Contains(ext) || type == typeof(UnityEditor.Animations.AnimatorController)) return "Animators";
|
|
if (new[] { ".shader", ".shadergraph" }.Contains(ext) || type == typeof(Shader)) return "Shaders";
|
|
if (new[] { ".png", ".jpg", ".jpeg", ".tga", ".psd", ".exr" }.Contains(ext) || type == typeof(Texture2D)) return "Textures";
|
|
if (new[] { ".wav", ".mp3", ".ogg" }.Contains(ext) || type == typeof(AudioClip)) return "Audio";
|
|
if (ext == ".mat" || type == typeof(Material)) return "Materials";
|
|
if (ext == ".physicmaterial" || type == typeof(PhysicMaterial)) return "Physics";
|
|
if (ext == ".prefab" || type == typeof(GameObject)) return "Prefabs";
|
|
return "Others";
|
|
}
|
|
|
|
private string GetUniqueAssetPath(string desiredPath)
|
|
{
|
|
string path = desiredPath;
|
|
string dir = Path.GetDirectoryName(desiredPath);
|
|
string filename = Path.GetFileNameWithoutExtension(desiredPath);
|
|
string ext = Path.GetExtension(desiredPath);
|
|
int count = 1;
|
|
|
|
while (AssetPathExists(path))
|
|
{
|
|
path = $"{dir}/{filename}_{count}{ext}";
|
|
count++;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
private bool AssetPathExists(string path)
|
|
{
|
|
return AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path) != null;
|
|
}
|
|
|
|
private void ExportPackage()
|
|
{
|
|
string exportRel = !string.IsNullOrEmpty(targetRootFolder) && AssetDatabase.IsValidFolder(targetRootFolder)
|
|
? targetRootFolder : "Assets/export";
|
|
if (!AssetDatabase.IsValidFolder(exportRel))
|
|
{
|
|
EditorUtility.DisplayDialog("错误", $"导出目录无效:{exportRel}", "确定");
|
|
return;
|
|
}
|
|
var guids = AssetDatabase.FindAssets("", new[] { exportRel });
|
|
var paths = guids.Select(AssetDatabase.GUIDToAssetPath).ToArray();
|
|
string save = EditorUtility.SaveFilePanel("保存 UnityPackage", Application.dataPath, "Package.unitypackage", "unitypackage");
|
|
if (string.IsNullOrEmpty(save)) return;
|
|
AssetDatabase.ExportPackage(paths, save, ExportPackageOptions.Default);
|
|
EditorUtility.DisplayDialog("导出成功", $"已导出 {paths.Length} 个资源到 {save}", "确定");
|
|
}
|
|
|
|
[Serializable]
|
|
private class MoveEntry
|
|
{
|
|
public string originalPath;
|
|
public string destPath;
|
|
public MoveEntry(string o, string d) { originalPath = o; destPath = d; }
|
|
}
|
|
|
|
[Serializable]
|
|
private class MoveManifest
|
|
{
|
|
public MoveEntry[] entries;
|
|
}
|
|
}
|