From 316d633525d3056cc904d24724d1bab228e40abf Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Fri, 7 Nov 2025 14:09:17 +0700 Subject: [PATCH] Add overrides multi prefabs tool and clear missing scripts --- Assets/Editor/MultiPrefabOverrideTool.cs | 337 ++++++++++++++++++ Assets/Editor/MultiPrefabOverrideTool.cs.meta | 2 + .../Utils/Editor/ClearMissingScript.cs | 192 +++++++++- 3 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 Assets/Editor/MultiPrefabOverrideTool.cs create mode 100644 Assets/Editor/MultiPrefabOverrideTool.cs.meta diff --git a/Assets/Editor/MultiPrefabOverrideTool.cs b/Assets/Editor/MultiPrefabOverrideTool.cs new file mode 100644 index 0000000000..b5d95584ad --- /dev/null +++ b/Assets/Editor/MultiPrefabOverrideTool.cs @@ -0,0 +1,337 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace BrewMonster.Scripts.Utils.Editor +{ + public class MultiPrefabOverrideTool : EditorWindow + { + private List selectedPrefabInstances = new List(); + private Vector2 scrollPosition; + private bool showAdvancedOptions = false; + private bool applyPropertyOverrides = true; + private bool applyAddedComponents = true; + private bool applyRemovedComponents = true; + private bool applyAddedGameObjects = true; + private bool applyRemovedGameObjects = true; + private bool applyObjectReferences = true; + + [MenuItem("Tools/Brew Monster/Multi Prefab Override Tool")] + public static void ShowWindow() + { + GetWindow("Multi Prefab Override"); + } + + private void OnEnable() + { + RefreshSelection(); + } + + private void OnSelectionChange() + { + RefreshSelection(); + Repaint(); + } + + private void RefreshSelection() + { + selectedPrefabInstances.Clear(); + GameObject[] selected = Selection.gameObjects; + + foreach (GameObject obj in selected) + { + if (obj != null && PrefabUtility.IsPartOfPrefabInstance(obj)) + { + selectedPrefabInstances.Add(obj); + } + } + } + + private void OnGUI() + { + EditorGUILayout.Space(10); + EditorGUILayout.LabelField("Multi Prefab Override Tool", EditorStyles.boldLabel); + EditorGUILayout.HelpBox("Select prefab instances in the scene to apply their overrides back to their prefabs.", MessageType.Info); + + EditorGUILayout.Space(10); + + // Selection info + EditorGUILayout.LabelField($"Selected Prefab Instances: {selectedPrefabInstances.Count}", EditorStyles.boldLabel); + + if (selectedPrefabInstances.Count == 0) + { + EditorGUILayout.HelpBox("No prefab instances selected. Please select prefab instances in the scene.", MessageType.Warning); + return; + } + + // List of selected prefab instances + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(200)); + foreach (GameObject obj in selectedPrefabInstances) + { + if (obj == null) continue; + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.ObjectField(obj, typeof(GameObject), true); + + PrefabAssetType prefabType = PrefabUtility.GetPrefabAssetType(obj); + string prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(obj); + string displayName = string.IsNullOrEmpty(prefabPath) ? "Unknown" : System.IO.Path.GetFileName(prefabPath); + + EditorGUILayout.LabelField($"→ {displayName}", GUILayout.Width(200)); + EditorGUILayout.EndHorizontal(); + } + EditorGUILayout.EndScrollView(); + + EditorGUILayout.Space(10); + + // Advanced options + showAdvancedOptions = EditorGUILayout.Foldout(showAdvancedOptions, "Advanced Options", true); + if (showAdvancedOptions) + { + EditorGUI.indentLevel++; + applyPropertyOverrides = EditorGUILayout.Toggle("Apply Property Overrides", applyPropertyOverrides); + applyAddedComponents = EditorGUILayout.Toggle("Apply Added Components", applyAddedComponents); + applyRemovedComponents = EditorGUILayout.Toggle("Apply Removed Components", applyRemovedComponents); + applyAddedGameObjects = EditorGUILayout.Toggle("Apply Added GameObjects", applyAddedGameObjects); + applyRemovedGameObjects = EditorGUILayout.Toggle("Apply Removed GameObjects", applyRemovedGameObjects); + applyObjectReferences = EditorGUILayout.Toggle("Apply Object References", applyObjectReferences); + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(10); + + // Action buttons + EditorGUILayout.BeginHorizontal(); + + if (GUILayout.Button("Apply All Overrides", GUILayout.Height(30))) + { + ApplyAllOverrides(); + } + + if (GUILayout.Button("Revert All Overrides", GUILayout.Height(30))) + { + RevertAllOverrides(); + } + + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(5); + + if (GUILayout.Button("Refresh Selection", GUILayout.Height(25))) + { + RefreshSelection(); + } + + EditorGUILayout.Space(10); + + // Statistics + if (selectedPrefabInstances.Count > 0) + { + EditorGUILayout.LabelField("Statistics:", EditorStyles.boldLabel); + int totalOverrides = GetTotalOverrideCount(); + EditorGUILayout.LabelField($"Total Overrides: {totalOverrides}"); + } + } + + private int GetTotalOverrideCount() + { + int count = 0; + foreach (GameObject obj in selectedPrefabInstances) + { + if (obj == null) continue; + count += PrefabUtility.GetObjectOverrides(obj).Count(); + } + return count; + } + + private void ApplyAllOverrides() + { + if (selectedPrefabInstances.Count == 0) + { + EditorUtility.DisplayDialog("No Selection", "Please select prefab instances first.", "OK"); + return; + } + + if (!EditorUtility.DisplayDialog("Apply Overrides", + $"This will apply all overrides from {selectedPrefabInstances.Count} prefab instance(s) back to their prefabs.\n\nThis action cannot be undone. Continue?", + "Yes", "Cancel")) + { + return; + } + + int successCount = 0; + int failCount = 0; + List failedObjects = new List(); + + Undo.SetCurrentGroupName("Apply Prefab Overrides"); + int undoGroup = Undo.GetCurrentGroup(); + + foreach (GameObject obj in selectedPrefabInstances) + { + if (obj == null) continue; + + try + { + Undo.RegisterFullObjectHierarchyUndo(obj, "Apply Prefab Overrides"); + + // Apply overrides based on selected options + if (applyPropertyOverrides || applyObjectReferences) + { + // Get all property overrides + var propertyOverrides = PrefabUtility.GetObjectOverrides(obj); + foreach (var objectOverride in propertyOverrides) + { + if (objectOverride.instanceObject == null) continue; + PrefabUtility.ApplyObjectOverride(objectOverride.instanceObject, PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(obj), InteractionMode.AutomatedAction); + } + } + + if (applyAddedComponents) + { + // Apply added components + var addedComponents = PrefabUtility.GetAddedComponents(obj); + foreach (var addedComponent in addedComponents) + { + if (addedComponent.instanceComponent == null) continue; + PrefabUtility.ApplyAddedComponent(addedComponent.instanceComponent, PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(obj), InteractionMode.AutomatedAction); + } + } + + if (applyRemovedComponents) + { + // Apply removed components (this removes them from the prefab) + var removedComponents = PrefabUtility.GetRemovedComponents(obj); + foreach (var removedComponent in removedComponents) + { + PrefabUtility.ApplyRemovedComponent(obj, removedComponent.assetComponent, InteractionMode.AutomatedAction); + } + } + + if (applyAddedGameObjects) + { + // Apply added GameObjects + var addedGameObjects = PrefabUtility.GetAddedGameObjects(obj); + foreach (var addedGameObject in addedGameObjects) + { + if (addedGameObject.instanceGameObject == null) continue; + PrefabUtility.ApplyAddedGameObject(addedGameObject.instanceGameObject, PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(obj), InteractionMode.AutomatedAction); + } + } + + if (applyRemovedGameObjects) + { + // Apply removed GameObjects (this removes them from the prefab) + var removedGameObjects = PrefabUtility.GetRemovedGameObjects(obj); + foreach (var removedGameObject in removedGameObjects) + { + PrefabUtility.ApplyRemovedGameObject(obj, removedGameObject.assetGameObject, InteractionMode.AutomatedAction); + } + } + + successCount++; + } + catch (System.Exception e) + { + failCount++; + failedObjects.Add($"{obj.name}: {e.Message}"); + Debug.LogError($"Failed to apply overrides for {obj.name}: {e.Message}", obj); + } + } + + Undo.CollapseUndoOperations(undoGroup); + + // Mark scenes as dirty + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene.isLoaded) + { + EditorSceneManager.MarkSceneDirty(scene); + } + } + + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + + // Show results + string message = $"Applied overrides to {successCount} prefab instance(s)."; + if (failCount > 0) + { + message += $"\n\nFailed: {failCount} instance(s).\n\nFailed objects:\n" + string.Join("\n", failedObjects); + } + + EditorUtility.DisplayDialog("Apply Overrides Complete", message, "OK"); + Debug.Log($"Multi Prefab Override: {message}"); + + RefreshSelection(); + Repaint(); + } + + private void RevertAllOverrides() + { + if (selectedPrefabInstances.Count == 0) + { + EditorUtility.DisplayDialog("No Selection", "Please select prefab instances first.", "OK"); + return; + } + + if (!EditorUtility.DisplayDialog("Revert Overrides", + $"This will revert all overrides from {selectedPrefabInstances.Count} prefab instance(s).\n\nThis action cannot be undone. Continue?", + "Yes", "Cancel")) + { + return; + } + + int successCount = 0; + int failCount = 0; + + Undo.SetCurrentGroupName("Revert Prefab Overrides"); + int undoGroup = Undo.GetCurrentGroup(); + + foreach (GameObject obj in selectedPrefabInstances) + { + if (obj == null) continue; + + try + { + Undo.RegisterFullObjectHierarchyUndo(obj, "Revert Prefab Overrides"); + PrefabUtility.RevertPrefabInstance(obj, InteractionMode.AutomatedAction); + successCount++; + } + catch (System.Exception e) + { + failCount++; + Debug.LogError($"Failed to revert overrides for {obj.name}: {e.Message}", obj); + } + } + + Undo.CollapseUndoOperations(undoGroup); + + // Mark scenes as dirty + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene.isLoaded) + { + EditorSceneManager.MarkSceneDirty(scene); + } + } + + string message = $"Reverted overrides from {successCount} prefab instance(s)."; + if (failCount > 0) + { + message += $"\n\nFailed: {failCount} instance(s)."; + } + + EditorUtility.DisplayDialog("Revert Overrides Complete", message, "OK"); + Debug.Log($"Multi Prefab Override: {message}"); + + RefreshSelection(); + Repaint(); + } + } +} + diff --git a/Assets/Editor/MultiPrefabOverrideTool.cs.meta b/Assets/Editor/MultiPrefabOverrideTool.cs.meta new file mode 100644 index 0000000000..c937a30298 --- /dev/null +++ b/Assets/Editor/MultiPrefabOverrideTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5eed077435b97514c881f50b0f342928 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Utils/Editor/ClearMissingScript.cs b/Assets/PerfectWorld/Scripts/Utils/Editor/ClearMissingScript.cs index feaf2159b9..7a9c44c3df 100644 --- a/Assets/PerfectWorld/Scripts/Utils/Editor/ClearMissingScript.cs +++ b/Assets/PerfectWorld/Scripts/Utils/Editor/ClearMissingScript.cs @@ -7,7 +7,7 @@ namespace BrewMonster.Scripts.Utils.Editor { public static class ClearMissingScript { - [MenuItem("Tools/Brew Monster/Clear Missing Script")] + [MenuItem("Tools/Brew Monster/Clear Missing Script/Active Scene")] public static void ClearMissingScriptsInActiveScene() { var scene = SceneManager.GetActiveScene(); @@ -30,7 +30,195 @@ namespace BrewMonster.Scripts.Utils.Editor EditorSceneManager.MarkSceneDirty(scene); } - EditorUtility.DisplayDialog("Clear Missing Script", $"Removed {removedCount} missing script(s).", "OK"); + 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"); + } + + 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)