Files
tools/Unity C++脚本/DependencyOrganizer.cs
T
2025-07-31 16:05:55 +08:00

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;
}
}