using System.Collections.Generic; using UnityEngine; using System.Reflection; using System.Linq; #if UNITY_EDITOR using UnityEditor; #endif namespace Lightbug.Utilities { public enum Direction { Right, Left, Up, Down, Forward, Back } /// /// This static class contains all kind of useful methods used across the package. /// public static class CustomUtilities { public static Vector3 Add(Vector3 vectorA, Vector3 vectorB) { vectorA.x += vectorB.x; vectorA.y += vectorB.y; return vectorA; } public static Vector3 Substract(Vector3 vectorA, Vector3 vectorB) { vectorA.x -= vectorB.x; vectorA.y -= vectorB.y; return vectorA; } public static Vector3 Multiply(Vector3 vectorValue, float floatValue) { vectorValue.x *= floatValue; vectorValue.y *= floatValue; vectorValue.z *= floatValue; return vectorValue; } public static Vector3 Multiply(Vector3 vectorValue, float floatValueA, float floatValueB) { vectorValue.x *= floatValueA * floatValueB; vectorValue.y *= floatValueA * floatValueB; vectorValue.z *= floatValueA * floatValueB; return vectorValue; } public static void AddMagnitude(ref Vector3 vector, float magnitude) { if (vector == Vector3.zero) return; float vectorMagnitude = Vector3.Magnitude(vector); Vector3 vectorDirection = vector / vectorMagnitude; vector += vectorDirection * magnitude; } public static void ChangeMagnitude(ref Vector3 vector, float magnitude) { if (vector == Vector3.zero) return; Vector3 vectorDirection = Vector3.Normalize(vector); vector = vectorDirection * magnitude; } public static void ChangeDirection(ref Vector3 vector, Vector3 direction) { if (vector == Vector3.zero) return; float vectorMagnitude = Vector3.Magnitude(vector); vector = direction * vectorMagnitude; } public static void ChangeDirectionOntoPlane(ref Vector3 vector, Vector3 planeNormal) { if (vector == Vector3.zero) return; Vector3 direction = Vector3.Normalize(Vector3.ProjectOnPlane(vector, planeNormal)); float vectorMagnitude = Vector3.Magnitude(vector); vector = direction * vectorMagnitude; } public static void GetMagnitudeAndDirection(this Vector3 vector, out Vector3 direction, out float magnitude) { magnitude = Vector3.Magnitude(vector); direction = Vector3.Normalize(vector); } /// /// Projects an input vector onto the tangent of a given plane (defined by its normal). /// public static Vector3 ProjectOnTangent(Vector3 inputVector, Vector3 planeNormal, Vector3 up) { Vector3 inputVectorDirection = Vector3.Normalize(inputVector); if (inputVectorDirection == -up) { inputVector += planeNormal * 0.01f; } else if (inputVectorDirection == up) { return Vector3.zero; } Vector3 rotationAxis = GetPerpendicularDirection(inputVector, up); Vector3 tangent = GetPerpendicularDirection(planeNormal, rotationAxis); return Multiply(tangent, Vector3.Magnitude(inputVector)); } /// /// Projects an input vector onto plane A and plane B orthonormal direction. /// public static Vector3 DeflectVector(Vector3 inputVector, Vector3 planeA, Vector3 planeB, bool maintainMagnitude = false) { Vector3 direction = GetPerpendicularDirection(planeA, planeB); if (maintainMagnitude) return direction * inputVector.magnitude; else return Vector3.Project(inputVector, direction); } public static Vector3 GetPerpendicularDirection(Vector3 vectorA, Vector3 vectorB) { return Vector3.Normalize(Vector3.Cross(vectorA, vectorB)); } public static float GetTriangleValue(float center, float height, float width, float independentVariable, float minIndependentVariableLimit = Mathf.NegativeInfinity, float maxIndependentVariableLimit = Mathf.Infinity) { float minValue = center - width / 2f; float maxValue = center + width / 2f; if (independentVariable < minValue || independentVariable > maxValue) { return 0f; } else if (independentVariable < center) { return height * (independentVariable - minValue) / (center - minValue); } else { return -height * (independentVariable - center) / (maxValue - center) + height; } } /// /// Makes a value greater than or equal to zero (default value). /// public static void SetPositive(ref T value) where T : System.IComparable { SetMin(ref value, default(T)); } /// /// Makes a value less than or equal to zero (default value). /// public static void SetNegative(ref T value) where T : System.IComparable { SetMax(ref value, default(T)); } /// /// Makes a value greater than or equal to a minimum value. /// public static void SetMin(ref T value, T minValue) where T : System.IComparable { bool isLess = value.CompareTo(minValue) < 0; if (isLess) value = minValue; } /// /// Makes a value less than or equal to a maximum value. /// public static void SetMax(ref T value, T maxValue) where T : System.IComparable { bool isGreater = value.CompareTo(maxValue) > 0; if (isGreater) value = maxValue; } /// /// Limits a value range from a minimum value to a maximum value (similar to Mathf.Clamp). /// public static void SetRange(ref T value, T minValue, T maxValue) where T : System.IComparable { SetMin(ref value, minValue); SetMax(ref value, maxValue); } /// /// Returns true if the target value is between a and b ( both exclusive ). /// To include the limits values set the "inclusive" parameter to true. /// public static bool isBetween(float target, float a, float b, bool inclusive = false) { if (b > a) return (inclusive ? target >= a : target > a) && (inclusive ? target <= b : target < b); else return (inclusive ? target >= b : target > b) && (inclusive ? target <= a : target < a); } /// /// Returns true if the target value is between a and b ( both exclusive ). /// To include the limits values set the "inclusive" parameter to true. /// public static bool isBetween(int target, int a, int b, bool inclusive = false) { if (b > a) return (inclusive ? target >= a : target > a) && (inclusive ? target <= b : target < b); else return (inclusive ? target >= b : target > b) && (inclusive ? target <= a : target < a); } public static bool isCloseTo(Vector3 input, Vector3 target, float tolerance) { return Vector3.Distance(input, target) <= tolerance; } public static bool isCloseTo(float input, float target, float tolerance) { return Mathf.Abs(target - input) <= tolerance; } public static Vector3 TransformVectorUnscaled(this Transform transform, Vector3 vector) { return transform.rotation * vector; } public static Vector3 InverseTransformVectorUnscaled(this Transform transform, Vector3 vector) { return Quaternion.Inverse(transform.rotation) * vector; } public static Vector3 RotatePointAround(Vector3 point, Vector3 center, float angle, Vector3 axis) { Quaternion rotation = Quaternion.AngleAxis(angle, axis); Vector3 pointToCenter = center - point; Vector3 rotatedPointToCenter = rotation * pointToCenter; return center - rotatedPointToCenter; } public static T GetOrAddComponent(this GameObject targetGameObject, bool includeChildren = false) where T : Component { T existingComponent = includeChildren ? targetGameObject.GetComponentInChildren() : targetGameObject.GetComponent(); if (existingComponent != null) { return existingComponent; } T component = targetGameObject.AddComponent(); return component; } /// /// Gets a "target" component within a particular branch (inside the hierarchy). The branch is defined by the "branch root object", which is also defined by the chosen /// "branch root component". The returned component must come from a child of the "branch root object". /// /// /// Include inactive objects? /// Branch root component type. /// Target component type. /// The target component. public static T2 GetComponentInBranch(this Component callerComponent, bool includeInactive = true) where T1 : Component where T2 : Component { T1[] rootComponents = callerComponent.transform.root.GetComponentsInChildren(includeInactive); if (rootComponents.Length == 0) { Debug.LogWarning($"Root component: No objects found with {typeof(T1).Name} component"); return null; } for (int i = 0; i < rootComponents.Length; i++) { T1 rootComponent = rootComponents[i]; // Is the caller a child of this root? if (!callerComponent.transform.IsChildOf(rootComponent.transform) && !rootComponent.transform.IsChildOf(callerComponent.transform)) continue; T2 targetComponent = rootComponent.GetComponentInChildren(includeInactive); if (targetComponent == null) continue; return targetComponent; } return null; } /// /// Gets a "target" component within a particular branch (inside the hierarchy). The branch is defined by the "branch root object", which is also defined by the chosen /// "branch root component". The returned component must come from a child of the "branch root object". /// /// /// Include inactive objects? /// Target component type. /// The target component. public static T1 GetComponentInBranch(this Component callerComponent, bool includeInactive = true) where T1 : Component { return callerComponent.GetComponentInBranch(includeInactive); } public static bool IsNullOrEmpty(this string target) { return target == null || target.Length == 0; } public static bool IsNullOrWhiteSpace(this string target) { if (target == null) return true; for (int i = 0; i < target.Length; i++) { if (target[i] != ' ') return false; } return true; } public static string Between(this string targetString, string firstString, string lastString) { int start = targetString.IndexOf(firstString) + firstString.Length; int end = targetString.IndexOf(lastString); if (end - start < 0) return ""; return targetString.Substring(start, end - start); } public static bool BelongsToLayerMask(int layer, int layerMask) { return (layerMask & (1 << layer)) > 0; } public static T1 GetOrAddComponent(this GameObject gameObject) where T1 : Component { if (!gameObject.TryGetComponent(out T1 component)) component = gameObject.AddComponent(); return component; } public static T1 GetOrAddComponent(this GameObject gameObject) where T1 : Component where T2 : Component { if (!gameObject.TryGetComponent(out T1 component)) { if (gameObject.TryGetComponent(out T2 requiredComponent)) component = gameObject.AddComponent(); } return component; } public static T1 GetOrAddComponent(this Component baseComponent) where T1 : Component { if (!baseComponent.TryGetComponent(out T1 component)) component = baseComponent.gameObject.AddComponent(); return component; } public static T1 GetOrAddComponent(this Component baseComponent) where T1 : Component where T2 : Component { if (!baseComponent.TryGetComponent(out T1 component)) { if (baseComponent.TryGetComponent(out T2 requiredComponent)) component = baseComponent.gameObject.AddComponent(); } return component; } public static T1 GetOrAddComponent(this Component baseComponent, T2 requiredComponentType) where T1 : Component where T2 : System.Type { if (!baseComponent.TryGetComponent(out T1 component)) { if (baseComponent.TryGetComponent(out T2 requiredComponent)) component = (T1)baseComponent.gameObject.AddComponent(requiredComponentType); } return component; } /// /// Gets a particular value from this dictionary. If the value isn't there, it gets added. /// /// Key type. /// Value type. /// A dictionary container. /// The key parameter /// Indicates if the component should be added if it doesn't exist. /// public static T2 GetOrRegisterValue(this Dictionary dictionary, T1 key, bool addIfNull = false) where T1 : Component where T2 : Component { if (key == null) return null; bool found = dictionary.TryGetValue(key, out T2 value); if (!found) { value = addIfNull ? key.gameObject.GetOrAddComponent() : key.GetComponent(); if (value != null) dictionary.Add(key, value); } return value; } /// /// Gets a particular value from this dictionary. If the value isn't there, it gets added. /// /// Key type. /// Value type. /// Required component type. /// A dictionary container. /// The key parameter /// Indicates if the component should be added if it doesn't exist and the /// required component exist. /// public static T2 GetOrRegisterValue(this Dictionary dictionary, T1 key, bool addIfNull = false) where T1 : Component where T2 : Component where T3 : Component { if (key == null) return null; bool found = dictionary.TryGetValue(key, out T2 value); if (!found) { value = addIfNull ? key.gameObject.GetOrAddComponent() : key.GetComponent(); if (value != null) dictionary.Add(key, value); } return value; } public static float SignedAngle(Vector3 from, Vector3 to, Vector3 axis) { float angle = Vector3.Angle(from, to); Vector3 cross = Vector3.Cross(from, to); cross.Normalize(); float sign = cross == axis ? 1f : -1f; return sign * angle; } public static void DebugRay(Vector3 point, Vector3 direction = default(Vector3), float duration = 2f, Color color = default(Color)) { Vector3 drawDirection = direction == default(Vector3) ? Vector3.up : direction; Color drawColor = color == default(Color) ? Color.blue : color; Debug.DrawRay(point, drawDirection, drawColor, duration); } public static void DrawArrowGizmo(Vector3 start, Vector3 end, Color color, float radius = 0.25f) { Gizmos.color = color; Gizmos.DrawLine(start, end); Gizmos.DrawRay( end, Quaternion.AngleAxis(45, Vector3.forward) * Vector3.Normalize(start - end) * radius ); Gizmos.DrawRay( end, Quaternion.AngleAxis(-45, Vector3.forward) * Vector3.Normalize(start - end) * radius ); } public static void DrawGizmoCross(Vector3 point, float radius, Color color) { Gizmos.color = color; Gizmos.DrawRay( point + Vector3.up * 0.5f * radius, Vector3.down * radius ); Gizmos.DrawRay( point + Vector3.right * 0.5f * radius, Vector3.left * radius ); } public static void DrawDebugCross(Vector3 point, float radius, Color color, float angleOffset = 0f) { Debug.DrawRay( point + Quaternion.Euler(0, 0, angleOffset) * Vector3.up * 0.5f * radius, Quaternion.Euler(0, 0, angleOffset) * Vector3.down * radius, color ); Debug.DrawRay( point + Quaternion.Euler(0, 0, angleOffset) * Vector3.right * 0.5f * radius, Quaternion.Euler(0, 0, angleOffset) * Vector3.left * radius, color ); } #region Animator /// /// Gets the current clip effective length, that is, the original length divided by the playback speed. The length value is always positive, regardless of the speed sign. /// It returns false if the clip is not valid. /// public static bool GetCurrentClipLength(this Animator animator, ref float length) { if (animator.runtimeAnimatorController == null) return false; AnimatorClipInfo[] clipInfo = animator.GetCurrentAnimatorClipInfo(0); if (clipInfo.Length == 0) return false; float clipLength = clipInfo[0].clip.length; float speed = animator.GetCurrentAnimatorStateInfo(0).speed; length = Mathf.Abs(clipLength / speed); return true; } public static bool MatchTarget(this Animator animator, Vector3 targetPosition, Quaternion targetRotation, AvatarTarget avatarTarget, float startNormalizedTime, float targetNormalizedTime) { if (animator.runtimeAnimatorController == null) return false; if (animator.isMatchingTarget) return false; if (animator.IsInTransition(0)) return false; MatchTargetWeightMask weightMask = new MatchTargetWeightMask(Vector3.one, 1f); animator.MatchTarget( targetPosition, targetRotation, avatarTarget, weightMask, startNormalizedTime, targetNormalizedTime ); return true; } public static bool MatchTarget(this Animator animator, Vector3 targetPosition, AvatarTarget avatarTarget, float startNormalizedTime, float targetNormalizedTime) { if (animator.runtimeAnimatorController == null) return false; if (animator.isMatchingTarget) return false; if (animator.IsInTransition(0)) return false; MatchTargetWeightMask weightMask = new MatchTargetWeightMask(Vector3.one, 0f); animator.MatchTarget( targetPosition, Quaternion.identity, avatarTarget, weightMask, startNormalizedTime, targetNormalizedTime ); return true; } public static bool MatchTarget(this Animator animator, Transform target, AvatarTarget avatarTarget, float startNormalizedTime, float targetNormalizedTime) { if (animator.runtimeAnimatorController == null) return false; if (animator.isMatchingTarget) return false; if (animator.IsInTransition(0)) return false; MatchTargetWeightMask weightMask = new MatchTargetWeightMask(Vector3.one, 1f); animator.MatchTarget( target.position, target.rotation, avatarTarget, weightMask, startNormalizedTime, targetNormalizedTime ); return true; } public static bool MatchTarget(this Animator animator, Transform target, AvatarTarget avatarTarget, float startNormalizedTime, float targetNormalizedTime, MatchTargetWeightMask weightMask) { if (animator.runtimeAnimatorController == null) return false; if (animator.isMatchingTarget) return false; if (animator.IsInTransition(0)) return false; animator.MatchTarget( target.position, target.rotation, AvatarTarget.Root, weightMask, startNormalizedTime, targetNormalizedTime ); return true; } #endregion // ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── #if UNITY_EDITOR public static List FindInterfaces() { List interfaces = new List(); GameObject[] rootGameObjects = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects(); foreach (var rootGameObject in rootGameObjects) { T[] childrenInterfaces = rootGameObject.GetComponentsInChildren(); foreach (T childInterface in childrenInterfaces) { interfaces.Add(childInterface); } } return interfaces; } public static MethodInfo[] GetMethods(this MonoBehaviour monoBehaviour) { MonoScript monoScript = MonoScript.FromMonoBehaviour(monoBehaviour); MethodInfo[] methods = monoScript.GetClass().GetMethods(); return methods; } public static MethodInfo[] GetMethods(this ScriptableObject scriptableObject) { MonoScript monoScript = MonoScript.FromScriptableObject(scriptableObject); MethodInfo[] methods = monoScript.GetClass().GetMethods(); return methods; } public static System.Type[] GetAllDerivedObjects(bool allowAbstract = true) where T : Component { List result = new List(); var assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var types = assembly.GetTypes(); foreach (var type in types) { if (type.IsSubclassOf(typeof(T))) { if (type.IsAbstract) { if (allowAbstract) result.Add(type); } else { result.Add(type); } } } } return result.ToArray(); } public static T GetInterface(this MonoBehaviour monoBehaviour) { T[] walkInterfaces = monoBehaviour.GetComponents(); for (int i = 0; i < walkInterfaces.Length; i++) { T @interface = walkInterfaces[i]; Object interfaceObject = @interface as object as Object; if (interfaceObject == monoBehaviour) return @interface; } return default(T); } public static System.Type[] GetAllDerivedComponents(System.Type componentType, bool allowAbstract = true) { if (!componentType.IsSubclassOf(typeof(Component))) return null; List result = new List(); var assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var types = assembly.GetTypes(); foreach (var type in types) { if (type.IsSubclassOf(componentType)) { if (type.IsAbstract) { if (allowAbstract) result.Add(type); } else { result.Add(type); } } } } return result.ToArray(); } public static System.Type[] GetAllDerivedObjectsClass(bool allowAbstract = true) where T : class { List result = new List(); var assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var types = assembly.GetTypes(); foreach (var type in types) { if (type.IsSubclassOf(typeof(T))) { if (type.IsAbstract) { if (allowAbstract) result.Add(type); } else { result.Add(type); } } } } return result.ToArray(); } public static void DrawMonoBehaviourField(T target) where T : MonoBehaviour { GUI.enabled = false; EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(target), typeof(T), false); GUI.enabled = true; } public static void DrawScriptableObjectField(T target) where T : ScriptableObject { GUI.enabled = false; EditorGUILayout.ObjectField("Script:", MonoScript.FromScriptableObject(target), typeof(T), false); GUI.enabled = true; } public static void DrawEditorLayoutHorizontalLine(Color color, int thickness = 1, int padding = 10) { Rect rect = EditorGUILayout.GetControlRect(GUILayout.Height(padding + thickness)); rect.height = thickness; rect.y += padding / 2; //rect.x -= 2; //rect.width +=6; EditorGUI.DrawRect(rect, color); } public static void DrawEditorHorizontalLine(ref Rect rect, Color color, int thickness = 1, int padding = 10) { rect.height = thickness; rect.y += padding / 2; //rect.x -= 2; //rect.width +=6; EditorGUI.DrawRect(rect, color); rect.y += padding; rect.height = EditorGUIUtility.singleLineHeight; } public static void DrawWireCapsule(Vector3 position, Quaternion rotation, float radius, float height, Color color = default(Color)) { if (color != default(Color)) Handles.color = color; Matrix4x4 angleMatrix = Matrix4x4.TRS(position, rotation, Handles.matrix.lossyScale); using (new Handles.DrawingScope(angleMatrix)) { var pointOffset = (height - (radius * 2)) / 2; //draw sideways Handles.DrawWireArc(Vector3.up * pointOffset, Vector3.left, Vector3.back, -180, radius); Handles.DrawLine(new Vector3(0, pointOffset, -radius), new Vector3(0, -pointOffset, -radius)); Handles.DrawLine(new Vector3(0, pointOffset, radius), new Vector3(0, -pointOffset, radius)); Handles.DrawWireArc(Vector3.down * pointOffset, Vector3.left, Vector3.back, 180, radius); //draw frontways Handles.DrawWireArc(Vector3.up * pointOffset, Vector3.back, Vector3.left, 180, radius); Handles.DrawLine(new Vector3(-radius, pointOffset, 0), new Vector3(-radius, -pointOffset, 0)); Handles.DrawLine(new Vector3(radius, pointOffset, 0), new Vector3(radius, -pointOffset, 0)); Handles.DrawWireArc(Vector3.down * pointOffset, Vector3.back, Vector3.left, -180, radius); //draw center Handles.DrawWireDisc(Vector3.up * pointOffset, Vector3.up, radius); Handles.DrawWireDisc(Vector3.down * pointOffset, Vector3.up, radius); } } public static void DrawArray(SerializedProperty rootProperty, string namePropertyString = null, bool showAddElement = true, bool removeIconToTheRight = true) { if (!rootProperty.isArray) return; for (int i = 0; i < rootProperty.arraySize; i++) { SerializedProperty item = rootProperty.GetArrayElementAtIndex(i); item.isExpanded = true; GUILayout.BeginVertical(EditorStyles.helpBox); // if( removeIconToTheRight ) // GUILayout.BeginHorizontal(); CustomUtilities.DrawArrayElement(item, namePropertyString); GUILayout.Space(20); if (GUILayout.Button("Remove element", EditorStyles.miniButton)) { rootProperty.DeleteArrayElementAtIndex(i); break; } // if( removeIconToTheRight ) // GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.Space(20); } if (showAddElement) if (GUILayout.Button("Add element")) rootProperty.arraySize++; } public static void DrawArrayElement(SerializedProperty property, string namePropertyString = null, bool skipFirstChild = false) { // if( property.isArray ) // return; EditorGUI.indentLevel++; SerializedProperty nameProperty = null; if (namePropertyString != null) { nameProperty = property.FindPropertyRelative(namePropertyString); if (nameProperty != null) { string name = nameProperty.stringValue; EditorGUILayout.LabelField(name, EditorStyles.boldLabel); } EditorGUI.indentLevel++; } SerializedProperty itr = property.Copy(); bool enterChildren = true; while (itr.Next(enterChildren)) { if (SerializedProperty.EqualContents(itr, property.GetEndProperty())) break; if (enterChildren && skipFirstChild) { enterChildren = false; continue; } EditorGUILayout.PropertyField(itr, enterChildren); enterChildren = false; } EditorGUI.indentLevel = nameProperty != null ? EditorGUI.indentLevel - 2 : EditorGUI.indentLevel - 1; } public static void DrawArrayElement(Rect rect, SerializedProperty property, string namePropertyString = null, bool skipFirstChild = false) { if (property.isArray) return; EditorGUI.indentLevel++; SerializedProperty nameProperty = null; if (namePropertyString != null) { nameProperty = property.FindPropertyRelative(namePropertyString); if (nameProperty != null) { string name = nameProperty.stringValue; EditorGUI.LabelField(rect, name, EditorStyles.boldLabel); rect.y += rect.height; } EditorGUI.indentLevel++; } SerializedProperty itr = property.Copy(); bool enterChildren = true; while (itr.Next(enterChildren)) { if (SerializedProperty.EqualContents(itr, property.GetEndProperty())) break; if (enterChildren && skipFirstChild) { enterChildren = false; continue; } EditorGUI.PropertyField(rect, itr, enterChildren); rect.y += rect.height; enterChildren = false; } EditorGUI.indentLevel = nameProperty != null ? EditorGUI.indentLevel - 2 : EditorGUI.indentLevel - 1; } #endif } }