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