Files
HauntedBloodlines/Assets/Editor/UnityEventScanner.cs
2025-05-29 22:31:40 +03:00

328 lines
14 KiB
C#

using UnityEngine;
using UnityEditor;
using UnityEngine.Events;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
public class UnityEventScanner : EditorWindow
{
private Vector2 scroll;
private List<UnityEventInfo> foundEvents = new List<UnityEventInfo>();
private string searchQuery = "";
// Keep persistent bulk values
private GameObject bulkTarget;
private int bulkComponentIndex = 0;
[MenuItem("Tools/Unity Event Scanner")]
public static void ShowWindow()
{
GetWindow<UnityEventScanner>("Unity Event Scanner");
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Scan All Scenes for UnityEvents"))
{
ScanAllSceneObjects();
}
if (GUILayout.Button("Scan Selected GameObjects"))
{
ScanSelectedObjectsFromUI();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Search by GameObject Name:");
searchQuery = EditorGUILayout.TextField(searchQuery);
EditorGUILayout.Space();
GUIStyle fieldLabelStyle = new GUIStyle(EditorStyles.label)
{
fontStyle = FontStyle.Bold,
normal = { textColor = Color.cyan }
};
scroll = EditorGUILayout.BeginScrollView(scroll);
foreach (var evt in foundEvents)
{
if (!string.IsNullOrEmpty(searchQuery) && !evt.gameObject.name.ToLower().Contains(searchQuery.ToLower()))
continue;
evt.selectedForBulk = EditorGUILayout.ToggleLeft("✔ Select for Bulk Add", evt.selectedForBulk);
EditorGUILayout.LabelField("GameObject:", evt.gameObject.name);
EditorGUILayout.LabelField("Component:", evt.component.GetType().Name);
EditorGUILayout.LabelField("Field:", evt.fieldName, fieldLabelStyle);
evt.targetGameObject = (GameObject)EditorGUILayout.ObjectField("Target GameObject", evt.targetGameObject, typeof(GameObject), true);
EditorGUILayout.Space();
}
EditorGUILayout.EndScrollView();
EditorGUILayout.Space();
EditorGUILayout.LabelField("⚡ Bulk Add Listener", EditorStyles.boldLabel);
bulkTarget = (GameObject)EditorGUILayout.ObjectField("Target GameObject", bulkTarget, typeof(GameObject), true);
if (bulkTarget != null)
{
var comps = bulkTarget.GetComponents<MonoBehaviour>();
if (comps.Length == 0)
{
EditorGUILayout.HelpBox("No MonoBehaviour components found on this GameObject.", MessageType.Warning);
return;
}
var compNames = comps.Select(c => c.GetType().Name).ToArray();
bulkComponentIndex = EditorGUILayout.Popup("Component", bulkComponentIndex, compNames);
var bulkComponent = comps[bulkComponentIndex];
var methodNames = GetValidMethodNames(bulkComponent, typeof(UnityEvent), out System.Type paramType).ToArray();
if (methodNames.Length == 0)
{
EditorGUILayout.HelpBox("No valid methods found on component.", MessageType.Warning);
return;
}
int methodIndex = EditorGUILayout.Popup("Method", 0, methodNames);
object paramValue = null;
if (paramType == typeof(string))
paramValue = EditorGUILayout.TextField("Value", "");
else if (paramType == typeof(int))
paramValue = EditorGUILayout.IntField("Value", 0);
else if (paramType == typeof(float))
paramValue = EditorGUILayout.FloatField("Value", 0f);
else if (paramType == typeof(bool))
paramValue = EditorGUILayout.Toggle("Value", false);
if (GUILayout.Button("👉 Apply to Selected UnityEvents"))
{
foreach (var evt in foundEvents.Where(e => e.selectedForBulk))
{
evt.targetGameObject = bulkTarget;
evt.targetComponents = new List<MonoBehaviour> { (MonoBehaviour)bulkComponent };
evt.componentNames = new List<string> { bulkComponent.GetType().Name };
evt.selectedComponentIndex = 0;
evt.availableMethods = methodNames.ToList();
evt.selectedMethodIndex = methodIndex;
evt.methodParameterType = paramType;
evt.parameterValue = paramValue;
}
AddListenersInBulk(foundEvents.Where(e => e.selectedForBulk).ToList(), bulkComponent, methodNames[methodIndex]);
Debug.Log("✅ Bulk listener added to selected UnityEvents.");
}
}
}
private void ScanAllSceneObjects()
{
foundEvents.Clear();
var allRoots = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().GetRootGameObjects();
foreach (var root in allRoots)
{
var components = root.GetComponentsInChildren<MonoBehaviour>(true);
foreach (var component in components)
{
if (component == null) continue;
var fields = component.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields)
{
var fieldType = field.FieldType;
if (typeof(UnityEventBase).IsAssignableFrom(fieldType))
{
foundEvents.Add(new UnityEventInfo { gameObject = component.gameObject, component = component, fieldName = field.Name, fieldType = fieldType });
}
else if (fieldType.IsClass && fieldType != typeof(string))
{
object nestedObject = field.GetValue(component);
if (nestedObject == null) continue;
var nestedFields = fieldType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var nestedField in nestedFields)
{
if (typeof(UnityEventBase).IsAssignableFrom(nestedField.FieldType))
{
foundEvents.Add(new UnityEventInfo
{
gameObject = component.gameObject,
component = component,
fieldName = field.Name + "." + nestedField.Name,
fieldType = nestedField.FieldType
});
}
}
}
}
}
}
}
private void ScanSelectedObjectsFromUI()
{
var selectedObjects = Selection.gameObjects;
if (selectedObjects == null || selectedObjects.Length == 0)
{
Debug.LogWarning("No GameObjects selected.");
return;
}
foundEvents.Clear();
foreach (var root in selectedObjects)
{
var components = root.GetComponentsInChildren<MonoBehaviour>(true);
foreach (var component in components)
{
if (component == null) continue;
var fields = component.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields)
{
var fieldType = field.FieldType;
if (typeof(UnityEventBase).IsAssignableFrom(fieldType))
{
foundEvents.Add(new UnityEventInfo { gameObject = component.gameObject, component = component, fieldName = field.Name, fieldType = fieldType });
}
else if (fieldType.IsClass && fieldType != typeof(string))
{
object nestedObject = field.GetValue(component);
if (nestedObject == null) continue;
var nestedFields = fieldType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var nestedField in nestedFields)
{
if (typeof(UnityEventBase).IsAssignableFrom(nestedField.FieldType))
{
foundEvents.Add(new UnityEventInfo
{
gameObject = component.gameObject,
component = component,
fieldName = field.Name + "." + nestedField.Name,
fieldType = nestedField.FieldType
});
}
}
}
}
}
}
}
private List<string> GetValidMethodNames(Component component, System.Type unityEventType, out System.Type parameterType)
{
List<string> methodNames = new();
parameterType = null;
if (unityEventType.IsGenericType)
{
var args = unityEventType.GetGenericArguments();
if (args.Length == 1) parameterType = args[0];
}
var methods = component.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var method in methods)
{
var parameters = method.GetParameters();
if (parameterType == null && parameters.Length == 0 && method.ReturnType == typeof(void))
methodNames.Add(method.Name);
else if (parameterType != null && parameters.Length == 1 && parameters[0].ParameterType == parameterType && method.ReturnType == typeof(void))
methodNames.Add(method.Name);
}
return methodNames;
}
private void AddListenersInBulk(List<UnityEventInfo> selectedEvents, Component bulkComponent, string method)
{
Undo.RecordObjects(selectedEvents.Select(e => e.component).ToArray(), "Bulk Add UnityEvent Listeners");
foreach (var evt in selectedEvents)
{
SerializedObject so = new SerializedObject(evt.component);
SerializedProperty eventProp = FindNestedProperty(so, evt.fieldName);
if (eventProp == null)
{
Debug.LogWarning($"Cannot find UnityEvent property: {evt.fieldName}");
continue;
}
SerializedProperty callsArray = eventProp.FindPropertyRelative("m_PersistentCalls.m_Calls");
callsArray.arraySize++;
so.ApplyModifiedProperties();
so.Update();
int newIndex = callsArray.arraySize - 1;
SerializedProperty newCall = callsArray.GetArrayElementAtIndex(newIndex);
SerializedProperty target = newCall.FindPropertyRelative("m_Target");
SerializedProperty methodName = newCall.FindPropertyRelative("m_MethodName");
SerializedProperty mode = newCall.FindPropertyRelative("m_Mode");
SerializedProperty arguments = newCall.FindPropertyRelative("m_Arguments");
SerializedProperty callState = newCall.FindPropertyRelative("m_CallState");
target.objectReferenceValue = bulkComponent;
methodName.stringValue = method;
callState.enumValueIndex = 2;
if (evt.methodParameterType == null)
{
mode.enumValueIndex = 1;
}
else if (evt.methodParameterType == typeof(string))
{
mode.enumValueIndex = 2;
arguments.FindPropertyRelative("m_ObjectArgument").stringValue = evt.parameterValue as string;
arguments.FindPropertyRelative("m_ObjectArgumentAssemblyTypeName").stringValue = typeof(string).AssemblyQualifiedName;
}
else if (evt.methodParameterType == typeof(int))
{
mode.enumValueIndex = 3;
arguments.FindPropertyRelative("m_IntArgument").intValue = (int)evt.parameterValue;
}
else if (evt.methodParameterType == typeof(float))
{
mode.enumValueIndex = 4;
arguments.FindPropertyRelative("m_FloatArgument").floatValue = (float)evt.parameterValue;
}
else if (evt.methodParameterType == typeof(bool))
{
mode.enumValueIndex = 5;
arguments.FindPropertyRelative("m_BoolArgument").boolValue = (bool)evt.parameterValue;
}
so.ApplyModifiedProperties();
EditorUtility.SetDirty(evt.component);
}
}
private SerializedProperty FindNestedProperty(SerializedObject obj, string path)
{
string[] parts = path.Split('.');
SerializedProperty current = obj.FindProperty(parts[0]);
for (int i = 1; i < parts.Length; i++)
{
if (current == null) return null;
current = current.FindPropertyRelative(parts[i]);
}
return current;
}
private class UnityEventInfo
{
public GameObject gameObject;
public Component component;
public string fieldName;
public System.Type fieldType;
public GameObject targetGameObject;
public List<MonoBehaviour> targetComponents;
public List<string> componentNames;
public int selectedComponentIndex = 0;
public int selectedMethodIndex = 0;
public List<string> availableMethods;
public System.Type methodParameterType;
public object parameterValue;
public bool selectedForBulk = false;
}
}