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 problematicPrefabs = new List(); private List problematicScenes = new List(); private Vector2 scrollPosition; private bool hasScanned; private int componentsFound; [MenuItem("AppleHills/Remove InteractableBase Components")] public static void ShowWindow() { var window = GetWindow("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($"[Scan Complete] 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(path); if (prefab != null) { // Check if this prefab or any of its children have EXACTLY InteractableBase (not derived types) InteractableBase[] components = prefab.GetComponentsInChildren(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(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($"[Prefab Cleanup Complete] 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($"[Scene Cleanup Complete] 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($"[Removal Complete] Removed {removedCount} InteractableBase components."); ScanAll(); } private int RemoveFromPrefab(string assetPath) { int removed = 0; try { GameObject prefab = AssetDatabase.LoadAssetAtPath(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(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() != null; bool hasItemSlot = targetObject.GetComponent() != null; bool hasOneClick = targetObject.GetComponent() != 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($"[Removed] Bare InteractableBase from '{objectName}' (already has {existingType}) in prefab '{Path.GetFileName(assetPath)}'"); continue; } // Check what other components depend on InteractableBase Component[] allComponents = targetObject.GetComponents(); List dependentComponents = new List(); 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($"[Replaced] 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($"[Removed] 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(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() != null; bool hasItemSlot = targetObject.GetComponent() != null; bool hasOneClick = targetObject.GetComponent() != 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($"[Removed] Bare InteractableBase from '{objectName}' (already has {existingType}) in scene '{Path.GetFileName(scenePath)}'"); continue; } Component[] allComponents = targetObject.GetComponents(); List dependentComponents = new List(); 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($"[Replaced] 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($"[Removed] 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; } } }