using System.IO; using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Settings; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; namespace BrewMonster.Scripts.Utils.Editor { public static class ClearMissingScript { [MenuItem("Tools/Brew Monster/Clear Missing Script/Active Scene")] public static void ClearMissingScriptsInActiveScene() { var scene = SceneManager.GetActiveScene(); if (!scene.IsValid()) { EditorUtility.DisplayDialog("Clear Missing Script", "No valid active scene.", "OK"); return; } int removedCount = 0; var roots = scene.GetRootGameObjects(); foreach (var root in roots) { Undo.RegisterFullObjectHierarchyUndo(root, "Clear Missing Scripts"); removedCount += RemoveMissingScriptsRecursive(root); } if (removedCount > 0) { EditorSceneManager.MarkSceneDirty(scene); } EditorUtility.DisplayDialog("Clear Missing Script", $"Removed {removedCount} missing script(s) from active scene.", "OK"); } [MenuItem("Tools/Brew Monster/Clear Missing Script/Prefabs in Selected Folder", true)] public static bool ValidateClearMissingScriptsInSelectedFolder() { Object selectedObject = Selection.activeObject; if (selectedObject == null) return false; string folderPath = AssetDatabase.GetAssetPath(selectedObject); return AssetDatabase.IsValidFolder(folderPath); } [MenuItem("Tools/Brew Monster/Clear Missing Script/Prefabs in Selected Folder")] public static void ClearMissingScriptsInSelectedFolder() { Object selectedObject = Selection.activeObject; if (selectedObject == null) { EditorUtility.DisplayDialog("Clear Missing Script", "Please select a folder in the Project window first.", "OK"); return; } string folderPath = AssetDatabase.GetAssetPath(selectedObject); if (!AssetDatabase.IsValidFolder(folderPath)) { EditorUtility.DisplayDialog("Clear Missing Script", "Selected object is not a valid folder. Please select a folder in the Project window.", "OK"); return; } if (!EditorUtility.DisplayDialog("Clear Missing Script in Folder", $"This will process all prefabs in:\n{folderPath}\n\nContinue?", "Yes", "Cancel")) { return; } ProcessPrefabsInFolder(folderPath); } [MenuItem("Tools/Brew Monster/Clear Missing Script/All Prefabs")] public static void ClearMissingScriptsInPrefabs() { if (!EditorUtility.DisplayDialog("Clear Missing Script in Prefabs", "This will process all prefabs in the project. This may take a while. Continue?", "Yes", "Cancel")) { return; } ProcessPrefabsInFolder("Assets"); } [MenuItem("Tools/Brew Monster/Set Addressable For Asset In Selected File (Group: models)")] public static bool SetAddressableForAssetInSelectedFileGroupModels() { Object selectedObject = Selection.activeObject; if (selectedObject == null) return false; AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings; if (settings == null) { EditorUtility.DisplayDialog("Set Addressable", "Addressable settings not found.", "OK"); return false; } AddressableAssetGroup modelsGroup = GetOrCreateAddressableGroup(settings, "models"); if (modelsGroup == null) { EditorUtility.DisplayDialog("Set Addressable", "Could not find or create Addressable group: models", "OK"); return false; } string inputFilePath = AssetDatabase.GetAssetPath(selectedObject); if (!string.IsNullOrEmpty(inputFilePath)) { inputFilePath = inputFilePath.Replace("Assets/", ""); inputFilePath = Path.Combine(Application.dataPath, inputFilePath); if (!File.Exists(inputFilePath)) { EditorUtility.DisplayDialog("Set Addressable", $"Input file does not exist:\n{inputFilePath}", "OK"); return false; } string[] lines = File.ReadAllLines(inputFilePath); int addedOrMovedCount = 0; int skippedCount = 0; foreach (string rawLine in lines) { if (string.IsNullOrWhiteSpace(rawLine)) continue; string assetPath = rawLine.Trim().Trim('"').Replace("\\", "/"); if (assetPath.StartsWith(Application.dataPath.Replace("\\", "/"))) { assetPath = "Assets" + assetPath.Substring(Application.dataPath.Replace("\\", "/").Length); } else if (!assetPath.StartsWith("Assets/")) { assetPath = assetPath.TrimStart('/'); assetPath = "Assets/" + assetPath; } string guid = AssetDatabase.AssetPathToGUID(assetPath); if (string.IsNullOrEmpty(guid)) { skippedCount++; Debug.LogWarning($"[SetAddressableForAssetInSelectedFile] Invalid asset path in file: {assetPath}"); continue; } AddressableAssetEntry entry = settings.CreateOrMoveEntry(guid, modelsGroup); if (entry == null) { skippedCount++; Debug.LogWarning($"[SetAddressableForAssetInSelectedFile] Failed to create/move entry: {assetPath}"); continue; } entry.SetAddress(assetPath); addedOrMovedCount++; } if (addedOrMovedCount > 0) { settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryModified, null, true); AssetDatabase.SaveAssets(); } EditorUtility.DisplayDialog( "Set Addressable", $"Processed {lines.Length} line(s).\nAdded/Moved: {addedOrMovedCount}\nSkipped: {skippedCount}\nGroup: models", "OK"); } return true; } private static AddressableAssetGroup GetOrCreateAddressableGroup(AddressableAssetSettings settings, string groupName) { if (settings == null || string.IsNullOrEmpty(groupName)) return null; AddressableAssetGroup group = settings.FindGroup(groupName); if (group != null) return group; group = settings.CreateGroup(groupName, false, false, true, null); if (group != null) { EditorUtility.SetDirty(settings); AssetDatabase.SaveAssets(); } return group; } private static void ProcessPrefabsInFolder(string folderPath) { string[] prefabGUIDs = AssetDatabase.FindAssets("t:Prefab", new[] { folderPath }); int totalPrefabs = prefabGUIDs.Length; if (totalPrefabs == 0) { EditorUtility.DisplayDialog("Clear Missing Script", $"No prefabs found in folder:\n{folderPath}", "OK"); return; } int processedCount = 0; int totalRemovedCount = 0; int prefabsWithRemovals = 0; EditorUtility.DisplayProgressBar("Clear Missing Scripts", "Processing prefabs...", 0f); try { foreach (string guid in prefabGUIDs) { string assetPath = AssetDatabase.GUIDToAssetPath(guid); processedCount++; EditorUtility.DisplayProgressBar("Clear Missing Scripts", $"Processing: {assetPath} ({processedCount}/{totalPrefabs})", (float)processedCount / totalPrefabs); GameObject prefabRoot = PrefabUtility.LoadPrefabContents(assetPath); if (prefabRoot != null) { int removedCount = RemoveMissingScriptsRecursive(prefabRoot); if (removedCount > 0) { PrefabUtility.SaveAsPrefabAsset(prefabRoot, assetPath); totalRemovedCount += removedCount; prefabsWithRemovals++; Debug.Log($"[ClearMissingScript] Removed {removedCount} missing script(s) from prefab: {assetPath}"); } PrefabUtility.UnloadPrefabContents(prefabRoot); } } AssetDatabase.SaveAssets(); } finally { EditorUtility.ClearProgressBar(); } EditorUtility.DisplayDialog("Clear Missing Script", $"Processed {processedCount} prefab(s) in folder:\n{folderPath}\n\nRemoved {totalRemovedCount} missing script(s) from {prefabsWithRemovals} prefab(s).", "OK"); } [MenuItem("Tools/Brew Monster/Clear Missing Script/Scene and Prefabs")] public static void ClearMissingScriptsInSceneAndPrefabs() { if (!EditorUtility.DisplayDialog("Clear Missing Script", "This will process the active scene and all prefabs in the project. This may take a while. Continue?", "Yes", "Cancel")) { return; } int sceneRemovedCount = 0; var scene = SceneManager.GetActiveScene(); if (scene.IsValid()) { var roots = scene.GetRootGameObjects(); foreach (var root in roots) { Undo.RegisterFullObjectHierarchyUndo(root, "Clear Missing Scripts"); sceneRemovedCount += RemoveMissingScriptsRecursive(root); } if (sceneRemovedCount > 0) { EditorSceneManager.MarkSceneDirty(scene); } } string[] prefabGUIDs = AssetDatabase.FindAssets("t:Prefab"); int totalPrefabs = prefabGUIDs.Length; int processedCount = 0; int prefabRemovedCount = 0; int prefabsWithRemovals = 0; EditorUtility.DisplayProgressBar("Clear Missing Scripts", "Processing prefabs...", 0f); try { foreach (string guid in prefabGUIDs) { string assetPath = AssetDatabase.GUIDToAssetPath(guid); processedCount++; EditorUtility.DisplayProgressBar("Clear Missing Scripts", $"Processing: {assetPath} ({processedCount}/{totalPrefabs})", (float)processedCount / totalPrefabs); GameObject prefabRoot = PrefabUtility.LoadPrefabContents(assetPath); if (prefabRoot != null) { int removedCount = RemoveMissingScriptsRecursive(prefabRoot); if (removedCount > 0) { PrefabUtility.SaveAsPrefabAsset(prefabRoot, assetPath); prefabRemovedCount += removedCount; prefabsWithRemovals++; } PrefabUtility.UnloadPrefabContents(prefabRoot); } } AssetDatabase.SaveAssets(); } finally { EditorUtility.ClearProgressBar(); } int totalRemoved = sceneRemovedCount + prefabRemovedCount; EditorUtility.DisplayDialog("Clear Missing Script", $"Scene: Removed {sceneRemovedCount} missing script(s).\n" + $"Prefabs: Processed {processedCount} prefab(s), removed {prefabRemovedCount} missing script(s) from {prefabsWithRemovals} prefab(s).\n" + $"Total: {totalRemoved} missing script(s) removed.", "OK"); } private static int RemoveMissingScriptsRecursive(GameObject gameObject) { int removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(gameObject); for (int i = 0; i < gameObject.transform.childCount; i++) { removed += RemoveMissingScriptsRecursive(gameObject.transform.GetChild(i).gameObject); } return removed; } } }