using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; namespace Editor { public class RemoveOldInteractableReferences : EditorWindow { private List problematicPrefabs = new List(); private Vector2 scrollPosition; private bool hasScanned = false; [MenuItem("AppleHills/Developer/Remove Old Interactable References")] public static void ShowWindow() { var window = GetWindow("Clean Old Interactables"); window.minSize = new Vector2(600, 400); } private void OnGUI() { GUILayout.Label("Remove Old Interactable/InteractableBase References", EditorStyles.boldLabel); EditorGUILayout.HelpBox( "This tool finds and removes references to:\n" + "- Interactable (old script name)\n" + "- InteractableBase (abstract class - not allowed on prefabs)\n\n" + "These should be replaced by concrete types: Pickup, ItemSlot, or OneClickInteraction", MessageType.Info); EditorGUILayout.Space(); if (GUILayout.Button("Scan All Prefabs", GUILayout.Height(30))) { ScanPrefabs(); } EditorGUILayout.Space(); if (hasScanned) { EditorGUILayout.LabelField($"Found {problematicPrefabs.Count} prefabs with old references", EditorStyles.boldLabel); if (problematicPrefabs.Count > 0) { EditorGUILayout.Space(); if (GUILayout.Button("Clean All Prefabs", GUILayout.Height(30))) { CleanAllPrefabs(); } EditorGUILayout.Space(); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); foreach (var prefabPath in problematicPrefabs) { EditorGUILayout.BeginHorizontal("box"); EditorGUILayout.LabelField(prefabPath); if (GUILayout.Button("Clean This", GUILayout.Width(80))) { CleanSinglePrefab(prefabPath); } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); } else { EditorGUILayout.HelpBox("No problematic prefabs found! All clean.", MessageType.Info); } } } private void ScanPrefabs() { problematicPrefabs.Clear(); hasScanned = true; 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); if (PrefabHasOldInteractableReference(path)) { problematicPrefabs.Add(path); } } EditorUtility.ClearProgressBar(); Debug.Log($"[Scan Complete] Found {problematicPrefabs.Count} prefabs with old Interactable/InteractableBase references."); } private bool PrefabHasOldInteractableReference(string assetPath) { try { string fullPath = Path.GetFullPath(assetPath); string content = File.ReadAllText(fullPath); // Look for GUID of Interactable script (11500000 is MonoBehaviour type) // We're looking for the script reference pattern in YAML // Pattern: m_Script: {fileID: 11500000, guid: SCRIPT_GUID, type: 3} // Check if content contains "Interactable" class name references // This is a simple text search - if the YAML contains these class names, it likely references them if (content.Contains("InteractableBase") || (content.Contains("Interactable") && !content.Contains("OneClickInteraction"))) { // Additional check: Look for MonoBehaviour blocks with missing scripts (fileID: 0) if (Regex.IsMatch(content, @"m_Script:\s*\{fileID:\s*0\}")) { return true; } // Check for direct class name matches in script references if (Regex.IsMatch(content, @"m_Name:\s*(Interactable|InteractableBase)")) { return true; } } return false; } catch (System.Exception ex) { Debug.LogWarning($"Error scanning {assetPath}: {ex.Message}"); return false; } } private void CleanAllPrefabs() { if (!EditorUtility.DisplayDialog("Confirm Cleanup", $"This will remove old Interactable/InteractableBase references from {problematicPrefabs.Count} prefabs.\n\nThis cannot be undone (unless you use version control).\n\nContinue?", "Yes, Clean", "Cancel")) { return; } int cleanedCount = 0; for (int i = 0; i < problematicPrefabs.Count; i++) { string path = problematicPrefabs[i]; EditorUtility.DisplayProgressBar("Cleaning Prefabs", $"Cleaning {i + 1}/{problematicPrefabs.Count}: {Path.GetFileName(path)}", (float)i / problematicPrefabs.Count); if (CleanPrefabFile(path)) { cleanedCount++; } } EditorUtility.ClearProgressBar(); AssetDatabase.Refresh(); Debug.Log($"[Cleanup Complete] Cleaned {cleanedCount} prefabs."); // Re-scan to update the list ScanPrefabs(); } private void CleanSinglePrefab(string assetPath) { if (CleanPrefabFile(assetPath)) { Debug.Log($"[Cleaned] {assetPath}"); AssetDatabase.Refresh(); // Re-scan to update the list ScanPrefabs(); } } private bool CleanPrefabFile(string assetPath) { try { string fullPath = Path.GetFullPath(assetPath); string content = File.ReadAllText(fullPath); string originalContent = content; // Pattern 1: Remove entire MonoBehaviour component blocks with missing scripts (fileID: 0) // This removes the component header and all its properties until the next component or end string missingScriptPattern = @"--- !u!114 &\d+\r?\nMonoBehaviour:(?:\r?\n(?!---).+)*?\r?\n m_Script: \{fileID: 0\}(?:\r?\n(?!---).+)*"; content = Regex.Replace(content, missingScriptPattern, "", RegexOptions.Multiline); // Pattern 2: Remove MonoBehaviour blocks that explicitly reference InteractableBase or Interactable // This is more aggressive and targets the class name directly string interactablePattern = @"--- !u!114 &\d+\r?\nMonoBehaviour:(?:\r?\n(?!---).+)*?\r?\n m_Name: (?:Interactable|InteractableBase)(?:\r?\n(?!---).+)*"; content = Regex.Replace(content, interactablePattern, "", RegexOptions.Multiline); if (content != originalContent) { // Clean up any double blank lines that might have been created content = Regex.Replace(content, @"(\r?\n){3,}", "\n\n"); File.WriteAllText(fullPath, content); return true; } return false; } catch (System.Exception ex) { Debug.LogError($"Error cleaning {assetPath}: {ex.Message}"); return false; } } } }