328 lines
14 KiB
C#
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;
|
|
}
|
|
}
|