749 lines
28 KiB
C#
749 lines
28 KiB
C#
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using UnityEditor;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using Interactions;
|
|||
|
|
using System.Reflection;
|
|||
|
|
using System;
|
|||
|
|
|
|||
|
|
namespace AppleHills.Editor.InteractionSystem
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Editor utility for managing and debugging interactable objects in the scene.
|
|||
|
|
/// Provides scene object locator, inspector editing, and runtime debugging capabilities.
|
|||
|
|
/// </summary>
|
|||
|
|
public class InteractableEditorWindow : EditorWindow
|
|||
|
|
{
|
|||
|
|
// Tab management
|
|||
|
|
private int _selectedTab = 0;
|
|||
|
|
private readonly string[] _tabNames = { "Scene", "Debug" };
|
|||
|
|
|
|||
|
|
// Scene interactables tracking
|
|||
|
|
private List<InteractableBase> _sceneInteractables = new List<InteractableBase>();
|
|||
|
|
private InteractableBase _selectedInteractable;
|
|||
|
|
private GameObject _selectedGameObject;
|
|||
|
|
|
|||
|
|
// UI state
|
|||
|
|
private Vector2 _listScrollPosition;
|
|||
|
|
private Vector2 _inspectorScrollPosition;
|
|||
|
|
private Vector2 _debugScrollPosition;
|
|||
|
|
private string _searchQuery = "";
|
|||
|
|
|
|||
|
|
// Runtime state
|
|||
|
|
private bool _isPlaying = false;
|
|||
|
|
|
|||
|
|
// Editor for selected interactable
|
|||
|
|
private UnityEditor.Editor _cachedEditor;
|
|||
|
|
|
|||
|
|
// Available interactable types for adding
|
|||
|
|
private static readonly Type[] AvailableInteractableTypes = new Type[]
|
|||
|
|
{
|
|||
|
|
typeof(OneClickInteraction),
|
|||
|
|
typeof(Pickup),
|
|||
|
|
typeof(ItemSlot),
|
|||
|
|
typeof(SaveableInteractable),
|
|||
|
|
typeof(InteractableBase)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
[MenuItem("AppleHills/Interactable Editor")]
|
|||
|
|
public static void ShowWindow()
|
|||
|
|
{
|
|||
|
|
var window = GetWindow<InteractableEditorWindow>("Interactable Editor");
|
|||
|
|
window.minSize = new Vector2(900, 600);
|
|||
|
|
window.Show();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnEnable()
|
|||
|
|
{
|
|||
|
|
RefreshSceneInteractables();
|
|||
|
|
|
|||
|
|
// Register for scene and selection changes
|
|||
|
|
UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened;
|
|||
|
|
UnityEditor.SceneManagement.EditorSceneManager.sceneClosed += OnSceneClosed;
|
|||
|
|
Selection.selectionChanged += OnSelectionChanged;
|
|||
|
|
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
|||
|
|
EditorApplication.hierarchyChanged += OnHierarchyChanged;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnDisable()
|
|||
|
|
{
|
|||
|
|
UnityEditor.SceneManagement.EditorSceneManager.sceneOpened -= OnSceneOpened;
|
|||
|
|
UnityEditor.SceneManagement.EditorSceneManager.sceneClosed -= OnSceneClosed;
|
|||
|
|
Selection.selectionChanged -= OnSelectionChanged;
|
|||
|
|
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
|||
|
|
EditorApplication.hierarchyChanged -= OnHierarchyChanged;
|
|||
|
|
|
|||
|
|
// Clean up cached editor
|
|||
|
|
if (_cachedEditor != null)
|
|||
|
|
{
|
|||
|
|
DestroyImmediate(_cachedEditor);
|
|||
|
|
_cachedEditor = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnSceneOpened(UnityEngine.SceneManagement.Scene scene, UnityEditor.SceneManagement.OpenSceneMode mode)
|
|||
|
|
{
|
|||
|
|
RefreshSceneInteractables();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnSceneClosed(UnityEngine.SceneManagement.Scene scene)
|
|||
|
|
{
|
|||
|
|
RefreshSceneInteractables();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnHierarchyChanged()
|
|||
|
|
{
|
|||
|
|
RefreshSceneInteractables();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnSelectionChanged()
|
|||
|
|
{
|
|||
|
|
// Check if selected object has changed
|
|||
|
|
if (Selection.activeGameObject != null && Selection.activeGameObject != _selectedGameObject)
|
|||
|
|
{
|
|||
|
|
var interactable = Selection.activeGameObject.GetComponent<InteractableBase>();
|
|||
|
|
if (interactable != null)
|
|||
|
|
{
|
|||
|
|
// GameObject has an interactable - select it
|
|||
|
|
SelectInteractable(interactable);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// GameObject doesn't have an interactable - track it for add menu
|
|||
|
|
_selectedGameObject = Selection.activeGameObject;
|
|||
|
|
_selectedInteractable = null;
|
|||
|
|
|
|||
|
|
// Clear cached editor
|
|||
|
|
if (_cachedEditor != null)
|
|||
|
|
{
|
|||
|
|
DestroyImmediate(_cachedEditor);
|
|||
|
|
_cachedEditor = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Repaint();
|
|||
|
|
}
|
|||
|
|
else if (Selection.activeGameObject == null)
|
|||
|
|
{
|
|||
|
|
// Nothing selected - clear selection
|
|||
|
|
_selectedGameObject = null;
|
|||
|
|
_selectedInteractable = null;
|
|||
|
|
|
|||
|
|
if (_cachedEditor != null)
|
|||
|
|
{
|
|||
|
|
DestroyImmediate(_cachedEditor);
|
|||
|
|
_cachedEditor = null;
|
|||
|
|
}
|
|||
|
|
Repaint();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnPlayModeStateChanged(PlayModeStateChange state)
|
|||
|
|
{
|
|||
|
|
_isPlaying = EditorApplication.isPlaying;
|
|||
|
|
if (_isPlaying)
|
|||
|
|
{
|
|||
|
|
RefreshSceneInteractables();
|
|||
|
|
}
|
|||
|
|
Repaint();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnGUI()
|
|||
|
|
{
|
|||
|
|
DrawHeader();
|
|||
|
|
|
|||
|
|
_selectedTab = GUILayout.Toolbar(_selectedTab, _tabNames);
|
|||
|
|
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
|
|||
|
|
switch (_selectedTab)
|
|||
|
|
{
|
|||
|
|
case 0: // Scene tab
|
|||
|
|
DrawSceneTab();
|
|||
|
|
break;
|
|||
|
|
case 1: // Debug tab
|
|||
|
|
DrawDebugTab();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region Header UI
|
|||
|
|
|
|||
|
|
private void DrawHeader()
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60)))
|
|||
|
|
{
|
|||
|
|
RefreshSceneInteractables();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
GUILayout.FlexibleSpace();
|
|||
|
|
|
|||
|
|
// Tab-specific toolbar options
|
|||
|
|
if (_selectedTab == 0) // Scene tab
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.LabelField($"Found: {_sceneInteractables.Count} interactables", EditorStyles.toolbarButton, GUILayout.Width(150));
|
|||
|
|
}
|
|||
|
|
else if (_selectedTab == 1) // Debug tab
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.LabelField(_isPlaying ? "Runtime Active" : "Editor Mode", EditorStyles.toolbarButton, GUILayout.Width(100));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Scene Tab
|
|||
|
|
|
|||
|
|
private void DrawSceneTab()
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
|
|||
|
|
// Left panel - interactable list
|
|||
|
|
EditorGUILayout.BeginVertical(GUILayout.Width(300));
|
|||
|
|
DrawInteractableListPanel();
|
|||
|
|
EditorGUILayout.EndVertical();
|
|||
|
|
|
|||
|
|
// Separator
|
|||
|
|
EditorGUILayout.Space(5);
|
|||
|
|
|
|||
|
|
// Right panel - inspector/editor
|
|||
|
|
EditorGUILayout.BeginVertical();
|
|||
|
|
DrawInspectorPanel();
|
|||
|
|
EditorGUILayout.EndVertical();
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DrawInteractableListPanel()
|
|||
|
|
{
|
|||
|
|
// Search field
|
|||
|
|
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
|||
|
|
_searchQuery = EditorGUILayout.TextField(_searchQuery, EditorStyles.toolbarSearchField);
|
|||
|
|
if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(20)) && !string.IsNullOrEmpty(_searchQuery))
|
|||
|
|
{
|
|||
|
|
_searchQuery = "";
|
|||
|
|
GUI.FocusControl(null);
|
|||
|
|
}
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
|
|||
|
|
_listScrollPosition = EditorGUILayout.BeginScrollView(_listScrollPosition);
|
|||
|
|
|
|||
|
|
// Filter interactables by search query
|
|||
|
|
var filteredInteractables = string.IsNullOrEmpty(_searchQuery)
|
|||
|
|
? _sceneInteractables
|
|||
|
|
: _sceneInteractables.Where(i => i != null && i.gameObject.name.ToLower().Contains(_searchQuery.ToLower())).ToList();
|
|||
|
|
|
|||
|
|
if (filteredInteractables.Count == 0)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.HelpBox("No interactables found in scene", MessageType.Info);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
foreach (var interactable in filteredInteractables)
|
|||
|
|
{
|
|||
|
|
if (interactable == null) continue;
|
|||
|
|
|
|||
|
|
if (DrawInteractableListItem(interactable))
|
|||
|
|
{
|
|||
|
|
SelectInteractable(interactable);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndScrollView();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool DrawInteractableListItem(InteractableBase interactable)
|
|||
|
|
{
|
|||
|
|
bool isSelected = interactable == _selectedInteractable;
|
|||
|
|
|
|||
|
|
Color originalColor = GUI.backgroundColor;
|
|||
|
|
if (isSelected)
|
|||
|
|
GUI.backgroundColor = new Color(0.3f, 0.5f, 0.8f);
|
|||
|
|
|
|||
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|||
|
|
GUI.backgroundColor = originalColor;
|
|||
|
|
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
|
|||
|
|
// Interactable info
|
|||
|
|
EditorGUILayout.BeginVertical();
|
|||
|
|
EditorGUILayout.LabelField(interactable.gameObject.name, EditorStyles.boldLabel);
|
|||
|
|
EditorGUILayout.LabelField(interactable.GetType().Name, EditorStyles.miniLabel);
|
|||
|
|
|
|||
|
|
// Show additional info for specific types
|
|||
|
|
if (interactable is Pickup pickup && pickup.itemData != null)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.LabelField($"Item: {pickup.itemData.itemName}", EditorStyles.miniLabel);
|
|||
|
|
}
|
|||
|
|
else if (interactable is ItemSlot slot && slot.itemData != null)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.LabelField($"Slot: {slot.itemData.itemName}", EditorStyles.miniLabel);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndVertical();
|
|||
|
|
|
|||
|
|
GUILayout.FlexibleSpace();
|
|||
|
|
|
|||
|
|
// Ping button
|
|||
|
|
if (GUILayout.Button("Ping", GUILayout.Width(50)))
|
|||
|
|
{
|
|||
|
|
EditorGUIUtility.PingObject(interactable.gameObject);
|
|||
|
|
Selection.activeGameObject = interactable.gameObject;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
EditorGUILayout.EndVertical();
|
|||
|
|
|
|||
|
|
Rect itemRect = GUILayoutUtility.GetLastRect();
|
|||
|
|
bool wasClicked = Event.current.type == EventType.MouseDown && Event.current.button == 0
|
|||
|
|
&& itemRect.Contains(Event.current.mousePosition);
|
|||
|
|
|
|||
|
|
if (wasClicked)
|
|||
|
|
{
|
|||
|
|
Event.current.Use();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return wasClicked;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DrawInspectorPanel()
|
|||
|
|
{
|
|||
|
|
_inspectorScrollPosition = EditorGUILayout.BeginScrollView(_inspectorScrollPosition);
|
|||
|
|
|
|||
|
|
if (_selectedInteractable == null && _selectedGameObject == null)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.HelpBox("Select an interactable from the list or in the scene hierarchy", MessageType.Info);
|
|||
|
|
}
|
|||
|
|
else if (_selectedGameObject != null && _selectedInteractable == null)
|
|||
|
|
{
|
|||
|
|
// Selected object doesn't have an interactable - show add menu
|
|||
|
|
DrawAddInteractableMenu();
|
|||
|
|
}
|
|||
|
|
else if (_selectedInteractable != null)
|
|||
|
|
{
|
|||
|
|
// Draw custom inspector for the selected interactable
|
|||
|
|
DrawInteractableInspector();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndScrollView();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DrawAddInteractableMenu()
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.HelpBox($"GameObject '{_selectedGameObject.name}' doesn't have an Interactable component", MessageType.Info);
|
|||
|
|
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
EditorGUILayout.LabelField("Add Interactable Component:", EditorStyles.boldLabel);
|
|||
|
|
|
|||
|
|
foreach (var interactableType in AvailableInteractableTypes)
|
|||
|
|
{
|
|||
|
|
if (GUILayout.Button($"Add {interactableType.Name}", GUILayout.Height(30)))
|
|||
|
|
{
|
|||
|
|
Undo.RecordObject(_selectedGameObject, $"Add {interactableType.Name}");
|
|||
|
|
var component = _selectedGameObject.AddComponent(interactableType) as InteractableBase;
|
|||
|
|
EditorUtility.SetDirty(_selectedGameObject);
|
|||
|
|
SelectInteractable(component);
|
|||
|
|
RefreshSceneInteractables();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DrawInteractableInspector()
|
|||
|
|
{
|
|||
|
|
// Header
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
EditorGUILayout.LabelField("Editing:", EditorStyles.boldLabel, GUILayout.Width(60));
|
|||
|
|
EditorGUILayout.LabelField(_selectedInteractable.gameObject.name, EditorStyles.boldLabel);
|
|||
|
|
|
|||
|
|
GUILayout.FlexibleSpace();
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Ping", GUILayout.Width(50)))
|
|||
|
|
{
|
|||
|
|
EditorGUIUtility.PingObject(_selectedInteractable.gameObject);
|
|||
|
|
Selection.activeGameObject = _selectedInteractable.gameObject;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
|
|||
|
|
// Draw default inspector using Editor
|
|||
|
|
if (_cachedEditor == null || _cachedEditor.target != _selectedInteractable)
|
|||
|
|
{
|
|||
|
|
if (_cachedEditor != null)
|
|||
|
|
{
|
|||
|
|
DestroyImmediate(_cachedEditor);
|
|||
|
|
}
|
|||
|
|
_cachedEditor = UnityEditor.Editor.CreateEditor(_selectedInteractable);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_cachedEditor != null)
|
|||
|
|
{
|
|||
|
|
EditorGUI.BeginChangeCheck();
|
|||
|
|
_cachedEditor.OnInspectorGUI();
|
|||
|
|
if (EditorGUI.EndChangeCheck())
|
|||
|
|
{
|
|||
|
|
EditorUtility.SetDirty(_selectedInteractable);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
|
|||
|
|
// Additional info section
|
|||
|
|
DrawAdditionalInfo();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DrawAdditionalInfo()
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.LabelField("Additional Information", EditorStyles.boldLabel);
|
|||
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|||
|
|
|
|||
|
|
// Show component information
|
|||
|
|
EditorGUILayout.LabelField("Type:", _selectedInteractable.GetType().Name);
|
|||
|
|
|
|||
|
|
// Show attached actions
|
|||
|
|
var actions = _selectedInteractable.GetComponents<InteractionActionBase>();
|
|||
|
|
if (actions.Length > 0)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.LabelField($"Actions: {actions.Length}");
|
|||
|
|
EditorGUI.indentLevel++;
|
|||
|
|
foreach (var action in actions)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
EditorGUILayout.LabelField(action.GetType().Name, EditorStyles.miniLabel);
|
|||
|
|
if (GUILayout.Button("Ping", GUILayout.Width(50)))
|
|||
|
|
{
|
|||
|
|
EditorGUIUtility.PingObject(action);
|
|||
|
|
}
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
}
|
|||
|
|
EditorGUI.indentLevel--;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Show specific type info
|
|||
|
|
if (_selectedInteractable is Pickup pickup)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
EditorGUILayout.LabelField("Pickup Info:", EditorStyles.boldLabel);
|
|||
|
|
if (pickup.itemData != null)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
EditorGUILayout.LabelField("Item Data:", GUILayout.Width(100));
|
|||
|
|
EditorGUILayout.ObjectField(pickup.itemData, typeof(PickupItemData), false);
|
|||
|
|
if (GUILayout.Button("Ping", GUILayout.Width(50)))
|
|||
|
|
{
|
|||
|
|
EditorGUIUtility.PingObject(pickup.itemData);
|
|||
|
|
}
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (_selectedInteractable is ItemSlot slot)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
EditorGUILayout.LabelField("Slot Info:", EditorStyles.boldLabel);
|
|||
|
|
if (slot.itemData != null)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
EditorGUILayout.LabelField("Slot Data:", GUILayout.Width(100));
|
|||
|
|
EditorGUILayout.ObjectField(slot.itemData, typeof(PickupItemData), false);
|
|||
|
|
if (GUILayout.Button("Ping", GUILayout.Width(50)))
|
|||
|
|
{
|
|||
|
|
EditorGUIUtility.PingObject(slot.itemData);
|
|||
|
|
}
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_isPlaying)
|
|||
|
|
{
|
|||
|
|
var slottedObject = slot.GetSlottedObject();
|
|||
|
|
if (slottedObject != null)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
EditorGUILayout.LabelField("Slotted Object:", GUILayout.Width(100));
|
|||
|
|
EditorGUILayout.ObjectField(slottedObject, typeof(GameObject), true);
|
|||
|
|
if (GUILayout.Button("Ping", GUILayout.Width(50)))
|
|||
|
|
{
|
|||
|
|
EditorGUIUtility.PingObject(slottedObject);
|
|||
|
|
}
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndVertical();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Debug Tab
|
|||
|
|
|
|||
|
|
private void DrawDebugTab()
|
|||
|
|
{
|
|||
|
|
if (!_isPlaying)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.HelpBox("Enter Play Mode to debug interactables at runtime", MessageType.Info);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_debugScrollPosition = EditorGUILayout.BeginScrollView(_debugScrollPosition);
|
|||
|
|
|
|||
|
|
EditorGUILayout.LabelField("Scene Interactables", EditorStyles.boldLabel);
|
|||
|
|
EditorGUILayout.HelpBox("Test interactions and trigger individual events", MessageType.Info);
|
|||
|
|
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
|
|||
|
|
foreach (var interactable in _sceneInteractables)
|
|||
|
|
{
|
|||
|
|
if (interactable == null) continue;
|
|||
|
|
|
|||
|
|
DrawDebugInteractableItem(interactable);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndScrollView();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DrawDebugInteractableItem(InteractableBase interactable)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|||
|
|
|
|||
|
|
// Header
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
EditorGUILayout.LabelField(interactable.gameObject.name, EditorStyles.boldLabel);
|
|||
|
|
EditorGUILayout.LabelField($"({interactable.GetType().Name})", EditorStyles.miniLabel);
|
|||
|
|
|
|||
|
|
GUILayout.FlexibleSpace();
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Ping", GUILayout.Width(50)))
|
|||
|
|
{
|
|||
|
|
EditorGUIUtility.PingObject(interactable.gameObject);
|
|||
|
|
Selection.activeGameObject = interactable.gameObject;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
|
|||
|
|
// Interaction buttons
|
|||
|
|
EditorGUILayout.LabelField("Trigger Interaction:", EditorStyles.boldLabel);
|
|||
|
|
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Full Interaction", GUILayout.Height(25)))
|
|||
|
|
{
|
|||
|
|
TriggerFullInteraction(interactable);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
|
|||
|
|
// Event buttons
|
|||
|
|
EditorGUILayout.LabelField("Trigger Individual Events:", EditorStyles.boldLabel);
|
|||
|
|
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Started"))
|
|||
|
|
{
|
|||
|
|
TriggerEvent(interactable, "OnInteractionStarted");
|
|||
|
|
TriggerUnityEvent(interactable, "interactionStarted");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Arrived"))
|
|||
|
|
{
|
|||
|
|
TriggerEvent(interactable, "OnInteractingCharacterArrived");
|
|||
|
|
TriggerUnityEvent(interactable, "characterArrived");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Do Interaction"))
|
|||
|
|
{
|
|||
|
|
TriggerEvent(interactable, "DoInteraction");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Complete (Success)"))
|
|||
|
|
{
|
|||
|
|
TriggerEventWithParam(interactable, "OnInteractionFinished", true);
|
|||
|
|
TriggerUnityEventWithParam(interactable, "interactionComplete", true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Complete (Fail)"))
|
|||
|
|
{
|
|||
|
|
TriggerEventWithParam(interactable, "OnInteractionFinished", false);
|
|||
|
|
TriggerUnityEventWithParam(interactable, "interactionComplete", false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("Interrupted"))
|
|||
|
|
{
|
|||
|
|
TriggerUnityEvent(interactable, "interactionInterrupted");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
|
|||
|
|
// Show registered actions
|
|||
|
|
var actions = interactable.GetComponents<InteractionActionBase>();
|
|||
|
|
if (actions.Length > 0)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
EditorGUILayout.LabelField($"Registered Actions ({actions.Length}):", EditorStyles.boldLabel);
|
|||
|
|
|
|||
|
|
foreach (var action in actions)
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
|
|||
|
|
EditorGUILayout.LabelField(action.GetType().Name);
|
|||
|
|
|
|||
|
|
if (action.respondToEvents != null && action.respondToEvents.Count > 0)
|
|||
|
|
{
|
|||
|
|
string events = string.Join(", ", action.respondToEvents);
|
|||
|
|
EditorGUILayout.LabelField($"Events: {events}", EditorStyles.miniLabel);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.EndVertical();
|
|||
|
|
EditorGUILayout.Space();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Data Management
|
|||
|
|
|
|||
|
|
private void RefreshSceneInteractables()
|
|||
|
|
{
|
|||
|
|
_sceneInteractables.Clear();
|
|||
|
|
|
|||
|
|
// Find all interactables in the scene
|
|||
|
|
var allInteractables = FindObjectsByType<InteractableBase>(FindObjectsSortMode.None);
|
|||
|
|
_sceneInteractables.AddRange(allInteractables);
|
|||
|
|
|
|||
|
|
// Sort by name for easier browsing
|
|||
|
|
_sceneInteractables.Sort((a, b) =>
|
|||
|
|
{
|
|||
|
|
if (a == null || b == null) return 0;
|
|||
|
|
return string.Compare(a.gameObject.name, b.gameObject.name, StringComparison.Ordinal);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void SelectInteractable(InteractableBase interactable)
|
|||
|
|
{
|
|||
|
|
_selectedInteractable = interactable;
|
|||
|
|
_selectedGameObject = interactable?.gameObject;
|
|||
|
|
|
|||
|
|
// Clear cached editor to force recreation
|
|||
|
|
if (_cachedEditor != null)
|
|||
|
|
{
|
|||
|
|
DestroyImmediate(_cachedEditor);
|
|||
|
|
_cachedEditor = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Debug Helpers
|
|||
|
|
|
|||
|
|
private void TriggerFullInteraction(InteractableBase interactable)
|
|||
|
|
{
|
|||
|
|
if (!_isPlaying || interactable == null) return;
|
|||
|
|
|
|||
|
|
// Simulate a tap on the interactable
|
|||
|
|
Vector3 worldPos = interactable.transform.position;
|
|||
|
|
interactable.OnTap(new Vector2(worldPos.x, worldPos.y));
|
|||
|
|
|
|||
|
|
Debug.Log($"[Interactable Editor] Triggered full interaction on {interactable.gameObject.name}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void TriggerEvent(InteractableBase interactable, string methodName)
|
|||
|
|
{
|
|||
|
|
if (!_isPlaying || interactable == null) return;
|
|||
|
|
|
|||
|
|
Type type = interactable.GetType();
|
|||
|
|
MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|||
|
|
|
|||
|
|
if (method != null)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
method.Invoke(interactable, null);
|
|||
|
|
Debug.Log($"[Interactable Editor] Invoked {methodName} on {interactable.gameObject.name}");
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"[Interactable Editor] Error invoking {methodName}: {e.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"[Interactable Editor] Method {methodName} not found on {type.Name}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void TriggerEventWithParam(InteractableBase interactable, string methodName, object param)
|
|||
|
|
{
|
|||
|
|
if (!_isPlaying || interactable == null) return;
|
|||
|
|
|
|||
|
|
Type type = interactable.GetType();
|
|||
|
|
MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|||
|
|
|
|||
|
|
if (method != null)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
method.Invoke(interactable, new object[] { param });
|
|||
|
|
Debug.Log($"[Interactable Editor] Invoked {methodName}({param}) on {interactable.gameObject.name}");
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"[Interactable Editor] Error invoking {methodName}: {e.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"[Interactable Editor] Method {methodName} not found on {type.Name}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void TriggerUnityEvent(InteractableBase interactable, string fieldName)
|
|||
|
|
{
|
|||
|
|
if (!_isPlaying || interactable == null) return;
|
|||
|
|
|
|||
|
|
Type type = interactable.GetType();
|
|||
|
|
FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|||
|
|
|
|||
|
|
if (field != null && field.GetValue(interactable) is UnityEngine.Events.UnityEventBase unityEvent)
|
|||
|
|
{
|
|||
|
|
// Use reflection to invoke the protected Invoke method
|
|||
|
|
MethodInfo invokeMethod = unityEvent.GetType().GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|||
|
|
if (invokeMethod != null)
|
|||
|
|
{
|
|||
|
|
invokeMethod.Invoke(unityEvent, null);
|
|||
|
|
Debug.Log($"[Interactable Editor] Invoked UnityEvent {fieldName} on {interactable.gameObject.name}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void TriggerUnityEventWithParam(InteractableBase interactable, string fieldName, bool param)
|
|||
|
|
{
|
|||
|
|
if (!_isPlaying || interactable == null) return;
|
|||
|
|
|
|||
|
|
Type type = interactable.GetType();
|
|||
|
|
FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|||
|
|
|
|||
|
|
if (field != null && field.GetValue(interactable) is UnityEngine.Events.UnityEvent<bool> unityEvent)
|
|||
|
|
{
|
|||
|
|
unityEvent.Invoke(param);
|
|||
|
|
Debug.Log($"[Interactable Editor] Invoked UnityEvent<bool> {fieldName}({param}) on {interactable.gameObject.name}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|