// Animancer // Copyright 2020 Kybernetik // using System; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer { /// Various extension methods and utilities. public static partial class AnimancerUtilities { /************************************************************************************************************************/ /// [Animancer Extension] Loops the `value` so that 0 <= value < 1. /// This is more efficient than using with a length of 1. public static float Wrap01(this float value) { var valueAsDouble = (double)value; return (float)(valueAsDouble - Math.Floor(valueAsDouble)); } /************************************************************************************************************************/ /// [Animancer Extension] /// Adds the specified type of , links it to the `animator`, and returns it. /// public static T AddAnimancerComponent(this Animator animator) where T : Component, IAnimancerComponent { var animancer = animator.gameObject.AddComponent(); animancer.Animator = animator; return animancer; } /************************************************************************************************************************/ /// [Animancer Extension] /// Returns the on the same as the `animator` if /// there is one. Otherwise this method adds a new one and returns it. /// public static T GetOrAddAnimancerComponent(this Animator animator) where T : Component, IAnimancerComponent { var animancer = animator.GetComponent(); if (animancer != null) return animancer; else return animator.AddAnimancerComponent(); } /************************************************************************************************************************/ /// /// Checks if any in the `source` has an animation event with the specified /// `functionName`. /// public static bool HasEvent(IAnimationClipCollection source, string functionName) { var clips = ObjectPool.AcquireSet(); source.GatherAnimationClips(clips); foreach (var clip in clips) { if (HasEvent(clip, functionName)) { ObjectPool.Release(clips); return true; } } ObjectPool.Release(clips); return false; } /// Checks if the `clip` has an animation event with the specified `functionName`. public static bool HasEvent(AnimationClip clip, string functionName) { var events = clip.events; var count = events.Length; for (int i = 0; i < count; i++) { if (events[i].functionName == functionName) return true; } return false; } /************************************************************************************************************************/ /// [Pro-Only] /// Calculates all thresholds in the `mixer` using the of each /// state on the X and Z axes. /// /// Note that this method requires the Root Transform Position (XZ) -> Bake Into Pose toggle to be /// disabled in the Import Settings of each in the mixer. /// public static void CalculateThresholdsFromAverageVelocityXZ(this MixerState mixer) { mixer.ValidateThresholdCount(); var count = mixer.States.Length; for (int i = 0; i < count; i++) { var state = mixer.States[i]; if (state == null) continue; var averageVelocity = state.AverageVelocity; mixer.SetThreshold(i, new Vector2(averageVelocity.x, averageVelocity.z)); } } /************************************************************************************************************************/ /// [Editor-Conditional] Marks the `target` as dirty. [System.Diagnostics.Conditional(Strings.EditorOnly)] public static void SetDirty(Object target) { #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(target); #endif } /************************************************************************************************************************/ /// [Editor-Conditional] /// If there are multiple components which inherit from , the first one is changed to /// the type of the second and any after the first are destroyed. This allows you to change the type without /// losing the values of any serialized fields they share. /// /// The `currentComponent` is used to determine which to examine and the base /// component type . /// /// /// protected void Reset() /// { /// AnimancerUtilities.IfMultiComponentThenChangeType(this); /// } /// [System.Diagnostics.Conditional(Strings.EditorOnly)] public static void IfMultiComponentThenChangeType(T currentComponent) where T : MonoBehaviour { #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) return; // If there is already another instance of this component on the same object, delete this new instance and // change the original's type to match this one. var components = currentComponent.GetComponents(); if (components.Length > 1) { var oldComponent = components[0]; var newComponent = components[1]; if (oldComponent.GetType() != newComponent.GetType()) { // All we have to do is change the Script field to the new type and Unity will immediately deserialize // the existing data as that type, so any fields shared between both types will keep their data. using (var serializedObject = new UnityEditor.SerializedObject(oldComponent)) { var scriptProperty = serializedObject.FindProperty("m_Script"); scriptProperty.objectReferenceValue = UnityEditor.MonoScript.FromMonoBehaviour(newComponent); serializedObject.ApplyModifiedProperties(); } } // Destroy all components other than the first (the oldest). UnityEditor.EditorApplication.delayCall += () => { var i = 1; for (; i < components.Length; i++) { Object.DestroyImmediate(components[i], true); } }; } #endif } /************************************************************************************************************************/ /// [Editor-Conditional] /// Plays the specified `clip` if called in Edit Mode and optionally pauses it immediately. /// /// /// Before Unity 2018.3, playing animations in Edit Mode didn't work properly. /// [System.Diagnostics.Conditional(Strings.EditorOnly)] public static void EditModePlay(IAnimancerComponent animancer, AnimationClip clip, bool pauseImmediately = true) { #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode || animancer == null || clip == null) return; // Delay for a frame in case this was called at a bad time (such as during OnValidate). UnityEditor.EditorApplication.delayCall += () => { if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode || animancer == null || clip == null) return; animancer.Playable.Play(clip); if (pauseImmediately) { animancer.Playable.Evaluate(); animancer.Playable.PauseGraph(); } }; #endif } /************************************************************************************************************************/ } }