/// ---------------------------------------------
/// 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.
}
}