using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace BaseGames.Editor { /// /// 扫描当前场景及 Project 中所有 Prefab,移除丢失(Missing)脚本引用。 /// /// 菜单:BaseGames/Tools/Missing Scripts/ /// public static class MissingScriptCleaner { // ────────────────────────────────────────────── // 场景 // ────────────────────────────────────────────── [MenuItem("BaseGames/Tools/Maintenance/Missing Scripts/Clear In Scene")] public static void ClearMissingScriptsInScene() { int totalRemoved = 0; var affected = new List(); foreach (var go in GetAllSceneObjects()) { int removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go); if (removed > 0) { affected.Add(go); totalRemoved += removed; EditorUtility.SetDirty(go); Debug.Log($"[MissingScriptCleaner] 场景已清理:{GetFullPath(go)}", go); } } if (totalRemoved == 0) Debug.Log("[MissingScriptCleaner] 场景中未发现丢失脚本。"); else Debug.Log($"[MissingScriptCleaner] 场景完成。共移除 {totalRemoved} 个丢失脚本,影响 {affected.Count} 个 GameObject。"); } [MenuItem("BaseGames/Tools/Maintenance/Missing Scripts/Find In Scene")] public static void FindMissingScriptsInScene() { int totalFound = 0; foreach (var go in GetAllSceneObjects()) { foreach (var component in go.GetComponents()) { if (component == null) { Debug.LogWarning($"[MissingScriptCleaner] 场景丢失脚本:{GetFullPath(go)}", go); totalFound++; break; } } } if (totalFound == 0) Debug.Log("[MissingScriptCleaner] 场景中未发现丢失脚本。"); else Debug.LogWarning($"[MissingScriptCleaner] 场景共发现 {totalFound} 个含丢失脚本的 GameObject。"); } // ────────────────────────────────────────────── // Prefab 资产 // ────────────────────────────────────────────── [MenuItem("BaseGames/Tools/Maintenance/Missing Scripts/Clear In All Prefabs")] public static void ClearMissingScriptsInPrefabs() { int totalRemoved = 0; int affectedPrefabs = 0; string[] guids = AssetDatabase.FindAssets("t:Prefab"); try { for (int i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); if (!path.StartsWith("Assets/")) continue; // 跳过 Packages 等只读路径 if (EditorUtility.DisplayCancelableProgressBar( "清理 Prefab 丢失脚本", path, (float)i / guids.Length)) break; int removed = CleanPrefabAtPath(path); if (removed > 0) { totalRemoved += removed; affectedPrefabs++; } } } finally { EditorUtility.ClearProgressBar(); AssetDatabase.SaveAssets(); } if (totalRemoved == 0) Debug.Log("[MissingScriptCleaner] 所有 Prefab 中未发现丢失脚本。"); else Debug.Log($"[MissingScriptCleaner] Prefab 完成。共移除 {totalRemoved} 个丢失脚本,影响 {affectedPrefabs} 个 Prefab。"); } [MenuItem("BaseGames/Tools/Maintenance/Missing Scripts/Find In All Prefabs")] public static void FindMissingScriptsInPrefabs() { int totalFound = 0; string[] guids = AssetDatabase.FindAssets("t:Prefab"); try { for (int i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); if (!path.StartsWith("Assets/")) continue; // 跳过 Packages 等只读路径 if (EditorUtility.DisplayCancelableProgressBar( "查找 Prefab 丢失脚本", path, (float)i / guids.Length)) break; var contentsRoot = PrefabUtility.LoadPrefabContents(path); if (contentsRoot == null) continue; try { var tempScene = contentsRoot.scene; foreach (var go in Resources.FindObjectsOfTypeAll()) { if (go.scene != tempScene) continue; foreach (var component in go.GetComponents()) { if (component == null) { Debug.LogWarning($"[MissingScriptCleaner] Prefab 丢失脚本:{path} → {go.name}"); totalFound++; break; } } } } finally { PrefabUtility.UnloadPrefabContents(contentsRoot); } } } finally { EditorUtility.ClearProgressBar(); } if (totalFound == 0) Debug.Log("[MissingScriptCleaner] 所有 Prefab 中未发现丢失脚本。"); else Debug.LogWarning($"[MissingScriptCleaner] Prefab 共发现 {totalFound} 个含丢失脚本的 GameObject。"); } // ────────────────────────────────────────────── // 内部辅助 // ────────────────────────────────────────────── static int CleanPrefabAtPath(string path) { // LoadPrefabContents 在临时预览场景中打开 Prefab, // 此时 Resources.FindObjectsOfTypeAll 可枚举到包括 HideInHierarchy 在内的全部对象。 // 修改完毕后必须用 SaveAsPrefabAsset 写回,否则隐藏对象的改动不会持久化。 var contentsRoot = PrefabUtility.LoadPrefabContents(path); if (contentsRoot == null) return 0; int totalRemoved = 0; try { var tempScene = contentsRoot.scene; foreach (var go in Resources.FindObjectsOfTypeAll()) { if (go.scene != tempScene) continue; int removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go); if (removed > 0) { totalRemoved += removed; Debug.Log($"[MissingScriptCleaner] Prefab 已清理:{path} → {go.name}"); } } if (totalRemoved > 0) PrefabUtility.SaveAsPrefabAsset(contentsRoot, path); } finally { PrefabUtility.UnloadPrefabContents(contentsRoot); } return totalRemoved; } static GameObject[] GetAllSceneObjects() { // FindObjectsByType 不返回 HideFlags.HideInHierarchy 的对象, // 使用 Resources.FindObjectsOfTypeAll 获取全部对象,再过滤出已加载场景内的实例。 var all = Resources.FindObjectsOfTypeAll(); var result = new List(); foreach (var go in all) { if (go.scene.IsValid() && go.scene.isLoaded) result.Add(go); } return result.ToArray(); } static string GetFullPath(GameObject go) { var path = go.name; var parent = go.transform.parent; while (parent != null) { path = parent.name + "/" + path; parent = parent.parent; } return path; } } }