/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Utility { using Opsive.Shared.Game; using Opsive.UltimateCharacterController.Camera; using System; using System.Reflection; using System.Collections.Generic; using UnityEngine; /// /// Contains a set of utility functions useful for interacting with the Unity Engine. /// public class UnityEngineUtility { private static Dictionary s_TypeLookup = new Dictionary(); private static List s_LoadedAssemblies = null; private static Dictionary s_GameObjectCameraMap = new Dictionary(); public static HashSet s_ObjectUpdated = new HashSet(); public static ScheduledEventBase s_ObjectClearEvent; private static Dictionary> s_FieldAttributeMap; private static Dictionary> s_PropertyAttributeMap; /// /// Searches through all of the loaded assembies for the specified type. /// /// The string value of the type. /// The found Type. Can be null. public static Type GetType(string name) { if (string.IsNullOrEmpty(name)) { return null; } Type type; // Cache the results for quick repeated lookup. if (s_TypeLookup.TryGetValue(name, out type)) { return type; } type = Type.GetType(name); // Look in the loaded assemblies. if (type == null) { if (s_LoadedAssemblies == null || s_LoadedAssemblies.Count == 0) { #if NETFX_CORE && !UNITY_EDITOR s_LoadedAssemblies = GetStorageFileAssemblies(typeName).Result; #else s_LoadedAssemblies = new List(); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; ++i) { s_LoadedAssemblies.Add(assemblies[i]); } #endif } // Continue until the type is found. for (int i = 0; i < s_LoadedAssemblies.Count; ++i) { type = s_LoadedAssemblies[i].GetType(name); if (type != null) { break; } } } if (type == null) { // TODO: QuickStart and QuickStop were renamed in version 2.1.3. if (name == "Opsive.UltimateCharacterController.Character.Abilities.StartMovement") { return GetType("Opsive.UltimateCharacterController.Character.Abilities.QuickStart"); } if (name == "Opsive.UltimateCharacterController.Character.Abilities.StopMovement") { return GetType("Opsive.UltimateCharacterController.Character.Abilities.QuickStop"); } // TODO: Add-on directory was renamed in 2.1.5. if (name.Contains("Opsive.UltimateCharacterController.Addons.")) { return GetType(name.Replace("Opsive.UltimateCharacterController.Addons.", "Opsive.UltimateCharacterController.AddOns.")); } } if (type != null) { s_TypeLookup.Add(name, type); } return type; } /// /// Returns a friendly name for the specified type. /// /// The type to retieve the name of. /// A friendly name for the specified type. public static string GetFriendlyName(Type type) { return GetFriendlyName(type.FullName, type.Name); } /// /// Returns a friendly name for the specified type. /// /// The full name of the type. /// The name of the type. /// A friendly name for the specified type. public static string GetFriendlyName(string fullName, string name) { if (fullName.Contains("FirstPersonController")) { return "First Person " + name; } else if (fullName.Contains("ThirdPersonController")) { return "Third Person " + name; } return name; } /// /// Returns true if the field has the specified attribute. /// /// The field to determine if it has the attribute. /// The attribute to compare against. /// Tue if the field has the specified attribute. public static bool HasAttribute(FieldInfo field, Type attribute) { if (field == null) { return false; } // Cache the results for quick repeated lookup. if (s_FieldAttributeMap == null) { s_FieldAttributeMap = new Dictionary>(); } Dictionary typeLookup; if (!s_FieldAttributeMap.TryGetValue(field, out typeLookup)) { typeLookup = new Dictionary(); s_FieldAttributeMap.Add(field, typeLookup); } // The static field attribute map contains a dictionary of attributes that the specified type has. Add to that dictionary if the current // attribute type hasn't been retrieved before. var hasAttribute = false; if (!typeLookup.TryGetValue(attribute, out hasAttribute)) { hasAttribute = field.GetCustomAttributes(attribute, false).Length > 0; typeLookup.Add(attribute, hasAttribute); } return hasAttribute; } /// /// Returns true if the property has the specified attribute. /// /// The property to determine if it has the attribute. /// The attribute to compare against. /// Tue if the property has the specified attribute. public static bool HasAttribute(PropertyInfo property, Type attribute) { if (property == null) { return false; } // Cache the results for quick repeated lookup. if (s_PropertyAttributeMap == null) { s_PropertyAttributeMap = new Dictionary>(); } Dictionary typeLookup; if (!s_PropertyAttributeMap.TryGetValue(property, out typeLookup)) { typeLookup = new Dictionary(); s_PropertyAttributeMap.Add(property, typeLookup); } // The static property attribute map contains a dictionary of attributes that the specified type has. Add to that dictionary if the current // attribute type hasn't been retrieved before. var hasAttribute = false; if (!typeLookup.TryGetValue(attribute, out hasAttribute)) { hasAttribute = property.GetCustomAttributes(attribute, false).Length > 0; typeLookup.Add(attribute, hasAttribute); } return hasAttribute; } /// /// Returns the camera with the MainCamera tag or the camera with the CameraController attached. /// /// The character that the camera is attached to. /// The found camera (if any). public static UnityEngine.Camera FindCamera(GameObject character) { UnityEngine.Camera camera; if (character != null) { if (s_GameObjectCameraMap.TryGetValue(character, out camera)) { // The reference may be null if the scene changed. if (camera != null) { return camera; } // The reference is null - search for the camera again. s_GameObjectCameraMap.Remove(character); } } // First try to find the camera with the character attached. If no camera has the character attached the return the first camera with the CameraController. camera = SearchForCamera(character); if (camera == null) { camera = SearchForCamera(null); if (camera != null) { // The camera controller's character field must be null or equal to the existing character. var cameraController = camera.GetComponent(); if (cameraController.Character != null && cameraController.Character != character) { camera = null; } } } if (camera != null && character != null) { s_GameObjectCameraMap.Add(character, camera); } return camera; } /// /// Loops through the cameras searching for a camera with the character assigned. /// /// The character to search for. Can be null. /// The camera with the character assigned. private static UnityEngine.Camera SearchForCamera(GameObject character) { CameraController cameraController; UnityEngine.Camera mainCamera; if ((mainCamera = UnityEngine.Camera.main) != null && (cameraController = mainCamera.GetComponent()) != null && (character == null || cameraController.Character == character)) { return mainCamera; } var cameraControllers = UnityEngine.Object.FindObjectsOfType(); for (int i = 0; i < cameraControllers.Length; ++i) { if (character == null || cameraControllers[i].Character == character) { return cameraControllers[i].GetComponent(); } } return null; } /// /// Returns true if the specified object has been updated. /// /// The object to check if it has been updated. /// True if the specified object has been updated. public static bool HasUpdatedObject(object obj) { return s_ObjectUpdated.Contains(obj); } /// /// Adds the specified object to the set. /// /// The object that has been updated. public static void AddUpdatedObject(object obj) { AddUpdatedObject(obj, false); } /// /// Adds the specified object to the set. /// /// The object that has been updated. /// Should the object updated map be automatically cleared on the next tick? public static void AddUpdatedObject(object obj, bool autoClear) { s_ObjectUpdated.Add(obj); if (autoClear && s_ObjectClearEvent == null) { s_ObjectClearEvent = Scheduler.Schedule(0.0001f, ClearUpdatedObjectsEvent); } } /// /// Removes all of the objects from the set. /// public static void ClearUpdatedObjects() { s_ObjectUpdated.Clear(); } /// /// Removes all of the objects from the set and sets the event to null. /// private static void ClearUpdatedObjectsEvent() { ClearUpdatedObjects(); s_ObjectClearEvent = null; } /// /// Change the size of the RectTransform according to the size of the sprite. /// /// The sprite that the RectTransform should change its size to. /// A reference to the sprite's RectTransform. public static void SizeSprite(Sprite sprite, RectTransform spriteRectTransform) { if (sprite != null) { var sizeDelta = spriteRectTransform.sizeDelta; sizeDelta.x = sprite.textureRect.width; sizeDelta.y = sprite.textureRect.height; spriteRectTransform.sizeDelta = sizeDelta; } } /// /// Clears the Unity Engine Utility cache. /// /// #if UNITY_2019_3_OR_NEWER [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] #endif public static void ClearCache() { if (s_TypeLookup != null) { s_TypeLookup.Clear(); } if (s_GameObjectCameraMap != null) { s_GameObjectCameraMap.Clear(); } if (s_ObjectUpdated != null) { s_ObjectUpdated.Clear(); } if (s_LoadedAssemblies != null) { s_LoadedAssemblies.Clear(); } if (s_FieldAttributeMap != null) { s_FieldAttributeMap.Clear(); } if (s_PropertyAttributeMap != null) { s_PropertyAttributeMap.Clear(); } } /// /// Allows for comparison between RaycastHit objects. /// public class RaycastHitComparer : IComparer { /// /// Compare RaycastHit x to RaycastHit y. If x has a smaller distance value compared to y then a negative value will be returned. /// If the distance values are equal then 0 will be returned, and if y has a smaller distance value compared to x then a positive value will be returned. /// /// The first RaycastHit to compare. /// The second RaycastHit to compare. /// The resulting difference between RaycastHit x and y. public int Compare(RaycastHit x, RaycastHit y) { if (x.transform == null) { return int.MaxValue; } if (y.transform == null) { return int.MinValue; } return x.distance.CompareTo(y.distance); } } /// /// Allows for equity comparison checks between RaycastHit objects. /// public struct RaycastHitEqualityComparer : IEqualityComparer { /// /// Determines if RaycastHit x is equal to RaycastHit y. /// /// The first RaycastHit to compare. /// The second RaycastHit to compare. /// True if the raycasts are equal. public bool Equals(RaycastHit x, RaycastHit y) { if (x.distance != y.distance) { return false; } if (x.point != y.point) { return false; } if (x.normal != y.normal) { return false; } if (x.transform != y.transform) { return false; } return true; } /// /// Returns a hash code for the RaycastHit. /// /// The RaycastHit to get the hash code of. /// The hash code for the RaycastHit. public int GetHashCode(RaycastHit hit) { // Don't use hit.GetHashCode because that has boxing. This hash function won't always prevent duplicates but it's fine for what it's used for. return ((int)(hit.distance * 10000)) ^ ((int)(hit.point.x * 10000)) ^ ((int)(hit.point.y * 10000)) ^ ((int)(hit.point.z * 10000)) ^ ((int)(hit.normal.x * 10000)) ^ ((int)(hit.normal.y * 10000)) ^ ((int)(hit.normal.z * 10000)); } } } /// /// A container for a min and max float value. /// [Serializable] public struct MinMaxFloat { [Tooltip("The minimum Vector3 value.")] [SerializeField] private float m_MinValue; [Tooltip("The maximum Vector3 value.")] [SerializeField] private float m_MaxValue; public float MinValue { get { return m_MinValue; } set { m_MinValue = value; } } public float MaxValue { get { return m_MaxValue; } set { m_MaxValue = value; } } public float RandomValue { get { return UnityEngine.Random.Range(m_MinValue, m_MaxValue); } } /// /// MinMaxFloat constructor which can specify the min and max values. /// /// The minimum float value. /// The maximum float value. public MinMaxFloat(float minValue, float maxValue) { m_MinValue = minValue; m_MaxValue = maxValue; } } /// /// A container for a min and max Vector3 value. /// [Serializable] public struct MinMaxVector3 { [Tooltip("The minimum Vector3 value.")] [SerializeField] private Vector3 m_MinValue; [Tooltip("The maximum Vector3 value.")] [SerializeField] private Vector3 m_MaxValue; [Tooltip("The minimum magnitude value when determining a random value.")] [SerializeField] private Vector3 m_MinMagnitude; public Vector3 MinValue { get { return m_MinValue; } set { m_MinValue = value; } } public Vector3 MaxValue { get { return m_MaxValue; } set { m_MaxValue = value; } } public Vector3 MinMagnitude { get { return m_MinMagnitude; } set { m_MinMagnitude = value; } } public Vector3 RandomValue { get { var value = Vector3.zero; value.x = GetRandomFloat(m_MinValue.x, m_MaxValue.x, m_MinMagnitude.x); value.y = GetRandomFloat(m_MinValue.y, m_MaxValue.y, m_MinMagnitude.y); value.z = GetRandomFloat(m_MinValue.z, m_MaxValue.z, m_MinMagnitude.z); return value; } } /// /// MinMaxVector3 constructor which can specify the min and max values. /// /// The minimum Vector3 value. /// The maximum Vector3 value. public MinMaxVector3(Vector3 minValue, Vector3 maxValue) { m_MinValue = minValue; m_MaxValue = maxValue; m_MinMagnitude = Vector3.zero; } /// /// MinMaxVector3 constructor which can specify the min and max values. /// /// The minimum Vector3 value. /// The maximum Vector3 value. /// The minimum magnitude of the random value. public MinMaxVector3(Vector3 minValue, Vector3 maxValue, Vector3 minMagnitude) { m_MinValue = minValue; m_MaxValue = maxValue; m_MinMagnitude = minMagnitude; } /// /// Returns a random float between the min and max value with the specified minimum magnitude. /// /// The minimum float value. /// The maximum float value. /// The minimum magnitude of the random value. /// A random float between the min and max value. private float GetRandomFloat(float minValue, float maxValue, float minMagnitude) { if (minMagnitude != 0 && Mathf.Sign(m_MinValue.x) != Mathf.Sign(m_MaxValue.x)) { if (Mathf.Sign(UnityEngine.Random.Range(m_MinValue.x, m_MaxValue.x)) > 0) { return UnityEngine.Random.Range(minMagnitude, Mathf.Max(minMagnitude, maxValue)); } return UnityEngine.Random.Range(-minMagnitude, Mathf.Min(-minMagnitude, minValue)); } else { return UnityEngine.Random.Range(minValue, maxValue); } } } /// /// Represents the object which can be spawned. /// [System.Serializable] public class ObjectSpawnInfo { #pragma warning disable 0649 [Tooltip("The object that can be spawned.")] [SerializeField] private GameObject m_Object; [Tooltip("The probability that the object can be spawned.")] [Range(0, 1)] [SerializeField] private float m_Probability = 1; [Tooltip("Should a random spin be applied to the object after it has been spawned?")] [SerializeField] private bool m_RandomSpin; #pragma warning restore 0649 public GameObject Object { get { return m_Object; } } public float Probability { get { return m_Probability; } } public bool RandomSpin { get { return m_RandomSpin; } } /// /// Instantiate the object. /// /// The position to instantiate the object at. /// The normal of the instantiated object. /// The normalized direction of the character's gravity. /// The instantiated object (can be null). public GameObject Instantiate(Vector3 position, Vector3 normal, Vector3 gravityDirection) { if (m_Object == null) { return null; } // There is a random chance that the object cannot be spawned. if (UnityEngine.Random.value < m_Probability) { var rotation = Quaternion.LookRotation(normal); // A random spin can be applied so the rotation isn't the same every hit. if (m_RandomSpin) { rotation *= Quaternion.AngleAxis(UnityEngine.Random.Range(0, 360), normal); } var instantiatedObject = ObjectPool.Instantiate(m_Object, position, rotation); // If the DirectionalConstantForce component exists then the gravity direction should be set so the object will move in the correct direction. var directionalConstantForce = instantiatedObject.GetCachedComponent(); if (directionalConstantForce != null) { directionalConstantForce.Direction = gravityDirection; } return instantiatedObject; } return null; } } /// /// Struct which stores the material values to revert back to after the material has been faded. /// public struct OriginalMaterialValue { [Tooltip("The color of the material.")] private Color m_Color; [Tooltip("Does the material have a mode property?")] private bool m_ContainsMode; [Tooltip("The render mode of the material.")] private float m_Mode; [Tooltip("The SourceBlend BlendMode of the material.")] private int m_SrcBlend; [Tooltip("The DestinationBlend BlendMode of the material.")] private int m_DstBlend; [Tooltip("Is alpha blend enabled?")] private bool m_AlphaBlend; [Tooltip("The render queue of the material.")] private int m_RenderQueue; public Color Color { get { return m_Color; } set { m_Color = value; } } public bool ContainsMode { get { return m_ContainsMode; } set { m_ContainsMode = value; } } public float Mode { get { return m_Mode; } set { m_Mode = value; } } public int SrcBlend { get { return m_SrcBlend; } set { m_SrcBlend = value; } } public int DstBlend { get { return m_DstBlend; } set { m_DstBlend = value; } } public bool AlphaBlend { get { return m_AlphaBlend; } set { m_AlphaBlend = value; } } public int RenderQueue { get { return m_RenderQueue; } set { m_RenderQueue = value; } } private static int s_ModeID; private static int s_SrcBlendID; private static int s_DstBlendID; private static string s_AlphaBlendString = "_ALPHABLEND_ON"; public static int ModeID { get { return s_ModeID; } } public static int SrcBlendID { get { return s_SrcBlendID; } } public static int DstBlendID { get { return s_DstBlendID; } } public static string AlphaBlendString { get { return s_AlphaBlendString; } } /// /// Initializes the OriginalMaterialValue. /// [RuntimeInitializeOnLoadMethod] private static void Initialize() { s_ModeID = Shader.PropertyToID("_Mode"); s_SrcBlendID = Shader.PropertyToID("_SrcBlend"); s_DstBlendID = Shader.PropertyToID("_DstBlend"); } /// /// Initializes the OriginalMaterialValue to the material values. /// /// The material to initialize. /// The id of the color property. /// Does the material have a Mode property? public void Initialize(Material material, int colorID, bool containsMode) { m_Color = material.GetColor(colorID); m_AlphaBlend = material.IsKeywordEnabled(s_AlphaBlendString); m_RenderQueue = material.renderQueue; m_ContainsMode = containsMode; if (containsMode) { m_Mode = material.GetFloat(s_ModeID); m_SrcBlend = material.GetInt(s_SrcBlendID); m_DstBlend = material.GetInt(s_DstBlendID); } } } /// /// Storage class for determining if an event is triggered based on an animation event or time. /// [System.Serializable] public class AnimationEventTrigger { [Tooltip("Is the event triggered with a Unity animation event?")] [SerializeField] private bool m_WaitForAnimationEvent; [Tooltip("The amount of time it takes to trigger the event if not using an animation event.")] [SerializeField] private float m_Duration; public bool WaitForAnimationEvent { get { return m_WaitForAnimationEvent; } set { m_WaitForAnimationEvent = value; } } public float Duration { get { return m_Duration; } set { m_Duration = value; } } /// /// Default constructor. /// public AnimationEventTrigger() { } /// /// Two parameter constructor for AnimationEventTrigger. /// /// Is the event triggered with a Unity animation event? /// The amount of time it takes to trigger the event if not using an animation event. public AnimationEventTrigger(bool waitForAnimationEvent, float duration) { m_WaitForAnimationEvent = waitForAnimationEvent; m_Duration = duration; } } /// /// Attribute which allows the inspector to draw a foldout without the need of a custom editor. /// [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public class InspectorFoldout : Attribute { private string m_Title; public string Title { get { return m_Title; } } public InspectorFoldout(string title) { m_Title = title; } } /// /// Attribute which allows the same type to be added multiple times. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class AllowDuplicateTypes : Attribute { // Intentionally left blank. } }