You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
727 lines
35 KiB
727 lines
35 KiB
// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 Kybernetik // |
|
|
|
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="AnimancerPlayable"/> to an |
|
/// <see cref="UnityEngine.Animator"/>. |
|
/// <para></para> |
|
/// Documentation: <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")] |
|
[HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(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("The Animator component which this script controls")] |
|
private Animator _Animator; |
|
|
|
/// <summary>[<see cref="SerializeField"/>] |
|
/// The <see cref="UnityEngine.Animator"/> component which this script controls. |
|
/// </summary> |
|
public Animator Animator |
|
{ |
|
get => _Animator; |
|
set |
|
{ |
|
_Animator = value; |
|
if (IsPlayableInitialized) |
|
{ |
|
_Playable.DestroyOutput(); |
|
_Playable.CreateOutput(value, this); |
|
} |
|
} |
|
} |
|
|
|
#if UNITY_EDITOR |
|
/// <summary>[Editor-Only] The name of the serialized backing field for the <see cref="Animator"/> property.</summary> |
|
string IAnimancerComponent.AnimatorFieldName => nameof(_Animator); |
|
#endif |
|
|
|
/************************************************************************************************************************/ |
|
|
|
private AnimancerPlayable _Playable; |
|
|
|
/// <summary> |
|
/// The internal system which manages the playing animations. |
|
/// Accessing this property will automatically initialize it. |
|
/// </summary> |
|
public AnimancerPlayable Playable |
|
{ |
|
get |
|
{ |
|
InitializePlayable(); |
|
return _Playable; |
|
} |
|
} |
|
|
|
/// <summary>Indicates whether the <see cref="Playable"/> has been initialized.</summary> |
|
public bool IsPlayableInitialized => _Playable != null && _Playable.IsValid; |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>The states managed by this component.</summary> |
|
public AnimancerPlayable.StateDictionary States => Playable.States; |
|
|
|
/// <summary>The layers which each manage their own set of animations.</summary> |
|
public AnimancerPlayable.LayerList Layers => Playable.Layers; |
|
|
|
/// <summary>Returns the <see cref="Playable"/>.</summary> |
|
public static implicit operator AnimancerPlayable(AnimancerComponent animancer) => animancer.Playable; |
|
|
|
/// <summary>Returns layer 0.</summary> |
|
public static implicit operator AnimancerLayer(AnimancerComponent animancer) => animancer.Playable.Layers[0]; |
|
|
|
/************************************************************************************************************************/ |
|
|
|
[SerializeField, Tooltip("Determines what happens when this component is disabled" + |
|
" or its " + nameof(GameObject) + " becomes inactive (i.e. in " + nameof(OnDisable) + "):" + |
|
"\n• " + nameof(DisableAction.Stop) + " all animations" + |
|
"\n• " + nameof(DisableAction.Pause) + " all animations" + |
|
"\n• " + nameof(DisableAction.Continue) + " playing" + |
|
"\n• " + nameof(DisableAction.Reset) + " to the original values" + |
|
"\n• " + nameof(DisableAction.Destroy) + " all layers and states")] |
|
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"/>.</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 all animations and rewind them, but leave all animated values as they are (unlike |
|
/// <see cref="Reset"/>). |
|
/// </summary> |
|
/// <remarks>Calls <see cref="Stop()"/> and <see cref="AnimancerPlayable.PauseGraph"/>.</remarks> |
|
Stop, |
|
|
|
/// <summary>Pause all animations in their current state so they can resume later.</summary> |
|
/// <remarks>Calls <see cref="AnimancerPlayable.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="AnimancerPlayable.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="AnimancerPlayable.DestroyGraph()"/>.</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 (!IsPlayableInitialized) |
|
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. |
|
_Playable.UpdateMode = value == AnimatorUpdateMode.UnscaledTime ? |
|
DirectorUpdateMode.UnscaledGameTime : |
|
DirectorUpdateMode.GameTime; |
|
|
|
#if UNITY_EDITOR |
|
if (InitialUpdateMode == null) |
|
{ |
|
InitialUpdateMode = value; |
|
} |
|
else if (UnityEditor.EditorApplication.isPlaying) |
|
{ |
|
if (AnimancerPlayable.HasChangedToOrFromAnimatePhysics(InitialUpdateMode, value)) |
|
Debug.LogWarning($"Changing the {nameof(Animator)}.{nameof(Animator.updateMode)}" + |
|
$" to or from {nameof(AnimatorUpdateMode.AnimatePhysics)} 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="Playable"/> if it was initialized and searches for an <see cref="Animator"/> on |
|
/// this object, or it's children or parents. |
|
/// </summary> |
|
/// <remarks> |
|
/// Called by the Unity Editor when this component is first added (in Edit Mode) and whenever the Reset command |
|
/// is executed from its context menu. |
|
/// </remarks> |
|
protected virtual void Reset() |
|
{ |
|
OnDestroy(); |
|
gameObject.GetComponentInParentOrChildren(ref _Animator); |
|
} |
|
#endif |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Ensures that the <see cref="PlayableGraph"/> is playing.</summary> |
|
/// <remarks>Called by Unity when this component becomes enabled and active.</remarks> |
|
protected virtual void OnEnable() |
|
{ |
|
if (IsPlayableInitialized) |
|
_Playable.UnpauseGraph(); |
|
} |
|
|
|
/// <summary>Acts according to the <see cref="ActionOnDisable"/>.</summary> |
|
/// <remarks>Called by Unity when this component becomes disabled or inactive.</remarks> |
|
protected virtual void OnDisable() |
|
{ |
|
if (!IsPlayableInitialized) |
|
return; |
|
|
|
switch (_ActionOnDisable) |
|
{ |
|
case DisableAction.Stop: |
|
Stop(); |
|
_Playable.PauseGraph(); |
|
break; |
|
|
|
case DisableAction.Pause: |
|
_Playable.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); |
|
|
|
Stop(); |
|
_Animator.Rebind(); |
|
_Playable.PauseGraph(); |
|
break; |
|
|
|
case DisableAction.Destroy: |
|
_Playable.DestroyGraph(); |
|
_Playable = null; |
|
break; |
|
|
|
default: |
|
throw new ArgumentOutOfRangeException(nameof(ActionOnDisable)); |
|
} |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Creates a new <see cref="AnimancerPlayable"/> if it doesn't already exist.</summary> |
|
public void InitializePlayable() |
|
{ |
|
if (IsPlayableInitialized) |
|
return; |
|
|
|
if (_Animator == null) |
|
_Animator = GetComponent<Animator>(); |
|
|
|
#if UNITY_ASSERTIONS |
|
ValidatePlayableInitialization(); |
|
#endif |
|
|
|
AnimancerPlayable.SetNextGraphName(name + " (Animancer)"); |
|
_Playable = AnimancerPlayable.Create(); |
|
_Playable.CreateOutput(_Animator, this); |
|
|
|
#if UNITY_EDITOR |
|
if (_Animator != null) |
|
InitialUpdateMode = UpdateMode; |
|
#endif |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Creates a new <see cref="AnimancerPlayable"/> in the specified `graph`.</summary> |
|
/// <exception cref="InvalidOperationException"> |
|
/// The <see cref="AnimancerPlayable"/> is already initialized. |
|
/// You must call <see cref="AnimancerPlayable.DestroyGraph"/> before re-initializing it. |
|
/// </exception> |
|
public void InitializePlayable(PlayableGraph graph) |
|
{ |
|
if (IsPlayableInitialized) |
|
throw new InvalidOperationException($"The {nameof(AnimancerPlayable)} is already initialized." + |
|
$" Either call this method before anything else uses it or call" + |
|
$" animancerComponent.{nameof(Playable)}.{nameof(AnimancerPlayable.DestroyGraph)} before re-initializing it."); |
|
|
|
if (_Animator == null) |
|
_Animator = GetComponent<Animator>(); |
|
|
|
#if UNITY_ASSERTIONS |
|
ValidatePlayableInitialization(); |
|
#endif |
|
|
|
_Playable = AnimancerPlayable.Create(graph); |
|
_Playable.CreateOutput(_Animator, this); |
|
|
|
#if UNITY_EDITOR |
|
if (_Animator != null) |
|
InitialUpdateMode = UpdateMode; |
|
#endif |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
#if UNITY_ASSERTIONS |
|
/// <summary>Validates various conditions relating to <see cref="AnimancerPlayable"/> initialization.</summary> |
|
private void ValidatePlayableInitialization() |
|
{ |
|
#if UNITY_EDITOR |
|
if (OptionalWarning.CreateGraphDuringGuiEvent.IsEnabled()) |
|
{ |
|
var currentEvent = Event.current; |
|
if (currentEvent != null && (currentEvent.type == EventType.Layout || currentEvent.type == EventType.Repaint)) |
|
OptionalWarning.CreateGraphDuringGuiEvent.Log( |
|
$"An {nameof(AnimancerPlayable)} is being created during a {currentEvent.type} event" + |
|
$" which is likely undesirable.", this); |
|
} |
|
|
|
if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) |
|
#endif |
|
{ |
|
if (!gameObject.activeInHierarchy) |
|
OptionalWarning.CreateGraphWhileDisabled.Log($"An {nameof(AnimancerPlayable)} is being created for '{this}'" + |
|
$" which is attached to an inactive {nameof(GameObject)}." + |
|
$" If that object is never activated then Unity will not call {nameof(OnDestroy)}" + |
|
$" so {nameof(AnimancerPlayable)}.{nameof(AnimancerPlayable.DestroyGraph)} will need to be called manually.", this); |
|
} |
|
|
|
if (_Animator != null && _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="Playable"/> is properly cleaned up.</summary> |
|
/// <remarks>Called by Unity when this component is destroyed.</remarks> |
|
protected virtual void OnDestroy() |
|
{ |
|
if (IsPlayableInitialized) |
|
{ |
|
_Playable.DestroyGraph(); |
|
_Playable = null; |
|
} |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
#if UNITY_EDITOR |
|
/// <summary>[Editor-Only] |
|
/// Ensures that the <see cref="AnimancerPlayable"/> is destroyed in Edit Mode, but not in Play Mode since we want |
|
/// to let Unity complain if that happens. |
|
/// </summary> |
|
~AnimancerComponent() |
|
{ |
|
if (_Playable != null) |
|
{ |
|
UnityEditor.EditorApplication.delayCall += () => |
|
{ |
|
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) |
|
OnDestroy(); |
|
}; |
|
} |
|
} |
|
#endif |
|
|
|
/************************************************************************************************************************/ |
|
#endregion |
|
/************************************************************************************************************************/ |
|
#region Play Management |
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Returns the `clip` itself. This method is used to determine the dictionary key to use for an animation |
|
/// when none is specified by the user, such as in <see cref="Play(AnimationClip)"/>. It can be overridden by |
|
/// child classes to use something else as the key. |
|
/// </summary> |
|
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) |
|
=> Playable.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) |
|
=> Playable.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) |
|
=> Playable.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) |
|
=> Playable.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) |
|
=> Playable.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) |
|
=> Playable.Play(transition, fadeDuration, mode); |
|
|
|
/************************************************************************************************************************/ |
|
// Try Play. |
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Stops all other animations on the same layer, plays the animation registered with the `key`, and returns |
|
/// that state. Or if no state is registered with that `key`, this method does nothing and returns null. |
|
/// </summary> |
|
/// <remarks> |
|
/// The animation will continue playing from its current <see cref="AnimancerState.Time"/>. |
|
/// If you wish to force it back to the start, 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) |
|
=> Playable.TryPlay(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) |
|
=> Playable.TryPlay(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) => _Playable?.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) => _Playable?.Stop(key); |
|
|
|
/// <summary> |
|
/// Stops all animations and rewinds them to the start. |
|
/// </summary> |
|
public void Stop() |
|
{ |
|
if (_Playable != null) |
|
_Playable.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) => _Playable != null && _Playable.IsPlaying(hasKey); |
|
|
|
/// <summary> |
|
/// Returns true if a state is registered with the `key` and it is currently playing. |
|
/// </summary> |
|
public bool IsPlaying(object key) => _Playable != null && _Playable.IsPlaying(key); |
|
|
|
/// <summary> |
|
/// Returns true if at least one animation is being played. |
|
/// </summary> |
|
public bool IsPlaying() => _Playable != null && _Playable.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) => _Playable != null && _Playable.IsPlayingClip(clip); |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Evaluates all of the currently playing animations to apply their states to the animated objects. |
|
/// </summary> |
|
public void Evaluate() => Playable.Evaluate(); |
|
|
|
/// <summary> |
|
/// Advances all currently playing animations by the specified amount of time (in seconds) and evaluates the |
|
/// graph to apply their states to the animated objects. |
|
/// </summary> |
|
public void Evaluate(float deltaTime) => Playable.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> |
|
/// Determines if any animations are still playing so this object can be used as a custom yield instruction. |
|
/// </summary> |
|
bool IEnumerator.MoveNext() |
|
{ |
|
if (!IsPlayableInitialized) |
|
return false; |
|
|
|
return ((IEnumerator)_Playable).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 = ObjectPool.AcquireSet<AnimationClip>(); |
|
set.UnionWith(clips); |
|
|
|
GatherAnimationClips(set); |
|
|
|
clips.Clear(); |
|
clips.AddRange(set); |
|
|
|
ObjectPool.Release(set); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>[<see cref="IAnimationClipCollection"/>] |
|
/// Gathers all the animations in the <see cref="Playable"/>. |
|
/// <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 (IsPlayableInitialized) |
|
_Playable.GatherAnimationClips(clips); |
|
|
|
#if UNITY_EDITOR |
|
Editor.AnimationGatherer.GatherFromGameObject(gameObject, clips); |
|
|
|
if (_Animator != null && _Animator.gameObject != gameObject) |
|
Editor.AnimationGatherer.GatherFromGameObject(_Animator.gameObject, clips); |
|
#endif |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
#endregion |
|
/************************************************************************************************************************/ |
|
} |
|
}
|
|
|