Files
AppleHillsProduction/Assets/Editor/Developer/RemoveInteractableBaseComponents.cs

650 lines
28 KiB
C#

using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
using System;
using System.Collections.Generic;
using System.IO;
using Interactions;
namespace Editor
{
public class RemoveInteractableBaseComponents : EditorWindow
{
private List<string> problematicPrefabs = new List<string>();
private List<string> problematicScenes = new List<string>();
private Vector2 scrollPosition;
private bool hasScanned;
private int componentsFound;
[MenuItem("AppleHills/Developer/Remove InteractableBase Components")]
public static void ShowWindow()
{
var window = GetWindow<RemoveInteractableBaseComponents>("Remove InteractableBase");
window.minSize = new Vector2(700, 500);
}
private void OnGUI()
{
GUILayout.Label("Remove InteractableBase Component References", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"This tool finds and removes EXACT InteractableBase components from prefabs and scenes.\n\n" +
"Only finds the bare base class, NOT derived types like Pickup/ItemSlot/OneClickInteraction.\n\n" +
"If components depend on InteractableBase, you'll be prompted to replace it.",
MessageType.Info);
EditorGUILayout.Space();
if (GUILayout.Button("Scan All Prefabs and Scenes", GUILayout.Height(35)))
{
ScanAll();
}
EditorGUILayout.Space();
if (hasScanned)
{
EditorGUILayout.LabelField($"Found {componentsFound} exact InteractableBase components", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"In {problematicPrefabs.Count} prefabs");
EditorGUILayout.LabelField($"In {problematicScenes.Count} scenes");
if (componentsFound > 0)
{
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
if (problematicPrefabs.Count > 0 && GUILayout.Button($"Remove from Prefabs ({problematicPrefabs.Count})", GUILayout.Height(35)))
{
RemoveFromAllPrefabs();
}
if (problematicScenes.Count > 0 && GUILayout.Button($"Remove from Scenes ({problematicScenes.Count})", GUILayout.Height(35)))
{
RemoveFromAllScenes();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button("Remove All (Prefabs + Scenes)", GUILayout.Height(35)))
{
RemoveAll();
}
EditorGUILayout.Space();
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
if (problematicPrefabs.Count > 0)
{
EditorGUILayout.LabelField("Prefabs:", EditorStyles.boldLabel);
foreach (var prefabPath in problematicPrefabs)
{
EditorGUILayout.BeginHorizontal("box");
EditorGUILayout.LabelField(prefabPath);
if (GUILayout.Button("Remove", GUILayout.Width(80)))
{
RemoveFromPrefab(prefabPath);
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.Space();
}
if (problematicScenes.Count > 0)
{
EditorGUILayout.LabelField("Scenes:", EditorStyles.boldLabel);
foreach (var scenePath in problematicScenes)
{
EditorGUILayout.BeginHorizontal("box");
EditorGUILayout.LabelField(scenePath);
if (GUILayout.Button("Remove", GUILayout.Width(80)))
{
RemoveFromScene(scenePath);
}
EditorGUILayout.EndHorizontal();
}
}
EditorGUILayout.EndScrollView();
}
else
{
EditorGUILayout.HelpBox("No exact InteractableBase components found! All clean.", MessageType.Info);
}
}
}
private void ScanAll()
{
problematicPrefabs.Clear();
problematicScenes.Clear();
componentsFound = 0;
hasScanned = true;
ScanPrefabs();
ScanScenes();
Debug.Log($"<color=cyan>[Scan Complete]</color> Found {componentsFound} exact InteractableBase components in {problematicPrefabs.Count} prefabs and {problematicScenes.Count} scenes.");
}
private void ScanPrefabs()
{
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets" });
EditorUtility.DisplayProgressBar("Scanning Prefabs", "Starting...", 0f);
for (int i = 0; i < prefabGuids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(prefabGuids[i]);
EditorUtility.DisplayProgressBar("Scanning Prefabs",
$"Checking {i + 1}/{prefabGuids.Length}: {Path.GetFileName(path)}",
(float)i / prefabGuids.Length);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab != null)
{
// Check if this prefab or any of its children have EXACTLY InteractableBase (not derived types)
InteractableBase[] components = prefab.GetComponentsInChildren<InteractableBase>(true);
int exactMatches = 0;
foreach (var component in components)
{
if (component != null && component.GetType() == typeof(InteractableBase))
{
exactMatches++;
}
}
if (exactMatches > 0)
{
problematicPrefabs.Add(path);
componentsFound += exactMatches;
}
}
}
EditorUtility.ClearProgressBar();
}
private void ScanScenes()
{
string[] sceneGuids = AssetDatabase.FindAssets("t:Scene", new[] { "Assets/Scenes" });
EditorUtility.DisplayProgressBar("Scanning Scenes", "Starting...", 0f);
string currentScenePath = SceneManager.GetActiveScene().path;
for (int i = 0; i < sceneGuids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(sceneGuids[i]);
EditorUtility.DisplayProgressBar("Scanning Scenes",
$"Checking {i + 1}/{sceneGuids.Length}: {Path.GetFileName(path)}",
(float)i / sceneGuids.Length);
EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
// Find all InteractableBase components in the scene
InteractableBase[] components = GameObject.FindObjectsByType<InteractableBase>(FindObjectsSortMode.None);
int exactMatches = 0;
foreach (var component in components)
{
if (component != null && component.GetType() == typeof(InteractableBase))
{
exactMatches++;
}
}
if (exactMatches > 0)
{
problematicScenes.Add(path);
componentsFound += exactMatches;
}
}
// Restore original scene
if (!string.IsNullOrEmpty(currentScenePath))
{
EditorSceneManager.OpenScene(currentScenePath);
}
EditorUtility.ClearProgressBar();
}
private void RemoveFromAllPrefabs()
{
if (!EditorUtility.DisplayDialog("Confirm Removal",
$"This will remove InteractableBase components from {problematicPrefabs.Count} prefabs.\n\n" +
"This cannot be undone (unless you use version control).\n\nContinue?",
"Yes, Remove", "Cancel"))
{
return;
}
int removedCount = 0;
for (int i = 0; i < problematicPrefabs.Count; i++)
{
string path = problematicPrefabs[i];
EditorUtility.DisplayProgressBar("Removing Components from Prefabs",
$"Processing {i + 1}/{problematicPrefabs.Count}: {Path.GetFileName(path)}",
(float)i / problematicPrefabs.Count);
removedCount += RemoveFromPrefab(path);
}
EditorUtility.ClearProgressBar();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"<color=green>[Prefab Cleanup Complete]</color> Removed {removedCount} InteractableBase components from prefabs.");
ScanAll();
}
private void RemoveFromAllScenes()
{
if (!EditorUtility.DisplayDialog("Confirm Removal",
$"This will remove InteractableBase components from {problematicScenes.Count} scenes.\n\n" +
"This cannot be undone (unless you use version control).\n\nContinue?",
"Yes, Remove", "Cancel"))
{
return;
}
int removedCount = 0;
string currentScenePath = SceneManager.GetActiveScene().path;
for (int i = 0; i < problematicScenes.Count; i++)
{
string path = problematicScenes[i];
EditorUtility.DisplayProgressBar("Removing Components from Scenes",
$"Processing {i + 1}/{problematicScenes.Count}: {Path.GetFileName(path)}",
(float)i / problematicScenes.Count);
removedCount += RemoveFromScene(path);
}
// Restore original scene
if (!string.IsNullOrEmpty(currentScenePath))
{
EditorSceneManager.OpenScene(currentScenePath);
}
EditorUtility.ClearProgressBar();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"<color=green>[Scene Cleanup Complete]</color> Removed {removedCount} InteractableBase components from scenes.");
ScanAll();
}
private void RemoveAll()
{
if (!EditorUtility.DisplayDialog("Confirm Removal",
$"This will remove {componentsFound} InteractableBase components from:\n" +
$"• {problematicPrefabs.Count} prefabs\n" +
$"• {problematicScenes.Count} scenes\n\n" +
"This cannot be undone (unless you use version control).\n\nContinue?",
"Yes, Remove", "Cancel"))
{
return;
}
int removedCount = 0;
// Remove from prefabs
for (int i = 0; i < problematicPrefabs.Count; i++)
{
string path = problematicPrefabs[i];
EditorUtility.DisplayProgressBar("Removing Components",
$"Prefabs {i + 1}/{problematicPrefabs.Count}: {Path.GetFileName(path)}",
(float)i / (problematicPrefabs.Count + problematicScenes.Count));
removedCount += RemoveFromPrefab(path);
}
// Remove from scenes
string currentScenePath = SceneManager.GetActiveScene().path;
for (int i = 0; i < problematicScenes.Count; i++)
{
string path = problematicScenes[i];
EditorUtility.DisplayProgressBar("Removing Components",
$"Scenes {i + 1}/{problematicScenes.Count}: {Path.GetFileName(path)}",
(float)(problematicPrefabs.Count + i) / (problematicPrefabs.Count + problematicScenes.Count));
removedCount += RemoveFromScene(path);
}
// Restore original scene
if (!string.IsNullOrEmpty(currentScenePath))
{
EditorSceneManager.OpenScene(currentScenePath);
}
EditorUtility.ClearProgressBar();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"<color=green>[Removal Complete]</color> Removed {removedCount} InteractableBase components.");
ScanAll();
}
private int RemoveFromPrefab(string assetPath)
{
int removed = 0;
try
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (prefab == null)
{
Debug.LogWarning($"Could not load prefab at path: {assetPath}");
return 0;
}
string prefabPath = AssetDatabase.GetAssetPath(prefab);
GameObject prefabContents = null;
try
{
prefabContents = PrefabUtility.LoadPrefabContents(prefabPath);
}
catch (Exception loadEx)
{
Debug.LogError($"Failed to load prefab contents for {assetPath}: {loadEx.Message}");
return 0;
}
if (prefabContents == null)
{
Debug.LogWarning($"Prefab contents are null for: {assetPath}");
return 0;
}
InteractableBase[] components = prefabContents.GetComponentsInChildren<InteractableBase>(true);
if (components == null || components.Length == 0)
{
PrefabUtility.UnloadPrefabContents(prefabContents);
return 0;
}
foreach (var component in components)
{
if (component == null)
continue;
// Check if it's EXACTLY InteractableBase (not a derived type)
if (component.GetType() == typeof(InteractableBase))
{
// Cache references before destroying
GameObject targetObject = component.gameObject;
string objectName = targetObject != null ? targetObject.name : "Unknown";
// Check if GameObject already has a derived InteractableBase type
bool hasPickup = targetObject.GetComponent<Pickup>() != null;
bool hasItemSlot = targetObject.GetComponent<ItemSlot>() != null;
bool hasOneClick = targetObject.GetComponent<OneClickInteraction>() != null;
if (hasPickup || hasItemSlot || hasOneClick)
{
// GameObject already has a concrete type, safe to remove bare base class
DestroyImmediate(component);
removed++;
string existingType = hasItemSlot ? "ItemSlot" : (hasPickup ? "Pickup" : "OneClickInteraction");
Debug.Log($"<color=green>[Removed]</color> Bare InteractableBase from '{objectName}' (already has {existingType}) in prefab '{Path.GetFileName(assetPath)}'");
continue;
}
// Check what other components depend on InteractableBase
Component[] allComponents = targetObject.GetComponents<Component>();
List<string> dependentComponents = new List<string>();
foreach (var otherComponent in allComponents)
{
if (otherComponent == null || otherComponent == component)
continue;
var requireAttributes = otherComponent.GetType().GetCustomAttributes(typeof(RequireComponent), true);
foreach (RequireComponent attr in requireAttributes)
{
if (attr.m_Type0 == typeof(InteractableBase) ||
attr.m_Type1 == typeof(InteractableBase) ||
attr.m_Type2 == typeof(InteractableBase))
{
dependentComponents.Add(otherComponent.GetType().Name);
}
}
}
if (dependentComponents.Count > 0)
{
string dependencyList = string.Join(", ", dependentComponents);
string message = $"GameObject '{objectName}' in prefab '{Path.GetFileName(assetPath)}' has InteractableBase, " +
$"but these components depend on it:\n\n{dependencyList}\n\n" +
"Replace InteractableBase with:";
int choice = EditorUtility.DisplayDialogComplex(
"Component Dependency Detected",
message,
"Pickup",
"ItemSlot",
"OneClickInteraction");
Type replacementType = choice switch
{
0 => typeof(Pickup),
1 => typeof(ItemSlot),
2 => typeof(OneClickInteraction),
_ => null
};
if (replacementType != null)
{
// Cache component data before destroying
bool isOneTime = component.isOneTime;
float cooldown = component.cooldown;
CharacterToInteract characterToInteract = component.characterToInteract;
DestroyImmediate(component);
var newComponent = targetObject.AddComponent(replacementType) as InteractableBase;
if (newComponent != null)
{
newComponent.isOneTime = isOneTime;
newComponent.cooldown = cooldown;
newComponent.characterToInteract = characterToInteract;
removed++;
Debug.Log($"<color=cyan>[Replaced]</color> InteractableBase with {replacementType.Name} on '{objectName}' in prefab '{Path.GetFileName(assetPath)}'");
}
}
else
{
Debug.LogWarning($"Skipped removing InteractableBase from '{objectName}' - no replacement chosen");
}
}
else
{
DestroyImmediate(component);
removed++;
Debug.Log($"<color=yellow>[Removed]</color> InteractableBase from '{objectName}' in prefab '{Path.GetFileName(assetPath)}'");
}
}
}
if (removed > 0)
{
try
{
PrefabUtility.SaveAsPrefabAsset(prefabContents, prefabPath);
}
catch (Exception saveEx)
{
Debug.LogError($"Failed to save prefab {assetPath}: {saveEx.Message}");
}
}
PrefabUtility.UnloadPrefabContents(prefabContents);
}
catch (Exception ex)
{
Debug.LogError($"Error removing components from prefab {assetPath}: {ex.Message}\nStack: {ex.StackTrace}");
}
return removed;
}
private int RemoveFromScene(string scenePath)
{
int removed = 0;
try
{
Scene scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
if (!scene.isLoaded)
{
Debug.LogWarning($"Scene not loaded: {scenePath}");
return 0;
}
InteractableBase[] components = GameObject.FindObjectsByType<InteractableBase>(FindObjectsSortMode.None);
if (components == null || components.Length == 0)
{
return 0;
}
foreach (var component in components)
{
if (component == null)
continue;
if (component.GetType() == typeof(InteractableBase))
{
// Cache references before destroying
GameObject targetObject = component.gameObject;
string objectName = targetObject != null ? targetObject.name : "Unknown";
// Check if GameObject already has a derived InteractableBase type
bool hasPickup = targetObject.GetComponent<Pickup>() != null;
bool hasItemSlot = targetObject.GetComponent<ItemSlot>() != null;
bool hasOneClick = targetObject.GetComponent<OneClickInteraction>() != null;
if (hasPickup || hasItemSlot || hasOneClick)
{
// GameObject already has a concrete type, safe to remove bare base class
DestroyImmediate(component);
removed++;
string existingType = hasItemSlot ? "ItemSlot" : (hasPickup ? "Pickup" : "OneClickInteraction");
Debug.Log($"<color=green>[Removed]</color> Bare InteractableBase from '{objectName}' (already has {existingType}) in scene '{Path.GetFileName(scenePath)}'");
continue;
}
Component[] allComponents = targetObject.GetComponents<Component>();
List<string> dependentComponents = new List<string>();
foreach (var otherComponent in allComponents)
{
if (otherComponent == null || otherComponent == component)
continue;
var requireAttributes = otherComponent.GetType().GetCustomAttributes(typeof(RequireComponent), true);
foreach (RequireComponent attr in requireAttributes)
{
if (attr.m_Type0 == typeof(InteractableBase) ||
attr.m_Type1 == typeof(InteractableBase) ||
attr.m_Type2 == typeof(InteractableBase))
{
dependentComponents.Add(otherComponent.GetType().Name);
}
}
}
if (dependentComponents.Count > 0)
{
string dependencyList = string.Join(", ", dependentComponents);
string message = $"GameObject '{objectName}' in scene '{Path.GetFileName(scenePath)}' has InteractableBase, " +
$"but these components depend on it:\n\n{dependencyList}\n\n" +
"Replace InteractableBase with:";
int choice = EditorUtility.DisplayDialogComplex(
"Component Dependency Detected",
message,
"Pickup",
"ItemSlot",
"OneClickInteraction");
Type replacementType = choice switch
{
0 => typeof(Pickup),
1 => typeof(ItemSlot),
2 => typeof(OneClickInteraction),
_ => null
};
if (replacementType != null)
{
// Cache component data before destroying
bool isOneTime = component.isOneTime;
float cooldown = component.cooldown;
CharacterToInteract characterToInteract = component.characterToInteract;
DestroyImmediate(component);
var newComponent = targetObject.AddComponent(replacementType) as InteractableBase;
if (newComponent != null)
{
newComponent.isOneTime = isOneTime;
newComponent.cooldown = cooldown;
newComponent.characterToInteract = characterToInteract;
removed++;
Debug.Log($"<color=cyan>[Replaced]</color> InteractableBase with {replacementType.Name} on '{objectName}' in scene '{Path.GetFileName(scenePath)}'");
}
}
else
{
Debug.LogWarning($"Skipped removing InteractableBase from '{objectName}' - no replacement chosen");
}
}
else
{
DestroyImmediate(component);
removed++;
Debug.Log($"<color=yellow>[Removed]</color> InteractableBase from '{objectName}' in scene '{Path.GetFileName(scenePath)}'");
}
}
}
if (removed > 0)
{
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
}
}
catch (Exception ex)
{
Debug.LogError($"Error removing components from scene {scenePath}: {ex.Message}\nStack: {ex.StackTrace}");
}
return removed;
}
}
}