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