| Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| Assets/Editor/UIComponent/TextLanguageAdapterScanTool.cs | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
Assets/Editor/UIComponent/ImageLanguageAdapterScanTool.cs
@@ -4,8 +4,6 @@ using UnityEditor.IMGUI.Controls; using UnityEngine; // ======================== 扫描数据模型 ======================== public class ImageScanResultItem { public string PrefabPath { get; } @@ -44,7 +42,10 @@ public int TotalPrefabsScanned { get; set; } public int TotalAdaptersFound { get; set; } public int AdaptersWithMissingConfig { get; private set; } public int PrefabsWithIssueCount { get; private set; } public int PrefabsWithoutIssueCount { get; private set; } public List<ImagePrefabScanResult> PrefabResults { get; } = new List<ImagePrefabScanResult>(); public List<ImagePrefabScanResult> PrefabResultsWithoutIssue { get; } = new List<ImagePrefabScanResult>(); public ImageScanResultSummary(string dir) => ScanDirectory = dir; @@ -55,13 +56,31 @@ { prefabResult = new ImagePrefabScanResult(item.PrefabPath, item.PrefabGUID); PrefabResults.Add(prefabResult); PrefabsWithIssueCount++; } prefabResult.Items.Add(item); AdaptersWithMissingConfig++; if (item.MissingLanguages.Count > 0) { AdaptersWithMissingConfig++; } } public void AddPrefabWithoutIssue(string path, string guid, List<ImageScanResultItem> items) { var prefabResult = new ImagePrefabScanResult(path, guid); prefabResult.Items.AddRange(items); PrefabResultsWithoutIssue.Add(prefabResult); PrefabsWithoutIssueCount++; } } // ======================== TreeView 实现 ======================== public enum ImageScanResultFilterMode { 全部, 仅显示有问题, 仅显示无问题 } public class ImageMetadataTreeViewItem : TreeViewItem { @@ -72,10 +91,12 @@ public class ImageScanResultTreeView : TreeView { private ImageScanResultSummary m_Summary; private ImageScanResultFilterMode m_FilterMode; public ImageScanResultTreeView(TreeViewState state, ImageScanResultSummary summary) : base(state) public ImageScanResultTreeView(TreeViewState state, ImageScanResultSummary summary, ImageScanResultFilterMode filterMode) : base(state) { m_Summary = summary; m_FilterMode = filterMode; Reload(); ExpandAll(); } @@ -86,21 +107,60 @@ root.children = new List<TreeViewItem>(); if (m_Summary == null || m_Summary.PrefabResults.Count == 0) if (m_Summary == null) return root; int itemId = 1; foreach (var prefabResult in m_Summary.PrefabResults) { string prefabName = Path.GetFileNameWithoutExtension(prefabResult.PrefabPath); var prefabItem = new ImageMetadataTreeViewItem(itemId++, $"{prefabName} ({prefabResult.Items.Count}个问题)", prefabResult); foreach (var adapterItem in prefabResult.Items) bool showWithIssue = m_FilterMode == ImageScanResultFilterMode.全部 || m_FilterMode == ImageScanResultFilterMode.仅显示有问题; bool showWithoutIssue = m_FilterMode == ImageScanResultFilterMode.全部 || m_FilterMode == ImageScanResultFilterMode.仅显示无问题; if (showWithIssue) { foreach (var prefabResult in m_Summary.PrefabResults) { var adapterTreeItem = new ImageMetadataTreeViewItem(itemId++, adapterItem.GetDisplayName(), adapterItem); prefabItem.AddChild(adapterTreeItem); string prefabName = Path.GetFileNameWithoutExtension(prefabResult.PrefabPath); int issueCount = 0; foreach (var item in prefabResult.Items) { if (item.MissingLanguages != null && item.MissingLanguages.Count > 0) issueCount++; } var prefabItem = new ImageMetadataTreeViewItem(itemId++, $"{prefabName} ({issueCount}个问题)", prefabResult); foreach (var adapterItem in prefabResult.Items) { string displayName; if (adapterItem.MissingLanguages.Count > 0) displayName = adapterItem.GetDisplayName(); else displayName = $"{adapterItem.GameObjectPath} (配置完整)"; var adapterTreeItem = new ImageMetadataTreeViewItem(itemId++, displayName, adapterItem); prefabItem.AddChild(adapterTreeItem); } root.AddChild(prefabItem); } root.AddChild(prefabItem); } if (showWithoutIssue) { foreach (var prefabResult in m_Summary.PrefabResultsWithoutIssue) { string prefabName = Path.GetFileNameWithoutExtension(prefabResult.PrefabPath); var prefabItem = new ImageMetadataTreeViewItem(itemId++, $"{prefabName} (无问题)", prefabResult); foreach (var adapterItem in prefabResult.Items) { var displayName = $"{adapterItem.GameObjectPath} (配置完整)"; var adapterTreeItem = new ImageMetadataTreeViewItem(itemId++, displayName, adapterItem); prefabItem.AddChild(adapterTreeItem); } root.AddChild(prefabItem); } } SetupDepthsFromParentsAndChildren(root); @@ -114,7 +174,12 @@ if (item != null && item.Metadata is ImagePrefabScanResult) GUI.Label(args.rowRect, item.displayName, EditorStyles.boldLabel); else if (item != null && item.Metadata is ImageScanResultItem adapterItem) GUI.Label(args.rowRect, $"{adapterItem.GameObjectPath} (缺少: {string.Join(", ", adapterItem.MissingLanguages)})"); { if (adapterItem.MissingLanguages.Count > 0) GUI.Label(args.rowRect, $"{adapterItem.GameObjectPath} (缺少: {string.Join(", ", adapterItem.MissingLanguages)})"); else GUI.Label(args.rowRect, item.displayName); } else base.RowGUI(args); } @@ -157,8 +222,6 @@ } } // ======================== 扫描工具主窗口 ======================== public class ImageLanguageAdapterScanTool : EditorWindow { private string m_ScanDirectory = "Assets"; @@ -170,6 +233,7 @@ private ImageScanResultTreeView m_TreeView; private TreeViewState m_TreeViewState; private ImageScanResultFilterMode m_ResultFilterMode = ImageScanResultFilterMode.全部; private int m_SourceLangIndex = 0; private int m_TargetLangIndex = 0; @@ -275,7 +339,20 @@ if (m_ScanResult != null) { EditorGUILayout.LabelField($"预制体总数: {m_ScanResult.TotalPrefabsScanned} | Adapter总数: {m_ScanResult.TotalAdaptersFound} | 缺失配置: {m_ScanResult.AdaptersWithMissingConfig}"); EditorGUILayout.LabelField($"预制体总数: {m_ScanResult.TotalPrefabsScanned} | 有Adapter的预制体: {m_ScanResult.PrefabsWithIssueCount + m_ScanResult.PrefabsWithoutIssueCount} | Adapter总数: {m_ScanResult.TotalAdaptersFound} | 缺失配置: {m_ScanResult.AdaptersWithMissingConfig} | 有问题预制体: {m_ScanResult.PrefabsWithIssueCount} | 无问题预制体: {m_ScanResult.PrefabsWithoutIssueCount}"); EditorGUILayout.Space(5f); using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("显示模式:", GUILayout.Width(60f)); EditorGUI.BeginChangeCheck(); m_ResultFilterMode = (ImageScanResultFilterMode)EditorGUILayout.EnumPopup(m_ResultFilterMode); if (EditorGUI.EndChangeCheck() && m_TreeView != null) { m_TreeViewState ??= new TreeViewState(); m_TreeView = new ImageScanResultTreeView(m_TreeViewState, m_ScanResult, m_ResultFilterMode); } } EditorGUILayout.Space(5f); if (m_TreeView != null) @@ -450,7 +527,7 @@ } m_TreeViewState ??= new TreeViewState(); m_TreeView = new ImageScanResultTreeView(m_TreeViewState, m_ScanResult); m_TreeView = new ImageScanResultTreeView(m_TreeViewState, m_ScanResult, m_ResultFilterMode); m_ScanStatus = $"扫描完成! 发现 {m_ScanResult.AdaptersWithMissingConfig} 个缺失配置"; Repaint(); } @@ -461,8 +538,12 @@ if (prefab == null) return; var adapters = prefab.GetComponentsInChildren<ImageLanguageAdapter>(true); if (adapters.Length == 0) return; m_ScanResult.TotalAdaptersFound += adapters.Length; bool hasIssue = false; List<ImageScanResultItem> allItems = new List<ImageScanResultItem>(); foreach (var adapter in adapters) { @@ -473,15 +554,30 @@ if (!adapter.HasConfig(langId)) missing.Add(langId); } var item = new ImageScanResultItem(path, GetGameObjectPath(adapter.gameObject, prefab), adapter.TargetImageType, guid) { MissingLanguages = missing }; allItems.Add(item); if (missing.Count > 0) { var item = new ImageScanResultItem(path, GetGameObjectPath(adapter.gameObject, prefab), adapter.TargetImageType, guid) { MissingLanguages = missing }; hasIssue = true; } } if (hasIssue) { foreach (var item in allItems) { m_ScanResult.AddResult(item); } } else { m_ScanResult.AddPrefabWithoutIssue(path, guid, allItems); } } private string GetGameObjectPath(GameObject go, GameObject root) Assets/Editor/UIComponent/TextLanguageAdapterScanTool.cs
@@ -44,7 +44,10 @@ public int TotalPrefabsScanned { get; set; } public int TotalAdaptersFound { get; set; } public int AdaptersWithMissingConfig { get; private set; } public int PrefabsWithIssueCount { get; private set; } public int PrefabsWithoutIssueCount { get; private set; } public List<PrefabScanResult> PrefabResults { get; } = new List<PrefabScanResult>(); public List<PrefabScanResult> PrefabResultsWithoutIssue { get; } = new List<PrefabScanResult>(); public ScanResultSummary(string dir) => ScanDirectory = dir; @@ -55,13 +58,33 @@ { prefabResult = new PrefabScanResult(item.PrefabPath, item.PrefabGUID); PrefabResults.Add(prefabResult); PrefabsWithIssueCount++; } prefabResult.Items.Add(item); AdaptersWithMissingConfig++; if (item.MissingLanguages.Count > 0) { AdaptersWithMissingConfig++; } } public void AddPrefabWithoutIssue(string path, string guid, List<ScanResultItem> items) { var prefabResult = new PrefabScanResult(path, guid); prefabResult.Items.AddRange(items); PrefabResultsWithoutIssue.Add(prefabResult); PrefabsWithoutIssueCount++; } } // ======================== TreeView 实现 ======================== public enum ScanResultFilterMode { 全部, 仅显示有问题, 仅显示无问题 } public class MetadataTreeViewItem : TreeViewItem { @@ -72,10 +95,12 @@ public class ScanResultTreeView : TreeView { private ScanResultSummary m_Summary; private ScanResultFilterMode m_FilterMode; public ScanResultTreeView(TreeViewState state, ScanResultSummary summary) : base(state) public ScanResultTreeView(TreeViewState state, ScanResultSummary summary, ScanResultFilterMode filterMode) : base(state) { m_Summary = summary; m_FilterMode = filterMode; Reload(); ExpandAll(); } @@ -84,28 +109,64 @@ { var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" }; // 【关键修复 1】:必须初始化 children 列表。 // 否则当扫描结果完美(0个错误)时,root.children 为 null 会导致 Unity 报错 root.children = new List<TreeViewItem>(); if (m_Summary == null || m_Summary.PrefabResults.Count == 0) if (m_Summary == null) return root; int itemId = 1; foreach (var prefabResult in m_Summary.PrefabResults) { string prefabName = Path.GetFileNameWithoutExtension(prefabResult.PrefabPath); var prefabItem = new MetadataTreeViewItem(itemId++, $"{prefabName} ({prefabResult.Items.Count}个问题)", prefabResult); foreach (var adapterItem in prefabResult.Items) bool showWithIssue = m_FilterMode == ScanResultFilterMode.全部 || m_FilterMode == ScanResultFilterMode.仅显示有问题; bool showWithoutIssue = m_FilterMode == ScanResultFilterMode.全部 || m_FilterMode == ScanResultFilterMode.仅显示无问题; if (showWithIssue) { foreach (var prefabResult in m_Summary.PrefabResults) { var adapterTreeItem = new MetadataTreeViewItem(itemId++, adapterItem.GetDisplayName(), adapterItem); prefabItem.AddChild(adapterTreeItem); string prefabName = Path.GetFileNameWithoutExtension(prefabResult.PrefabPath); int issueCount = 0; foreach (var item in prefabResult.Items) { if (item.MissingLanguages != null && item.MissingLanguages.Count > 0) issueCount++; } var prefabItem = new MetadataTreeViewItem(itemId++, $"{prefabName} ({issueCount}个问题)", prefabResult); foreach (var adapterItem in prefabResult.Items) { string displayName; if (adapterItem.MissingLanguages.Count > 0) displayName = adapterItem.GetDisplayName(); else displayName = $"{adapterItem.GameObjectPath} (配置完整)"; var adapterTreeItem = new MetadataTreeViewItem(itemId++, displayName, adapterItem); prefabItem.AddChild(adapterTreeItem); } root.AddChild(prefabItem); } root.AddChild(prefabItem); } // 【关键修复 2】:Unity 官方规范要求,手动使用 AddChild 构建树之后,必须调用此方法刷新深度和层级关系 if (showWithoutIssue) { foreach (var prefabResult in m_Summary.PrefabResultsWithoutIssue) { string prefabName = Path.GetFileNameWithoutExtension(prefabResult.PrefabPath); var prefabItem = new MetadataTreeViewItem(itemId++, $"{prefabName} (无问题)", prefabResult); foreach (var adapterItem in prefabResult.Items) { var displayName = $"{adapterItem.GameObjectPath} (配置完整)"; var adapterTreeItem = new MetadataTreeViewItem(itemId++, displayName, adapterItem); prefabItem.AddChild(adapterTreeItem); } root.AddChild(prefabItem); } } SetupDepthsFromParentsAndChildren(root); return root; @@ -117,7 +178,12 @@ if (item != null && item.Metadata is PrefabScanResult) GUI.Label(args.rowRect, item.displayName, EditorStyles.boldLabel); else if (item != null && item.Metadata is ScanResultItem adapterItem) GUI.Label(args.rowRect, $"{adapterItem.GameObjectPath} (缺少: {string.Join(", ", adapterItem.MissingLanguages)})"); { if (adapterItem.MissingLanguages.Count > 0) GUI.Label(args.rowRect, $"{adapterItem.GameObjectPath} (缺少: {string.Join(", ", adapterItem.MissingLanguages)})"); else GUI.Label(args.rowRect, item.displayName); } else base.RowGUI(args); } @@ -173,8 +239,8 @@ private ScanResultTreeView m_TreeView; private TreeViewState m_TreeViewState; private ScanResultFilterMode m_ResultFilterMode = ScanResultFilterMode.全部; // 批量操作的UI状态 private int m_SourceLangIndex = 0; private int m_TargetLangIndex = 0; private bool m_OverwriteExisting = false; @@ -279,7 +345,20 @@ if (m_ScanResult != null) { EditorGUILayout.LabelField($"预制体总数: {m_ScanResult.TotalPrefabsScanned} | Adapter总数: {m_ScanResult.TotalAdaptersFound} | 缺失配置: {m_ScanResult.AdaptersWithMissingConfig}"); EditorGUILayout.LabelField($"预制体总数: {m_ScanResult.TotalPrefabsScanned} | 有Adapter的预制体: {m_ScanResult.PrefabsWithIssueCount + m_ScanResult.PrefabsWithoutIssueCount} | Adapter总数: {m_ScanResult.TotalAdaptersFound} | 缺失配置: {m_ScanResult.AdaptersWithMissingConfig} | 有问题预制体: {m_ScanResult.PrefabsWithIssueCount} | 无问题预制体: {m_ScanResult.PrefabsWithoutIssueCount}"); EditorGUILayout.Space(5f); using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("显示模式:", GUILayout.Width(60f)); EditorGUI.BeginChangeCheck(); m_ResultFilterMode = (ScanResultFilterMode)EditorGUILayout.EnumPopup(m_ResultFilterMode); if (EditorGUI.EndChangeCheck() && m_TreeView != null) { m_TreeViewState ??= new TreeViewState(); m_TreeView = new ScanResultTreeView(m_TreeViewState, m_ScanResult, m_ResultFilterMode); } } EditorGUILayout.Space(5f); if (m_TreeView != null) @@ -304,7 +383,6 @@ } } // ======================== 新增:批量操作功能 ======================== private void DrawBatchOperations() { using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) @@ -370,7 +448,6 @@ string path = AssetDatabase.GUIDToAssetPath(prefabGuids[i]); EditorUtility.DisplayProgressBar("批量复制配置", $"处理中 ({i + 1}/{prefabGuids.Length}): {path}", (float)i / prefabGuids.Length); // 【核心修改】:直接加载资产内存,不使用 LoadPrefabContents 进行实例化 GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(path); bool isModified = false; @@ -380,7 +457,6 @@ { if (m_OverwriteExisting || !adapter.HasConfig(targetLang)) { // 必须标记对象为脏,否则 Unity 不会把资产的修改存盘 Undo.RecordObject(adapter, "Batch Copy Language Config"); var clonedConfig = adapter.GetConfig(sourceLang).Clone(); @@ -402,7 +478,6 @@ finally { EditorUtility.ClearProgressBar(); // 统一保存所有标记为 Dirty 的资产修改 AssetDatabase.SaveAssets(); PerformScan(); @@ -412,8 +487,6 @@ "确认"); } } // ======================== 核心扫描逻辑 ======================== private void StartScan() { @@ -460,7 +533,7 @@ } m_TreeViewState ??= new TreeViewState(); m_TreeView = new ScanResultTreeView(m_TreeViewState, m_ScanResult); m_TreeView = new ScanResultTreeView(m_TreeViewState, m_ScanResult, m_ResultFilterMode); m_ScanStatus = $"扫描完成! 发现 {m_ScanResult.AdaptersWithMissingConfig} 个缺失配置"; Repaint(); } @@ -471,8 +544,12 @@ if (prefab == null) return; var adapters = prefab.GetComponentsInChildren<TextLanguageAdapter>(true); if (adapters.Length == 0) return; m_ScanResult.TotalAdaptersFound += adapters.Length; bool hasIssue = false; List<ScanResultItem> allItems = new List<ScanResultItem>(); foreach (var adapter in adapters) { @@ -483,15 +560,30 @@ if (!adapter.HasConfig(langId)) missing.Add(langId); } var item = new ScanResultItem(path, GetGameObjectPath(adapter.gameObject, prefab), adapter.TargetTextType, guid) { MissingLanguages = missing }; allItems.Add(item); if (missing.Count > 0) { var item = new ScanResultItem(path, GetGameObjectPath(adapter.gameObject, prefab), adapter.TargetTextType, guid) { MissingLanguages = missing }; hasIssue = true; } } if (hasIssue) { foreach (var item in allItems) { m_ScanResult.AddResult(item); } } else { m_ScanResult.AddPrefabWithoutIssue(path, guid, allItems); } } private string GetGameObjectPath(GameObject go, GameObject root)