using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; namespace Passer { public class Event_Editor { public static bool showParameterSettings = false; public delegate bool MethodCheck(MethodInfo method, out string label); public delegate void InitEvent(SerializedProperty eventProp); public delegate void LabelField(SerializedProperty eventHandlerProp); public static void EventInspector( SerializedProperty eventHandlerProp, ref int selectedEventSourceIx, ref int selectedEventIx, MethodCheck methodCheck, InitEvent InitEvent, LabelField LabelField = null, bool showOverrideMode = false ) { if (!Application.isPlaying) CheckEmptySlot(eventHandlerProp, InitEvent); SerializedProperty eventsProp = eventHandlerProp.FindPropertyRelative("events"); int eventCount = eventsProp.arraySize; string[] eventTypeLabels = GetEventTypeLabels(eventHandlerProp); SerializedProperty eventIdProp = eventHandlerProp.FindPropertyRelative("id"); bool selected = eventIdProp.intValue == selectedEventSourceIx; for (int i = 0; i < eventCount; i++) { EditorGUILayout.BeginHorizontal(); if (i == 0) { if (LabelField == null) { SerializedProperty eventLabel = eventHandlerProp.FindPropertyRelative("label"); EditorGUILayout.LabelField(eventLabel.stringValue, GUILayout.Width(140)); } else LabelField(eventHandlerProp); } else EditorGUILayout.LabelField(" ", GUILayout.Width(140)); SerializedProperty selectedButton = eventsProp.GetArrayElementAtIndex(i); if (selected && selectedEventIx == i) { EventDetails(eventHandlerProp, selectedButton, methodCheck, eventTypeLabels, InitEvent, showOverrideMode); } else { string label = GetButtonLabel(selectedButton, eventTypeLabels); if (GUILayout.Button(label)) { selectedEventSourceIx = eventIdProp.intValue; selectedEventIx = i; } } EditorGUILayout.EndHorizontal(); } if (eventCount > 1) EditorGUILayout.Space(); } protected static void CheckEmptySlot( SerializedProperty eventHandlerProp, InitEvent InitEvent) { SerializedProperty eventsProp = eventHandlerProp.FindPropertyRelative("events"); int eventCount = eventsProp.arraySize; if (eventCount == 0) { eventsProp.InsertArrayElementAtIndex(0); SerializedProperty eventProp = eventsProp.GetArrayElementAtIndex(0); InitEvent(eventProp); SerializedProperty eventTypeProp = eventProp.FindPropertyRelative("eventType"); eventTypeProp.intValue = 0; SerializedProperty functionCallProp = eventProp.FindPropertyRelative("functionCall"); SerializedProperty eventTargetGameObjectProp = functionCallProp.FindPropertyRelative("targetGameObject"); eventTargetGameObjectProp.objectReferenceValue = null; } else { SerializedProperty lastEventProp = eventsProp.GetArrayElementAtIndex(eventCount - 1); SerializedProperty lastEventTypeProp = lastEventProp.FindPropertyRelative("eventType"); if (lastEventTypeProp.intValue != 0) { eventsProp.InsertArrayElementAtIndex(eventCount); SerializedProperty eventProp = eventsProp.GetArrayElementAtIndex(eventCount); InitEvent(eventProp); SerializedProperty eventTypeProp = eventProp.FindPropertyRelative("eventType"); eventTypeProp.intValue = 0; SerializedProperty functionCallProp = eventProp.FindPropertyRelative("functionCall"); SerializedProperty eventTargetGameObjectProp = functionCallProp.FindPropertyRelative("targetGameObject"); eventTargetGameObjectProp.objectReferenceValue = null; } } } public static string GetButtonLabel(SerializedProperty eventProp, string[] eventTypeLabels) { SerializedProperty eventTypeProp = eventProp.FindPropertyRelative("eventType"); EventHandler.Type eventType = (EventHandler.Type)eventTypeProp.intValue; //if (eventType == EventHandler.Type.Never) // return ""; string eventTypeLabel = eventTypeLabels[eventTypeProp.intValue]; SerializedProperty functionCallProp = eventProp.FindPropertyRelative("functionCall"); SerializedProperty eventTargetGameObjectProp = functionCallProp.FindPropertyRelative("targetGameObject"); if (eventTargetGameObjectProp.objectReferenceValue == null) return ""; string targetName = eventTargetGameObjectProp.objectReferenceValue.ToString(); int braceIndex = targetName.IndexOf('('); if (braceIndex > 0) targetName = targetName.Substring(0, braceIndex - 1); string methodName = GetTargetMethodName(eventProp); methodName = methodName.Replace('/', '.'); string label = eventTypeLabel + ": [" + targetName + "]" + methodName; SerializedProperty parametersProp = functionCallProp.FindPropertyRelative("parameters"); if (parametersProp.arraySize == 0) { label += "()"; return label; } SerializedProperty parameterProp = parametersProp.GetArrayElementAtIndex(0); SerializedProperty fromEventProp = parameterProp.FindPropertyRelative("fromEvent"); if (fromEventProp.boolValue) { label += "(...)"; return label; } SerializedProperty parameterTypeProp = parameterProp.FindPropertyRelative("type"); switch ((FunctionCall.ParameterType)parameterTypeProp.intValue) { case FunctionCall.ParameterType.Void: label += "()"; break; case FunctionCall.ParameterType.Float: SerializedProperty floatValueProp = parameterProp.FindPropertyRelative("floatConstant"); label += "(" + floatValueProp.floatValue + ")"; break; case FunctionCall.ParameterType.Bool: SerializedProperty boolValueProp = parameterProp.FindPropertyRelative("boolConstant"); label += "(" + boolValueProp.boolValue + ")"; break; case FunctionCall.ParameterType.Int: SerializedProperty intValueProp = parameterProp.FindPropertyRelative("intConstant"); label += "(" + intValueProp.intValue + ")"; break; case FunctionCall.ParameterType.String: SerializedProperty stringValueProp = parameterProp.FindPropertyRelative("stringConstant"); label += "(" + stringValueProp.stringValue + ")"; break; default: label += "(...)"; break; } return label; } protected static string GetTargetMethodName(SerializedProperty eventProp) { SerializedProperty functionCallProp = eventProp.FindPropertyRelative("functionCall"); SerializedProperty methodNameProp = functionCallProp.FindPropertyRelative("methodName"); string methodName = methodNameProp.stringValue; if (methodName == "Execute") { // user Custom Script Name SerializedProperty eventTargetProp = functionCallProp.FindPropertyRelative("target"); Object eventTarget = eventTargetProp.objectReferenceValue; if (eventTarget is Script) { Script script = (Script)eventTarget; return script.scriptName; } } int ix = methodName.LastIndexOf('.'); if (ix > -1) { methodName = methodName.Substring(ix + 1); } return methodName; } #region Details protected static void EventDetails( SerializedProperty eventHandlerProp, SerializedProperty eventSourceProp, MethodCheck methodCheck, string[] eventTypeLabels, InitEvent InitEvent, bool showOverrideMode) { if (eventSourceProp == null) return; GUIStyle style = new GUIStyle(GUI.skin.box) { margin = new RectOffset(0, 0, 4, 10) }; Rect rect = EditorGUILayout.BeginVertical(style); GUI.Box(rect, "", style); int indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 1; EventTypeInspector(eventSourceProp, eventTypeLabels); EventTargetInspector(eventHandlerProp, eventSourceProp, methodCheck, InitEvent); #if hNW_PHOTON EventNetworkingInspector(eventSourceProp); #endif ParameterOptionsInspector(eventSourceProp); if (showOverrideMode) OverrideModeInspector(eventSourceProp); EditorGUI.indentLevel = indentLevel; EditorGUILayout.EndVertical(); } private static string[] GetEventTypeLabels(SerializedProperty eventSourceProp) { SerializedProperty eventTypeLabelsProp = eventSourceProp.FindPropertyRelative("eventTypeLabels"); string[] eventTypeLabels = new string[7]; if (eventTypeLabelsProp.arraySize == 7) { for (int i = 0; i < eventTypeLabelsProp.arraySize; i++) { SerializedProperty eventTypeLabelProp = eventTypeLabelsProp.GetArrayElementAtIndex(i); eventTypeLabels[i] = eventTypeLabelProp.stringValue; } } else { for (int i = 0; i < 7; i++) { eventTypeLabels[i] = ((EventHandler.Type)i).ToString(); } } return eventTypeLabels; } public static SerializedProperty EventTypeInspector(SerializedProperty eventProp, string[] eventTypeLabels) { SerializedProperty eventTypeProp = eventProp.FindPropertyRelative("eventType"); using (new EditorGUILayout.HorizontalScope()) { GUIContent text = new GUIContent( "Event Type", "Never: the function is never called\n" + "OnStart: when the button is pressed\n" + "OnEnd: when the button is released\n" + "WhileActive: while the button is pressed\n" + "WhileInactive: while the button is released\n" + "OnChange: when the button press changes\n" + "Continuous: the function is called for every frame" ); EditorGUILayout.LabelField(text.text, GUILayout.Width(90)); eventTypeProp.intValue = EditorGUILayout.Popup(eventTypeProp.intValue, eventTypeLabels); } return eventTypeProp; } protected static void OverrideModeInspector(SerializedProperty eventProp) { SerializedProperty overrideModeProp = eventProp.FindPropertyRelative("overrideMode"); using (new EditorGUILayout.HorizontalScope()) { GUIContent text = new GUIContent( "Add by", "How will this handler interact with existing handlers?" ); EditorGUILayout.LabelField(text.text, GUILayout.Width(90)); overrideModeProp.intValue = (int)(EventHandler.OverrideMode)EditorGUILayout.EnumPopup((EventHandler.OverrideMode)overrideModeProp.intValue); } } #region Event Target protected static void EventTargetInspector( SerializedProperty eventHandlerProp, SerializedProperty eventProp, MethodCheck methodCheck, InitEvent InitEvent) { SerializedProperty functionCallProp = eventProp.FindPropertyRelative("functionCall"); SerializedProperty functionParametersProp = eventProp.FindPropertyRelative("functionCall.parameters"); if (functionParametersProp.arraySize == 0) InitEvent(eventProp); SerializedProperty eventTargetGameObjectProp = functionCallProp.FindPropertyRelative("targetGameObject"); GUIContent text = new GUIContent( "Target", "The Object on which the method is called" ); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(text, GUILayout.Width(90)); eventTargetGameObjectProp.objectReferenceValue = EditorGUILayout.ObjectField(eventTargetGameObjectProp.objectReferenceValue, typeof(GameObject), true); EditorGUILayout.EndHorizontal(); SerializedProperty parametersProp = functionCallProp.FindPropertyRelative("parameters"); if (parametersProp.arraySize > 0) { SerializedProperty parameterProp = parametersProp.GetArrayElementAtIndex(0); if (!parameterProp.FindPropertyRelative("fromEvent").boolValue) { methodCheck = ConstantCheck; } } // Component / Method string[] methodNames = GetMethodNames(eventTargetGameObjectProp, methodCheck); SerializedProperty methodNameProp = functionCallProp.FindPropertyRelative("methodName"); int methodNameIndex = GetMethodIndex(methodNameProp.stringValue, methodNames); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Method", GUILayout.Width(90)); methodNameIndex = EditorGUILayout.Popup(methodNameIndex, methodNames); EditorGUILayout.EndHorizontal(); if (methodNameIndex >= 0 && methodNameIndex < methodNames.Length) { string fullMethodName = methodNames[methodNameIndex]; SetMethod(functionCallProp, fullMethodName); SerializedProperty newFunctionCallProp = eventProp.FindPropertyRelative("functionCallFloat"); if (newFunctionCallProp != null) { SerializedProperty newEventTargetGameObjectProp = newFunctionCallProp.FindPropertyRelative("targetGameObject"); SerializedProperty newMethodNameProp = newFunctionCallProp.FindPropertyRelative("methodName"); newEventTargetGameObjectProp.objectReferenceValue = eventTargetGameObjectProp.objectReferenceValue; newMethodNameProp.stringValue = methodNameProp.stringValue; } MethodParametersInspector(eventProp, eventHandlerProp); } } private static bool ConstantCheck(MethodInfo method, out string label) { ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length == 0 && method.ReturnType == typeof(void)) { label = method.Name + " ()"; return true; } else if (parameters.Length == 1) { label = method.Name + " (" + parameters[0].ParameterType.Name + ")"; return true; } label = ""; return false; } #endregion #region Method public static int GetMethodIndex(string methodName, string[] methodNames) { for (int i = 0; i < methodNames.Length; i++) { int brace1Pos = methodNames[i].LastIndexOf("("); if (methodName == methodNames[i].Substring(0, brace1Pos - 1)) return i; } return -1; } public static string[] GetMethodNames(SerializedProperty targetGameObjectProp, MethodCheck methodCheck) { GameObject callTargetObject = (GameObject)targetGameObjectProp.objectReferenceValue; if (callTargetObject == null) return new string[0]; Component[] components = callTargetObject.GetComponents(); List nameList = new List(); AddMethodNames(ref nameList, callTargetObject, methodCheck); foreach (Component component in components) { AddMethodNames(ref nameList, component, methodCheck); } string[] names = new string[nameList.Count]; for (int i = 0; i < nameList.Count; i++) { names[i] = nameList[i]; } return names; } public static GameObject GetTargetGameObject(SerializedProperty callTargetProp) { Object callTarget = callTargetProp.objectReferenceValue; if (callTarget == null) return null; if (callTarget.GetType() == typeof(GameObject)) return (GameObject)callTarget; else return ((Component)callTarget).gameObject; } protected static void AddMethodNames(ref List names, Object component, MethodCheck methodCheck) { if (component == null) return; System.Type componentType = component.GetType(); MethodInfo[] methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.Instance); for (int i = 0; i < methods.Length; i++) { if (BlackListed(methods[i].Name)) continue; if (methods[i].ReturnType != typeof(void)) continue; if (componentType == typeof(Script) && component is Script) { Script script = (Script)component; names.Add("Script/" + script.scriptName + " ()"); } else { string methodLabel; if (methodCheck(methods[i], out methodLabel)) { names.Add(componentType.FullName + "/" + methodLabel); } } } if (componentType == typeof(Humanoid.HumanoidControl)) { AddAnimatorParameterMethods(ref names, component); } } protected static void AddAnimatorParameterMethods(ref List names, Object component) { Humanoid.HumanoidControl humanoid = component as Humanoid.HumanoidControl; if (humanoid == null || humanoid.targetsRig.runtimeAnimatorController == null) return; AnimatorControllerParameter[] animatorParameters = humanoid.targetsRig.parameters; for (int i = 0; i < animatorParameters.Length; i++) { string fullMethodName = "SetAnimatorParameter/" + animatorParameters[i].name; switch (animatorParameters[i].type) { case AnimatorControllerParameterType.Bool: fullMethodName += " (Boolean)"; break; case AnimatorControllerParameterType.Float: fullMethodName += " (Single)"; break; case AnimatorControllerParameterType.Int: fullMethodName += " (Int32)"; break; case AnimatorControllerParameterType.Trigger: default: break; } names.Add(fullMethodName); } } protected static string[] blackList = { "GetComponentInChildren", "GetComponentsInChildren", "GetComponentsInParent", "SetSiblingIndex", "set_hasChanged", "set_active", "GetChild", "set_hierarchyCapacity", "set_useGUILayout", "set_runInEditMode", "CancelInvoke", "StopAllCoroutines", "Awake", "Start", "Update", "FixedUpdate", "OnApplicationQuit", "DetachChildren", "SetAsFirstSibling", "SetAsLastSibling", "IsInvoking", "StopCoroutine", "SetAnimationParameterTrigger" // is done via SetAnimationParamater construction }; public static bool BlackListed(string methodName) { foreach (string blackListEntry in blackList) { if (methodName == blackListEntry) return true; } return false; } public static void SetMethod(SerializedProperty functionCallProp, string fullMethodName) { int brace1Pos = fullMethodName.LastIndexOf("("); int brace2Pos = fullMethodName.LastIndexOf(")"); string methodName = fullMethodName.Substring(0, brace1Pos - 1); SerializedProperty methodNameProp = functionCallProp.FindPropertyRelative("methodName"); methodNameProp.stringValue = methodName; string parameterTypeName = fullMethodName.Substring(brace1Pos + 1, brace2Pos - brace1Pos - 1); SerializedProperty parametersProp = functionCallProp.FindPropertyRelative("parameters"); SerializedProperty parameterProp; if (parametersProp.arraySize <= 0) { parametersProp.InsertArrayElementAtIndex(0); parameterProp = parametersProp.GetArrayElementAtIndex(0); InitParameter(parameterProp); } else { parameterProp = parametersProp.GetArrayElementAtIndex(0); } SerializedProperty parameterTypeProp = parameterProp.FindPropertyRelative("type"); switch (parameterTypeName) { case "Single": parameterTypeProp.intValue = (int)FunctionCall.ParameterType.Float; break; case "Int32": parameterTypeProp.intValue = (int)FunctionCall.ParameterType.Int; break; case "Boolean": parameterTypeProp.intValue = (int)FunctionCall.ParameterType.Bool; break; case "String": parameterTypeProp.intValue = (int)FunctionCall.ParameterType.String; break; case "Vector3": parameterTypeProp.intValue = (int)FunctionCall.ParameterType.Vector3; break; case "GameObject": parameterTypeProp.intValue = (int)FunctionCall.ParameterType.GameObject; break; case "Rigidbody": parameterTypeProp.intValue = (int)FunctionCall.ParameterType.Rigidbody; break; case "Object": parameterTypeProp.intValue = (int)FunctionCall.ParameterType.Void; break; default: parameterTypeProp.intValue = (int)FunctionCall.ParameterType.Void; break; } } protected static void InitParameter(SerializedProperty parameterProp) { parameterProp.FindPropertyRelative("fromEvent").boolValue = true; } public static bool EventMethodCheck(MethodInfo method, out string label) { ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length == 0 && method.ReturnType == typeof(void)) { label = method.Name + " ()"; return true; } else if (parameters.Length == 1 && ( parameters[0].ParameterType == typeof(float) || parameters[0].ParameterType == typeof(int) || parameters[0].ParameterType == typeof(bool) || parameters[0].ParameterType == typeof(string) || parameters[0].ParameterType == typeof(Vector3) || parameters[0].ParameterType == typeof(GameObject) || parameters[0].ParameterType == typeof(Rigidbody) || parameters[0].ParameterType.IsEnum )) { label = method.Name + " (" + parameters[0].ParameterType.Name + ")"; return true; } else if (parameters.Length == 1 && parameters[0].ParameterType.IsSubclassOf(typeof(Object))) { label = method.Name + " (Object)"; return true; } label = ""; return false; } #endregion #region Parameters public static void MethodParametersInspector(SerializedProperty eventProp, SerializedProperty eventHandlerProp) { SerializedProperty functionCallProp = eventProp.FindPropertyRelative("functionCall"); SerializedProperty parametersProp = functionCallProp.FindPropertyRelative("parameters"); if (parametersProp.arraySize == 0) return; SerializedProperty parameterProp = parametersProp.GetArrayElementAtIndex(0); SerializedProperty parameterTypeProp = parameterProp.FindPropertyRelative("type"); FunctionCall.ParameterType parameterType = (FunctionCall.ParameterType)parameterTypeProp.intValue; if (parameterType == FunctionCall.ParameterType.Void) return; System.Type parameterSystemType = FunctionCall.ToSystemType(parameterType); EditorGUI.indentLevel++; EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Parameter", GUILayout.Width(120)); int indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; FunctionCall_Editor.PropertyParameterInspector(parameterProp.serializedObject, parameterProp, parameterSystemType); EditorGUI.indentLevel = indentLevel; EditorGUILayout.EndHorizontal(); EditorGUI.indentLevel--; } #endregion protected static void EventNetworkingInspector(SerializedProperty eventProp) { GUIContent text = new GUIContent( "Networking", "When enabled, the event will be synchronized across the network" ); SerializedProperty eventNetworkingProp = eventProp.FindPropertyRelative("eventNetworking"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(text, GUILayout.Width(90)); eventNetworkingProp.boolValue = EditorGUILayout.Toggle(eventNetworkingProp.boolValue); EditorGUILayout.EndHorizontal(); } public static void ParameterOptionsInspector(SerializedProperty eventSourceProp) { SerializedProperty functionCallProp = eventSourceProp.FindPropertyRelative("functionCall"); SerializedProperty parametersProp = functionCallProp.FindPropertyRelative("parameters"); if (parametersProp.arraySize == 0) return; SerializedProperty parameterProp = parametersProp.GetArrayElementAtIndex(0); SerializedProperty parameterTypeProp = parameterProp.FindPropertyRelative("type"); FunctionCall.ParameterType parameterType = (FunctionCall.ParameterType)parameterTypeProp.intValue; showParameterSettings = EditorGUILayout.Foldout(showParameterSettings, "Options", true); if (showParameterSettings) { EditorGUI.indentLevel++; switch (parameterType) { case FunctionCall.ParameterType.Float: MultiplicationInspector(eventSourceProp); TriggerLevelInspector(eventSourceProp); break; case FunctionCall.ParameterType.Int: MultiplicationInspector(eventSourceProp); TriggerLevelInspector(eventSourceProp); break; case FunctionCall.ParameterType.Bool: TriggerLevelInspector(eventSourceProp); InverseInspector(eventSourceProp); break; default: TriggerLevelInspector(eventSourceProp); break; } EditorGUI.indentLevel--; } } protected static SerializedProperty MultiplicationInspector(SerializedProperty eventProp) { GUIContent text = new GUIContent( "Multiplication", "Multiply the value before calling event trigger" ); SerializedProperty multiplicationProp = eventProp.FindPropertyRelative("multiplicationFactor"); multiplicationProp.floatValue = EditorGUILayout.FloatField(text, multiplicationProp.floatValue); return multiplicationProp; } protected static void TriggerLevelInspector(SerializedProperty eventProp) { SerializedProperty floatTriggerLowProp = eventProp.FindPropertyRelative("floatTriggerLow"); SerializedProperty floatTriggerHighProp = eventProp.FindPropertyRelative("floatTriggerHigh"); float floatTriggerLow = floatTriggerLowProp.floatValue; float floatTriggerHigh = floatTriggerHighProp.floatValue; EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Trigger Level", GUILayout.MinWidth(115)); int indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; floatTriggerLow = EditorGUILayout.FloatField(floatTriggerLow, GUILayout.Width(40)); EditorGUILayout.MinMaxSlider(ref floatTriggerLow, ref floatTriggerHigh, -1, 1); floatTriggerHigh = EditorGUILayout.FloatField(floatTriggerHigh, GUILayout.Width(40)); EditorGUI.indentLevel = indentLevel; EditorGUILayout.EndHorizontal(); floatTriggerLowProp.floatValue = floatTriggerLow; floatTriggerHighProp.floatValue = floatTriggerHigh; } protected static SerializedProperty InverseInspector(SerializedProperty eventProp) { GUIContent text = new GUIContent( "Inverse", "Negate the boolean state before calling event trigger" ); SerializedProperty boolInverseProp = eventProp.FindPropertyRelative("boolInverse"); boolInverseProp.boolValue = EditorGUILayout.Toggle(text, boolInverseProp.boolValue); return boolInverseProp; } #endregion } }