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

229 lines
8.8 KiB
C#
Raw Normal View History

Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44) ### Interactables Architecture Refactor - Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc. - Created `InteractableBase` abstract base class with common functionality that replaces the old component - Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes - Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience ### State Machine Integration - Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements - Replaced all previous StateMachines by `AppleMachine` - Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game - Restores directly to target state without triggering transitional logic - Migration tool converts existing instances ### Prefab Organization - Saved changes from scenes into prefabs - Cleaned up duplicated components, confusing prefabs hierarchies - Created prefab variants where possible - Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder - Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder - Updated prefab references - All scene references updated to new locations - Removed placeholder files from Characters, Levels, UI, and Minigames folders ### Scene Updates - Quarry scene with major updates - Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD) - Added proper lighting data - Updated all interactable components to new architecture ### Minor editor tools - New tool for testing cards from an editor window (no in-scene object required) - Updated Interactable Inspector - New debug option to opt in-and-out of the save/load system - Tooling for easier migration Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace Editor
{
public class RemoveOldInteractableReferences : EditorWindow
{
private List<string> problematicPrefabs = new List<string>();
private Vector2 scrollPosition;
private bool hasScanned = false;
[MenuItem("AppleHills/Developer/Remove Old Interactable References")]
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44) ### Interactables Architecture Refactor - Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc. - Created `InteractableBase` abstract base class with common functionality that replaces the old component - Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes - Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience ### State Machine Integration - Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements - Replaced all previous StateMachines by `AppleMachine` - Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game - Restores directly to target state without triggering transitional logic - Migration tool converts existing instances ### Prefab Organization - Saved changes from scenes into prefabs - Cleaned up duplicated components, confusing prefabs hierarchies - Created prefab variants where possible - Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder - Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder - Updated prefab references - All scene references updated to new locations - Removed placeholder files from Characters, Levels, UI, and Minigames folders ### Scene Updates - Quarry scene with major updates - Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD) - Added proper lighting data - Updated all interactable components to new architecture ### Minor editor tools - New tool for testing cards from an editor window (no in-scene object required) - Updated Interactable Inspector - New debug option to opt in-and-out of the save/load system - Tooling for easier migration Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
public static void ShowWindow()
{
var window = GetWindow<RemoveOldInteractableReferences>("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($"<color=cyan>[Scan Complete]</color> 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($"<color=green>[Cleanup Complete]</color> Cleaned {cleanedCount} prefabs.");
// Re-scan to update the list
ScanPrefabs();
}
private void CleanSinglePrefab(string assetPath)
{
if (CleanPrefabFile(assetPath))
{
Debug.Log($"<color=green>[Cleaned]</color> {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;
}
}
}
}