2025-05-09 15:40:34 +08:00

874 lines
40 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using Animancer.TransitionLibraries;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
namespace Animancer
{
/// <summary>
/// The main component through which other scripts can interact with <see cref="Animancer"/>. It allows you to play
/// animations on an <see cref="UnityEngine.Animator"/> without using a <see cref="RuntimeAnimatorController"/>.
/// </summary>
/// <remarks>
/// This class can be used as a custom yield instruction to wait until all animations finish playing.
/// <para></para>
/// This class is mostly just a wrapper that connects an <see cref="AnimancerGraph"/> to an
/// <see cref="UnityEngine.Animator"/>.
/// <para></para>
/// <strong>Documentation:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/component-types">
/// Component Types</see>
/// </remarks>
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerComponent
///
[AddComponentMenu(Strings.MenuPrefix + "Animancer Component")]
[AnimancerHelpUrl(typeof(AnimancerComponent))]
[DefaultExecutionOrder(DefaultExecutionOrder)]
public class AnimancerComponent : MonoBehaviour,
IAnimancerComponent,
IEnumerator,
IAnimationClipSource,
IAnimationClipCollection
{
/************************************************************************************************************************/
#region Fields and Properties
/************************************************************************************************************************/
/// <summary>Initialize before anything else tries to use this component.</summary>
public const int DefaultExecutionOrder = -5000;
/************************************************************************************************************************/
[SerializeField]
[Tooltip("Animancer works by using Unity's Playables API to control an Animator component." +
"\n\nThe Animator's Controller field should be empty unless you intend to use it.")]
private Animator _Animator;
/// <summary>[<see cref="SerializeField"/>]
/// Animancer works by using Unity's Playables API to control an <see cref="UnityEngine.Animator"/> component.
/// </summary>
/// <remarks>
/// The <see cref="Animator.runtimeAnimatorController"/> should be empty unless you intend to use it.
/// </remarks>
public Animator Animator
{
get => _Animator;
set
{
_Animator = value;
if (IsGraphInitialized)
{
_Graph.DestroyOutput();
_Graph.CreateOutput(value, this);
}
}
}
#if UNITY_EDITOR
/// <inheritdoc/>
string IAnimancerComponent.AnimatorFieldName
=> nameof(_Animator);
#endif
/************************************************************************************************************************/
[SerializeField]
[Tooltip(Strings.ProOnlyTag + "An optional Transition Library" +
" which can modify the way Animancer transitions between animations.")]
private TransitionLibraryAsset _Transitions;
/// <summary>[<see cref="SerializeField"/>] [Pro-Only]
/// An optional <see cref="TransitionLibraryAsset"/>
/// which can modify the way Animancer transitions between animations.
/// </summary>
public TransitionLibraryAsset Transitions
{
get => _Transitions;
set
{
_Transitions = value;
if (IsGraphInitialized)
_Graph.Transitions = value?.Library;
}
}
/************************************************************************************************************************/
private AnimancerGraph _Graph;
/// <summary>
/// The internal system which manages the playing animations.
/// Accessing this property will automatically initialize it.
/// </summary>
public AnimancerGraph Graph
{
get
{
InitializeGraph();
return _Graph;
}
}
/// <summary>Has the <see cref="Graph"/> been initialized?</summary>
public bool IsGraphInitialized
=> _Graph != null
&& _Graph.IsValidOrDispose();
/************************************************************************************************************************/
/// <summary>The layers which each manage their own set of animations.</summary>
public AnimancerLayerList Layers
=> Graph.Layers;
/// <summary>The states managed by this component.</summary>
public AnimancerStateDictionary States
=> Graph.States;
/// <summary>Dynamic parameters which anything can get or set.</summary>
public ParameterDictionary Parameters
=> Graph.Parameters;
/// <summary>A dictionary of callbacks to be triggered by any event with a matching name.</summary>
public NamedEventDictionary Events
=> Graph.Events;
/************************************************************************************************************************/
/// <summary>Returns the <see cref="Graph"/>.</summary>
public static implicit operator AnimancerGraph(AnimancerComponent animancer)
=> animancer.Graph;
/// <summary>Returns layer 0.</summary>
public static implicit operator AnimancerLayer(AnimancerComponent animancer)
=> animancer.Graph.Layers[0];
/************************************************************************************************************************/
[SerializeField, Tooltip("Determines what happens when this component is disabled" +
" or its " + nameof(GameObject) + " becomes inactive (i.e. in OnDisable):" +
"\n• [" + nameof(DisableAction.Stop) + "] and reset all animations" +
"\n• [" + nameof(DisableAction.Pause) + "] to later resume from the current state" +
"\n• [" + nameof(DisableAction.Continue) + "] playing while inactive" +
"\n• [" + nameof(DisableAction.Reset) + "] to the original values" +
"\n• [" + nameof(DisableAction.Destroy) + "] all layers and states" +
"\n• If you're only destroying objects and not disabling them," +
" using " + nameof(DisableAction.Continue) + " is the most efficient" +
" because it avoids wasting performance stopping things that will be destroyed anyway.")]
private DisableAction _ActionOnDisable;
#if UNITY_EDITOR
/// <summary>[Editor-Only]
/// The name of the serialized backing field for the <see cref="ActionOnDisable"/> property.
/// </summary>
string IAnimancerComponent.ActionOnDisableFieldName
=> nameof(_ActionOnDisable);
#endif
/// <summary>[<see cref="SerializeField"/>]
/// Determines what happens when this component is disabled
/// or its <see cref="GameObject"/> becomes inactive
/// (i.e. in <see cref="OnDisable"/>).
/// </summary>
/// <remarks>
/// The default value is <see cref="DisableAction.Stop"/>.
/// <para></para>
/// If you're only destroying objects and not disabling them,
/// using <see cref="DisableAction.Continue"/> is the most efficient
/// because it avoids wasting performance stopping things that will be destroyed anyway.
/// </remarks>
public ref DisableAction ActionOnDisable
=> ref _ActionOnDisable;
/// <inheritdoc/>
bool IAnimancerComponent.ResetOnDisable
=> _ActionOnDisable == DisableAction.Reset;
/// <summary>
/// An action to perform when disabling an <see cref="AnimancerComponent"/>.
/// See <see cref="ActionOnDisable"/>.
/// </summary>
public enum DisableAction
{
/// <summary>
/// Stop and reset all animations, but leave all animated values as they are (unlike <see cref="Reset"/>).
/// </summary>
/// <remarks>Calls <see cref="Stop()"/> and <see cref="AnimancerGraph.PauseGraph"/>.</remarks>
Stop,
/// <summary>Pause to later resume from the current state.</summary>
/// <remarks>Calls <see cref="AnimancerGraph.PauseGraph"/>.</remarks>
Pause,
/// <summary>Keep playing while inactive.</summary>
Continue,
/// <summary>
/// Stop all animations, rewind them, and force the object back into its original state (often called the
/// bind pose).
/// </summary>
/// <remarks>
/// The <see cref="AnimancerComponent"/> must be either above the <see cref="UnityEngine.Animator"/> in
/// the Inspector or on a child object so that so that this <see cref="OnDisable"/> gets called first.
/// <para></para>
/// Calls <see cref="Stop()"/>, <see cref="Animator.Rebind"/>, and <see cref="AnimancerGraph.PauseGraph"/>.
/// </remarks>
Reset,
/// <summary>
/// Destroy the <see cref="PlayableGraph"/> and all its layers and states. This means that any layers or
/// states referenced by other scripts will no longer be valid so they will need to be recreated if you
/// want to use this object again.
/// </summary>
/// <remarks>Calls <see cref="AnimancerGraph.Destroy()"/>.</remarks>
Destroy,
}
/************************************************************************************************************************/
#region Update Mode
/************************************************************************************************************************/
/// <summary>
/// Determines when animations are updated and which time source is used. This property is mainly a wrapper
/// around the <see cref="Animator.updateMode"/>.
/// </summary>
/// <remarks>Note that changing to or from <see cref="AnimatorUpdateMode.AnimatePhysics"/> at runtime has no effect.</remarks>
/// <exception cref="NullReferenceException">No <see cref="Animator"/> is assigned.</exception>
public AnimatorUpdateMode UpdateMode
{
get => _Animator.updateMode;
set
{
_Animator.updateMode = value;
if (!IsGraphInitialized)
return;
// UnscaledTime on the Animator is actually identical to Normal when using the Playables API so we need
// to set the graph's DirectorUpdateMode to determine how it gets its delta time.
_Graph.UpdateMode = value == AnimatorUpdateMode.UnscaledTime ?
DirectorUpdateMode.UnscaledGameTime :
DirectorUpdateMode.GameTime;
#if UNITY_EDITOR
if (InitialUpdateMode == null)
{
InitialUpdateMode = value;
}
else if (UnityEditor.EditorApplication.isPlaying)
{
if (Editor.AnimancerGraphCleanup.HasChangedToOrFromAnimatePhysics(InitialUpdateMode, value))
Debug.LogWarning(
$"Changing the {nameof(Animator)}.{nameof(Animator.updateMode)} to or from " +
#if UNITY_2023_1_OR_NEWER
nameof(AnimatorUpdateMode.Fixed) +
#else
nameof(AnimatorUpdateMode.AnimatePhysics) +
#endif
" at runtime will have no effect." +
" You must set it in the Unity Editor or on startup.", this);
}
#endif
}
}
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <inheritdoc/>
public AnimatorUpdateMode? InitialUpdateMode { get; private set; }
#endif
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Initialization
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <summary>[Editor-Only]
/// Destroys the <see cref="Graph"/> if it was initialized and searches for an <see cref="Animator"/> on
/// this object, or it's children or parents.
/// </summary>
protected virtual void Reset()
{
OnDestroy();
gameObject.GetComponentInParentOrChildren(ref _Animator);
}
#endif
/************************************************************************************************************************/
/// <summary>Ensures that the <see cref="PlayableGraph"/> is playing.</summary>
protected virtual void OnEnable()
{
if (IsGraphInitialized)
{
_Graph.UnpauseGraph();
#if UNITY_EDITOR
AnimancerGraph.ClearInactiveInitializationStackTrace(this);
#endif
}
}
/// <summary>Acts according to the <see cref="ActionOnDisable"/>.</summary>
protected virtual void OnDisable()
{
if (!IsGraphInitialized)
return;
switch (_ActionOnDisable)
{
case DisableAction.Stop:
_Graph.Stop();
_Graph.PauseGraph();
break;
case DisableAction.Pause:
_Graph.PauseGraph();
break;
case DisableAction.Continue:
break;
case DisableAction.Reset:
Debug.Assert(_Animator.isActiveAndEnabled,
$"{nameof(DisableAction)}.{nameof(DisableAction.Reset)} failed because the {nameof(Animator)}" +
$" is not enabled. This most likely means you are disabling the {nameof(GameObject)} and the" +
$" {nameof(Animator)} is above the {nameof(AnimancerComponent)} in the Inspector so it got" +
$" disabled right before this method was called." +
$" See the Inspector of {this} to fix the issue" +
$" or use {nameof(DisableAction)}.{nameof(DisableAction.Stop)}" +
$" and call {nameof(Animator)}.{nameof(Animator.Rebind)} manually" +
$" before disabling the {nameof(GameObject)}.",
this);
_Graph.Stop();
_Animator.Rebind();
_Graph.PauseGraph();
break;
case DisableAction.Destroy:
_Graph.Destroy();
_Graph = null;
break;
default:
throw new ArgumentOutOfRangeException(nameof(ActionOnDisable));
}
}
/************************************************************************************************************************/
/// <summary>Creates and initializes the <see cref="Graph"/> if it wasn't already initialized.</summary>
public void InitializeGraph()
{
if (IsGraphInitialized)
return;
TryGetAnimator();
AnimancerGraph.SetNextGraphName(name + " (Animancer)");
_Graph = new(_Transitions?.Library);
_Graph.CreateOutput(_Animator, this);
OnInitializeGraph();
}
/************************************************************************************************************************/
/// <summary>Sets the <see cref="Graph"/> and connects it to the <see cref="Animator"/>.</summary>
/// <exception cref="InvalidOperationException">
/// The <see cref="AnimancerGraph"/> is already initialized.
/// You must call <see cref="AnimancerGraph.Destroy"/> before re-initializing it.
/// </exception>
public void InitializePlayable(AnimancerGraph graph)
{
if (IsGraphInitialized)
throw new InvalidOperationException(
$"The {nameof(AnimancerGraph)} is already initialized." +
$" Either call this method before anything else uses it or call" +
$" animancerComponent.{nameof(Graph)}.{nameof(AnimancerGraph.Destroy)}" +
$" before re-initializing it.");
TryGetAnimator();
_Graph = graph;
_Graph.Transitions = _Transitions?.Library;
_Graph.CreateOutput(_Animator, this);
OnInitializeGraph();
}
/************************************************************************************************************************/
/// <summary>Called right after the <see cref="Graph"/> is initialized.</summary>
protected virtual void OnInitializeGraph()
{
#if UNITY_ASSERTIONS
ValidateGraphInitialization();
#endif
}
/************************************************************************************************************************/
/// <summary>
/// Tries to ensure that an <see cref="Animator"/> is present using
/// <see cref="Component.TryGetComponent{T}(out T)"/> if necessary.
/// </summary>
public bool TryGetAnimator()
=> _Animator != null
|| TryGetComponent(out _Animator);
/************************************************************************************************************************/
#if UNITY_ASSERTIONS
/// <summary>[Assert-Only]
/// Validates various conditions relating to <see cref="AnimancerGraph"/> initialization.
/// </summary>
private void ValidateGraphInitialization()
{
#if UNITY_EDITOR
if (_Animator != null)
InitialUpdateMode = UpdateMode;
#if UNITY_IMGUI
if (OptionalWarning.CreateGraphDuringGuiEvent.IsEnabled())
{
var currentEvent = Event.current;
if (currentEvent != null)
{
var eventType = currentEvent.type;
if (eventType == EventType.Layout ||
eventType == EventType.Repaint)
{
OptionalWarning.CreateGraphDuringGuiEvent.Log(
$"An {nameof(AnimancerGraph)} is being initialized" +
$" during a {eventType} event which is likely undesirable.",
this);
}
}
}
#endif
#endif
if (_Animator != null)
{
if (!_Animator.enabled)
OptionalWarning.AnimatorDisabled.Log(Strings.AnimatorDisabledMessage, this);
if (_Animator.isHuman &&
_Animator.runtimeAnimatorController != null)
OptionalWarning.NativeControllerHumanoid.Log(
$"An Animator Controller is assigned to the {nameof(Animator)} component" +
$" but the Rig is Humanoid so it can't be blended with Animancer." +
$" See the documentation for more information: {Strings.DocsURLs.AnimatorControllersNative}",
this);
}
}
#endif
/************************************************************************************************************************/
/// <summary>Ensures that the <see cref="Graph"/> is properly cleaned up.</summary>
protected virtual void OnDestroy()
{
if (IsGraphInitialized)
{
_Graph.Destroy();
_Graph = null;
}
}
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <summary>[Editor-Only]
/// Ensures that the <see cref="AnimancerGraph"/> is destroyed in Edit Mode, but not in Play Mode since we want
/// to let Unity complain if that happens.
/// </summary>
~AnimancerComponent()
{
if (_Graph != null)
{
UnityEditor.EditorApplication.delayCall += () =>
{
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
OnDestroy();
};
}
}
#endif
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Play Management
/************************************************************************************************************************/
/// <summary>Returns the `clip` itself.</summary>
/// <remarks>
/// This method is used to determine the dictionary key to use for an animation when none is specified by the
/// caller, such as in <see cref="Play(AnimationClip)"/>.
/// </remarks>
public virtual object GetKey(AnimationClip clip)
=> clip;
/************************************************************************************************************************/
// Play Immediately.
/************************************************************************************************************************/
/// <summary>Stops all other animations on the same layer, plays the `clip`, and returns its state.</summary>
/// <remarks>
/// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
/// To restart it from the beginning you can use <c>...Play(clip).Time = 0;</c>.
/// <para></para>
/// This method is safe to call repeatedly without checking whether the `clip` was already playing.
/// </remarks>
public AnimancerState Play(AnimationClip clip)
=> Graph.Layers[0].Play(States.GetOrCreate(clip));
/// <summary>Stops all other animations on the same layer, plays the `state`, and returns it.</summary>
/// <remarks>
/// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
/// To restart it from the beginning you can use <c>...Play(state).Time = 0;</c>.
/// <para></para>
/// This method is safe to call repeatedly without checking whether the `state` was already playing.
/// </remarks>
public AnimancerState Play(AnimancerState state)
=> Graph.Layers[0].Play(state);
/************************************************************************************************************************/
// Cross Fade.
/************************************************************************************************************************/
/// <summary>
/// Starts fading in the `clip` while fading out all other states in the same layer over the course of the
/// `fadeDuration`. Returns its state.
/// </summary>
/// <remarks>
/// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
/// method will allow it to complete the existing fade rather than starting a slower one.
/// <para></para>
/// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
/// and simply <see cref="AnimancerState.Play"/> the `state`.
/// <para></para>
/// This method is safe to call repeatedly without checking whether the `clip` was already playing.
/// <para></para>
/// <em>Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
/// </remarks>
public AnimancerState Play(AnimationClip clip, float fadeDuration, FadeMode mode = default)
=> Graph.Layers[0].Play(States.GetOrCreate(clip), fadeDuration, mode);
/// <summary>
/// Starts fading in the `state` while fading out all others in the same layer over the course of the
/// `fadeDuration`. Returns the `state`.
/// </summary>
/// <remarks>
/// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
/// method will allow it to complete the existing fade rather than starting a slower one.
/// <para></para>
/// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
/// and simply <see cref="AnimancerState.Play"/> the `state`.
/// <para></para>
/// This method is safe to call repeatedly without checking whether the `state` was already playing.
/// <para></para>
/// <em>Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
/// </remarks>
public AnimancerState Play(AnimancerState state, float fadeDuration, FadeMode mode = default)
=> Graph.Layers[0].Play(state, fadeDuration, mode);
/************************************************************************************************************************/
// Transition.
/************************************************************************************************************************/
/// <summary>
/// Creates a state for the `transition` if it didn't already exist, then calls
/// <see cref="Play(AnimancerState)"/> or <see cref="Play(AnimancerState, float, FadeMode)"/>
/// depending on <see cref="ITransition.CrossFadeFromStart"/>.
/// </summary>
/// <remarks>
/// This method is safe to call repeatedly without checking whether the `transition` was already playing.
/// </remarks>
public AnimancerState Play(ITransition transition)
=> Graph.Layers[0].Play(transition);
/// <summary>
/// Creates a state for the `transition` if it didn't already exist, then calls
/// <see cref="Play(AnimancerState)"/> or <see cref="Play(AnimancerState, float, FadeMode)"/>
/// depending on <see cref="ITransition.CrossFadeFromStart"/>.
/// </summary>
/// <remarks>
/// This method is safe to call repeatedly without checking whether the `transition` was already playing.
/// </remarks>
public AnimancerState Play(ITransition transition, float fadeDuration, FadeMode mode = default)
=> Graph.Layers[0].Play(transition, fadeDuration, mode);
/************************************************************************************************************************/
// Try Play.
/************************************************************************************************************************/
/// <summary>
/// Stops all other animations on the base layer,
/// plays the animation registered with the `key`,
/// and returns the animation's state.
/// </summary>
/// <remarks>
/// If no state is registered with the `key`, this method does nothing and returns null.
/// <para></para>
/// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
/// To restart it from the beginning you can simply set the returned state's time to 0.
/// <para></para>
/// This method is safe to call repeatedly without checking whether the animation was already playing.
/// </remarks>
/// <exception cref="ArgumentNullException">The `key` is null.</exception>
public AnimancerState TryPlay(object key)
=> Graph.Layers[0].TryPlay(key);
/// <summary>
/// Stops all other animations on the base layer,
/// plays the animation registered with the `key`,
/// and returns the animation's state.
/// </summary>
/// <remarks>
/// If no state is registered with the `key`, this method does nothing and returns null.
/// <para></para>
/// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
/// To restart it from the beginning you can simply set the returned state's time to 0.
/// <para></para>
/// This method is safe to call repeatedly without checking whether the animation was already playing.
/// </remarks>
public AnimancerState TryPlay(
IHasKey hasKey)
=> TryPlay(hasKey.Key);
/// <summary>
/// Starts fading in the animation registered with the `key` while fading out all others in the same layer
/// over the course of the `fadeDuration`. Or if no state is registered with that `key`, this method does
/// nothing and returns null.
/// </summary>
/// <remarks>
/// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
/// method will allow it to complete the existing fade rather than starting a slower one.
/// <para></para>
/// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
/// and simply <see cref="AnimancerState.Play"/> the `state`.
/// <para></para>
/// This method is safe to call repeatedly without checking whether the animation was already playing.
/// <para></para>
/// <em>Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
/// </remarks>
/// <exception cref="ArgumentNullException">The `key` is null.</exception>
public AnimancerState TryPlay(object key, float fadeDuration, FadeMode mode = default)
=> Graph.Layers[0].TryPlay(key, fadeDuration, mode);
/// <summary>
/// Starts fading in the animation registered with the `key`
/// while fading out all others in the same layer over the course of the `fadeDuration`
/// and returns the animation's state.
/// </summary>
/// <remarks>
/// If no state is registered with the `key`, this method does nothing and returns null.
/// <para></para>
/// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`,
/// this method allows it to continue the existing fade rather than starting a slower one.
/// <para></para>
/// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will
/// fade in the layer itself and simply <see cref="AnimancerState.Play"/> the `state`.
/// <para></para>
/// This method is safe to call repeatedly without checking whether the animation was already playing.
/// <para></para>
/// <em>Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
/// </remarks>
public AnimancerState TryPlay(
IHasKey hasKey,
float fadeDuration,
FadeMode mode = default)
=> TryPlay(hasKey.Key, fadeDuration, mode);
/************************************************************************************************************************/
/// <summary>
/// Gets the state associated with the `clip`, stops and rewinds it to the start, then returns it.
/// </summary>
public AnimancerState Stop(AnimationClip clip)
=> Stop(GetKey(clip));
/// <summary>
/// Gets the state registered with the <see cref="IHasKey.Key"/>, stops and rewinds it to the start, then
/// returns it.
/// </summary>
public AnimancerState Stop(IHasKey hasKey)
=> _Graph?.Stop(hasKey);
/// <summary>
/// Gets the state associated with the `key`, stops and rewinds it to the start, then returns it.
/// </summary>
public AnimancerState Stop(object key)
=> _Graph?.Stop(key);
/// <summary>Stops all animations and rewinds them to the start.</summary>
public void Stop()
{
if (IsGraphInitialized)
_Graph.Stop();
}
/************************************************************************************************************************/
/// <summary>
/// Returns true if a state is registered for the `clip` and it is currently playing.
/// <para></para>
/// The actual dictionary key is determined using <see cref="GetKey"/>.
/// </summary>
public bool IsPlaying(AnimationClip clip)
=> IsPlaying(GetKey(clip));
/// <summary>
/// Returns true if a state is registered with the <see cref="IHasKey.Key"/> and it is currently playing.
/// </summary>
public bool IsPlaying(IHasKey hasKey)
=> IsGraphInitialized
&& _Graph.IsPlaying(hasKey);
/// <summary>
/// Returns true if a state is registered with the `key` and it is currently playing.
/// </summary>
public bool IsPlaying(object key)
=> IsGraphInitialized
&& _Graph.IsPlaying(key);
/// <summary>
/// Returns true if at least one animation is being played.
/// </summary>
public bool IsPlaying()
=> IsGraphInitialized
&& _Graph.IsPlaying();
/************************************************************************************************************************/
/// <summary>
/// Returns true if the `clip` is currently being played by at least one state.
/// <para></para>
/// This method is inefficient because it searches through every state to find any that are playing the `clip`,
/// unlike <see cref="IsPlaying(AnimationClip)"/> which only checks the state registered using the `clip`s key.
/// </summary>
public bool IsPlayingClip(AnimationClip clip)
=> IsGraphInitialized
&& _Graph.IsPlayingClip(clip);
/************************************************************************************************************************/
/// <summary>
/// Immediately applies the current states of all animations to the animated objects.
/// </summary>
public void Evaluate()
=> Graph.Evaluate();
/// <summary>
/// Advances time by the specified value (in seconds)
/// and immediately applies the current states of all animations to the animated objects.
/// </summary>
public void Evaluate(float deltaTime)
=> Graph.Evaluate(deltaTime);
/************************************************************************************************************************/
#region Key Error Methods
#if UNITY_EDITOR
/************************************************************************************************************************/
// These are overloads of other methods that take a System.Object key to ensure the user doesn't try to use an
// AnimancerState as a key, since the whole point of a key is to identify a state in the first place.
/************************************************************************************************************************/
/// <summary>[Warning]
/// You should not use an <see cref="AnimancerState"/> as a key.
/// Just call <see cref="AnimancerState.Stop"/>.
/// </summary>
[Obsolete("You should not use an AnimancerState as a key. Just call AnimancerState.Stop().", true)]
public AnimancerState Stop(AnimancerState key)
{
key.Stop();
return key;
}
/// <summary>[Warning]
/// You should not use an <see cref="AnimancerState"/> as a key.
/// Just check <see cref="AnimancerState.IsPlaying"/>.
/// </summary>
[Obsolete("You should not use an AnimancerState as a key. Just check AnimancerState.IsPlaying.", true)]
public bool IsPlaying(AnimancerState key)
=> key.IsPlaying;
/************************************************************************************************************************/
#endif
#endregion
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Enumeration
/************************************************************************************************************************/
// IEnumerator for yielding in a coroutine to wait until all animations have stopped.
/************************************************************************************************************************/
/// <summary>Are any animations are still playing?</summary>
/// <remarks>This allows this object to be used as a custom yield instruction.</remarks>
bool IEnumerator.MoveNext()
=> IsGraphInitialized
&& ((IEnumerator)_Graph).MoveNext();
/// <summary>Returns null.</summary>
object IEnumerator.Current
=> null;
/// <summary>Does nothing.</summary>
void IEnumerator.Reset()
{ }
/************************************************************************************************************************/
/// <summary>[<see cref="IAnimationClipSource"/>]
/// Calls <see cref="GatherAnimationClips(ICollection{AnimationClip})"/>.
/// </summary>
public void GetAnimationClips(List<AnimationClip> clips)
{
var set = SetPool.Acquire<AnimationClip>();
set.UnionWith(clips);
GatherAnimationClips(set);
clips.Clear();
clips.AddRange(set);
SetPool.Release(set);
}
/************************************************************************************************************************/
/// <summary>[<see cref="IAnimationClipCollection"/>]
/// Gathers all the animations in the <see cref="Graph"/>.
/// <para></para>
/// In the Unity Editor this method also gathers animations from other components on parent and child objects.
/// </summary>
public virtual void GatherAnimationClips(ICollection<AnimationClip> clips)
{
if (IsGraphInitialized)
_Graph.GatherAnimationClips(clips);
#if UNITY_EDITOR
Editor.AnimationGatherer.GatherFromGameObject(gameObject, clips);
if (_Animator != null && _Animator.gameObject != gameObject)
Editor.AnimationGatherer.GatherFromGameObject(_Animator.gameObject, clips);
#endif
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}