Add overrides multi prefabs tool and clear missing scripts
This commit is contained in:
@@ -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<GameObject> selectedPrefabInstances = new List<GameObject>();
|
||||
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<MultiPrefabOverrideTool>("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<string> failedObjects = new List<string>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5eed077435b97514c881f50b0f342928
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user