New file |
| | |
| | | using System;
|
| | | using System.Collections.Generic;
|
| | | using System.IO;
|
| | | using System.Linq;
|
| | | using System.Security.Cryptography;
|
| | | using System.Threading.Tasks;
|
| | | using UnityEditor;
|
| | | using UnityEngine;
|
| | |
|
| | | namespace ProjSG.Editor.UI
|
| | | {
|
| | | public class AssetDuplicateCheck : EditorWindow
|
| | | {
|
| | | private Vector2 scrollPosition;
|
| | | private bool showSettings = true;
|
| | | private bool showResults = false;
|
| | | private bool showFullPath = false;
|
| | | |
| | | private string[] searchFolders = new string[] { "Assets" };
|
| | | private string[] searchExtensions = new string[] { ".png", ".jpg", ".jpeg", ".tga", ".psd", ".tif", ".tiff" };
|
| | | private bool includeSubfolders = true;
|
| | | private bool compareByContent = true;
|
| | | private bool compareByName = false;
|
| | | private int minFileSize = 10;
|
| | | |
| | | private Dictionary<string, List<string>> duplicateGroups = new Dictionary<string, List<string>>();
|
| | | private int totalFilesScanned = 0;
|
| | | private int totalDuplicatesFound = 0;
|
| | | private long totalSpaceSaved = 0;
|
| | | |
| | | private bool keepNewestFiles = true;
|
| | | private bool createBackup = true;
|
| | | private string backupFolder = "";
|
| | | private Dictionary<string, bool> selectedForRemoval = new Dictionary<string, bool>();
|
| | | |
| | | private bool isProcessing = false;
|
| | | private float progress = 0f;
|
| | | private string progressInfo = "";
|
| | |
|
| | | [MenuItem("工具/资源管理/资源查重工具")]
|
| | | public static void ShowWindow()
|
| | | {
|
| | | AssetDuplicateCheck window = GetWindow<AssetDuplicateCheck>("资源查重工具");
|
| | | window.minSize = new Vector2(600, 400);
|
| | | window.Show();
|
| | | }
|
| | |
|
| | | private string GetAssetPath(string filePath)
|
| | | {
|
| | | string fullPath = Path.GetFullPath(filePath);
|
| | | string projectDataPath = Path.GetFullPath(Application.dataPath);
|
| | |
|
| | | if (fullPath.StartsWith(projectDataPath, StringComparison.OrdinalIgnoreCase))
|
| | | {
|
| | | return "Assets" + fullPath.Substring(projectDataPath.Length).Replace('\\', '/');
|
| | | }
|
| | | |
| | | string projectRootPath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
| | | if (fullPath.StartsWith(projectRootPath, StringComparison.OrdinalIgnoreCase))
|
| | | {
|
| | | string relativeToProjectRoot = fullPath.Substring(projectRootPath.Length);
|
| | | if (relativeToProjectRoot.StartsWith("/") || relativeToProjectRoot.StartsWith("\\"))
|
| | | {
|
| | | relativeToProjectRoot = relativeToProjectRoot.Substring(1);
|
| | | }
|
| | | return relativeToProjectRoot.Replace('\\', '/');
|
| | | }
|
| | |
|
| | | Debug.LogWarning($"路径 '{filePath}' 无法可靠地转换为项目相对的资产路径。将返回原始路径(已规范化斜杠)。");
|
| | | return filePath.Replace('\\', '/');
|
| | | }
|
| | |
|
| | | private void OnGUI()
|
| | | {
|
| | | GUILayout.Label("资源查重工具", EditorStyles.boldLabel);
|
| | | EditorGUILayout.Space();
|
| | |
|
| | | scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
| | |
|
| | | showSettings = EditorGUILayout.Foldout(showSettings, "搜索设置", true);
|
| | | if (showSettings)
|
| | | {
|
| | | DrawSettings();
|
| | | }
|
| | |
|
| | | EditorGUILayout.Space();
|
| | |
|
| | | if (duplicateGroups.Count > 0)
|
| | | {
|
| | | showResults = EditorGUILayout.Foldout(showResults, $"搜索结果 (找到 {totalDuplicatesFound} 个重复资源,可节省 {totalSpaceSaved / 1024f:F2} MB)", true);
|
| | | if (showResults)
|
| | | {
|
| | | DrawResults();
|
| | | }
|
| | | }
|
| | |
|
| | | EditorGUILayout.EndScrollView();
|
| | |
|
| | | EditorGUILayout.Space();
|
| | |
|
| | | if (isProcessing)
|
| | | {
|
| | | EditorGUI.ProgressBar(EditorGUILayout.GetControlRect(false, 20f), progress, progressInfo);
|
| | | if (GUILayout.Button("取消"))
|
| | | {
|
| | | isProcessing = false;
|
| | | }
|
| | | }
|
| | | else
|
| | | {
|
| | | EditorGUILayout.BeginHorizontal();
|
| | | if (GUILayout.Button("扫描重复资源", GUILayout.Height(30)))
|
| | | {
|
| | | FindDuplicateAssets();
|
| | | }
|
| | |
|
| | | GUI.enabled = duplicateGroups.Count > 0;
|
| | | if (GUILayout.Button("处理选中的重复资源", GUILayout.Height(30)))
|
| | | {
|
| | | ProcessDuplicateAssets();
|
| | | }
|
| | | GUI.enabled = true;
|
| | | EditorGUILayout.EndHorizontal();
|
| | | }
|
| | | }
|
| | |
|
| | | private void DrawSettings()
|
| | | {
|
| | | EditorGUI.indentLevel++;
|
| | |
|
| | | EditorGUILayout.LabelField("搜索文件夹:");
|
| | | EditorGUILayout.BeginHorizontal();
|
| | | string displaySearchFolder = searchFolders.Length > 0 ? searchFolders[0] : "Assets";
|
| | | EditorGUILayout.SelectableLabel(displaySearchFolder, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
|
| | | |
| | | if (GUILayout.Button("选择文件夹", GUILayout.Width(100)))
|
| | | {
|
| | | string folderPath = EditorUtility.OpenFolderPanel("选择要搜索的文件夹", Application.dataPath, "");
|
| | | if (!string.IsNullOrEmpty(folderPath))
|
| | | {
|
| | | string relativePath = GetAssetPath(folderPath);
|
| | | if (!string.IsNullOrEmpty(relativePath) && |
| | | (relativePath.StartsWith("Assets", StringComparison.OrdinalIgnoreCase) || |
| | | relativePath.StartsWith("Packages", StringComparison.OrdinalIgnoreCase) || |
| | | Directory.Exists(Path.Combine(Path.GetFullPath(Path.Combine(Application.dataPath, "..")), relativePath))))
|
| | | {
|
| | | searchFolders = new string[] { relativePath };
|
| | | }
|
| | | else
|
| | | {
|
| | | Debug.LogWarning($"选择的文件夹 '{folderPath}' 不在项目 Assets 或 Packages 内,或无法转换为有效的项目相对路径。");
|
| | | }
|
| | | }
|
| | | }
|
| | | EditorGUILayout.EndHorizontal();
|
| | |
|
| | | EditorGUILayout.LabelField("文件类型:");
|
| | | string extensionsString = string.Join(", ", searchExtensions);
|
| | | extensionsString = EditorGUILayout.TextField(extensionsString);
|
| | | searchExtensions = extensionsString.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
|
| | | .Select(e => e.Trim().ToLower())
|
| | | .Select(e => e.StartsWith(".") ? e : "." + e)
|
| | | .Distinct()
|
| | | .ToArray();
|
| | |
|
| | | EditorGUILayout.Space();
|
| | | EditorGUILayout.LabelField("搜索选项:");
|
| | | includeSubfolders = EditorGUILayout.Toggle("包含子文件夹", includeSubfolders);
|
| | | compareByContent = EditorGUILayout.Toggle("按内容比较", compareByContent);
|
| | | compareByName = EditorGUILayout.Toggle("按名称比较", compareByName);
|
| | | minFileSize = EditorGUILayout.IntField("最小文件大小 (KB)", minFileSize);
|
| | | |
| | | EditorGUILayout.Space();
|
| | | EditorGUILayout.LabelField("处理选项:");
|
| | | keepNewestFiles = EditorGUILayout.Toggle("保留最新文件", keepNewestFiles);
|
| | | createBackup = EditorGUILayout.Toggle("创建备份", createBackup);
|
| | | |
| | | if (createBackup)
|
| | | {
|
| | | EditorGUILayout.BeginHorizontal();
|
| | | backupFolder = EditorGUILayout.TextField("备份文件夹:", backupFolder);
|
| | | if (GUILayout.Button("浏览", GUILayout.Width(60)))
|
| | | {
|
| | | string path = EditorUtility.OpenFolderPanel("选择备份文件夹", backupFolder, "");
|
| | | if (!string.IsNullOrEmpty(path))
|
| | | {
|
| | | backupFolder = path;
|
| | | }
|
| | | }
|
| | | EditorGUILayout.EndHorizontal();
|
| | | }
|
| | |
|
| | | EditorGUI.indentLevel--;
|
| | | }
|
| | |
|
| | | private void DrawResults()
|
| | | {
|
| | | EditorGUI.indentLevel++;
|
| | | |
| | | EditorGUILayout.BeginHorizontal();
|
| | | showFullPath = EditorGUILayout.Toggle("显示完整路径", showFullPath);
|
| | | |
| | | GUILayout.FlexibleSpace();
|
| | | |
| | | if (GUILayout.Button("全选", GUILayout.Width(60)))
|
| | | {
|
| | | SelectAll(true);
|
| | | }
|
| | | |
| | | if (GUILayout.Button("全不选", GUILayout.Width(60)))
|
| | | {
|
| | | SelectAll(false);
|
| | | }
|
| | | |
| | | if (GUILayout.Button("智能选择", GUILayout.Width(80)))
|
| | | {
|
| | | SmartSelect();
|
| | | }
|
| | | |
| | | EditorGUILayout.EndHorizontal();
|
| | | |
| | | EditorGUILayout.Space();
|
| | | |
| | | int groupIndex = 0;
|
| | | foreach (var group in duplicateGroups)
|
| | | {
|
| | | if (group.Value.Count <= 1) continue;
|
| | | |
| | | groupIndex++;
|
| | | EditorGUILayout.BeginVertical(GUI.skin.box);
|
| | | |
| | | string groupTitle = $"组 {groupIndex}: {group.Value.Count} 个文件";
|
| | | if (group.Value.Count > 0)
|
| | | {
|
| | | string fileName = Path.GetFileName(group.Value[0]);
|
| | | groupTitle += $" - {fileName}";
|
| | | }
|
| | | |
| | | EditorGUILayout.LabelField(groupTitle, EditorStyles.boldLabel);
|
| | | |
| | | foreach (string filePath in group.Value)
|
| | | {
|
| | | EditorGUILayout.BeginHorizontal();
|
| | | |
| | | bool isSelected = false;
|
| | | if (selectedForRemoval.ContainsKey(filePath))
|
| | | {
|
| | | isSelected = selectedForRemoval[filePath];
|
| | | }
|
| | | |
| | | bool newSelected = EditorGUILayout.Toggle(isSelected, GUILayout.Width(20));
|
| | | if (newSelected != isSelected)
|
| | | {
|
| | | selectedForRemoval[filePath] = newSelected;
|
| | | }
|
| | | |
| | | string displayPath = showFullPath ? filePath : Path.GetFileName(filePath);
|
| | | |
| | | FileInfo fileInfo = new FileInfo(Path.Combine(Application.dataPath, "..", filePath));
|
| | | string fileDetails = $"{displayPath} - {fileInfo.Length / 1024} KB";
|
| | | |
| | | if (fileInfo.Exists)
|
| | | {
|
| | | fileDetails += $" - {fileInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")}";
|
| | | }
|
| | | |
| | | EditorGUILayout.LabelField(fileDetails);
|
| | | |
| | | if (GUILayout.Button("选择", GUILayout.Width(60)))
|
| | | {
|
| | | Selection.activeObject = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(filePath);
|
| | | }
|
| | | |
| | | EditorGUILayout.EndHorizontal();
|
| | | }
|
| | | |
| | | EditorGUILayout.EndVertical();
|
| | | EditorGUILayout.Space();
|
| | | }
|
| | | |
| | | EditorGUI.indentLevel--;
|
| | | }
|
| | |
|
| | | private void SelectAll(bool select)
|
| | | {
|
| | | foreach (var group in duplicateGroups)
|
| | | {
|
| | | if (group.Value.Count <= 1) continue;
|
| | | |
| | | foreach (string filePath in group.Value)
|
| | | {
|
| | | selectedForRemoval[filePath] = select;
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | private void SmartSelect()
|
| | | {
|
| | | foreach (var group in duplicateGroups)
|
| | | {
|
| | | if (group.Value.Count <= 1) continue;
|
| | | |
| | | string fileToKeep = null;
|
| | | |
| | | if (keepNewestFiles)
|
| | | {
|
| | | fileToKeep = group.Value
|
| | | .OrderByDescending(f => File.GetLastWriteTime(Path.Combine(Application.dataPath, "..", f)))
|
| | | .FirstOrDefault();
|
| | | }
|
| | | else
|
| | | {
|
| | | fileToKeep = group.Value.FirstOrDefault();
|
| | | }
|
| | | |
| | | foreach (string filePath in group.Value)
|
| | | {
|
| | | selectedForRemoval[filePath] = (filePath != fileToKeep);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // 计算文件的MD5哈希值
|
| | | private string GetFileHash(string filePath)
|
| | | {
|
| | | try
|
| | | {
|
| | | using (var md5 = MD5.Create())
|
| | | using (var stream = File.OpenRead(filePath))
|
| | | {
|
| | | byte[] hash = md5.ComputeHash(stream);
|
| | | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
| | | }
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | Debug.LogError($"计算文件哈希值时出错: {filePath}, {ex.Message}");
|
| | | return string.Empty;
|
| | | }
|
| | | }
|
| | |
|
| | | private async void FindDuplicateAssets()
|
| | | {
|
| | | isProcessing = true;
|
| | | progress = 0f;
|
| | | progressInfo = "正在收集文件...";
|
| | | |
| | | // 清空之前的结果
|
| | | duplicateGroups.Clear();
|
| | | selectedForRemoval.Clear();
|
| | | totalFilesScanned = 0;
|
| | | totalDuplicatesFound = 0;
|
| | | totalSpaceSaved = 0;
|
| | | |
| | | try
|
| | | {
|
| | | // 收集所有文件
|
| | | List<string> allFiles = new List<string>();
|
| | | foreach (string folder in searchFolders)
|
| | | {
|
| | | if (string.IsNullOrEmpty(folder)) continue;
|
| | | |
| | | string fullPath = Path.Combine(Application.dataPath, "..", folder);
|
| | | if (!Directory.Exists(fullPath)) continue;
|
| | | |
| | | string[] files = Directory.GetFiles(fullPath, "*.*", includeSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
| | | .Where(f => searchExtensions.Contains(Path.GetExtension(f).ToLower()))
|
| | | .Select(f => GetAssetPath(f))
|
| | | .ToArray();
|
| | | |
| | | allFiles.AddRange(files);
|
| | | }
|
| | | |
| | | totalFilesScanned = allFiles.Count;
|
| | | progressInfo = $"找到 {totalFilesScanned} 个文件,正在分析...";
|
| | | progress = 0.1f;
|
| | | Repaint();
|
| | | |
| | | // 按内容或名称分组
|
| | | Dictionary<string, List<string>> groups = new Dictionary<string, List<string>>();
|
| | | |
| | | for (int i = 0; i < allFiles.Count; i++)
|
| | | {
|
| | | string filePath = allFiles[i];
|
| | | string fullPath = Path.Combine(Application.dataPath, "..", filePath);
|
| | | |
| | | // 检查文件大小
|
| | | FileInfo fileInfo = new FileInfo(fullPath);
|
| | | if (!fileInfo.Exists || fileInfo.Length < minFileSize * 1024) continue;
|
| | | |
| | | string key = "";
|
| | | |
| | | if (compareByContent)
|
| | | {
|
| | | // 计算文件哈希值
|
| | | key = GetFileHash(fullPath);
|
| | | }
|
| | | else if (compareByName)
|
| | | {
|
| | | // 使用文件名作为键
|
| | | key = Path.GetFileNameWithoutExtension(filePath);
|
| | | }
|
| | | |
| | | if (string.IsNullOrEmpty(key)) continue;
|
| | | |
| | | if (!groups.ContainsKey(key))
|
| | | {
|
| | | groups[key] = new List<string>();
|
| | | }
|
| | | |
| | | groups[key].Add(filePath);
|
| | | |
| | | // 更新进度
|
| | | if (i % 10 == 0)
|
| | | {
|
| | | progress = 0.1f + 0.8f * ((float)i / allFiles.Count);
|
| | | progressInfo = $"正在分析文件 {i+1}/{allFiles.Count}...";
|
| | | Repaint();
|
| | | await Task.Delay(1);
|
| | | }
|
| | | }
|
| | | |
| | | // 过滤出重复组
|
| | | foreach (var group in groups)
|
| | | {
|
| | | if (group.Value.Count > 1)
|
| | | {
|
| | | duplicateGroups[group.Key] = group.Value;
|
| | | totalDuplicatesFound += group.Value.Count - 1;
|
| | | |
| | | // 计算可节省空间
|
| | | string firstFile = group.Value[0];
|
| | | string fullPath = Path.Combine(Application.dataPath, "..", firstFile);
|
| | | FileInfo fileInfo = new FileInfo(fullPath);
|
| | | if (fileInfo.Exists)
|
| | | {
|
| | | totalSpaceSaved += (group.Value.Count - 1) * fileInfo.Length / 1024;
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | progressInfo = $"分析完成,找到 {totalDuplicatesFound} 个重复资源";
|
| | | progress = 1f;
|
| | | Repaint();
|
| | | |
| | | // 自动选择
|
| | | if (duplicateGroups.Count > 0)
|
| | | {
|
| | | SmartSelect();
|
| | | }
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | Debug.LogError($"查找重复资源时出错: {ex.Message}\n{ex.StackTrace}");
|
| | | EditorUtility.DisplayDialog("错误", $"查找重复资源时出错: {ex.Message}", "确定");
|
| | | }
|
| | | finally
|
| | | {
|
| | | isProcessing = false;
|
| | | Repaint();
|
| | | }
|
| | | }
|
| | |
|
| | | private async void ProcessDuplicateAssets()
|
| | | {
|
| | | isProcessing = true;
|
| | | progress = 0f;
|
| | | progressInfo = "正在准备处理...";
|
| | | Repaint();
|
| | |
|
| | | // 统计要处理的文件数量
|
| | | int totalToProcess = selectedForRemoval.Count(pair => pair.Value);
|
| | | if (totalToProcess == 0)
|
| | | {
|
| | | EditorUtility.DisplayDialog("提示", "没有选择任何文件进行处理", "确定");
|
| | | isProcessing = false;
|
| | | return;
|
| | | }
|
| | | |
| | | // 确认操作
|
| | | bool confirm = EditorUtility.DisplayDialog("确认操作", |
| | | $"将处理 {totalToProcess} 个重复资源。\n" +
|
| | | (createBackup ? $"备份将保存到: {backupFolder}" : "不创建备份") +
|
| | | "\n\n此操作不可撤销,是否继续?", |
| | | "继续", "取消");
|
| | | |
| | | if (!confirm)
|
| | | {
|
| | | isProcessing = false;
|
| | | return;
|
| | | }
|
| | | |
| | | try
|
| | | {
|
| | | // 处理每个重复组
|
| | | foreach (var group in duplicateGroups)
|
| | | {
|
| | | if (group.Value.Count <= 1) continue;
|
| | | |
| | | // 找出要保留的文件(通常是最新的文件)
|
| | | string keepFile = null;
|
| | | if (keepNewestFiles)
|
| | | {
|
| | | keepFile = group.Value
|
| | | .OrderByDescending(f => new FileInfo(Path.Combine(Application.dataPath, "..", f)).LastWriteTime)
|
| | | .FirstOrDefault();
|
| | | }
|
| | | else
|
| | | {
|
| | | // 如果不是保留最新的,则保留第一个未被选中删除的文件
|
| | | keepFile = group.Value.FirstOrDefault(f => !selectedForRemoval.ContainsKey(f) || !selectedForRemoval[f]);
|
| | | }
|
| | | |
| | | // 如果没有找到要保留的文件,跳过这个组
|
| | | if (string.IsNullOrEmpty(keepFile)) |
| | | {
|
| | | throw new Exception("没有找到要保留的文件");
|
| | | continue;
|
| | | }
|
| | | // 处理这个组中的其他文件
|
| | | foreach (string filePath in group.Value)
|
| | | {
|
| | | if (filePath == keepFile) continue;
|
| | | |
| | | // 检查是否选中了这个文件进行删除
|
| | | if (!selectedForRemoval.ContainsKey(filePath) || !selectedForRemoval[filePath]) continue;
|
| | | |
| | | // 更新引用关系,将引用从filePath重定向到keepFile
|
| | | UpdateReferences(filePath, keepFile);
|
| | | |
| | | // ... existing code ...
|
| | | |
| | | // 创建备份和删除文件的代码
|
| | | }
|
| | | }
|
| | |
|
| | | // 刷新资源数据库
|
| | | AssetDatabase.Refresh();
|
| | |
|
| | | // 创建备份文件夹
|
| | | if (createBackup && !string.IsNullOrEmpty(backupFolder))
|
| | | {
|
| | | Directory.CreateDirectory(backupFolder);
|
| | | }
|
| | | |
| | | // 收集要删除的文件和保留的文件
|
| | | List<string> filesToRemove = new List<string>();
|
| | | Dictionary<string, string> fileReplaceMap = new Dictionary<string, string>();
|
| | | |
| | | var tempGroups = new Dictionary<string, List<string>>(duplicateGroups);
|
| | | var tempRemoval = new Dictionary<string, bool>(selectedForRemoval);
|
| | |
|
| | | // 清空结果数据
|
| | | duplicateGroups.Clear();
|
| | | selectedForRemoval.Clear();
|
| | | totalFilesScanned = 0;
|
| | | totalDuplicatesFound = 0;
|
| | | totalSpaceSaved = 0;
|
| | | showResults = false;
|
| | |
|
| | | foreach (var group in tempGroups)
|
| | | {
|
| | | if (group.Value.Count <= 1) continue;
|
| | | |
| | | // 找出要保留的文件
|
| | | string fileToKeep = null;
|
| | | |
| | | foreach (string filePath in group.Value)
|
| | | {
|
| | | if (!tempRemoval.ContainsKey(filePath) || !tempRemoval[filePath])
|
| | | {
|
| | | fileToKeep = filePath;
|
| | | break;
|
| | | }
|
| | | }
|
| | | |
| | | // 如果所有文件都被标记为删除,保留第一个
|
| | | if (string.IsNullOrEmpty(fileToKeep) && group.Value.Count > 0)
|
| | | {
|
| | | if (keepNewestFiles)
|
| | | {
|
| | | // 按最后修改时间排序,保留最新的
|
| | | fileToKeep = group.Value
|
| | | .OrderByDescending(f => File.GetLastWriteTime(Path.Combine(Application.dataPath, "..", f)))
|
| | | .FirstOrDefault();
|
| | | }
|
| | | else
|
| | | {
|
| | | fileToKeep = group.Value[0];
|
| | | }
|
| | | |
| | | if (tempRemoval.ContainsKey(fileToKeep))
|
| | | {
|
| | | tempRemoval[fileToKeep] = false;
|
| | | }
|
| | | }
|
| | | |
| | | // 收集要删除的文件
|
| | | foreach (string filePath in group.Value)
|
| | | {
|
| | | if (filePath != fileToKeep && tempRemoval.ContainsKey(filePath) && tempRemoval[filePath])
|
| | | {
|
| | | filesToRemove.Add(filePath);
|
| | | fileReplaceMap[filePath] = fileToKeep;
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | // 处理文件
|
| | | int processed = 0;
|
| | | foreach (string filePath in filesToRemove)
|
| | | {
|
| | | processed++;
|
| | | progress = (float)processed / totalToProcess;
|
| | | progressInfo = $"正在处理 {processed}/{totalToProcess}: {Path.GetFileName(filePath)}";
|
| | | Repaint();
|
| | | |
| | | try
|
| | | {
|
| | | // 备份文件
|
| | | if (createBackup && !string.IsNullOrEmpty(backupFolder))
|
| | | {
|
| | | string backupPath = Path.Combine(backupFolder, Path.GetFileName(filePath));
|
| | | File.Copy(Path.Combine(Application.dataPath, "..", filePath), backupPath, true);
|
| | | }
|
| | | |
| | | // 更新引用
|
| | | if (fileReplaceMap.ContainsKey(filePath))
|
| | | {
|
| | | UpdateReferences(filePath, fileReplaceMap[filePath]);
|
| | | }
|
| | | |
| | | // 删除文件
|
| | | AssetDatabase.DeleteAsset(filePath);
|
| | | |
| | | await Task.Delay(1);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | Debug.LogError($"处理文件时出错: {filePath}, {ex.Message}");
|
| | | }
|
| | | }
|
| | | |
| | | // 刷新资源数据库
|
| | | AssetDatabase.Refresh();
|
| | | |
| | |
|
| | | |
| | | // 完成
|
| | | progressInfo = $"处理完成,已处理 {processed} 个文件";
|
| | | progress = 1f;
|
| | | EditorUtility.DisplayDialog("完成", $"已处理 {processed} 个重复资源", "确定");
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | Debug.LogError($"处理重复资源时出错: {ex.Message}\n{ex.StackTrace}");
|
| | | EditorUtility.DisplayDialog("错误", $"处理重复资源时出错: {ex.Message}", "确定");
|
| | | }
|
| | | finally
|
| | | {
|
| | | isProcessing = false;
|
| | | Repaint();
|
| | | }
|
| | | }
|
| | |
|
| | | // 更新引用关系
|
| | | private void UpdateReferences(string oldPath, string newPath)
|
| | | {
|
| | | string[] guids = AssetDatabase.FindAssets("t:Object");
|
| | | |
| | | foreach (string guid in guids)
|
| | | {
|
| | | string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
| | | |
| | | if (assetPath == oldPath || assetPath == newPath) continue;
|
| | | |
| | | if (!assetPath.EndsWith(".asset") && !assetPath.EndsWith(".prefab") && !assetPath.EndsWith(".unity") && !assetPath.EndsWith(".mat"))
|
| | | continue;
|
| | | |
| | | UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);
|
| | | if (obj == null) continue;
|
| | | |
| | | string[] dependencies = AssetDatabase.GetDependencies(assetPath, false);
|
| | | if (dependencies.Contains(oldPath))
|
| | | {
|
| | | // 获取旧资源和新资源
|
| | | UnityEngine.Object oldAsset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(oldPath);
|
| | | UnityEngine.Object newAsset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(newPath);
|
| | | |
| | | if (oldAsset != null && newAsset != null)
|
| | | {
|
| | | // 使用SerializedObject更新引用
|
| | | SerializedObject serializedObj = new SerializedObject(obj);
|
| | | bool modified = false;
|
| | | |
| | | SerializedProperty iterator = serializedObj.GetIterator();
|
| | | while (iterator.NextVisible(true))
|
| | | {
|
| | | if (iterator.propertyType == SerializedPropertyType.ObjectReference && |
| | | iterator.objectReferenceValue == oldAsset)
|
| | | {
|
| | | iterator.objectReferenceValue = newAsset;
|
| | | modified = true;
|
| | | }
|
| | | }
|
| | | |
| | | if (modified)
|
| | | {
|
| | | serializedObj.ApplyModifiedProperties();
|
| | | Debug.Log($"已更新资源 {assetPath} 中的引用: {oldPath} -> {newPath}");
|
| | | }
|
| | | }
|
| | | |
| | | EditorUtility.SetDirty(obj);
|
| | | }
|
| | | }
|
| | | |
| | | AssetDatabase.SaveAssets();
|
| | | }
|
| | | }
|
| | | }
|
| | |
|