三国卡牌客户端基础资源仓库
yyl
2025-05-08 777db9b445cf7e558a5e8659038bc21fd0edd220
psd 2 unity, duplicated res check
3个文件已修改
10个文件已添加
27831 ■■■■■ 已修改文件
Assets/Editor/Backups.meta 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/ConfigGen/ConfigDataTemplate.txt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/ConfigGen/ConfigGenerater.cs 116 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/UI.meta 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/UI/AssetDuplicateCheck.cs 722 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/UI/AssetDuplicateCheck.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/UI/UIUtility.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/UI/UIUtility.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Psd2UnityPro.meta 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Psd2UnityPro/Editor.meta 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Psd2UnityPro/Editor/Psd2UnityPro.dll 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Psd2UnityPro/Editor/Psd2UnityPro.dll.meta 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Resources/Scenes/Launch.unity 26846 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/Backups.meta
New file
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 27b0ccb168f5272418e0998fdb0e23af
folderAsset: yes
DefaultImporter:
  externalObjects: {}
  userData:
  assetBundleName:
  assetBundleVariant:
Assets/Editor/ConfigGen/ConfigDataTemplate.txt
@@ -18,6 +18,7 @@
    public override #KEY# LoadKey(string _key)
    {
        #KEY# key = GetKey(_key);
        return key;
    }
    public override void LoadConfig(string input)
Assets/Editor/ConfigGen/ConfigGenerater.cs
@@ -17,9 +17,9 @@
    private const string ConfigManagerPath = "Assets/Scripts/Main/Manager/ConfigManager.cs";
    private const string ConfigBasePattern = @"public\s+class\s+(\w+)\s*:\s*ConfigBase\s*<";
    
    // 正则表达式模式
    private static readonly Regex LoadConfigsRegex = new Regex(@"protected\s+async\s+UniTask\s+LoadConfigs\(\)\s*\{[^\}]*\}", RegexOptions.Compiled);
    private static readonly Regex ReleaseRegex = new Regex(@"public\s+override\s+void\s+Release\(\)\s*\{[^\}]*\}", RegexOptions.Compiled);
    // 正则表达式模式 - 更新以匹配新的ConfigManager结构
    private static readonly Regex LoadConfigsRegex = new Regex(@"protected\s+async\s+UniTask\s+LoadConfigs\(\)\s*\{[^\}]*\}", RegexOptions.Compiled | RegexOptions.Singleline);
    private static readonly Regex ReleaseRegex = new Regex(@"public\s+override\s+void\s+Release\(\)\s*\{[^\}]*\}", RegexOptions.Compiled | RegexOptions.Singleline);
    
    /// <summary>
    /// 生成配置管理器菜单项
@@ -31,6 +31,13 @@
        {
            // 获取所有配置类
            List<string> configClasses = GetAllConfigClasses();
            // if (configClasses.Count == 0)
            // {
            //     Debug.LogWarning("未找到任何配置类!请确保配置类继承自ConfigBase<T>并位于正确的目录中。");
            //     return;
            // }
            // 生成配置管理器代码
            GenerateConfigManager(configClasses);
            
@@ -61,18 +68,26 @@
            return new List<string>();
        }
        
        // 获取所有C#文件并解析配置类
        return Directory.GetFiles(fullConfigsPath, "*.cs", SearchOption.TopDirectoryOnly)
            .Select(file => new {
                Path = file,
                Content = File.ReadAllText(file)
            })
            .Select(file => {
                Match match = Regex.Match(file.Content, ConfigBasePattern);
                return match.Success ? match.Groups[1].Value : null;
            })
            .Where(className => !string.IsNullOrEmpty(className))
            .ToList();
        try
        {
            // 获取所有C#文件并解析配置类
            return Directory.GetFiles(fullConfigsPath, "*.cs", SearchOption.TopDirectoryOnly)
                .Select(file => new {
                    Path = file,
                    Content = File.ReadAllText(file)
                })
                .Select(file => {
                    Match match = Regex.Match(file.Content, ConfigBasePattern);
                    return match.Success ? match.Groups[1].Value : null;
                })
                .Where(className => !string.IsNullOrEmpty(className))
                .ToList();
        }
        catch (Exception ex)
        {
            Debug.LogError($"扫描配置类时发生错误: {ex.Message}");
            return new List<string>();
        }
    }
    
    /// <summary>
@@ -126,19 +141,69 @@
        
        sb.AppendLine("protected async UniTask LoadConfigs()");
        sb.AppendLine("    {");
        sb.AppendLine("        loadingProgress = 0f;");
        sb.AppendLine("        isLoadFinished = false;");
        sb.AppendLine();
        sb.AppendLine("        // 加载配置文件");
        
        // 为每个配置类生成加载代码
        foreach (string className in configClasses)
        // 如果有配置类,添加进度跟踪代码
        if (configClasses.Count > 0)
        {
            sb.AppendLine($"        await LoadConfig<{className}>();");
            sb.AppendLine($"        int totalConfigs = {configClasses.Count};");
            // 方法1:使用数组和循环加载配置
            sb.AppendLine("        Type[] configTypes = new Type[] {");
            for (int i = 0; i < configClasses.Count; i++)
            {
                sb.Append($"            typeof({configClasses[i]})");
                sb.AppendLine(i < configClasses.Count - 1 ? "," : "");
            }
            sb.AppendLine("        };");
            sb.AppendLine();
            sb.AppendLine("        // 逐个加载配置并更新进度");
            sb.AppendLine("        for (int i = 0; i < configTypes.Length; i++)");
            sb.AppendLine("        {");
            sb.AppendLine("            await LoadConfigByType(configTypes[i]);");
            sb.AppendLine("            loadingProgress = (float)(i + 1) / totalConfigs;");
            sb.AppendLine("        }");
        }
        
        sb.AppendLine();
        sb.AppendLine("        // 加载完成后设置isLoadFinished为true");
        sb.AppendLine("        loadingProgress = 1f;");
        sb.AppendLine("        isLoadFinished = true;");
        sb.AppendLine("    }");
        // 添加LoadConfigByType方法
        sb.AppendLine();
        sb.AppendLine("    private async UniTask LoadConfigByType(Type configType)");
        sb.AppendLine("    {");
        sb.AppendLine("        string configName = configType.Name;");
        sb.AppendLine("        TextAsset textAsset = await ResManager.Instance.LoadAsset<TextAsset>(\"Config\", configName);");
        sb.AppendLine("        if (textAsset != null)");
        sb.AppendLine("        {");
        sb.AppendLine("            string[] lines = textAsset.text.Split('\\n');");
        sb.AppendLine("            var methodInfo = configType.GetMethod(\"Init\", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);");
        sb.AppendLine("            if (methodInfo != null)");
        sb.AppendLine("            {");
        sb.AppendLine("                methodInfo.Invoke(null, new object[] { lines });");
        sb.AppendLine("                // 设置初始化标志");
        sb.AppendLine("                var isInitField = configType.GetField(\"isInit\", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);");
        sb.AppendLine("                if (isInitField != null)");
        sb.AppendLine("                {");
        sb.AppendLine("                    isInitField.SetValue(null, true);");
        sb.AppendLine("                }");
        sb.AppendLine("                Debug.Log($\"加载配置: {configType.Name} 成功\");");
        sb.AppendLine("            }");
        sb.AppendLine("            else");
        sb.AppendLine("            {");
        sb.AppendLine("                Debug.LogError($\"配置类 {configType.Name} 没有静态Init方法\");");
        sb.AppendLine("            }");
        sb.AppendLine("        }");
        sb.AppendLine("        else");
        sb.AppendLine("        {");
        sb.AppendLine("            Debug.LogError($\"找不到配置文件: {configName}\");");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        
        return sb.ToString();
@@ -156,11 +221,18 @@
        sb.AppendLine("public override void Release()");
        sb.AppendLine("    {");
        
        foreach (string className in configClasses)
        if (configClasses.Count > 0)
        {
            // 清空字典
            sb.AppendLine($"        // 清空 {className} 字典");
            sb.AppendLine($"        ClearConfigDictionary<{className}>();");
            foreach (string className in configClasses)
            {
                // 清空字典
                sb.AppendLine($"        // 清空 {className} 字典");
                sb.AppendLine($"        ClearConfigDictionary<{className}>();");
            }
        }
        else
        {
            sb.AppendLine("        // 没有找到配置类");
        }
        
        sb.AppendLine("    }");
Assets/Editor/UI.meta
New file
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4aef847db35ab134ab835b79a14d0e5f
folderAsset: yes
DefaultImporter:
  externalObjects: {}
  userData:
  assetBundleName:
  assetBundleVariant:
Assets/Editor/UI/AssetDuplicateCheck.cs
New file
@@ -0,0 +1,722 @@
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();
        }
    }
}
Assets/Editor/UI/AssetDuplicateCheck.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ba018f8c4232e946ab933b182d0efed
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Assets/Editor/UI/UIUtility.cs
New file
@@ -0,0 +1,23 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
public class UIUtility
{
    [UnityEditor.MenuItem("Assets/PSDTOUGUI后处理")]
    public static void BaseSettings()
    {
        GameObject go = Selection.activeGameObject;
        if (go == null)
        {
            return;
        }
        // go.AddMissingComponent<Canvas>();
        // go.AddMissingComponent<Canvas>();
        // go.AddMissingComponent<Canvas>();
    }
}
Assets/Editor/UI/UIUtility.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a98ee6d4bbc11d40af09789e7f5921a
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Assets/Psd2UnityPro.meta
New file
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 97f9320cf7b0f6b409f3c29130aa95fe
folderAsset: yes
DefaultImporter:
  externalObjects: {}
  userData:
  assetBundleName:
  assetBundleVariant:
Assets/Psd2UnityPro/Editor.meta
New file
@@ -0,0 +1,26 @@
fileFormatVersion: 2
guid: b834b56085aef474aae3fd82fd7df653
labels:
- Arrange
- Auto
- Automatic
- Button
- GUI
- Import
- Layer
- Layers
- Layout
- Photoshop
- Position
- Psd
- Size
- Text
- UI
- uGUI
folderAsset: yes
timeCreated: 1498630073
licenseType: Store
DefaultImporter:
  userData:
  assetBundleName:
  assetBundleVariant:
Assets/Psd2UnityPro/Editor/Psd2UnityPro.dll
Binary files differ
Assets/Psd2UnityPro/Editor/Psd2UnityPro.dll.meta
New file
@@ -0,0 +1,51 @@
fileFormatVersion: 2
guid: 393acd78c52e246f5b0089f469f36000
labels:
- Arrange
- Auto
- Automatic
- Button
- GUI
- Import
- Layer
- Layers
- Layout
- Photoshop
- Position
- Psd
- Size
- Text
- UI
- uGUI
timeCreated: 1498630074
licenseType: Store
PluginImporter:
  serializedVersion: 2
  iconMap: {}
  executionOrder: {}
  isPreloaded: 0
  isOverridable: 0
  platformData:
    data:
      first:
        Any:
      second:
        enabled: 0
        settings: {}
    data:
      first:
        Editor: Editor
      second:
        enabled: 1
        settings:
          DefaultValueInitialized: true
    data:
      first:
        Windows Store Apps: WindowsStoreApps
      second:
        enabled: 0
        settings:
          CPU: AnyCPU
  userData:
  assetBundleName:
  assetBundleVariant:
Assets/Resources/Scenes/Launch.unity
Diff too large