├── .gitignore ├── AnalyticsHelper.cs ├── AnimationUtilities.cs ├── Attributes ├── InspectorButtonAttribute.cs ├── LogRangeAttribute.cs └── SplitRangeAttribute.cs ├── Camera └── ForceCameraAspectRatio.cs ├── Collision ├── EventOnCollision.cs └── PassCollisionEvents.cs ├── D.cs ├── Editor ├── Drawers │ ├── LogRangeDrawer.cs │ └── SplitRangeDrawer.cs ├── InspectorButtonDrawer.cs ├── ReplaceSelection.cs └── Transform │ └── PositionSelectedObjects.cs ├── Enums.cs ├── Extension ├── AnimatorExtensions.cs ├── ArrayExtensions.cs ├── AudioClipExtensions.cs ├── ColorExtensions.cs ├── DirectionExtensions.cs ├── FloatExtensions.cs ├── GameObjectExtensions.cs ├── HSBColor.cs ├── IntExtensions.cs ├── ParticleExtensions.cs ├── StopwatchExtensions.cs ├── StringExtensions.cs ├── Vector2Extensions.cs └── Vector3Extensions.cs ├── Globals.cs ├── ListDict.cs ├── ListDictMulti.cs ├── Manager.cs ├── Movement ├── AlphaPulser.cs ├── FloatNear.cs ├── GhostPulser.cs ├── MatchTransform.cs ├── Moving.cs ├── Rotating.cs ├── Shaker.cs └── ValuePulser.cs ├── Pool ├── NonGameObjectPool.cs ├── ObjectPool.cs ├── PoolOfList.cs ├── PooledObject.cs ├── ReleaseAfterTime.cs └── ReleaseWhenDead.cs ├── ProgressiveFunc.cs ├── README.md ├── SetVersionText.cs ├── Time ├── AnimationSpeedUnscaled.cs └── DestroyAfterTime.cs ├── Tween ├── Interpolate.cs ├── Tween.cs └── Tweening.cs ├── UnityEvents.cs └── WithRandomSeed.cs /.gitignore: -------------------------------------------------------------------------------- 1 | *.meta 2 | -------------------------------------------------------------------------------- /AnalyticsHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | #if !UNITY_PS4 7 | using UnityEngine.Analytics; 8 | #endif 9 | 10 | public class AnalyticsHelper { 11 | static readonly Dictionary dict = new Dictionary(10); 12 | 13 | public static void Event(string eventName, 14 | string param1Name = null, object param1 = null, 15 | string param2Name = null, object param2 = null, 16 | string param3Name = null, object param3 = null, 17 | string param4Name = null, object param4 = null, 18 | string param5Name = null, object param5 = null, 19 | string param6Name = null, object param6 = null, 20 | string param7Name = null, object param7 = null, 21 | string param8Name = null, object param8 = null, 22 | string param9Name = null, object param9 = null 23 | ) { 24 | dict.Clear(); 25 | if (param1Name != null) dict.Add(param1Name, param1); 26 | if (param2Name != null) dict.Add(param2Name, param2); 27 | if (param3Name != null) dict.Add(param3Name, param3); 28 | if (param4Name != null) dict.Add(param4Name, param4); 29 | if (param5Name != null) dict.Add(param5Name, param5); 30 | if (param6Name != null) dict.Add(param6Name, param6); 31 | if (param7Name != null) dict.Add(param7Name, param7); 32 | if (param8Name != null) dict.Add(param8Name, param8); 33 | if (param9Name != null) dict.Add(param9Name, param9); 34 | dict.Add("version", Application.version); 35 | #if !UNITY_PS4 && !DEBUG 36 | Analytics.CustomEvent(eventName, dict); 37 | #endif 38 | } 39 | 40 | public static void Event(string eventName, Vector3 position) { 41 | #if !UNITY_PS4 && !DEBUG 42 | Analytics.CustomEvent(eventName, position); 43 | #endif 44 | } 45 | } -------------------------------------------------------------------------------- /AnimationUtilities.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using UnityEngine.SceneManagement; 4 | 5 | public class AnimationUtilities : MonoBehaviour { 6 | 7 | public AudioClip[] Sounds; 8 | 9 | public float[] Delays; 10 | float delayRemaining; 11 | float preDelaySpeed; 12 | 13 | public float[] Speeds; 14 | 15 | public GameObject[] Objects; 16 | 17 | public Sprite[] Sprites; 18 | 19 | Animator animator; 20 | SpriteRenderer spriteRenderer; 21 | 22 | void Awake() { 23 | animator = GetComponent(); 24 | spriteRenderer = GetComponent(); 25 | } 26 | 27 | void FixedUpdate() { 28 | // handle delays 29 | { 30 | if (delayRemaining > 0) { 31 | animator.speed = 0; 32 | delayRemaining -= Time.deltaTime; 33 | if (delayRemaining < 0) { 34 | delayRemaining = 0; 35 | animator.speed = preDelaySpeed; 36 | } 37 | } 38 | } 39 | } 40 | 41 | public void LoadScene(string scene) { 42 | SceneManager.LoadScene(scene); 43 | } 44 | 45 | public void LoadSceneById(int scene) { 46 | SceneManager.LoadScene(scene); 47 | } 48 | 49 | public void DelayByIndexOncePerPlay(int delayIndex) { 50 | if (delayIndex >= 0 && delayIndex < Delays.Length) { 51 | DelayByValueOncePerPlay(Delays[delayIndex]); 52 | } else { 53 | Debug.LogError("No delay with index " + delayIndex + " found on this AnimationUtilities component!"); 54 | } 55 | } 56 | 57 | public void DelayByIndexEveryLoop(int delayIndex) { 58 | if (delayIndex >= 0 && delayIndex < Delays.Length) { 59 | DelayByValueEveryLoop(Delays[delayIndex]); 60 | } else { 61 | Debug.LogError("No delay with index " + delayIndex + " found on this AnimationUtilities component!"); 62 | } 63 | } 64 | 65 | public void DelayByValueOncePerPlay(float delay) { 66 | if (animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1) // this means it's after the first loop 67 | return; 68 | DelayByValueEveryLoop(delay); 69 | } 70 | 71 | public void DelayByValueEveryLoop(float delay) { 72 | if (animator.speed != 0) { 73 | preDelaySpeed = animator.speed; 74 | } 75 | delayRemaining = delay; 76 | } 77 | 78 | public void SetSpeedByIndex(int speedIndex) { 79 | if (speedIndex >= 0 && speedIndex < Speeds.Length) { 80 | SetSpeedByValue(Speeds[speedIndex]); 81 | } else { 82 | Debug.LogError("No speed with index " + speedIndex + " found on this AnimationUtilities component!"); 83 | } 84 | } 85 | 86 | public void SetSpeedByValue(float speed) { 87 | animator.speed = speed; 88 | } 89 | 90 | public void PlaySoundByIndex(int soundIndex) { 91 | if (soundIndex >= 0 && soundIndex < Sounds.Length) { 92 | Sounds[soundIndex].Play(); 93 | } else { 94 | Debug.LogError("No sound with index " + soundIndex + " found on this AnimationUtilities component!"); 95 | } 96 | } 97 | 98 | public void PlaySoundByName(string soundName) { 99 | for (int i = 0; i < Sounds.Length; i++) { 100 | if (Sounds[i].name == soundName) { 101 | PlaySoundByIndex(i); 102 | } 103 | } 104 | } 105 | 106 | public void SpawnPrefab(GameObject prefab) { 107 | Instantiate(prefab); 108 | } 109 | 110 | public void EnableGameObjectByIndex(int objIndex) { 111 | if (objIndex >= 0 && objIndex < Objects.Length) { 112 | Objects[objIndex].SetActive(true); 113 | } else { 114 | Debug.LogError("No object with index " + objIndex + " found on this AnimationUtilities component!"); 115 | } 116 | } 117 | 118 | public void DisableGameObjectByIndex(int objIndex) { 119 | if (objIndex >= 0 && objIndex < Objects.Length) { 120 | Objects[objIndex].SetActive(false); 121 | } else { 122 | Debug.LogError("No object with index " + objIndex + " found on this AnimationUtilities component!"); 123 | } 124 | } 125 | 126 | public void DestroySelf() { 127 | Destroy(gameObject); 128 | } 129 | 130 | public void DestroyParent() { 131 | if (gameObject.transform.parent != null) 132 | Destroy(gameObject.transform.parent.gameObject); 133 | } 134 | 135 | public void ReleaseSelf() { 136 | gameObject.Release(); 137 | } 138 | 139 | public void ReleaseParent() { 140 | if (gameObject.transform.parent != null) 141 | gameObject.transform.parent.gameObject.Release(); 142 | } 143 | 144 | public void PlayAnimationState(string state) { 145 | animator.Play(state); 146 | } 147 | 148 | public void SetSpriteByIndex(int spriteIndex) { 149 | if (spriteIndex >= 0 && spriteIndex < Sprites.Length) { 150 | spriteRenderer.sprite = Sprites[spriteIndex]; 151 | } else { 152 | Debug.LogError("No sprite with index " + spriteIndex + " found on this AnimationUtilities component!"); 153 | } 154 | } 155 | 156 | public void SetSpriteByName(string spritename) { 157 | for (int i = 0; i < Sprites.Length; i++) { 158 | if (Sprites[i].name == spritename) { 159 | SetSpriteByIndex(i); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Attributes/InspectorButtonAttribute.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | [System.AttributeUsage(System.AttributeTargets.Field)] 4 | public class InspectorButtonAttribute : PropertyAttribute { 5 | public readonly string MethodName; 6 | 7 | public InspectorButtonAttribute(string methodName) { 8 | MethodName = methodName; 9 | } 10 | } -------------------------------------------------------------------------------- /Attributes/LogRangeAttribute.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | [System.AttributeUsage(System.AttributeTargets.Field)] 4 | public class LogRangeAttribute : PropertyAttribute { 5 | public float Min { get; private set; } 6 | public float Max { get; private set; } 7 | 8 | public LogRangeAttribute(float min, float max) { 9 | Min = min; 10 | Max = max; 11 | } 12 | } -------------------------------------------------------------------------------- /Attributes/SplitRangeAttribute.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | [System.AttributeUsage(System.AttributeTargets.Field)] 4 | public class SplitRangeAttribute : PropertyAttribute { 5 | public float Start { get; private set; } 6 | public float Midpoint { get; private set; } 7 | public float End { get; private set; } 8 | 9 | public SplitRangeAttribute(float start, float midpoint, float end) { 10 | Start = start; 11 | Midpoint = midpoint; 12 | End = end; 13 | } 14 | } -------------------------------------------------------------------------------- /Camera/ForceCameraAspectRatio.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using UnityEngine.UI; 4 | 5 | public class ForceCameraAspectRatio : MonoBehaviour { 6 | 7 | public Vector2 CameraSize = new Vector2(1920, 1080); 8 | 9 | new Camera camera; 10 | 11 | void Awake() { 12 | camera = GetComponent(); 13 | } 14 | 15 | void Start() { 16 | ScaleToAspectRatio(); 17 | } 18 | 19 | public void ScaleToAspectRatio() { 20 | var rect = camera.rect; 21 | 22 | var targetAspect = CameraSize.aspect(); 23 | var windowAspect = new Vector2(Screen.width, Screen.height).aspect(); 24 | var scaleHeight = windowAspect / targetAspect; 25 | 26 | // if scaled height is less than current height, add letterbox 27 | if (scaleHeight < 1.0f) { 28 | rect.width = 1.0f; 29 | rect.height = scaleHeight; 30 | rect.x = 0; 31 | rect.y = (1.0f - scaleHeight) / 2.0f; 32 | } else { 33 | // add pillarbox 34 | var scaleWidth = 1.0f / scaleHeight; 35 | rect.width = scaleWidth; 36 | rect.height = 1.0f; 37 | rect.x = (1.0f - scaleWidth) / 2.0f; 38 | rect.y = 0; 39 | } 40 | 41 | camera.rect = rect; 42 | } 43 | } -------------------------------------------------------------------------------- /Collision/EventOnCollision.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | 6 | public class EventOnCollision : MonoBehaviour 7 | { 8 | public event Action OnTriggerEnter = delegate { }; 9 | public event Action OnTriggerExit = delegate { }; 10 | public event Action OnTriggerStay = delegate { }; 11 | public event Action OnCollisionEnter = delegate { }; 12 | public event Action OnCollisionExit = delegate { }; 13 | public event Action OnCollisionStay = delegate { }; 14 | 15 | void OnTriggerEnter2D(Collider2D other) { 16 | OnTriggerEnter(other); 17 | } 18 | 19 | void OnTriggerExit2D(Collider2D other) 20 | { 21 | OnTriggerExit(other); 22 | } 23 | 24 | void OnTriggerStay2D(Collider2D other) 25 | { 26 | OnTriggerStay(other); 27 | } 28 | 29 | void OnCollisionEnter2D(Collision2D collision) { 30 | OnCollisionEnter(collision); 31 | } 32 | 33 | void OnCollisionExit2D(Collision2D collision) 34 | { 35 | OnCollisionExit(collision); 36 | 37 | } 38 | 39 | void OnCollisionStay2D(Collision2D collision) 40 | { 41 | OnCollisionStay(collision); 42 | } 43 | } -------------------------------------------------------------------------------- /Collision/PassCollisionEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | 6 | public class PassCollisionEvents : MonoBehaviour { 7 | 8 | public GameObject EventReceiver; 9 | 10 | public void Start() { 11 | D.Assert(EventReceiver, "Event receiver must not be null!"); 12 | } 13 | 14 | public void OnTriggerEnter2D(Collider2D other) { 15 | EventReceiver.SendMessage("OnTriggerEnter2D", other, SendMessageOptions.DontRequireReceiver); 16 | } 17 | 18 | public void OnTriggerExit2D(Collider2D other) { 19 | EventReceiver.SendMessage("OnTriggerExit2D", other, SendMessageOptions.DontRequireReceiver); 20 | } 21 | 22 | public void OnTriggerStay2D(Collider2D other) { 23 | EventReceiver.SendMessage("OnTriggerStay2D", other, SendMessageOptions.DontRequireReceiver); 24 | } 25 | 26 | public void OnCollisionEnter2D(Collision2D collision) { 27 | EventReceiver.SendMessage("OnCollisionEnter2D", collision, SendMessageOptions.DontRequireReceiver); 28 | 29 | } 30 | 31 | public void OnCollisionExit2D(Collision2D collision) { 32 | EventReceiver.SendMessage("OnCollisionExit2D", collision, SendMessageOptions.DontRequireReceiver); 33 | 34 | } 35 | 36 | public void OnCollisionStay2D(Collision2D collision) { 37 | EventReceiver.SendMessage("OnCollisionStay2D", collision, SendMessageOptions.DontRequireReceiver); 38 | } 39 | } -------------------------------------------------------------------------------- /D.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Diagnostics; 4 | using Debug = UnityEngine.Debug; 5 | 6 | public static class D { 7 | 8 | [Conditional("UNITY_EDITOR")] 9 | public static void Assert(bool condition, string message) { 10 | if (!condition) { 11 | throw new System.Exception(message); 12 | } 13 | } 14 | 15 | [Conditional("UNITY_EDITOR")] 16 | public static void Warn(bool condition, string message) { 17 | if (!condition) { 18 | Debug.LogWarning(message); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Editor/Drawers/LogRangeDrawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | 7 | [CustomPropertyDrawer(typeof(LogRangeAttribute))] 8 | class LogRangeDrawer : PropertyDrawer { 9 | const int TEXT_FIELD_WIDTH = 50; 10 | 11 | float sliderValue; 12 | float textValue; 13 | float logValue; 14 | 15 | public override float GetPropertyHeight (SerializedProperty prop, GUIContent label) { 16 | return base.GetPropertyHeight(prop, label) * 2; 17 | } 18 | 19 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { 20 | var logRangeAttribute = (LogRangeAttribute) attribute; 21 | EditorGUI.BeginProperty(position, label, property); 22 | if (property.propertyType == SerializedPropertyType.Float) { 23 | // TODO(momin): handle a bunch of edge cases 24 | sliderValue = EditorGUI.Slider(new Rect(position.x, position.y, position.width, position.height / 2), label, sliderValue, logRangeAttribute.Min, logRangeAttribute.Max); 25 | textValue = LogInterp(logRangeAttribute.Min, logRangeAttribute.Max, sliderValue / (logRangeAttribute.Max - logRangeAttribute.Min)); 26 | EditorGUI.LabelField(new Rect(position.x + position.width - TEXT_FIELD_WIDTH - 20, position.y + position.height / 2, TEXT_FIELD_WIDTH, position.height / 2), "eˣ"); 27 | if (float.TryParse(EditorGUI.TextField( 28 | new Rect(position.x + position.width - TEXT_FIELD_WIDTH, position.y + position.height/2, TEXT_FIELD_WIDTH, position.height/2), 29 | textValue.ToString(CultureInfo.InvariantCulture) 30 | ), out textValue)) { 31 | } else { 32 | textValue = logRangeAttribute.Min; 33 | } 34 | sliderValue = LogInterpInverse(logRangeAttribute.Min, logRangeAttribute.Max, textValue) * (logRangeAttribute.Max - logRangeAttribute.Min); 35 | if (logValue != LogInterp(logRangeAttribute.Min, logRangeAttribute.Max, sliderValue) / (logRangeAttribute.Max - logRangeAttribute.Min)) { 36 | logValue = LogInterp(logRangeAttribute.Min, logRangeAttribute.Max, sliderValue) / (logRangeAttribute.Max - logRangeAttribute.Min); 37 | //property.floatValue = logValue; 38 | Debug.Log(logValue); 39 | } 40 | } else { 41 | EditorGUI.LabelField(position, label.text, "Use LogRange with float."); 42 | } 43 | EditorGUI.EndProperty(); 44 | } 45 | 46 | // interps logarithmically between min and max using the following function: y = (M-N)^x - (1-x) + N 47 | // t should be between 0 and 1 48 | public float LogInterp(float min, float max, float t) { 49 | return Mathf.Pow(max - min, t) - (1 - t) + min; 50 | } 51 | 52 | // solve the LogInterp function for x: y = (M-N)^x - (1-x) + N 53 | // and you get: x = (y-N+1) - W(ln(M-N)*(M-N)^(y-N+1))/ln(M-N) 54 | // so this return the x (between 0 and 1) given a y, M, and N 55 | public float LogInterpInverse(double min, double max, double value) { 56 | // the guess is derived from the simplified LogInterp function: y = (M-N)^x + N 57 | // solved for x: x = ln(y-N)/ln(M-N) 58 | // so to find a good initial guess for the W function, set this simplified function and the real inverse function as equal: 59 | // ln(y-N)/ln(M-N) ~= (y-N+1) - W(ln(M-N)*(M-N)^(y-N+1))/ln(M-N) 60 | // then solve for the W part of the right side and you get: 61 | // ln(M-N)*(y-N+1) - ln(y-N) 62 | var guess = Math.Log(max - min) * (value - min + 1) - Math.Log(value - min); 63 | // with the guess and the W function we can just evaluate the inverse LogInterp function here: (y-N+1) - W(ln(M-N)*(M-N)^(y-N+1))/ln(M-N) 64 | var realValue = (value - min + 1) - LambertWNewtownApproximation(Math.Log(max - min) * Math.Pow(max - min, value - min + 1), guess) / Math.Log(max - min); 65 | return (float) realValue; 66 | } 67 | 68 | public double LambertWNewtownApproximation(double value, double guess, double precision = 0.001, int maxIterations = 10) { 69 | const float E = (float) Math.E; 70 | double w = guess; 71 | for (int i = 0; i < maxIterations; i++) { 72 | double oldW = w; 73 | w = w - ((w - (value / Math.Pow(E, w))) / (1 + w)); // Newton approximation http://en.wikipedia.org/wiki/Lambert_W_function#Numerical_evaluation 74 | if (Math.Abs(w - oldW) <= precision) // go to a certain precision 75 | break; 76 | } 77 | return w; 78 | } 79 | } -------------------------------------------------------------------------------- /Editor/Drawers/SplitRangeDrawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | 7 | [CustomPropertyDrawer(typeof(SplitRangeAttribute))] 8 | class SplitRangeDrawer : PropertyDrawer { 9 | const int TEXT_FIELD_WIDTH = 54; 10 | 11 | float sliderValue; 12 | float textValue; 13 | 14 | public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) { 15 | return base.GetPropertyHeight(prop, label) * 2; 16 | } 17 | 18 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { 19 | var splitRangeAttribute = (SplitRangeAttribute) attribute; 20 | EditorGUI.BeginProperty(position, label, property); 21 | if (property.propertyType == SerializedPropertyType.Float) { 22 | var val = property.floatValue; 23 | // get normalized 0-1 value from current real value 24 | { 25 | var wasInLastHalf = sliderValue >= 0.5f; // allows the algorithm to prefer the half that it previously was on, instead of blindly choosing left 26 | var inFirstHalf = (val >= splitRangeAttribute.Start && val <= splitRangeAttribute.Midpoint) || (val <= splitRangeAttribute.Start && val >= splitRangeAttribute.Midpoint); 27 | var inLastHalf = (val >= splitRangeAttribute.Midpoint && val <= splitRangeAttribute.End) || (val <= splitRangeAttribute.Midpoint && val >= splitRangeAttribute.End); 28 | var useFirstHalf = (!wasInLastHalf && inFirstHalf) || (wasInLastHalf && inFirstHalf && !inLastHalf); 29 | var useLastHalf = (wasInLastHalf && inLastHalf) || (!wasInLastHalf && !inFirstHalf && inLastHalf); 30 | 31 | if (useFirstHalf) { 32 | sliderValue = Mathf.InverseLerp(splitRangeAttribute.Start, splitRangeAttribute.Midpoint, val) / 2; 33 | } else if (useLastHalf) { 34 | sliderValue = Mathf.InverseLerp(splitRangeAttribute.Midpoint, splitRangeAttribute.End, val) / 2 + 0.5f; 35 | } else { 36 | sliderValue = 0; 37 | } 38 | } 39 | // make slider with current normalized value and get new slider value 40 | { 41 | EditorGUI.LabelField(new Rect(position.x, position.y + position.height * 0.28f, position.width, position.height / 2), label.text, " "); 42 | sliderValue = EditorGUI.Slider(new Rect(position.x, position.y, position.width + TEXT_FIELD_WIDTH, position.height / 2), " ", sliderValue, 0, 1); 43 | } 44 | // convert new slider position to new real value 45 | float newRealValue; 46 | { 47 | if (sliderValue >= 0 && sliderValue <= 0.5) { 48 | newRealValue = Mathf.Lerp(splitRangeAttribute.Start, splitRangeAttribute.Midpoint, sliderValue * 2); 49 | } else if (sliderValue >= 0.5 && sliderValue <= 1) { 50 | newRealValue = Mathf.Lerp(splitRangeAttribute.Midpoint, splitRangeAttribute.End, (sliderValue - 0.5f) * 2); 51 | } else { 52 | newRealValue = splitRangeAttribute.Start; 53 | } 54 | } 55 | // make text field with the new real value and get the new text value and parse it into a number 56 | { 57 | string newTextValue = EditorGUI.TextField( 58 | new Rect(position.x, position.y + position.height / 2, position.width, position.height / 2), 59 | " ", 60 | newRealValue.ToString(CultureInfo.InvariantCulture) 61 | ); 62 | if (float.TryParse(newTextValue, out newRealValue)) { 63 | } else { 64 | newRealValue = splitRangeAttribute.Start; 65 | } 66 | } 67 | // set the new real value as the property value 68 | { 69 | //Debug.Log(newRealValue); 70 | property.floatValue = newRealValue; 71 | } 72 | } else { 73 | EditorGUI.LabelField(position, label.text, "Use SplitRange with float."); 74 | } 75 | EditorGUI.EndProperty(); 76 | } 77 | } -------------------------------------------------------------------------------- /Editor/InspectorButtonDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System.Reflection; 4 | 5 | // From: https://www.reddit.com/r/Unity3D/comments/1s6czv/inspectorbutton_add_a_custom_button_to_your/ 6 | 7 | [CustomPropertyDrawer(typeof(InspectorButtonAttribute))] 8 | public class InspectorButtonDrawer : PropertyDrawer { 9 | MethodInfo eventMethodInfo; 10 | 11 | public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label) { 12 | var inspectorButtonAttribute = (InspectorButtonAttribute) attribute; 13 | var isPrefab = PrefabUtility.GetCorrespondingObjectFromSource(prop.serializedObject.targetObject) == null && PrefabUtility.GetPrefabObject(prop.serializedObject.targetObject) != null; 14 | label.text = (isPrefab ? "PREFAB - " : "") + label.text; 15 | var width = Mathf.Min(position.width, GUI.skin.button.CalcSize(label).x + 12); 16 | var buttonRect = new Rect( 17 | position.x + (position.width - width) * 0.5f, 18 | position.y, 19 | width, 20 | position.height 21 | ); 22 | if (GUI.Button(buttonRect, label.text)) { 23 | var eventOwnerType = prop.serializedObject.targetObject.GetType(); 24 | var eventName = inspectorButtonAttribute.MethodName; 25 | 26 | if (eventMethodInfo == null) { 27 | eventMethodInfo = eventOwnerType.GetMethod(eventName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); 28 | } 29 | 30 | if (eventMethodInfo != null) { 31 | eventMethodInfo.Invoke(prop.serializedObject.targetObject, null); 32 | } else { 33 | Debug.LogError(string.Format("InspectorButton: Unable to find method {0} in {1}", eventName, eventOwnerType)); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Editor/ReplaceSelection.cs: -------------------------------------------------------------------------------- 1 | /* From: http://wiki.unity3d.com/index.php?title=ReplaceSelection 2 | * 3 | * This wizard will replace a selection with an object or prefab. 4 | * Scene objects will be cloned (destroying their prefab links). 5 | * Original coding by 'yesfish', nabbed from Unity Forums 6 | * 'keep parent' added by Dave A (also removed 'rotation' option, using localRotation 7 | */ 8 | using UnityEngine; 9 | using UnityEditor; 10 | using System.Collections; 11 | 12 | public class ReplaceSelection : ScriptableWizard { 13 | static GameObject replacement = null; 14 | static bool keep = false; 15 | 16 | public GameObject ReplacementObject = null; 17 | public bool KeepOriginals = false; 18 | 19 | [MenuItem("Custom Tools/Replace Selection...")] 20 | static void CreateWizard() { 21 | DisplayWizard("Replace Selection", typeof(ReplaceSelection), "Replace"); 22 | } 23 | 24 | public ReplaceSelection() { 25 | ReplacementObject = replacement; 26 | KeepOriginals = keep; 27 | } 28 | 29 | void OnWizardUpdate() { 30 | replacement = ReplacementObject; 31 | keep = KeepOriginals; 32 | } 33 | 34 | void OnWizardCreate() { 35 | if (replacement == null) { 36 | Debug.LogError("You must choose an object to replace your selection with!"); 37 | return; 38 | } 39 | 40 | Undo.RegisterSceneUndo("Replace Selection"); 41 | 42 | Transform[] transforms = Selection.GetTransforms( 43 | SelectionMode.TopLevel | SelectionMode.OnlyUserModifiable); 44 | 45 | foreach (Transform t in transforms) { 46 | GameObject g; 47 | PrefabType pref = PrefabUtility.GetPrefabType(replacement); 48 | 49 | if (pref == PrefabType.Prefab || pref == PrefabType.ModelPrefab) { 50 | g = (GameObject) PrefabUtility.InstantiatePrefab(replacement); 51 | } else { 52 | g = (GameObject) Instantiate(replacement); 53 | } 54 | 55 | Transform gTransform = g.transform; 56 | gTransform.parent = t.parent; 57 | g.name = replacement.name; 58 | gTransform.localPosition = t.localPosition; 59 | gTransform.localScale = t.localScale; 60 | gTransform.localRotation = t.localRotation; 61 | } 62 | 63 | if (!keep) { 64 | foreach (GameObject g in Selection.gameObjects) { 65 | DestroyImmediate(g); 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Editor/Transform/PositionSelectedObjects.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System.Collections; 4 | using System.Linq; 5 | 6 | public class PositionSelectedObjects : ScriptableWizard { 7 | static Transform[] objectsToPosition = { }; 8 | static Vector3 initialPosition = Vector3.zero; 9 | static Vector3 positionStep = Vector3.zero; 10 | static bool local = true; 11 | 12 | public Transform[] ObjectsToPosition = { }; 13 | public Vector3 InitialPosition = Vector3.zero; 14 | public Vector3 PositionStep = Vector3.zero; 15 | public bool Local = true; 16 | 17 | [MenuItem("Transform/Position Selected Objects...")] 18 | static void CreateWizard() { 19 | DisplayWizard("Position Selected Objects", typeof(PositionSelectedObjects), "Done"); 20 | } 21 | 22 | public PositionSelectedObjects() { 23 | InitialPosition = initialPosition; 24 | PositionStep = positionStep; 25 | Local = local; 26 | } 27 | 28 | void OnEnable() { 29 | ObjectsToPosition = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.OnlyUserModifiable).OrderBy(obj => obj.GetSiblingIndex()).ToArray(); 30 | InitialPosition = ObjectsToPosition.Length > 0 ? ObjectsToPosition[0].localPosition : Vector3.zero; 31 | Undo.RecordObjects(ObjectsToPosition, "Position Selected Objects"); 32 | } 33 | 34 | void OnWizardUpdate() { 35 | objectsToPosition = ObjectsToPosition; 36 | initialPosition = InitialPosition; 37 | positionStep = PositionStep; 38 | local = Local; 39 | 40 | // process results in update for better UX 41 | for (int i = 0; i < objectsToPosition.Length; i++) { 42 | var obj = objectsToPosition[i]; 43 | if (local) { 44 | obj.localPosition = initialPosition + positionStep * i; 45 | } else { 46 | obj.position = initialPosition + positionStep * i; 47 | } 48 | } 49 | } 50 | 51 | void OnWizardCreate() { 52 | } 53 | } -------------------------------------------------------------------------------- /Enums.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public enum Direction { 5 | Up, 6 | Down, 7 | Left, 8 | Right, 9 | None 10 | } -------------------------------------------------------------------------------- /Extension/AnimatorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | 6 | public static class AnimatorExtensions { 7 | 8 | public static void PlayFromBeginning(this Animator animator, string state, int layer = 0) { 9 | animator.Play(state, layer, 0); 10 | animator.Update(0); 11 | } 12 | 13 | public static void PlayAtEnd(this Animator animator, string state, int layer = 0) { 14 | animator.Play(state, layer, 1); 15 | animator.Update(0); 16 | } 17 | 18 | public static void setNormalizedTime(this Animator animator, float normalizedTime, int layer = 0) { 19 | if (animator.isActiveAndEnabled) { 20 | animator.Play(animator.GetCurrentAnimatorStateInfo(0).fullPathHash, layer, normalizedTime); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Extension/ArrayExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | public static class ArrayExtensions { 8 | 9 | public static T First(this IList array) { 10 | return array[0]; 11 | } 12 | public static T FirstOrDefault(this IList array, T defaultValue = default(T)) { 13 | return array.Count > 0 ? array[0] : defaultValue; 14 | } 15 | 16 | public static T Last(this IList array) { 17 | return array[array.Count - 1]; 18 | } 19 | public static T LastOrDefault(this IList array, T defaultValue = default(T)) { 20 | return array.Count > 0 ? array[array.Count - 1] : defaultValue; 21 | } 22 | 23 | public static T Random(this IList array) { 24 | return array[Mathf.FloorToInt(UnityEngine.Random.value * array.Count)]; 25 | } 26 | 27 | public static T RandomExceptRange(this IList array, int indexStart, int indexRange = 1) { 28 | var indexExceptRange = (indexStart + indexRange + Mathf.FloorToInt(UnityEngine.Random.value * (array.Count - indexRange))) % array.Count; 29 | return array[indexExceptRange]; 30 | } 31 | 32 | public static TArray RandomWhere(this IList array, TItem item, Func filterPredicate) { 33 | var items = Count(array, item, filterPredicate); 34 | var matchingItemToPick = Mathf.FloorToInt(UnityEngine.Random.value * items); 35 | var matchingItems = 0; 36 | for (int i = 0; i < array.Count; i++) { 37 | if (filterPredicate(array[i], item)) { 38 | if (matchingItems == matchingItemToPick) { 39 | return array[i]; 40 | } 41 | matchingItems++; 42 | } 43 | } 44 | return default(TArray); 45 | } 46 | 47 | public static T RandomWhere(this IList array, Func filterPredicate) { 48 | var items = Count(array, filterPredicate); 49 | var matchingItemToPick = Mathf.FloorToInt(UnityEngine.Random.value * items); 50 | var matchingItems = 0; 51 | for (int i = 0; i < array.Count; i++) { 52 | if (filterPredicate(array[i])) { 53 | if (matchingItems == matchingItemToPick) { 54 | return array[i]; 55 | } 56 | matchingItems++; 57 | } 58 | } 59 | return default(T); 60 | } 61 | 62 | public static T Next(this IList array, T current) { 63 | var currentIndex = array.IndexOf(current); 64 | var nextIndex = (currentIndex + 1) % array.Count; 65 | return array[nextIndex]; 66 | } 67 | 68 | public static int Count(this IList array, Func countPredicate) { 69 | var count = 0; 70 | foreach (var it in array) { 71 | if (countPredicate(it)) 72 | count++; 73 | } 74 | return count; 75 | } 76 | 77 | public static int Count(this IList array, TItem item, Func countPredicate) { 78 | var count = 0; 79 | foreach (var it in array) { 80 | if (countPredicate(it, item)) 81 | count++; 82 | } 83 | return count; 84 | } 85 | 86 | public static bool All(this IList array, Func condition) { 87 | foreach (var it in array) { 88 | if (!condition(it)) { 89 | return false; 90 | } 91 | } 92 | return true; 93 | } 94 | 95 | public static bool All(this IList array, TItem item, Func comparator) { 96 | foreach (var it in array) { 97 | if (!comparator(it, item)) { 98 | return false; 99 | } 100 | } 101 | return true; 102 | } 103 | 104 | public static bool Any(this IList array, Func condition) { 105 | foreach (var it in array) { 106 | if (condition(it)) { 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | 113 | public static bool Any(this IList array, TItem item, Func comparator) { 114 | for (int i = 0; i < array.Count; i++) { 115 | if (comparator(array[i], item)) { 116 | return true; 117 | } 118 | } 119 | return false; 120 | } 121 | 122 | public static int Sum(this IList array, Func sumFunction) { 123 | var sum = 0; 124 | for (int i = 0; i < array.Count; i++) { 125 | sum += sumFunction(array[i]); 126 | } 127 | return sum; 128 | } 129 | 130 | public static float Sum(this IList array, Func sumFunction) { 131 | var sum = 0f; 132 | for (int i = 0; i < array.Count; i++) { 133 | sum += sumFunction(array[i]); 134 | } 135 | return sum; 136 | } 137 | 138 | public static void ForEach(this IList array, Action action) { 139 | for (int i = 0; i < array.Count; i++) { 140 | action(array[i]); 141 | } 142 | } 143 | 144 | public static TReturn[] Map(this IList array, Func map) { 145 | var newArray = new TReturn[array.Count]; 146 | for (int i = 0; i < array.Count; i++) { 147 | newArray[i] = map(array[i]); 148 | } 149 | return newArray; 150 | } 151 | 152 | public static TReturn[] Map(this IList array, Func map, TReturn[] preallocatedArray) { 153 | for (int i = 0; i < preallocatedArray.Length; i++) { 154 | preallocatedArray[i] = i < array.Count ? map(array[i]) : default(TReturn); 155 | } 156 | return preallocatedArray; 157 | } 158 | 159 | public static int IndexOf(this IList array, T item) { 160 | for (int i = 0; i < array.Count; i++) { 161 | if ((array[i] == null && item == null) || (array[i] != null && array[i].Equals(item))) 162 | return i; 163 | } 164 | return -1; 165 | } 166 | 167 | public static int IndexOf(this IList array, TItem item, Func comparator) { 168 | for (int i = 0; i < array.Count; i++) { 169 | if (comparator(array[i], item)) 170 | return i; 171 | } 172 | return -1; 173 | } 174 | 175 | public static T Find(this IList array, Func searchPredicate) { 176 | for (int i = 0; i < array.Count; i++) { 177 | if (searchPredicate(array[i])) 178 | return array[i]; 179 | } 180 | return default(T); 181 | } 182 | 183 | public static TArray Find(this IList array, TItem item, Func comparator) { 184 | for (int i = 0; i < array.Count; i++) { 185 | if (comparator(array[i], item)) 186 | return array[i]; 187 | } 188 | return default(TArray); 189 | } 190 | 191 | public static bool Contains(this IList array, T item) { 192 | for (int i = 0; i < array.Count; i++) { 193 | if ((array[i] == null && item == null) || (array[i] != null && array[i].Equals(item))) 194 | return true; 195 | } 196 | return false; 197 | } 198 | 199 | public static bool Contains(this IList array, TItem item, Func comparator) { 200 | for (int i = 0; i < array.Count; i++) { 201 | if (comparator(array[i], item)) 202 | return true; 203 | } 204 | return false; 205 | } 206 | 207 | public static bool ContainsIgnoreCase(this IList array, string item) { 208 | for (int i = 0; i < array.Count; i++) { 209 | if (string.Compare(array[i], item, StringComparison.OrdinalIgnoreCase) == 0) 210 | return true; 211 | } 212 | return false; 213 | } 214 | 215 | public static IList Shuffle(this IList array) { 216 | // fisher-yates 217 | for (int i = 0; i < array.Count - 1; i++) { 218 | // random index [i, n) 219 | var index = Mathf.FloorToInt(UnityEngine.Random.value * (array.Count - i)) + i; 220 | // swap 221 | var swap = array[i]; 222 | array[i] = array[index]; 223 | array[index] = swap; 224 | } 225 | return array; 226 | } 227 | 228 | public static void AddOnce(this List list, T item) { 229 | if (!list.Contains(item)) { 230 | list.Add(item); 231 | } 232 | } 233 | 234 | public static int RemoveAll(this List list, T item) { 235 | var count = 0; 236 | while (list.Contains(item)) { 237 | list.Remove(item); 238 | count++; 239 | } 240 | return count; 241 | } 242 | 243 | public static int RemoveInactive(this List list) { 244 | var count = 0; 245 | for (int i = 0; i < list.Count; i++) { 246 | if (!list[i] || !list[i].activeSelf) { 247 | list.RemoveAt(i); 248 | i--; 249 | count++; 250 | } 251 | } 252 | return count; 253 | } 254 | 255 | public static int RemoveDead(this List list) { 256 | var count = 0; 257 | for (int i = 0; i < list.Count; i++) { 258 | if (!list[i]) { 259 | list.RemoveAt(i); 260 | i--; 261 | count++; 262 | } 263 | } 264 | return count; 265 | } 266 | 267 | public static int RemoveInactive(this List list) where T : MonoBehaviour { 268 | var count = 0; 269 | for (int i = 0; i < list.Count; i++) { 270 | if (!list[i] || !list[i].isActiveAndEnabled) { 271 | list.RemoveAt(i); 272 | i--; 273 | count++; 274 | } 275 | } 276 | return count; 277 | } 278 | 279 | public static int RemoveDead(this List list) where T : MonoBehaviour { 280 | var count = 0; 281 | for (int i = 0; i < list.Count; i++) { 282 | if (!list[i]) { 283 | list.RemoveAt(i); 284 | i--; 285 | count++; 286 | } 287 | } 288 | return count; 289 | } 290 | } -------------------------------------------------------------------------------- /Extension/AudioClipExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public static class AudioClipExtensions { 5 | 6 | public static void Play(this AudioClip audioClip, Vector3? pos = null, float volume = 1.0f, float pitch = 1.0f, float pan = 0.0f) { 7 | if (!audioClip) return; 8 | 9 | var position = pos ?? Vector3.zero; 10 | float originalTimeScale = Time.timeScale; 11 | Time.timeScale = 1.0f; // ensure that all audio plays 12 | 13 | GameObject go = new GameObject("One-shot audio"); 14 | AudioSource goSource = go.AddComponent(); 15 | goSource.clip = audioClip; 16 | go.transform.position = position; 17 | goSource.volume = volume; 18 | goSource.pitch = pitch; 19 | goSource.panStereo = pan; 20 | 21 | goSource.Play(); 22 | Object.Destroy(go, audioClip.length); 23 | 24 | Time.timeScale = originalTimeScale; 25 | } 26 | 27 | public static void Play(this AudioClip[] clips, Vector3? pos = null, float volume = 1.0f, float pitch = 1.0f, float pan = 0.0f) { 28 | if (clips == null || clips.Length == 0) return; 29 | 30 | clips.Random().Play(pos, volume, pitch, pan); 31 | } 32 | } -------------------------------------------------------------------------------- /Extension/ColorExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public static class ColorExtensions { 5 | 6 | public static Color withAlpha(this Color color, float alpha) { 7 | color.a = alpha; 8 | return color; 9 | } 10 | 11 | public static Color withValue(this Color color, float value) { 12 | float h, s, v; 13 | Color.RGBToHSV(color, out h, out s, out v); 14 | v = value; 15 | return Color.HSVToRGB(h, s, v).withAlpha(color.a); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Extension/DirectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public static class DirectionExtensions { 5 | 6 | public static float toAngle(this Direction direction) { 7 | switch (direction) { 8 | case Direction.Up: 9 | return 90; 10 | case Direction.Down: 11 | return 270; 12 | case Direction.Left: 13 | return 180; 14 | case Direction.Right: 15 | return 0; 16 | default: 17 | return 0; 18 | } 19 | } 20 | 21 | public static float toRad(this Direction direction) { 22 | switch (direction) { 23 | case Direction.Up: 24 | return Mathf.PI / 2; 25 | case Direction.Down: 26 | return 3 * Mathf.PI / 2; 27 | case Direction.Left: 28 | return Mathf.PI; 29 | case Direction.Right: 30 | return 0; 31 | default: 32 | return 0; 33 | } 34 | } 35 | 36 | public static Vector2 toVector(this Direction direction) { 37 | switch (direction) { 38 | case Direction.Up: 39 | return Vector2.up; 40 | case Direction.Down: 41 | return -Vector2.up; 42 | case Direction.Left: 43 | return -Vector2.right; 44 | case Direction.Right: 45 | return Vector2.right; 46 | default: 47 | return Vector2.zero; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Extension/FloatExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public static class FloatExtensions { 5 | 6 | public static Vector2 toVectorRad(this float angle) { 7 | return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)); 8 | } 9 | 10 | public static Vector2 toVectorDeg(this float angle) { 11 | angle = angle * Mathf.Deg2Rad; 12 | return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)); 13 | } 14 | 15 | public static float sign(this float value) { 16 | return value > 0 ? 1 : value < 0 ? -1 : 0; 17 | } 18 | 19 | public static float inverse(this float val, float minForZero = 0f) { 20 | return val <= minForZero ? 0 : 1 / val; 21 | } 22 | 23 | public static float to01(this float val) { 24 | // maps [-1, 1] to [0, 1], useful for trig functions, input axes, etc 25 | return (Mathf.Clamp(val, -1, 1) + 1) / 2; 26 | } 27 | 28 | public static float multiLerp(this float t, float min, float middle, float max) { 29 | // t of range [-1, 0, 1] lerps through [min, middle, max] 30 | return t >= 0 ? Mathf.Lerp(middle, max, t) : Mathf.Lerp(middle, min, -t); 31 | } 32 | 33 | public static Direction toAnimationDirection(this float angle) { 34 | var angleOfInputRotated = (angle - 45 + 360) % 360; // rotate so 0 is top-right corner 35 | if (angleOfInputRotated >= 0 && angleOfInputRotated <= 90) { // down/up use inclusive comparators to prefer them for diagonal movement 36 | return Direction.Up; 37 | } else if (angleOfInputRotated > 90 && angleOfInputRotated < 180) { 38 | return Direction.Left; 39 | } else if (angleOfInputRotated >= 180 && angleOfInputRotated <= 270) { 40 | return Direction.Down; 41 | } else if (angleOfInputRotated > 270 && angleOfInputRotated < 360) { 42 | return Direction.Right; 43 | } 44 | return Direction.None; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Extension/GameObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEngine; 4 | using UnityEngine.Networking; 5 | 6 | public static class GameObjectExtensions { 7 | 8 | public static T GetComponentInSelfOrChildren(this GameObject gameObject) where T : Component { 9 | var t = gameObject.GetComponent(); 10 | if (!t) { 11 | t = gameObject.GetComponentInChildren(); 12 | } 13 | return t; 14 | } 15 | 16 | public static T GetComponentInSelfOrChildren(this Component component) where T : Component { 17 | var t = component.GetComponent(); 18 | if (!t) { 19 | t = component.GetComponentInChildren(); 20 | } 21 | return t; 22 | } 23 | 24 | public static bool isNetworked(this GameObject go) { 25 | return go.GetComponent() != null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Extension/HSBColor.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | /* http://wiki.unity3d.com/index.php?title=HSBColor */ 4 | [System.Serializable] 5 | public struct HSBColor { 6 | [Range(0, 1)] 7 | public float h; 8 | [Range(0, 1)] 9 | public float s; 10 | [Range(0, 1)] 11 | public float b; 12 | [Range(0, 1)] 13 | public float a; 14 | 15 | public HSBColor(float h, float s, float b, float a) { 16 | this.h = h; 17 | this.s = s; 18 | this.b = b; 19 | this.a = a; 20 | } 21 | 22 | public HSBColor(float h, float s, float b) { 23 | this.h = h; 24 | this.s = s; 25 | this.b = b; 26 | this.a = 1f; 27 | } 28 | 29 | public HSBColor(Color col) { 30 | HSBColor temp = FromColor(col); 31 | h = temp.h; 32 | s = temp.s; 33 | b = temp.b; 34 | a = temp.a; 35 | } 36 | 37 | public static HSBColor FromColor(Color color) { 38 | HSBColor ret = new HSBColor(0f, 0f, 0f, color.a); 39 | 40 | float r = color.r; 41 | float g = color.g; 42 | float b = color.b; 43 | 44 | float max = Mathf.Max(r, Mathf.Max(g, b)); 45 | 46 | if (max <= 0) { 47 | return ret; 48 | } 49 | 50 | float min = Mathf.Min(r, Mathf.Min(g, b)); 51 | float dif = max - min; 52 | 53 | if (max > min) { 54 | if (g == max) { 55 | ret.h = (b - r) / dif * 60f + 120f; 56 | } 57 | else if (b == max) { 58 | ret.h = (r - g) / dif * 60f + 240f; 59 | } 60 | else if (b > g) { 61 | ret.h = (g - b) / dif * 60f + 360f; 62 | } 63 | else { 64 | ret.h = (g - b) / dif * 60f; 65 | } 66 | if (ret.h < 0) { 67 | ret.h = ret.h + 360f; 68 | } 69 | } 70 | else { 71 | ret.h = 0; 72 | } 73 | 74 | ret.h *= 1f / 360f; 75 | ret.s = (dif / max) * 1f; 76 | ret.b = max; 77 | 78 | return ret; 79 | } 80 | 81 | public static Color ToColor(HSBColor hsbColor) { 82 | float r = hsbColor.b; 83 | float g = hsbColor.b; 84 | float b = hsbColor.b; 85 | if (hsbColor.s != 0) { 86 | float max = hsbColor.b; 87 | float dif = hsbColor.b * hsbColor.s; 88 | float min = hsbColor.b - dif; 89 | 90 | float h = hsbColor.h * 360f; 91 | 92 | if (h < 60f) { 93 | r = max; 94 | g = h * dif / 60f + min; 95 | b = min; 96 | } 97 | else if (h < 120f) { 98 | r = -(h - 120f) * dif / 60f + min; 99 | g = max; 100 | b = min; 101 | } 102 | else if (h < 180f) { 103 | r = min; 104 | g = max; 105 | b = (h - 120f) * dif / 60f + min; 106 | } 107 | else if (h < 240f) { 108 | r = min; 109 | g = -(h - 240f) * dif / 60f + min; 110 | b = max; 111 | } 112 | else if (h < 300f) { 113 | r = (h - 240f) * dif / 60f + min; 114 | g = min; 115 | b = max; 116 | } 117 | else if (h <= 360f) { 118 | r = max; 119 | g = min; 120 | b = -(h - 360f) * dif / 60 + min; 121 | } 122 | else { 123 | r = 0; 124 | g = 0; 125 | b = 0; 126 | } 127 | } 128 | 129 | return new Color(Mathf.Clamp01(r), Mathf.Clamp01(g), Mathf.Clamp01(b), hsbColor.a); 130 | } 131 | 132 | public Color ToColor() { 133 | return ToColor(this); 134 | } 135 | 136 | public override string ToString() { 137 | return "H:" + h + " S:" + s + " B:" + b; 138 | } 139 | 140 | public static HSBColor Lerp(HSBColor a, HSBColor b, float t) { 141 | float h, s; 142 | 143 | //check special case black (color.b==0): interpolate neither hue nor saturation! 144 | //check special case grey (color.s==0): don't interpolate hue! 145 | if (a.b == 0) { 146 | h = b.h; 147 | s = b.s; 148 | } 149 | else if (b.b == 0) { 150 | h = a.h; 151 | s = a.s; 152 | } 153 | else { 154 | if (a.s == 0) { 155 | h = b.h; 156 | } 157 | else if (b.s == 0) { 158 | h = a.h; 159 | } 160 | else { 161 | // works around bug with LerpAngle 162 | float angle = Mathf.LerpAngle(a.h * 360f, b.h * 360f, t); 163 | while (angle < 0f) 164 | angle += 360f; 165 | while (angle > 360f) 166 | angle -= 360f; 167 | h = angle / 360f; 168 | } 169 | s = Mathf.Lerp(a.s, b.s, t); 170 | } 171 | return new HSBColor(h, s, Mathf.Lerp(a.b, b.b, t), Mathf.Lerp(a.a, b.a, t)); 172 | } 173 | } -------------------------------------------------------------------------------- /Extension/IntExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | 7 | public static class IntExtensions { 8 | 9 | public static readonly int[] POWERS_OF_2 = 10 | Enumerable.Range(0, 32).Select(x => 1 << x).ToArray(); 11 | 12 | public static int indexOfFirstTrueBit(this int value) { 13 | // Kind of a weird function... it returns the index (which goes from the least significant bit #0 to the most #31) of the first true (1) bit. 14 | // Useful for getting the sequential index of a layer from a LayerMask bitfield, which means you can use a LayerMask field to expose a layer 15 | // selection field to the user using LayerMask instead of making a custom inspector and using EditorGUI.LayerField. 16 | // Basically if you need a user-specified layer and are too lazy for EditorGUI.LayerField, use LayerMask in conjunction with this function. 17 | for (int i = 0; i < POWERS_OF_2.Length; i++) { 18 | if ((value & POWERS_OF_2[i]) != 0) { 19 | return i; 20 | } 21 | 22 | } 23 | return -1; 24 | } 25 | 26 | public static int indexOfFirstFalseBit(this int value) { 27 | // Same as above, but looks for the first false (0) bit instead. 28 | for (int i = 0; i < POWERS_OF_2.Length; i++) { 29 | if ((value & POWERS_OF_2[i]) == 0) { 30 | return i; 31 | } 32 | } 33 | return -1; 34 | } 35 | 36 | public static int countTrueBits(this int value) { 37 | // Counts the number of true (1) bits in the number, no matter its value TODO: make less dumb? (https://en.wikipedia.org/wiki/Hamming_weight) 38 | int trueBits = 0; 39 | for (int i = 0; i < POWERS_OF_2.Length; i++) { 40 | if ((value & POWERS_OF_2[i]) != 0) { 41 | trueBits++; 42 | } 43 | } 44 | return trueBits; 45 | } 46 | 47 | public static int countFalseBits(this int value) { 48 | // Counts the number of false (0) bits in the number, no matter its value TODO: make less dumb? (https://en.wikipedia.org/wiki/Hamming_weight) 49 | int falseBits = 0; 50 | for (int i = 0; i < POWERS_OF_2.Length; i++) { 51 | if ((value & POWERS_OF_2[i]) == 0) { 52 | falseBits++; 53 | } 54 | } 55 | return falseBits; 56 | } 57 | 58 | public static int sign(this int value) { 59 | return value > 0 ? 1 : value < 0 ? -1 : 0; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Extension/ParticleExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public static class ParticleExtensions { 5 | 6 | public static void enableEmission(this ParticleSystem particles, bool enabled) { 7 | var em = particles.emission; 8 | em.enabled = enabled; 9 | } 10 | 11 | public static void setLoop(this ParticleSystem particles, bool loop) { 12 | var m = particles.main; 13 | m.loop = loop; 14 | } 15 | 16 | public static void setColor(this ParticleSystem particles, Color color) { 17 | var m = particles.main; 18 | m.startColor = color; 19 | } 20 | 21 | public static void setSpeed(this ParticleSystem particles, float speed) { 22 | var m = particles.main; 23 | m.startSpeed = speed; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Extension/StopwatchExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | using System.Diagnostics; 7 | 8 | public static class StopwatchExtensions { 9 | 10 | public static void Restart(this Stopwatch stopwatch) { 11 | stopwatch.Reset(); 12 | stopwatch.Start(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Extension/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Diagnostics; 4 | using System.Text; 5 | 6 | public static class StringExtensions { 7 | 8 | static readonly StringBuilder sb = new StringBuilder(40); 9 | public static string LineWrap(this string text, int maxLineLength, char wordDelimiter = ' ', string lineDelimiter = "\n") { 10 | sb.Length = 0; 11 | var words = text.Split(wordDelimiter); 12 | var lineLength = 0; 13 | var currentWord = 0; 14 | while (currentWord < words.Length) { 15 | var word = words[currentWord]; 16 | if (lineLength == 0 || (lineLength + word.Length + 1) < maxLineLength) { 17 | sb.Append(wordDelimiter); 18 | sb.Append(word); 19 | lineLength += 1 + word.Length; 20 | currentWord++; 21 | } else { 22 | sb.AppendLine(); 23 | lineLength = 0; 24 | } 25 | } 26 | return sb.ToString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Extension/Vector2Extensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public static class Vector2Extensions { 5 | 6 | public static Vector3 to3(this Vector2 vector) { 7 | return vector; 8 | } 9 | 10 | public static Vector3 to3(this Vector2 vector, float z) { 11 | return new Vector3(vector.x, vector.y, z); 12 | } 13 | 14 | public static Vector2 withX(this Vector2 vector, float x) { 15 | return new Vector2(x, vector.y); 16 | } 17 | 18 | public static Vector2 withY(this Vector2 vector, float y) { 19 | return new Vector2(vector.x, y); 20 | } 21 | 22 | public static Vector2 plusX(this Vector2 vector, float plusX) { 23 | return new Vector2(vector.x + plusX, vector.y); 24 | } 25 | 26 | public static Vector2 plusY(this Vector2 vector, float plusY) { 27 | return new Vector2(vector.x, vector.y + plusY); 28 | } 29 | 30 | public static Vector2 timesX(this Vector2 vector, float timesX) { 31 | return new Vector2(vector.x * timesX, vector.y); 32 | } 33 | 34 | public static Vector2 timesY(this Vector2 vector, float timesY) { 35 | return new Vector2(vector.x, vector.y * timesY); 36 | } 37 | 38 | public static Vector2 scaledWith(this Vector2 vector, Vector2 other) { 39 | vector.Scale(other); 40 | return vector; 41 | } 42 | 43 | public static Vector2 orthogonal(this Vector2 vector) { 44 | return new Vector2(-vector.y, vector.x); 45 | } 46 | 47 | public static float angle(this Vector2 vector) { 48 | return Mathf.Atan2(vector.y, vector.x); 49 | } 50 | 51 | public static float angleDeg(this Vector2 vector) { 52 | var deg = Mathf.Atan2(vector.y, vector.x) * Mathf.Rad2Deg; 53 | deg = (deg + 360f) % 360f; 54 | return deg; 55 | } 56 | 57 | public static float aspect(this Vector2 vector) { 58 | return vector.x / vector.y; 59 | } 60 | 61 | public static Vector2 Rotate(this Vector2 vector, float degrees) { 62 | float sin = Mathf.Sin(degrees * Mathf.Deg2Rad); 63 | float cos = Mathf.Cos(degrees * Mathf.Deg2Rad); 64 | 65 | float tx = vector.x; 66 | float ty = vector.y; 67 | vector.x = (cos * tx) - (sin * ty); 68 | vector.y = (sin * tx) + (cos * ty); 69 | return vector; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Extension/Vector3Extensions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public static class Vector3Extensions { 5 | 6 | public static Vector2 to2(this Vector3 vector) { 7 | return vector; 8 | } 9 | 10 | public static Vector3 withX(this Vector3 vector, float x) { 11 | return new Vector3(x, vector.y, vector.z); 12 | } 13 | 14 | public static Vector3 withY(this Vector3 vector, float y) { 15 | return new Vector3(vector.x, y, vector.z); 16 | } 17 | 18 | public static Vector3 withZ(this Vector3 vector, float z) { 19 | return new Vector3(vector.x, vector.y, z); 20 | } 21 | 22 | public static Vector3 plusX(this Vector3 vector, float plusX) { 23 | return new Vector3(vector.x + plusX, vector.y, vector.z); 24 | } 25 | 26 | public static Vector3 plusY(this Vector3 vector, float plusY) { 27 | return new Vector3(vector.x, vector.y + plusY, vector.z); 28 | } 29 | 30 | public static Vector3 plusZ(this Vector3 vector, float plusZ) { 31 | return new Vector3(vector.x, vector.y, vector.z + plusZ); 32 | } 33 | 34 | public static Vector3 timesX(this Vector3 vector, float timesX) { 35 | return new Vector3(vector.x * timesX, vector.y, vector.z); 36 | } 37 | 38 | public static Vector3 timesY(this Vector3 vector, float timesY) { 39 | return new Vector3(vector.x, vector.y * timesY, vector.z); 40 | } 41 | 42 | public static Vector3 timesZ(this Vector3 vector, float timesZ) { 43 | return new Vector3(vector.x, vector.y, vector.z * timesZ); 44 | } 45 | 46 | public static Vector3 scaledWith(this Vector3 vector, Vector3 other) { 47 | vector.Scale(other); 48 | return vector; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Globals.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class Globals : MonoBehaviour { 5 | 6 | static Globals TheOnlyOne; 7 | 8 | void Awake() { 9 | if (TheOnlyOne == null) { 10 | TheOnlyOne = this; 11 | DontDestroyOnLoad(gameObject); 12 | } else { 13 | // have to use DestroyImmediate because if you use Destroy then 14 | // the object only gets destroyed at the end of the frame 15 | // which means that any Starts that run during the same frame as this Awake 16 | // can accidentally get this object when doing FindObjectOfType 17 | // and are left hanging onto a dead reference instead of the 18 | // correct Globals object 19 | DestroyImmediate(gameObject); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ListDict.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class ListDict { 7 | public readonly List Keys; 8 | public readonly List Values; 9 | public int Count { get { return Keys.Count; } } 10 | 11 | public ListDict(int capacity = 0) { 12 | Keys = new List(capacity); 13 | Values = new List(capacity); 14 | } 15 | 16 | public void Add(TKey key, TValue value = default(TValue)) { 17 | var index = Keys.IndexOf(key); 18 | if (index < 0) { 19 | Keys.Add(key); 20 | Values.Add(default(TValue)); 21 | index = Keys.Count - 1; 22 | } 23 | Values[index] = value; 24 | } 25 | 26 | public TValue GetOrDefault(TKey key) { 27 | var index = Keys.IndexOf(key); 28 | return index >= 0 && index < Count ? Values[index] : default(TValue); 29 | } 30 | 31 | public bool Remove(TKey key) { 32 | return RemoveAt(Keys.IndexOf(key)); 33 | } 34 | 35 | public int RemoveAll(TKey key) { 36 | var removedCount = 0; 37 | var nextIndex = Keys.IndexOf(key); 38 | while (nextIndex >= 0) { 39 | RemoveAt(nextIndex); 40 | removedCount++; 41 | nextIndex = Keys.IndexOf(key); 42 | } 43 | return removedCount; 44 | } 45 | 46 | public bool RemoveAt(int index) { 47 | if (index < 0 || index >= Count) { 48 | return false; 49 | } 50 | Keys.RemoveAt(index); 51 | Values.RemoveAt(index); 52 | return true; 53 | } 54 | 55 | public void Clear() { 56 | Keys.Clear(); 57 | Values.Clear(); 58 | } 59 | 60 | public bool ContainsKey(TKey key) { 61 | return Keys.IndexOf(key) >= 0; 62 | } 63 | 64 | public TValue this[TKey key] { 65 | get { return Values[Keys.IndexOf(key)]; } 66 | set { Add(key, value); } 67 | } 68 | 69 | public override string ToString() { 70 | return string.Format("Count = {0}", Count); 71 | } 72 | } -------------------------------------------------------------------------------- /ListDictMulti.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | public class ListDict2 { 7 | public readonly List Keys; 8 | public readonly List Values1; 9 | public readonly List Values2; 10 | public int Count { get { return Keys.Count; } } 11 | 12 | public ListDict2(int capacity = 0) { 13 | Keys = new List(capacity); 14 | Values1 = new List(capacity); 15 | Values2 = new List(capacity); 16 | } 17 | 18 | public void Add(TKey key, TValue1 value1, TValue2 value2 = default(TValue2)) { 19 | var index = Keys.IndexOf(key); 20 | if (index < 0) { 21 | Keys.Add(key); 22 | Values1.Add(default(TValue1)); 23 | Values2.Add(default(TValue2)); 24 | index = Keys.Count - 1; 25 | } 26 | Values1[index] = value1; 27 | Values2[index] = value2; 28 | } 29 | 30 | public bool Remove(TKey key) { 31 | return RemoveAt(Keys.IndexOf(key)); 32 | } 33 | 34 | public int RemoveAll(TKey key) { 35 | var removedCount = 0; 36 | var nextIndex = Keys.IndexOf(key); 37 | while (nextIndex >= 0) { 38 | RemoveAt(nextIndex); 39 | removedCount++; 40 | nextIndex = Keys.IndexOf(key); 41 | } 42 | return removedCount; 43 | } 44 | 45 | public bool RemoveAt(int index) { 46 | if (index < 0 || index >= Count) { 47 | return false; 48 | } 49 | Keys.RemoveAt(index); 50 | Values1.RemoveAt(index); 51 | Values2.RemoveAt(index); 52 | return true; 53 | } 54 | 55 | public void Clear() { 56 | Keys.Clear(); 57 | Values1.Clear(); 58 | Values2.Clear(); 59 | } 60 | 61 | public bool ContainsKey(TKey key) { 62 | return Keys.IndexOf(key) >= 0; 63 | } 64 | 65 | public TValue1 this[TKey key] { 66 | get { return Values1[Keys.IndexOf(key)]; } 67 | set { Add(key, value); } 68 | } 69 | 70 | public TValue1 GetValue1(TKey key) { 71 | return Values1[Keys.IndexOf(key)]; 72 | } 73 | public TValue1 GetValue1OrDefault(TKey key) { 74 | var index = Keys.IndexOf(key); 75 | return index >= 0 && index < Count ? Values1[index] : default(TValue1); 76 | } 77 | public void SetValue1(TKey key, TValue1 value1) { 78 | Values1[Keys.IndexOf(key)] = value1; 79 | } 80 | public TValue2 GetValue2(TKey key) { 81 | return Values2[Keys.IndexOf(key)]; 82 | } 83 | public TValue2 GetValue2OrDefault(TKey key) { 84 | var index = Keys.IndexOf(key); 85 | return index >= 0 && index < Count ? Values2[index] : default(TValue2); 86 | } 87 | public void SetValue2(TKey key, TValue2 value2) { 88 | Values2[Keys.IndexOf(key)] = value2; 89 | } 90 | 91 | public override string ToString() { 92 | return string.Format("Count = {0}", Count); 93 | } 94 | } 95 | 96 | public class ListDict3 { 97 | public readonly List Keys; 98 | public readonly List Values1; 99 | public readonly List Values2; 100 | public readonly List Values3; 101 | public int Count { get { return Keys.Count; } } 102 | 103 | public ListDict3(int capacity = 0) { 104 | Keys = new List(capacity); 105 | Values1 = new List(capacity); 106 | Values2 = new List(capacity); 107 | Values3 = new List(capacity); 108 | } 109 | 110 | public void Add(TKey key, TValue1 value1, TValue2 value2 = default(TValue2), TValue3 value3 = default(TValue3)) { 111 | var index = Keys.IndexOf(key); 112 | if (index < 0) { 113 | Keys.Add(key); 114 | Values1.Add(default(TValue1)); 115 | Values2.Add(default(TValue2)); 116 | Values3.Add(default(TValue3)); 117 | index = Keys.Count - 1; 118 | } 119 | Values1[index] = value1; 120 | Values2[index] = value2; 121 | Values3[index] = value3; 122 | } 123 | 124 | public bool Remove(TKey key) { 125 | return RemoveAt(Keys.IndexOf(key)); 126 | } 127 | 128 | public int RemoveAll(TKey key) { 129 | var removedCount = 0; 130 | var nextIndex = Keys.IndexOf(key); 131 | while (nextIndex >= 0) { 132 | RemoveAt(nextIndex); 133 | removedCount++; 134 | nextIndex = Keys.IndexOf(key); 135 | } 136 | return removedCount; 137 | } 138 | 139 | public bool RemoveAt(int index) { 140 | if (index < 0 || index >= Count) { 141 | return false; 142 | } 143 | Keys.RemoveAt(index); 144 | Values1.RemoveAt(index); 145 | Values2.RemoveAt(index); 146 | Values3.RemoveAt(index); 147 | return true; 148 | } 149 | 150 | public void Clear() { 151 | Keys.Clear(); 152 | Values1.Clear(); 153 | Values2.Clear(); 154 | Values3.Clear(); 155 | } 156 | 157 | public bool ContainsKey(TKey key) { 158 | return Keys.IndexOf(key) >= 0; 159 | } 160 | 161 | public TValue1 this[TKey key] { 162 | get { return Values1[Keys.IndexOf(key)]; } 163 | set { Add(key, value); } 164 | } 165 | 166 | public TValue1 GetValue1(TKey key) { 167 | return Values1[Keys.IndexOf(key)]; 168 | } 169 | public TValue1 GetValue1OrDefault(TKey key) { 170 | var index = Keys.IndexOf(key); 171 | return index >= 0 && index < Count ? Values1[index] : default(TValue1); 172 | } 173 | public void SetValue1(TKey key, TValue1 value1) { 174 | Values1[Keys.IndexOf(key)] = value1; 175 | } 176 | public TValue2 GetValue2(TKey key) { 177 | return Values2[Keys.IndexOf(key)]; 178 | } 179 | public TValue2 GetValue2OrDefault(TKey key) { 180 | var index = Keys.IndexOf(key); 181 | return index >= 0 && index < Count ? Values2[index] : default(TValue2); 182 | } 183 | public void SetValue2(TKey key, TValue2 value2) { 184 | Values2[Keys.IndexOf(key)] = value2; 185 | } 186 | public TValue3 GetValue3(TKey key) { 187 | return Values3[Keys.IndexOf(key)]; 188 | } 189 | public TValue3 GetValue3OrDefault(TKey key) { 190 | var index = Keys.IndexOf(key); 191 | return index >= 0 && index < Count ? Values3[index] : default(TValue3); 192 | } 193 | public void SetValue3(TKey key, TValue3 value3) { 194 | Values3[Keys.IndexOf(key)] = value3; 195 | } 196 | 197 | public override string ToString() { 198 | return string.Format("Count = {0}", Count); 199 | } 200 | } -------------------------------------------------------------------------------- /Manager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | 4 | using UnityEngine; 5 | 6 | public abstract class Manager : MonoBehaviour where T : Manager { 7 | static T inst; 8 | public static T Inst { 9 | get { 10 | if (!inst) { 11 | inst = FindObjectOfType(); // allows Inst to be accessed during anyone's Awake 12 | } 13 | return inst; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Movement/AlphaPulser.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | [RequireComponent(typeof(SpriteRenderer))] 5 | public class AlphaPulser : MonoBehaviour { 6 | 7 | [Range(0, 10f)] 8 | public float PulseTime = 0.5f; 9 | [Range(0, 2f)] 10 | public float PulseOffset = 0f; 11 | [Range(0, 1)] 12 | public float MinAlpha = 0.4f; 13 | [Range(0, 1)] 14 | public float MaxAlpha = 1f; 15 | 16 | SpriteRenderer spriteRenderer; 17 | 18 | void Start() { 19 | spriteRenderer = GetComponent(); 20 | } 21 | 22 | void Update() { 23 | var offsetPulseTime = (Time.time + PulseOffset) % PulseTime; 24 | var interp = (offsetPulseTime / PulseTime) * 2 - 1; 25 | spriteRenderer.color = spriteRenderer.color.withAlpha(Mathf.Lerp(MinAlpha, MaxAlpha, Mathf.Abs(interp))); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Movement/FloatNear.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class FloatNear : MonoBehaviour { 6 | 7 | public Vector3 BaseTarget; 8 | public Vector2 CurrentOffset; 9 | public Vector2 OffsetRange; 10 | [Range(0, 3f)] 11 | public float OffsetInterval; 12 | float offsetTime; 13 | [Range(0, 0.3f)] 14 | public float Speed = 0.1f; 15 | 16 | void Awake() { 17 | BaseTarget = transform.localPosition; 18 | } 19 | 20 | void FixedUpdate() { 21 | // timing 22 | if (offsetTime > 0) { 23 | offsetTime -= Time.deltaTime; 24 | } else { 25 | CurrentOffset = OffsetRange.scaledWith(new Vector2(Random.value - 0.5f, Random.value - 0.5f)); 26 | offsetTime = OffsetInterval; 27 | } 28 | // move 29 | var target = BaseTarget + CurrentOffset.to3(); 30 | transform.localPosition = Vector3.Lerp(transform.localPosition, target, Speed); 31 | } 32 | } -------------------------------------------------------------------------------- /Movement/GhostPulser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | 7 | [RequireComponent(typeof(SpriteRenderer))] 8 | public class GhostPulser : MonoBehaviour { 9 | 10 | [Range(0.001f, 3f)] 11 | public float Duration = 0.5f; 12 | public Vector2 MoveAmount = new Vector2(0, 0); 13 | public Vector2 ScaleAmount = new Vector2(0, 0); 14 | 15 | [Range(0.001f, 3f)] 16 | public float PulseInterval = 0.4f; 17 | public bool AutoPulse = false; 18 | float pulseTime = float.PositiveInfinity; 19 | 20 | public bool OnlyCloneSprite; 21 | public bool GloballySynchronizePulses; 22 | public bool WorldSpace; 23 | 24 | public bool ForcePulse; 25 | 26 | SpriteRenderer spriteRenderer; 27 | float prevRealtime; 28 | 29 | // parallel lists 30 | List ghostObjects = new List(10); 31 | List ghostDurations = new List(10); 32 | 33 | void Awake() { 34 | spriteRenderer = GetComponent(); 35 | } 36 | 37 | void FixedUpdate() { 38 | // handle pulsing 39 | { 40 | var realtime = Time.realtimeSinceStartup; 41 | if (ForcePulse) { 42 | Pulse(); 43 | } 44 | ForcePulse = false; 45 | 46 | if (AutoPulse && !GloballySynchronizePulses) { 47 | pulseTime += Time.deltaTime; 48 | if (pulseTime >= PulseInterval) { 49 | Pulse(); 50 | } 51 | } else if (AutoPulse && GloballySynchronizePulses) { 52 | var pulseRemainderBeforeThisFrame = prevRealtime % PulseInterval; 53 | var pulseRemainderNow = realtime % PulseInterval; 54 | // if there's was more time remaining in the interval in the last frame than now, that means we have rolled over the synchronized pulse interval in this frame, so pulse! 55 | if (pulseRemainderBeforeThisFrame > pulseRemainderNow) { 56 | Pulse(); 57 | } 58 | } 59 | prevRealtime = realtime; 60 | } 61 | // destroy ghosts after they run out of duration 62 | { 63 | for (int i = 0; i < ghostDurations.Count; i++) { 64 | ghostDurations[i] -= Time.deltaTime; 65 | if (ghostDurations[i] <= 0) { 66 | Destroy(ghostObjects[i]); 67 | ghostObjects.RemoveAt(i); 68 | ghostDurations.RemoveAt(i); 69 | i--; 70 | } 71 | } 72 | } 73 | } 74 | 75 | public void Pulse() { 76 | Pulse(Duration, MoveAmount, ScaleAmount); 77 | } 78 | 79 | public void Pulse(float duration, Vector2 moveAmount, Vector2 scaleAmount) { 80 | if (spriteRenderer.enabled == false || spriteRenderer.sprite == null) 81 | return; 82 | 83 | // create new ghost 84 | pulseTime = 0; 85 | GameObject newObj = null; 86 | { 87 | if (OnlyCloneSprite) { 88 | newObj = new GameObject(gameObject.name); 89 | newObj.transform.position = transform.position.plusZ(-0.00001f); 90 | newObj.transform.rotation = transform.rotation; 91 | var newSprite = newObj.AddComponent(); 92 | newSprite.sprite = spriteRenderer.sprite; 93 | newSprite.color = spriteRenderer.color; 94 | } else { 95 | newObj = ((GameObject) Instantiate(gameObject, transform.position.plusZ(-0.00001f), transform.rotation)); 96 | newObj.GetComponent().enabled = false; 97 | } 98 | newObj.transform.parent = transform.parent; 99 | newObj.transform.localScale = transform.localScale; 100 | if (WorldSpace) { 101 | newObj.transform.parent = null; 102 | } 103 | } 104 | // start tweening ghost 105 | { 106 | Tween.MoveBy(newObj, new Vector3(moveAmount.x, moveAmount.y, 0), duration, Interpolate.EaseType.EaseOutCirc); 107 | Tween.ScaleBy(newObj, new Vector3(scaleAmount.x, scaleAmount.y, 0), duration, Interpolate.EaseType.EaseOutCirc); 108 | Tween.ColorTo(newObj, spriteRenderer.color.withAlpha(0), duration, Interpolate.EaseType.Linear); 109 | } 110 | // register new ghost to keep track of when to destroy it 111 | { 112 | ghostObjects.Add(newObj); 113 | ghostDurations.Add(duration); 114 | } 115 | } 116 | 117 | void OnDisable() { 118 | // destroy all ghosts 119 | for (int i = 0; i < ghostObjects.Count; i++) { 120 | Destroy(ghostObjects[i]); 121 | } 122 | ghostObjects.Clear(); 123 | ghostDurations.Clear(); 124 | ForcePulse = true; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Movement/MatchTransform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | public class MatchTransform : MonoBehaviour { 6 | 7 | [Flags] 8 | public enum Options { 9 | All = 7, 10 | NoPosition = 3, 11 | NoRotation = 5, 12 | NoScale = 6, 13 | OnlyPosition = 1 << 0, 14 | OnlyRotation = 1 << 1, 15 | OnlyScale = 1 << 2, 16 | } 17 | 18 | public Transform Target; 19 | public Options MatchOptions = Options.All; 20 | 21 | public Vector3 LocalPosition; 22 | public Vector3 LocalRotation; 23 | public Vector3 LocalScale = Vector3.one; 24 | 25 | 26 | void LateUpdate() { 27 | if ((MatchOptions & Options.OnlyPosition) > 0) { 28 | transform.position = Target.position + transform.TransformVector(LocalPosition); 29 | } 30 | if ((MatchOptions & Options.OnlyRotation) > 0) { 31 | transform.rotation = Target.rotation * Quaternion.Euler(LocalRotation); 32 | } 33 | if ((MatchOptions & Options.OnlyScale) > 0) { 34 | transform.localScale = Vector3.Scale(Target.lossyScale, LocalScale); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Movement/Moving.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class Moving : MonoBehaviour { 5 | 6 | public Vector3 MovementPerSecond; 7 | 8 | void Update() { 9 | transform.position += MovementPerSecond * Time.deltaTime; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Movement/Rotating.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class Rotating : MonoBehaviour { 5 | 6 | public Vector3 DegreesPerSecond; 7 | 8 | void Update() { 9 | transform.Rotate(DegreesPerSecond * Time.deltaTime); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Movement/Shaker.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class Shaker : MonoBehaviour { 5 | 6 | public bool Shaking = false; 7 | [Range(0, 1)] 8 | public float Strength = 0.05f; 9 | public float StrengthMultiple = 1; 10 | [Range(1, 10)] 11 | public int FrameInterval = 1; 12 | 13 | bool frameShaking = true; 14 | int framesToShake; 15 | 16 | int frameCount; 17 | Vector3 previousShake; 18 | 19 | void Update() { 20 | if (!frameShaking && Time.deltaTime == 0) // still run update if shaking by frames 21 | return; 22 | 23 | if (Shaking) { 24 | frameCount++; 25 | if (frameCount % FrameInterval == 0) { 26 | Shake(); 27 | } 28 | if (frameShaking && frameCount >= framesToShake) { 29 | frameCount = 0; 30 | Shaking = false; 31 | } 32 | } else { 33 | frameShaking = false; 34 | if (previousShake != Vector3.zero) { 35 | transform.localPosition = transform.localPosition - previousShake; 36 | previousShake = Vector3.zero; 37 | } 38 | } 39 | } 40 | 41 | void Shake() { 42 | transform.localPosition -= previousShake; 43 | Vector3 shake = Random.insideUnitCircle.normalized * Strength * StrengthMultiple; 44 | transform.localPosition += shake; 45 | previousShake = shake; 46 | } 47 | 48 | public void ShakeForFrames(int numFrames) { 49 | if (numFrames <= 0) { 50 | return; 51 | } 52 | framesToShake = numFrames; 53 | frameShaking = true; 54 | Shaking = true; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Movement/ValuePulser.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | [RequireComponent(typeof(SpriteRenderer))] 5 | public class ValuePulser : MonoBehaviour { 6 | 7 | [Range(0, 10f)] 8 | public float PulseTime = 0.5f; 9 | [Range(0, 2f)] 10 | public float PulseOffset = 0f; 11 | [Range(0, 1)] 12 | public float MinValue = 0.4f; 13 | [Range(0, 1)] 14 | public float MaxValue = 1f; 15 | 16 | SpriteRenderer spriteRenderer; 17 | 18 | void Start() { 19 | spriteRenderer = GetComponent(); 20 | } 21 | 22 | void Update() { 23 | var offsetPulseTime = (Time.time + PulseOffset) % PulseTime; 24 | var interp = (offsetPulseTime / PulseTime) * 2 - 1; 25 | spriteRenderer.color = spriteRenderer.color.withValue(Mathf.Lerp(MinValue, MaxValue, Mathf.Abs(interp))); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pool/NonGameObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class NonGameObjectPool where T : class, new() { 6 | 7 | // using a Stack because it has convenient push/pop semantics, but internally it's still an array with nice performance 8 | // downside is that you can't access objects via index, but if that's really necessary then just change it to a List and add an extra count tracking variable 9 | public Stack Objects { get; private set; } 10 | public Action OnObtain; 11 | public Action OnRelease; 12 | 13 | public NonGameObjectPool(int initialCount = 1) { 14 | Objects = new Stack(initialCount); 15 | for (int i = 0; i < initialCount; i++) { 16 | Release(new T()); 17 | } 18 | } 19 | 20 | /// Obtains an object from the pool. 21 | public T Obtain() { 22 | if (Objects.Count < 1) { 23 | Debug.LogWarning("Had to instantiate pooled object at runtime! Raise the initial count of [" + GetType().Name + "]."); 24 | Release(new T()); 25 | } 26 | var obj = Objects.Pop(); 27 | if (OnObtain != null) { 28 | OnObtain(obj); 29 | } 30 | return obj; 31 | } 32 | 33 | /// Releases an object into the pool. 34 | public void Release(T obj) { 35 | if (OnRelease != null) { 36 | OnRelease(obj); 37 | } 38 | Objects.Push(obj); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Pool/ObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using Object = UnityEngine.Object; 5 | 6 | public static class ObjectPoolExtensions { 7 | 8 | /// Releases the to its ObjectPool, creating a new pool if necessary. 9 | public static void Release(this GameObject obj) { 10 | var pooledObj = obj.GetComponent(); 11 | if (!pooledObj) { 12 | pooledObj = obj.AddComponent(); 13 | pooledObj.Source = obj.GetObjectPool(); 14 | } 15 | pooledObj.Release(); 16 | } 17 | 18 | /// Returns an ObjectPool in the scene that provides , or creates a new one for with the given . 19 | public static ObjectPool GetObjectPool(this GameObject obj, int initialCount = 5, bool keepObjectsParented = false) { 20 | var allPools = Object.FindObjectsOfType(); 21 | for (int i = 0; i < allPools.Length; i++) { 22 | var pool = allPools[i]; 23 | if (pool.Object == obj) { 24 | return pool; 25 | } 26 | } 27 | var newPoolObj = new GameObject("Pool - " + obj.name); 28 | var newPool = newPoolObj.AddComponent(); 29 | newPool.Object = obj; 30 | newPool.InitialCount = initialCount; 31 | newPool.KeepObjectsParented = keepObjectsParented; 32 | return newPool; 33 | } 34 | } 35 | 36 | public class ObjectPool : MonoBehaviour { 37 | 38 | public event Action OnObtain = delegate { }; 39 | public event Action OnRelease = delegate { }; 40 | 41 | public GameObject Object; 42 | [Range(0, 100)] 43 | public int InitialCount; 44 | public bool KeepObjectsParented; 45 | 46 | // using a Stack because it has convenient push/pop semantics, but internally it's still an array with nice performance 47 | // downside is that you can't access objects via index, but if that's really necessary then just change it to a List and add an extra count tracking variable 48 | Stack objects; 49 | 50 | // all objects that came through this ObjectPool 51 | public List AllObjects { get; private set; } 52 | 53 | void Awake() { 54 | if (objects == null) { 55 | Init(); 56 | } 57 | } 58 | 59 | void Init() { 60 | AllObjects = new List(InitialCount); 61 | objects = new Stack(InitialCount); 62 | for (int i = 0; i < InitialCount; i++) { 63 | Release(Instantiate(Object)); 64 | } 65 | } 66 | 67 | /// Obtains an object from the pool and sends the OnObtain message. 68 | public GameObject ObtainSimple(Vector3? position = null) { 69 | if (objects == null) { 70 | Init(); 71 | } 72 | if (objects.Count < 1) { 73 | Debug.LogWarning("Had to instantiate pooled object at runtime! Raise the initial count of [" + Object.name + "].", gameObject); 74 | Release(Instantiate(Object)); 75 | } 76 | 77 | var obj = objects.Pop(); 78 | obj.transform.parent = KeepObjectsParented ? transform : null; 79 | if (position.HasValue) { // allow setting a position BEFORE activating object, so unintended collisions don't occur 80 | obj.transform.position = position.Value; 81 | } 82 | obj.SetActive(true); 83 | obj.SendMessage("OnObtain", SendMessageOptions.DontRequireReceiver); 84 | return obj; 85 | } 86 | 87 | /// Obtains an object from the pool (simple), and returns the component of the given type. 88 | public T ObtainSimple(Vector3? position = null) where T : Component { 89 | return ObtainSimple(position).GetComponent(); 90 | } 91 | 92 | /// Obtains an object from the pool, sends the OnObtain message, and resets common components. 93 | public GameObject Obtain(Vector3? position = null) { 94 | var obj = ObtainSimple(position); 95 | // reset Animator or FSM or something 96 | { 97 | } 98 | // reset another component, etc. 99 | { 100 | } 101 | return obj; 102 | } 103 | 104 | /// Obtains an object from the pool, and returns the component of the given type. 105 | public T Obtain(Vector3? position = null) where T : Component { 106 | var component = Obtain(position).GetComponent(); 107 | D.Assert(component != null, "Component '" + typeof(T).Name + "' does not exist on pooled object!"); 108 | if (component) { 109 | OnObtain(component.gameObject); 110 | } 111 | return component; 112 | } 113 | 114 | /// Releases an object into the pool and sends the OnRelease message. 115 | public void Release(GameObject obj) { 116 | if (objects == null) { 117 | Init(); 118 | } 119 | if (objects.Contains(obj)) { 120 | return; 121 | } 122 | AllObjects.AddOnce(obj); 123 | 124 | var pooledObj = obj.GetComponent() ?? obj.AddComponent(); 125 | pooledObj.Source = this; 126 | objects.Push(obj); 127 | obj.transform.parent = transform; 128 | obj.SendMessage("OnRelease", SendMessageOptions.DontRequireReceiver); 129 | obj.SetActive(false); // SendMessage only works on active objects, so deactive after 130 | OnRelease(obj); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Pool/PoolOfList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class PoolOfList : NonGameObjectPool> { 6 | 7 | public PoolOfList(int initialCount = 1) : base(initialCount) { 8 | if (OnRelease == null) { 9 | OnRelease = obj => obj.Clear(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Pool/PooledObject.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class PooledObject : MonoBehaviour { 5 | public ObjectPool Source; 6 | 7 | public void Release() { 8 | Source.Release(gameObject); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Pool/ReleaseAfterTime.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class ReleaseAfterTime : MonoBehaviour { 5 | [Range(0, 120)] 6 | public float TimeToRelease = 10; 7 | float releaseTimer; 8 | bool obtained; 9 | 10 | void Update() { 11 | if (obtained) { 12 | releaseTimer -= Time.deltaTime; 13 | if (releaseTimer <= 0) { 14 | gameObject.Release(); 15 | } 16 | } 17 | } 18 | 19 | void OnObtain() { 20 | obtained = true; 21 | releaseTimer = TimeToRelease; 22 | } 23 | 24 | void OnRelease() { 25 | obtained = false; 26 | releaseTimer = TimeToRelease; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Pool/ReleaseWhenDead.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | [RequireComponent(typeof(ParticleSystem))] 5 | public class ReleaseWhenDead : MonoBehaviour { 6 | ParticleSystem particles; 7 | 8 | void Awake() { 9 | particles = GetComponent(); 10 | } 11 | 12 | void Update() { 13 | if (!particles.IsAlive()) { 14 | gameObject.Release(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ProgressiveFunc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Diagnostics; 4 | using UnityEngine; 5 | 6 | public class ProgressiveFunc : IEnumerator { 7 | 8 | public float FrameBudgetMillis = 1f; 9 | 10 | readonly Stopwatch sw = new Stopwatch(); 11 | readonly WaitForEndOfFrame endOfFrame = new WaitForEndOfFrame(); 12 | 13 | readonly IEnumerator workRoutine; 14 | readonly IEnumerator progressiveRoutine; 15 | 16 | public ProgressiveFunc(IEnumerator workRoutine) { 17 | this.workRoutine = workRoutine; 18 | progressiveRoutine = WorkProgressively(); 19 | } 20 | 21 | IEnumerator WorkProgressively() { 22 | // start timer 23 | sw.Start(); 24 | // step through work 25 | while (workRoutine.MoveNext()) { 26 | // frame break if over budget or yield requests frame break 27 | var tickBudget = (int)(FrameBudgetMillis * 10000); 28 | if (sw.ElapsedTicks >= tickBudget || workRoutine.Current is WaitForEndOfFrame) { 29 | yield return endOfFrame; 30 | sw.Restart(); 31 | } 32 | // handle other non-null yields 33 | else if (workRoutine.Current is YieldInstruction) { 34 | yield return workRoutine.Current; 35 | } 36 | } 37 | } 38 | 39 | public bool MoveNext() { return progressiveRoutine.MoveNext(); } 40 | public void Reset() { progressiveRoutine.Reset(); } 41 | public object Current { get { return progressiveRoutine.Current; } } 42 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fool's Unity Utils 2 | C# extensions and other scripting utilities that I carry around into practically every Unity project. You can just toss it into the Assets folder (or Assets/Scripts/Util). 3 | -------------------------------------------------------------------------------- /SetVersionText.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using UnityEngine.UI; 4 | 5 | public class SetVersionText : MonoBehaviour { 6 | 7 | public string Prefix; 8 | 9 | void Awake() { 10 | if (GetComponent()) { 11 | GetComponent().text = Prefix + Application.version; 12 | } else if (GetComponent()) { 13 | GetComponent().text = Prefix + Application.version; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Time/AnimationSpeedUnscaled.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | 6 | public class AnimationSpeedUnscaled : MonoBehaviour { 7 | 8 | public float ActualSpeed = 1; 9 | Animator animator; 10 | 11 | void Awake() { 12 | animator = GetComponent(); 13 | } 14 | 15 | void Update() { 16 | animator.speed = Time.timeScale.inverse() * ActualSpeed; 17 | } 18 | } -------------------------------------------------------------------------------- /Time/DestroyAfterTime.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class DestroyAfterTime : MonoBehaviour { 5 | 6 | [Range(0, 120)] 7 | public float TimeToDestroy = 10; 8 | float destroyTimer; 9 | 10 | void Update() { 11 | destroyTimer += Time.deltaTime; 12 | if (destroyTimer >= TimeToDestroy) { 13 | Destroy(gameObject); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tween/Interpolate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | /** 4 | * Interpolation utility functions: easing, bezier, and catmull-rom. 5 | * Consider using Unity's Animation curve editor and AnimationCurve class 6 | * before scripting the desired behaviour using this utility. 7 | * 8 | * Interpolation functionality available at different levels of abstraction. 9 | * Low level access via individual easing functions (ex. EaseInOutCirc), 10 | * Bezier(), and CatmullRom(). High level access using sequence generators, 11 | * NewEase(), NewBezier(), and NewCatmullRom(). 12 | * 13 | * Sequence generators are typically used as follows: 14 | * 15 | * IEnumerable sequence = Interpolate.New[Ease|Bezier|CatmulRom](configuration); 16 | * foreach (Vector3 newPoint in sequence) { 17 | * transform.position = newPoint; 18 | * yield return WaitForSeconds(1.0f); 19 | * } 20 | * 21 | * Or: 22 | * 23 | * IEnumerator sequence = Interpolate.New[Ease|Bezier|CatmulRom](configuration).GetEnumerator(); 24 | * function Update() { 25 | * if (sequence.MoveNext()) { 26 | * transform.position = sequence.Current; 27 | * } 28 | * } 29 | * 30 | * The low level functions work similarly to Unity's built in Lerp and it is 31 | * up to you to track and pass in elapsedTime and duration on every call. The 32 | * functions take this form (or the logical equivalent for Bezier() and CatmullRom()). 33 | * 34 | * transform.position = ease(start, distance, elapsedTime, duration); 35 | * 36 | * For convenience in configuration you can use the Ease(EaseType) function to 37 | * look up a concrete easing function: 38 | * 39 | * [SerializeField] 40 | * Interpolate.EaseType easeType; // set using Unity's property inspector 41 | * Interpolate.Function ease; // easing of a particular EaseType 42 | * function Awake() { 43 | * ease = Interpolate.Ease(easeType); 44 | * } 45 | * 46 | * @author Fernando Zapata (fernando@cpudreams.com) 47 | * @Traduzione Andrea85cs (andrea85cs@dynematica.it) 48 | */ 49 | using System.Collections; 50 | 51 | public class Interpolate { 52 | 53 | 54 | /** 55 | * Different methods of easing interpolation. 56 | */ 57 | public enum EaseType { 58 | Linear, 59 | EaseInQuad, 60 | EaseOutQuad, 61 | EaseInOutQuad, 62 | EaseInCubic, 63 | EaseOutCubic, 64 | EaseInOutCubic, 65 | EaseInQuart, 66 | EaseOutQuart, 67 | EaseInOutQuart, 68 | EaseInQuint, 69 | EaseOutQuint, 70 | EaseInOutQuint, 71 | EaseInSine, 72 | EaseOutSine, 73 | EaseInOutSine, 74 | EaseInExpo, 75 | EaseOutExpo, 76 | EaseInOutExpo, 77 | EaseInCirc, 78 | EaseOutCirc, 79 | EaseInOutCirc 80 | } 81 | 82 | /** 83 | * Sequence of eleapsedTimes until elapsedTime is >= duration. 84 | * 85 | * Note: elapsedTimes are calculated using the value of Time.deltatTime each 86 | * time a value is requested. 87 | */ 88 | static Vector3 Identity(Vector3 v) { 89 | return v; 90 | } 91 | 92 | static Vector3 TransformDotPosition(Transform t) { 93 | return t.position; 94 | } 95 | 96 | 97 | static IEnumerable NewTimer(float duration) { 98 | float elapsedTime = 0.0f; 99 | while (elapsedTime < duration) { 100 | yield return elapsedTime; 101 | elapsedTime += Time.deltaTime; 102 | // make sure last value is never skipped 103 | if (elapsedTime >= duration) { 104 | yield return elapsedTime; 105 | } 106 | } 107 | } 108 | 109 | public delegate Vector3 ToVector3(T v); 110 | public delegate float Function(float start, float distance, float elapsedTime, float duration); 111 | 112 | /** 113 | * Generates sequence of integers from start to end (inclusive) one step 114 | * at a time. 115 | */ 116 | static IEnumerable NewCounter(int start, int end, int step) { 117 | for (int i = start; i <= end; i += step) { 118 | yield return i; 119 | } 120 | } 121 | 122 | /** 123 | * Returns sequence generator from start to end over duration using the 124 | * given easing function. The sequence is generated as it is accessed 125 | * using the Time.deltaTime to calculate the portion of duration that has 126 | * elapsed. 127 | */ 128 | public static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, float duration) { 129 | IEnumerable timer = Interpolate.NewTimer(duration); 130 | return NewEase(ease, start, end, duration, timer); 131 | } 132 | 133 | /** 134 | * Instead of easing based on time, generate n interpolated points (slices) 135 | * between the start and end positions. 136 | */ 137 | public static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, int slices) { 138 | IEnumerable counter = Interpolate.NewCounter(0, slices + 1, 1); 139 | return NewEase(ease, start, end, slices + 1, counter); 140 | } 141 | 142 | 143 | 144 | /** 145 | * Generic easing sequence generator used to implement the time and 146 | * slice variants. Normally you would not use this function directly. 147 | */ 148 | static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, float total, IEnumerable driver) { 149 | Vector3 distance = end - start; 150 | foreach (float i in driver) { 151 | yield return Ease(ease, start, distance, i, total); 152 | } 153 | } 154 | 155 | /** 156 | * Vector3 interpolation using given easing method. Easing is done independently 157 | * on all three vector axis. 158 | */ 159 | static Vector3 Ease(Function ease, Vector3 start, Vector3 distance, float elapsedTime, float duration) { 160 | start.x = ease(start.x, distance.x, elapsedTime, duration); 161 | start.y = ease(start.y, distance.y, elapsedTime, duration); 162 | start.z = ease(start.z, distance.z, elapsedTime, duration); 163 | return start; 164 | } 165 | 166 | /** 167 | * Returns the static method that implements the given easing type for scalars. 168 | * Use this method to easily switch between easing interpolation types. 169 | * 170 | * All easing methods clamp elapsedTime so that it is always <= duration. 171 | * 172 | * var ease = Interpolate.Ease(EaseType.EaseInQuad); 173 | * i = ease(start, distance, elapsedTime, duration); 174 | */ 175 | public static Function Ease(EaseType type) { 176 | // Source Flash easing functions: 177 | // http://gizma.com/easing/ 178 | // http://www.robertpenner.com/easing/easing_demo.html 179 | // 180 | // Changed to use more friendly variable names, that follow my Lerp 181 | // conventions: 182 | // start = b (start value) 183 | // distance = c (change in value) 184 | // elapsedTime = t (current time) 185 | // duration = d (time duration) 186 | 187 | Function f = null; 188 | switch (type) { 189 | case EaseType.Linear: f = Interpolate.Linear; break; 190 | case EaseType.EaseInQuad: f = Interpolate.EaseInQuad; break; 191 | case EaseType.EaseOutQuad: f = Interpolate.EaseOutQuad; break; 192 | case EaseType.EaseInOutQuad: f = Interpolate.EaseInOutQuad; break; 193 | case EaseType.EaseInCubic: f = Interpolate.EaseInCubic; break; 194 | case EaseType.EaseOutCubic: f = Interpolate.EaseOutCubic; break; 195 | case EaseType.EaseInOutCubic: f = Interpolate.EaseInOutCubic; break; 196 | case EaseType.EaseInQuart: f = Interpolate.EaseInQuart; break; 197 | case EaseType.EaseOutQuart: f = Interpolate.EaseOutQuart; break; 198 | case EaseType.EaseInOutQuart: f = Interpolate.EaseInOutQuart; break; 199 | case EaseType.EaseInQuint: f = Interpolate.EaseInQuint; break; 200 | case EaseType.EaseOutQuint: f = Interpolate.EaseOutQuint; break; 201 | case EaseType.EaseInOutQuint: f = Interpolate.EaseInOutQuint; break; 202 | case EaseType.EaseInSine: f = Interpolate.EaseInSine; break; 203 | case EaseType.EaseOutSine: f = Interpolate.EaseOutSine; break; 204 | case EaseType.EaseInOutSine: f = Interpolate.EaseInOutSine; break; 205 | case EaseType.EaseInExpo: f = Interpolate.EaseInExpo; break; 206 | case EaseType.EaseOutExpo: f = Interpolate.EaseOutExpo; break; 207 | case EaseType.EaseInOutExpo: f = Interpolate.EaseInOutExpo; break; 208 | case EaseType.EaseInCirc: f = Interpolate.EaseInCirc; break; 209 | case EaseType.EaseOutCirc: f = Interpolate.EaseOutCirc; break; 210 | case EaseType.EaseInOutCirc: f = Interpolate.EaseInOutCirc; break; 211 | } 212 | return f; 213 | } 214 | 215 | /** 216 | * Returns sequence generator from the first node to the last node over 217 | * duration time using the points in-between the first and last node 218 | * as control points of a bezier curve used to generate the interpolated points 219 | * in the sequence. If there are no control points (ie. only two nodes, first 220 | * and last) then this behaves exactly the same as NewEase(). In other words 221 | * a zero-degree bezier spline curve is just the easing method. The sequence 222 | * is generated as it is accessed using the Time.deltaTime to calculate the 223 | * portion of duration that has elapsed. 224 | */ 225 | public static IEnumerable NewBezier(Function ease, Transform[] nodes, float duration) { 226 | IEnumerable timer = Interpolate.NewTimer(duration); 227 | return NewBezier(ease, nodes, TransformDotPosition, duration, timer); 228 | } 229 | 230 | /** 231 | * Instead of interpolating based on time, generate n interpolated points 232 | * (slices) between the first and last node. 233 | */ 234 | public static IEnumerable NewBezier(Function ease, Transform[] nodes, int slices) { 235 | IEnumerable counter = NewCounter(0, slices + 1, 1); 236 | return NewBezier(ease, nodes, TransformDotPosition, slices + 1, counter); 237 | } 238 | 239 | /** 240 | * A Vector3[] variation of the Transform[] NewBezier() function. 241 | * Same functionality but using Vector3s to define bezier curve. 242 | */ 243 | public static IEnumerable NewBezier(Function ease, Vector3[] points, float duration) { 244 | IEnumerable timer = NewTimer(duration); 245 | return NewBezier(ease, points, Identity, duration, timer); 246 | } 247 | 248 | /** 249 | * A Vector3[] variation of the Transform[] NewBezier() function. 250 | * Same functionality but using Vector3s to define bezier curve. 251 | */ 252 | public static IEnumerable NewBezier(Function ease, Vector3[] points, int slices) { 253 | IEnumerable counter = NewCounter(0, slices + 1, 1); 254 | return NewBezier(ease, points, Identity, slices + 1, counter); 255 | } 256 | 257 | /** 258 | * Generic bezier spline sequence generator used to implement the time and 259 | * slice variants. Normally you would not use this function directly. 260 | */ 261 | static IEnumerable NewBezier(Function ease, IList nodes, ToVector3 toVector3, float maxStep, IEnumerable steps) { 262 | // need at least two nodes to spline between 263 | if (nodes.Count >= 2) { 264 | // copy nodes array since Bezier is destructive 265 | Vector3[] points = new Vector3[nodes.Count]; 266 | 267 | foreach (float step in steps) { 268 | // re-initialize copy before each destructive call to Bezier 269 | for (int i = 0; i < nodes.Count; i++) { 270 | points[i] = toVector3((T)nodes[i]); 271 | } 272 | yield return Bezier(ease, points, step, maxStep); 273 | // make sure last value is always generated 274 | } 275 | } 276 | } 277 | 278 | /** 279 | * A Vector3 n-degree bezier spline. 280 | * 281 | * WARNING: The points array is modified by Bezier. See NewBezier() for a 282 | * safe and user friendly alternative. 283 | * 284 | * You can pass zero control points, just the start and end points, for just 285 | * plain easing. In other words a zero-degree bezier spline curve is just the 286 | * easing method. 287 | * 288 | * @param points start point, n control points, end point 289 | */ 290 | static Vector3 Bezier(Function ease, Vector3[] points, float elapsedTime, float duration) { 291 | // Reference: http://ibiblio.org/e-notes/Splines/Bezier.htm 292 | // Interpolate the n starting points to generate the next j = (n - 1) points, 293 | // then interpolate those n - 1 points to generate the next n - 2 points, 294 | // continue this until we have generated the last point (n - (n - 1)), j = 1. 295 | // We store the next set of output points in the same array as the 296 | // input points used to generate them. This works because we store the 297 | // result in the slot of the input point that is no longer used for this 298 | // iteration. 299 | for (int j = points.Length - 1; j > 0; j--) { 300 | for (int i = 0; i < j; i++) { 301 | points[i].x = ease(points[i].x, points[i + 1].x - points[i].x, elapsedTime, duration); 302 | points[i].y = ease(points[i].y, points[i + 1].y - points[i].y, elapsedTime, duration); 303 | points[i].z = ease(points[i].z, points[i + 1].z - points[i].z, elapsedTime, duration); 304 | } 305 | } 306 | return points[0]; 307 | } 308 | 309 | /** 310 | * Returns sequence generator from the first node, through each control point, 311 | * and to the last node. N points are generated between each node (slices) 312 | * using Catmull-Rom. 313 | */ 314 | public static IEnumerable NewCatmullRom(Transform[] nodes, int slices, bool loop) { 315 | return NewCatmullRom(nodes, TransformDotPosition, slices, loop); 316 | } 317 | 318 | /** 319 | * A Vector3[] variation of the Transform[] NewCatmullRom() function. 320 | * Same functionality but using Vector3s to define curve. 321 | */ 322 | public static IEnumerable NewCatmullRom(Vector3[] points, int slices, bool loop) { 323 | return NewCatmullRom(points, Identity, slices, loop); 324 | } 325 | 326 | /** 327 | * Generic catmull-rom spline sequence generator used to implement the 328 | * Vector3[] and Transform[] variants. Normally you would not use this 329 | * function directly. 330 | */ 331 | static IEnumerable NewCatmullRom(IList nodes, ToVector3 toVector3, int slices, bool loop) { 332 | // need at least two nodes to spline between 333 | if (nodes.Count >= 2) { 334 | 335 | // yield the first point explicitly, if looping the first point 336 | // will be generated again in the step for loop when interpolating 337 | // from last point back to the first point 338 | yield return toVector3((T)nodes[0]); 339 | 340 | int last = nodes.Count - 1; 341 | for (int current = 0; loop || current < last; current++) { 342 | // wrap around when looping 343 | if (loop && current > last) { 344 | current = 0; 345 | } 346 | // handle edge cases for looping and non-looping scenarios 347 | // when looping we wrap around, when not looping use start for previous 348 | // and end for next when you at the ends of the nodes array 349 | int previous = (current == 0) ? ((loop) ? last : current) : current - 1; 350 | int start = current; 351 | int end = (current == last) ? ((loop) ? 0 : current) : current + 1; 352 | int next = (end == last) ? ((loop) ? 0 : end) : end + 1; 353 | 354 | // adding one guarantees yielding at least the end point 355 | int stepCount = slices + 1; 356 | for (int step = 1; step <= stepCount; step++) { 357 | yield return CatmullRom(toVector3((T)nodes[previous]), 358 | toVector3((T)nodes[start]), 359 | toVector3((T)nodes[end]), 360 | toVector3((T)nodes[next]), 361 | step, stepCount); 362 | } 363 | } 364 | } 365 | } 366 | 367 | /** 368 | * A Vector3 Catmull-Rom spline. Catmull-Rom splines are similar to bezier 369 | * splines but have the useful property that the generated curve will go 370 | * through each of the control points. 371 | * 372 | * NOTE: The NewCatmullRom() functions are an easier to use alternative to this 373 | * raw Catmull-Rom implementation. 374 | * 375 | * @param previous the point just before the start point or the start point 376 | * itself if no previous point is available 377 | * @param start generated when elapsedTime == 0 378 | * @param end generated when elapsedTime >= duration 379 | * @param next the point just after the end point or the end point itself if no 380 | * next point is available 381 | */ 382 | static Vector3 CatmullRom(Vector3 previous, Vector3 start, Vector3 end, Vector3 next, 383 | float elapsedTime, float duration) { 384 | // References used: 385 | // p.266 GemsV1 386 | // 387 | // tension is often set to 0.5 but you can use any reasonable value: 388 | // http://www.cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf 389 | // 390 | // bias and tension controls: 391 | // http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/interpolation/ 392 | 393 | float percentComplete = elapsedTime / duration; 394 | float percentCompleteSquared = percentComplete * percentComplete; 395 | float percentCompleteCubed = percentCompleteSquared * percentComplete; 396 | 397 | return previous * (-0.5f * percentCompleteCubed + 398 | percentCompleteSquared - 399 | 0.5f * percentComplete) + 400 | start * ( 1.5f * percentCompleteCubed + 401 | -2.5f * percentCompleteSquared + 1.0f) + 402 | end * (-1.5f * percentCompleteCubed + 403 | 2.0f * percentCompleteSquared + 404 | 0.5f * percentComplete) + 405 | next * ( 0.5f * percentCompleteCubed - 406 | 0.5f * percentCompleteSquared); 407 | } 408 | 409 | 410 | 411 | 412 | /** 413 | * Linear interpolation (same as Mathf.Lerp) 414 | */ 415 | static float Linear(float start, float distance, float elapsedTime, float duration) { 416 | // clamp elapsedTime to be <= duration 417 | if (elapsedTime > duration) { elapsedTime = duration; } 418 | return distance * (elapsedTime / duration) + start; 419 | } 420 | 421 | /** 422 | * quadratic easing in - accelerating from zero velocity 423 | */ 424 | static float EaseInQuad(float start, float distance, float elapsedTime, float duration) { 425 | // clamp elapsedTime so that it cannot be greater than duration 426 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 427 | return distance * elapsedTime * elapsedTime + start; 428 | } 429 | 430 | /** 431 | * quadratic easing out - decelerating to zero velocity 432 | */ 433 | static float EaseOutQuad(float start, float distance, float elapsedTime, float duration) { 434 | // clamp elapsedTime so that it cannot be greater than duration 435 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 436 | return -distance * elapsedTime * (elapsedTime - 2) + start; 437 | } 438 | 439 | /** 440 | * quadratic easing in/out - acceleration until halfway, then deceleration 441 | */ 442 | static float EaseInOutQuad(float start, float distance, float elapsedTime, float duration) { 443 | // clamp elapsedTime so that it cannot be greater than duration 444 | elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2); 445 | if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime + start; 446 | elapsedTime--; 447 | return -distance / 2 * (elapsedTime * (elapsedTime - 2) - 1) + start; 448 | } 449 | 450 | /** 451 | * cubic easing in - accelerating from zero velocity 452 | */ 453 | static float EaseInCubic(float start, float distance, float elapsedTime, float duration) { 454 | // clamp elapsedTime so that it cannot be greater than duration 455 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 456 | return distance * elapsedTime * elapsedTime * elapsedTime + start; 457 | } 458 | 459 | /** 460 | * cubic easing out - decelerating to zero velocity 461 | */ 462 | static float EaseOutCubic(float start, float distance, float elapsedTime, float duration) { 463 | // clamp elapsedTime so that it cannot be greater than duration 464 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 465 | elapsedTime--; 466 | return distance * (elapsedTime * elapsedTime * elapsedTime + 1) + start; 467 | } 468 | 469 | /** 470 | * cubic easing in/out - acceleration until halfway, then deceleration 471 | */ 472 | static float EaseInOutCubic(float start, float distance, float elapsedTime, float duration) { 473 | // clamp elapsedTime so that it cannot be greater than duration 474 | elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2); 475 | if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime + start; 476 | elapsedTime -= 2; 477 | return distance / 2 * (elapsedTime * elapsedTime * elapsedTime + 2) + start; 478 | } 479 | 480 | /** 481 | * quartic easing in - accelerating from zero velocity 482 | */ 483 | static float EaseInQuart(float start, float distance, float elapsedTime, float duration) { 484 | // clamp elapsedTime so that it cannot be greater than duration 485 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 486 | return distance * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start; 487 | } 488 | 489 | /** 490 | * quartic easing out - decelerating to zero velocity 491 | */ 492 | static float EaseOutQuart(float start, float distance, float elapsedTime, float duration) { 493 | // clamp elapsedTime so that it cannot be greater than duration 494 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 495 | elapsedTime--; 496 | return -distance * (elapsedTime * elapsedTime * elapsedTime * elapsedTime - 1) + start; 497 | } 498 | 499 | /** 500 | * quartic easing in/out - acceleration until halfway, then deceleration 501 | */ 502 | static float EaseInOutQuart(float start, float distance, float elapsedTime, float duration) { 503 | // clamp elapsedTime so that it cannot be greater than duration 504 | elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2); 505 | if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start; 506 | elapsedTime -= 2; 507 | return -distance / 2 * (elapsedTime * elapsedTime * elapsedTime * elapsedTime - 2) + start; 508 | } 509 | 510 | 511 | /** 512 | * quintic easing in - accelerating from zero velocity 513 | */ 514 | static float EaseInQuint(float start, float distance, float elapsedTime, float duration) { 515 | // clamp elapsedTime so that it cannot be greater than duration 516 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 517 | return distance * elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start; 518 | } 519 | 520 | /** 521 | * quintic easing out - decelerating to zero velocity 522 | */ 523 | static float EaseOutQuint(float start, float distance, float elapsedTime, float duration) { 524 | // clamp elapsedTime so that it cannot be greater than duration 525 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 526 | elapsedTime--; 527 | return distance * (elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + 1) + start; 528 | } 529 | 530 | /** 531 | * quintic easing in/out - acceleration until halfway, then deceleration 532 | */ 533 | static float EaseInOutQuint(float start, float distance, float elapsedTime, float duration) { 534 | // clamp elapsedTime so that it cannot be greater than duration 535 | elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2f); 536 | if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start; 537 | elapsedTime -= 2; 538 | return distance / 2 * (elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + 2) + start; 539 | } 540 | 541 | /** 542 | * sinusoidal easing in - accelerating from zero velocity 543 | */ 544 | static float EaseInSine(float start, float distance, float elapsedTime, float duration) { 545 | // clamp elapsedTime to be <= duration 546 | if (elapsedTime > duration) { elapsedTime = duration; } 547 | return -distance * Mathf.Cos(elapsedTime / duration * (Mathf.PI / 2)) + distance + start; 548 | } 549 | 550 | /** 551 | * sinusoidal easing out - decelerating to zero velocity 552 | */ 553 | static float EaseOutSine(float start, float distance, float elapsedTime, float duration) { 554 | if (elapsedTime > duration) { elapsedTime = duration; } 555 | return distance * Mathf.Sin(elapsedTime / duration * (Mathf.PI / 2)) + start; 556 | } 557 | 558 | /** 559 | * sinusoidal easing in/out - accelerating until halfway, then decelerating 560 | */ 561 | static float EaseInOutSine(float start, float distance, float elapsedTime, float duration) { 562 | // clamp elapsedTime to be <= duration 563 | if (elapsedTime > duration) { elapsedTime = duration; } 564 | return -distance / 2 * (Mathf.Cos(Mathf.PI * elapsedTime / duration) - 1) + start; 565 | } 566 | 567 | /** 568 | * exponential easing in - accelerating from zero velocity 569 | */ 570 | static float EaseInExpo(float start, float distance, float elapsedTime, float duration) { 571 | // clamp elapsedTime to be <= duration 572 | if (elapsedTime > duration) { elapsedTime = duration; } 573 | return distance * Mathf.Pow(2, 10 * (elapsedTime / duration - 1)) + start; 574 | } 575 | 576 | /** 577 | * exponential easing out - decelerating to zero velocity 578 | */ 579 | static float EaseOutExpo(float start, float distance, float elapsedTime, float duration) { 580 | // clamp elapsedTime to be <= duration 581 | if (elapsedTime > duration) { elapsedTime = duration; } 582 | return distance * (-Mathf.Pow(2, -10 * elapsedTime / duration) + 1) + start; 583 | } 584 | 585 | /** 586 | * exponential easing in/out - accelerating until halfway, then decelerating 587 | */ 588 | static float EaseInOutExpo(float start, float distance, float elapsedTime, float duration) { 589 | // clamp elapsedTime so that it cannot be greater than duration 590 | elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2); 591 | if (elapsedTime < 1) return distance / 2 * Mathf.Pow(2, 10 * (elapsedTime - 1)) + start; 592 | elapsedTime--; 593 | return distance / 2 * (-Mathf.Pow(2, -10 * elapsedTime) + 2) + start; 594 | } 595 | 596 | /** 597 | * circular easing in - accelerating from zero velocity 598 | */ 599 | static float EaseInCirc(float start, float distance, float elapsedTime, float duration) { 600 | // clamp elapsedTime so that it cannot be greater than duration 601 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 602 | return -distance * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) - 1) + start; 603 | } 604 | 605 | /** 606 | * circular easing out - decelerating to zero velocity 607 | */ 608 | static float EaseOutCirc(float start, float distance, float elapsedTime, float duration) { 609 | // clamp elapsedTime so that it cannot be greater than duration 610 | elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration; 611 | elapsedTime--; 612 | return distance * Mathf.Sqrt(1 - elapsedTime * elapsedTime) + start; 613 | } 614 | 615 | /** 616 | * circular easing in/out - acceleration until halfway, then deceleration 617 | */ 618 | static float EaseInOutCirc(float start, float distance, float 619 | elapsedTime, float duration) { 620 | // clamp elapsedTime so that it cannot be greater than duration 621 | elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2); 622 | if (elapsedTime < 1) return -distance / 2 * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) - 1) + start; 623 | elapsedTime -= 2; 624 | return distance / 2 * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) + 1) + start; 625 | } 626 | } -------------------------------------------------------------------------------- /Tween/Tween.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEngine; 4 | using System.Collections; 5 | 6 | public static class Tween 7 | { 8 | public static void MoveTo(GameObject obj, Vector3 destination, float duration, Interpolate.EaseType easingFunction, Action onComplete = null, bool world = false) 9 | { 10 | Tweening tweening = obj.GetComponent(); 11 | if (!tweening) 12 | tweening = obj.AddComponent(); 13 | 14 | tweening.MoveTo(destination, duration, easingFunction, onComplete, world); 15 | } 16 | 17 | public static void MoveBy(GameObject obj, Vector3 displacement, float duration, Interpolate.EaseType easingFunction, Action onComplete = null, bool world = false) 18 | { 19 | Vector3 destination = obj.transform.localPosition + displacement; 20 | MoveTo(obj, destination, duration, easingFunction, onComplete, world); 21 | } 22 | 23 | public static void RotateTo(GameObject obj, Vector3 destination, float duration, Interpolate.EaseType easingFunction, Action onComplete = null, bool world = false) 24 | { 25 | Tweening tweening = obj.GetComponent(); 26 | if (!tweening) 27 | tweening = obj.AddComponent(); 28 | 29 | tweening.RotateTo(destination, duration, easingFunction, onComplete, world); 30 | } 31 | 32 | public static void RotateBy(GameObject obj, Vector3 displacement, float duration, Interpolate.EaseType easingFunction, Action onComplete = null, bool world = false) 33 | { 34 | Vector3 destination = obj.transform.localRotation.eulerAngles + displacement; 35 | RotateTo(obj, destination, duration, easingFunction, onComplete, world); 36 | } 37 | 38 | public static void ScaleTo(GameObject obj, Vector3 destination, float duration, Interpolate.EaseType easingFunction, Action onComplete = null) 39 | { 40 | Tweening tweening = obj.GetComponent(); 41 | if (!tweening) 42 | tweening = obj.AddComponent(); 43 | 44 | tweening.ScaleTo(destination, duration, easingFunction, onComplete); 45 | } 46 | 47 | public static void ScaleBy(GameObject obj, Vector3 displacement, float duration, Interpolate.EaseType easingFunction, Action onComplete = null) 48 | { 49 | Vector3 destination = obj.transform.localScale + displacement; 50 | ScaleTo(obj, destination, duration, easingFunction, onComplete); 51 | } 52 | 53 | 54 | public static void ColorTo(GameObject obj, Color destination, float duration, Interpolate.EaseType easingFunction, Action onComplete = null) 55 | { 56 | Tweening tweening = obj.GetComponent(); 57 | if (!tweening) 58 | tweening = obj.AddComponent(); 59 | 60 | tweening.ColorTo(destination, duration, easingFunction, onComplete); 61 | } 62 | } -------------------------------------------------------------------------------- /Tween/Tweening.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | using UnityEngine; 5 | using System.Collections; 6 | 7 | public class Tweening : MonoBehaviour 8 | { 9 | bool positionWorld; 10 | Vector3 positionInitial; 11 | Vector3 positionDisplacement; 12 | Interpolate.Function positionFunc; 13 | Action positionOnComplete; 14 | float positionDuration; 15 | float positionDurationTotal; 16 | 17 | bool rotationWorld; 18 | Vector3 rotationDisplacement; 19 | Vector3 rotationInitial; 20 | Interpolate.Function rotationFunc; 21 | Action rotationOnComplete; 22 | float rotationDuration; 23 | float rotationDurationTotal; 24 | 25 | Vector3 scaleDisplacement; 26 | Vector3 scaleInitial; 27 | Interpolate.Function scaleFunc; 28 | Action scaleOnComplete; 29 | float scaleDuration; 30 | float scaleDurationTotal; 31 | 32 | Color colorDisplacement; 33 | Color colorInitial; 34 | Interpolate.Function colorFunc; 35 | Action colorOnComplete; 36 | float colorDuration; 37 | float colorDurationTotal; 38 | 39 | new Renderer renderer; 40 | 41 | void Awake() { 42 | renderer = GetComponent(); 43 | } 44 | 45 | public void MoveTo(Vector3 destination, float duration, Interpolate.EaseType easingFunction, Action onComplete, bool world) 46 | { 47 | positionWorld = world; 48 | positionInitial = positionWorld ? transform.position : transform.localPosition; 49 | positionDisplacement = destination - positionInitial; 50 | positionFunc = Interpolate.Ease(easingFunction); 51 | positionOnComplete = onComplete; 52 | positionDurationTotal = duration; 53 | positionDuration = 0; 54 | } 55 | 56 | public void RotateTo(Vector3 destination, float duration, Interpolate.EaseType easingFunction, Action onComplete, bool world) 57 | { 58 | rotationWorld = world; 59 | rotationInitial = rotationWorld ? transform.rotation.eulerAngles : transform.localRotation.eulerAngles; 60 | 61 | rotationDisplacement = destination - rotationInitial; 62 | var rotX = rotationDisplacement.x; // check the other way around the circle 63 | var rotY = rotationDisplacement.y; // for each component 64 | var rotZ = rotationDisplacement.z; 65 | var rotX2 = (rotationDisplacement.x + 360) % 360; 66 | var rotY2 = (rotationDisplacement.y + 360) % 360; 67 | var rotZ2 = (rotationDisplacement.z + 360) % 360; 68 | if (Mathf.Abs(rotX2) < Mathf.Abs(rotX)) rotX = rotX2; 69 | if (Mathf.Abs(rotY2) < Mathf.Abs(rotY)) rotY = rotY2; 70 | if (Mathf.Abs(rotZ2) < Mathf.Abs(rotZ)) rotZ = rotZ2; 71 | rotationDisplacement = new Vector3(rotX, rotY, rotZ); 72 | 73 | rotationFunc = Interpolate.Ease(easingFunction); 74 | rotationOnComplete = onComplete; 75 | rotationDurationTotal = duration; 76 | rotationDuration = 0; 77 | } 78 | 79 | public void ScaleTo(Vector3 destination, float duration, Interpolate.EaseType easingFunction, Action onComplete) 80 | { 81 | scaleInitial = transform.localScale; 82 | scaleDisplacement = destination - scaleInitial; 83 | scaleFunc = Interpolate.Ease(easingFunction); 84 | scaleOnComplete = onComplete; 85 | scaleDurationTotal = duration; 86 | scaleDuration = 0; 87 | } 88 | 89 | public void ColorTo(Color destination, float duration, Interpolate.EaseType easingFunction, Action onComplete) 90 | { 91 | colorInitial = renderer.material.color; 92 | colorDisplacement = destination - colorInitial; 93 | colorFunc = Interpolate.Ease(easingFunction); 94 | colorOnComplete = onComplete; 95 | colorDurationTotal = duration; 96 | colorDuration = 0; 97 | } 98 | 99 | public void FixedUpdate() 100 | { 101 | if (positionDuration < positionDurationTotal) { 102 | positionDuration += Time.deltaTime; 103 | if (positionDuration >= positionDurationTotal) { 104 | positionDuration = positionDurationTotal; 105 | } 106 | Vector3 pos = positionInitial; 107 | pos.x = positionFunc(pos.x, positionDisplacement.x, positionDuration, positionDurationTotal); 108 | pos.y = positionFunc(pos.y, positionDisplacement.y, positionDuration, positionDurationTotal); 109 | pos.z = positionFunc(pos.z, positionDisplacement.z, positionDuration, positionDurationTotal); 110 | if (positionWorld) transform.position = pos; else transform.localPosition = pos; 111 | if (positionDuration >= positionDurationTotal) { // call event at the end after all properties have been set 112 | if (positionOnComplete != null) positionOnComplete(gameObject); 113 | } 114 | } 115 | 116 | rotationDuration += Time.deltaTime; 117 | if (rotationDuration < rotationDurationTotal) { 118 | if (rotationDuration >= rotationDurationTotal) { 119 | rotationDuration = rotationDurationTotal; 120 | } 121 | Vector3 rot = rotationInitial; 122 | rot.x = rotationFunc(rot.x, rotationDisplacement.x, rotationDuration, rotationDurationTotal); 123 | rot.y = rotationFunc(rot.y, rotationDisplacement.y, rotationDuration, rotationDurationTotal); 124 | rot.z = rotationFunc(rot.z, rotationDisplacement.z, rotationDuration, rotationDurationTotal); 125 | if (rotationWorld) transform.rotation = Quaternion.Euler(rot); else transform.localRotation = Quaternion.Euler(rot); 126 | if (rotationDuration >= rotationDurationTotal) { // call event at the end after all properties have been set 127 | if (rotationOnComplete != null) rotationOnComplete(gameObject); 128 | } 129 | } 130 | 131 | scaleDuration += Time.deltaTime; 132 | if (scaleDuration < scaleDurationTotal) { 133 | if (scaleDuration >= scaleDurationTotal) { 134 | scaleDuration = scaleDurationTotal; 135 | } 136 | Vector3 scale = scaleInitial; 137 | scale.x = scaleFunc(scale.x, scaleDisplacement.x, scaleDuration, scaleDurationTotal); 138 | scale.y = scaleFunc(scale.y, scaleDisplacement.y, scaleDuration, scaleDurationTotal); 139 | scale.z = scaleFunc(scale.z, scaleDisplacement.z, scaleDuration, scaleDurationTotal); 140 | transform.localScale = scale; 141 | if (scaleDuration >= scaleDurationTotal) { // call event at the end after all properties have been set 142 | if (scaleOnComplete != null) scaleOnComplete(gameObject); 143 | } 144 | } 145 | 146 | colorDuration += Time.deltaTime; 147 | if (colorDuration < colorDurationTotal) { 148 | if (colorDuration >= colorDurationTotal) { 149 | colorDuration = colorDurationTotal; 150 | } 151 | Color color = colorInitial; 152 | color.r = colorFunc(color.r, colorDisplacement.r, colorDuration, colorDurationTotal); 153 | color.g = colorFunc(color.g, colorDisplacement.g, colorDuration, colorDurationTotal); 154 | color.b = colorFunc(color.b, colorDisplacement.b, colorDuration, colorDurationTotal); 155 | color.a = colorFunc(color.a, colorDisplacement.a, colorDuration, colorDurationTotal); 156 | renderer.material.color = color; 157 | if (colorDuration >= colorDurationTotal) { // call event at the end after all properties have been set 158 | if (colorOnComplete != null) colorOnComplete(gameObject); 159 | } 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /UnityEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using System.Collections; 4 | using UnityEngine.Events; 5 | 6 | [Serializable] 7 | public class UnityEventInt : UnityEvent { } 8 | [Serializable] 9 | public class UnityEventFloat : UnityEvent { } 10 | [Serializable] 11 | public class UnityEventBool : UnityEvent { } 12 | [Serializable] 13 | public class UnityEventString : UnityEvent { } 14 | [Serializable] 15 | public class UnityEventVector2 : UnityEvent { } 16 | [Serializable] 17 | public class UnityEventVector3 : UnityEvent { } 18 | [Serializable] 19 | public class UnityEventGameObject : UnityEvent { } 20 | [Serializable] 21 | public class UnityEventRaycastHit2D : UnityEvent { } -------------------------------------------------------------------------------- /WithRandomSeed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using Random = UnityEngine.Random; 4 | 5 | public struct WithRandomSeed : IDisposable { 6 | 7 | readonly Random.State prevState; 8 | 9 | public WithRandomSeed(int seed) { 10 | prevState = Random.state; 11 | Random.InitState(seed); 12 | } 13 | 14 | public void Dispose() { 15 | Random.state = prevState; 16 | } 17 | } 18 | --------------------------------------------------------------------------------