123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- using Animancer.Units;
- using System;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Animations;
- using UnityEngine.Playables;
- namespace Animancer
- {
- /// <summary>Plays a single <see cref="AnimationClip"/>.</summary>
- ///
- /// <remarks>
- /// <para></para>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/component-types">
- /// Component Types</see>
- /// <para></para>
- /// <strong>Sample:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/samples/fine-control/doors">Doors</see>
- /// </remarks>
- ///
- /// https://kybernetik.com.au/animancer/api/Animancer/SoloAnimation
- ///
- [AddComponentMenu(Strings.MenuPrefix + "Solo Animation")]
- [AnimancerHelpUrl(typeof(SoloAnimation))]
- [DefaultExecutionOrder(DefaultExecutionOrder)]
- public class SoloAnimation : MonoBehaviour, IAnimationClipSource
- {
- /************************************************************************************************************************/
- #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>
- /// <remarks>
- /// If you need to set this value at runtime you are likely better off using a proper
- /// <see cref="AnimancerComponent"/>.
- /// </remarks>
- public Animator Animator
- {
- get => _Animator;
- set
- {
- _Animator = value;
- if (IsInitialized)
- Play();
- }
- }
- /************************************************************************************************************************/
- [SerializeField, Tooltip("The animation that will be played")]
- private AnimationClip _Clip;
- /// <summary>[<see cref="SerializeField"/>] The <see cref="AnimationClip"/> that will be played.</summary>
- /// <remarks>
- /// If you need to set this value at runtime you are likely better off using a proper
- /// <see cref="AnimancerComponent"/>.
- /// </remarks>
- public AnimationClip Clip
- {
- get => _Clip;
- set
- {
- _Clip = value;
- if (IsInitialized)
- Play();
- }
- }
- /// <summary><see cref="AnimationClip.length"/></summary>
- /// <remarks>
- /// This value is cached on startup
- /// and is <see cref="float.NaN"/> if there's no <see cref="Clip"/>.
- /// </remarks>
- public float Length { get; private set; } = float.NaN;
- /// <summary><see cref="Motion.isLooping"/></summary>
- /// <remarks>This value is cached on startup.</remarks>
- public bool IsLooping { get; private set; }
- /************************************************************************************************************************/
- /// <summary>
- /// Should disabling this object stop and rewind the animation?
- /// Otherwise, it will simply be paused and will resume from its current state when re-enabled.
- /// </summary>
- /// <remarks>
- /// The default value is true.
- /// <para></para>
- /// This property inverts <see cref="Animator.keepAnimatorStateOnDisable"/>
- /// and is serialized by the <see cref="UnityEngine.Animator"/>.
- /// </remarks>
- public bool StopOnDisable
- {
- get => !_Animator.keepAnimatorStateOnDisable;
- set => _Animator.keepAnimatorStateOnDisable = !value;
- }
- /************************************************************************************************************************/
- /// <summary>The <see cref="PlayableGraph"/> being used to play the <see cref="Clip"/>.</summary>
- private PlayableGraph _Graph;
- /// <summary>The <see cref="AnimationClipPlayable"/> being used to play the <see cref="Clip"/>.</summary>
- private AnimationClipPlayable _Playable;
- /************************************************************************************************************************/
- private bool _IsPlaying;
- /// <summary>Is the animation playing (true) or paused (false)?</summary>
- public bool IsPlaying
- {
- get => _IsPlaying;
- set
- {
- _IsPlaying = value;
- if (value)
- {
- if (!IsInitialized)
- {
- Play();
- }
- else
- {
- _Graph.Play();
- #if UNITY_EDITOR
- // In Edit Mode, unpausing the graph doesn't work properly unless we force it to change.
- if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
- _Graph.Evaluate(0.00001f);
- #endif
- }
- }
- else
- {
- if (IsInitialized)
- _Graph.Stop();
- }
- }
- }
- /************************************************************************************************************************/
- [SerializeField, Range(0, 1)]
- [Tooltip("The normalized time that the animation will start at")]
- private float _NormalizedStartTime;
- /// <summary>[<see cref="SerializeField"/>] The normalized time that the animation will start at.</summary>
- public float NormalizedStartTime
- {
- get => _NormalizedStartTime;
- set => _NormalizedStartTime = value;
- }
- /************************************************************************************************************************/
- /// <summary>[<see cref="SerializeField"/>]
- /// The number of seconds that have passed since the start of the animation.
- /// </summary>
- /// <remarks>
- /// This value will continue increasing after the animation passes the end of its length
- /// and it will either freeze in place or start again from the beginning according to
- /// whether it's looping or not.
- /// </remarks>
- public float Time
- {
- get => _Playable.IsValid()
- ? (float)_Playable.GetTime()
- : _NormalizedStartTime * Length;
- set
- {
- if (_Playable.IsValid())
- SetTime(value);
- }
- }
- /// <summary>
- /// Calls <see cref="PlayableExtensions.SetTime{U}"/> twice
- /// to ensure that animation events aren't triggered incorrectly.
- /// </summary>
- private void SetTime(double value)
- {
- _Playable.SetTime(value);
- _Playable.SetTime(value);
- }
- /// <summary>[<see cref="SerializeField"/>]
- /// The <see cref="Time"/> of this state as a portion of the <see cref="AnimationClip.length"/>,
- /// meaning the value goes from 0 to 1 as it plays from start to end,
- /// regardless of how long that actually takes.
- /// </summary>
- /// <remarks>
- /// This value will continue increasing after the animation passes the end of its length
- /// and it will either freeze in place or start again from the beginning according to
- /// whether it's looping or not.
- /// <para></para>
- /// The fractional part of the value (<c>NormalizedTime % 1</c>) is the percentage (0-1)
- /// of progress in the current loop while the integer part (<c>(int)NormalizedTime</c>)
- /// is the number of times the animation has been looped.
- /// </remarks>
- public float NormalizedTime
- {
- get => _Playable.IsValid()
- ? (float)_Playable.GetTime() / Length
- : _NormalizedStartTime;
- set
- {
- if (_Playable.IsValid())
- SetTime(value * Length);
- }
- }
- /************************************************************************************************************************/
- [SerializeField, Multiplier, Tooltip("The speed at which the animation plays (default 1)")]
- private float _Speed = 1;
- /// <summary>[<see cref="SerializeField"/>] The speed at which the animation is playing (default 1).</summary>
- /// <exception cref="ArgumentException">This component is not yet <see cref="IsInitialized"/>.</exception>
- public float Speed
- {
- get => _Speed;
- set
- {
- _Speed = value;
- if (_Playable.IsValid())
- _Playable.SetSpeed(value);
- }
- }
- /************************************************************************************************************************/
- /// <summary>Indicates whether the <see cref="PlayableGraph"/> is valid.</summary>
- public bool IsInitialized
- => _Graph.IsValid();
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #if UNITY_EDITOR
- /************************************************************************************************************************/
- [SerializeField, Tooltip("Should the " + nameof(Clip) + " be automatically applied to the object in Edit Mode?")]
- private bool _ApplyInEditMode;
- /// <summary>[Editor-Only] Should the <see cref="Clip"/> be automatically applied to the object in Edit Mode?</summary>
- public ref bool ApplyInEditMode
- => ref _ApplyInEditMode;
- /************************************************************************************************************************/
- /// <summary>[Editor-Only]
- /// Tries to find an <see cref="UnityEngine.Animator"/> component on this <see cref="GameObject"/> or its
- /// children or parents (in that order).
- /// </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()
- {
- gameObject.GetComponentInParentOrChildren(ref _Animator);
- }
- /************************************************************************************************************************/
- /// <summary>[Editor-Only]
- /// Applies the <see cref="Speed"/>, <see cref="FootIK"/>, and <see cref="ApplyInEditMode"/>.
- /// </summary>
- /// <remarks>Called in Edit Mode whenever this script is loaded or a value is changed in the Inspector.</remarks>
- protected virtual void OnValidate()
- {
- if (!UnityEditor.EditorApplication.isPlaying)
- {
- if (_ApplyInEditMode && enabled)
- {
- if (!IsInitialized)
- {
- Play();
- IsPlaying = false;
- _Graph.Evaluate();
- }
- if (NormalizedTime != _NormalizedStartTime)
- {
- NormalizedTime = _NormalizedStartTime;
- _Graph.Evaluate();
- }
- }
- else
- {
- if (IsInitialized)
- _Graph.Destroy();
- }
- }
- if (IsInitialized)
- Speed = Speed;
- }
- /************************************************************************************************************************/
- #endif
- /************************************************************************************************************************/
- /// <summary>Plays the <see cref="Clip"/>.</summary>
- public void Play()
- => Play(_Clip);
- /// <summary>Plays the `clip`.</summary>
- public void Play(AnimationClip clip)
- {
- if (clip == null)
- {
- Length = 0;
- IsLooping = false;
- return;
- }
- Length = clip.length;
- IsLooping = clip.isLooping;
- if (_Animator == null)
- return;
- if (_Graph.IsValid())
- _Graph.Destroy();
- _Playable = AnimationPlayableUtilities.PlayClip(_Animator, clip, out _Graph);
- _Playable.SetSpeed(_Speed);
- SetTime(_NormalizedStartTime * Length);
- if (_Speed != 0)
- {
- _IsPlaying = true;
- }
- else
- {
- _IsPlaying = false;
- _Graph.Stop();
- }
- }
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="PlayableGraph.Evaluate()"/>.</summary>
- public void Evaluate()
- {
- if (_Graph.IsValid())
- _Graph.Evaluate();
- }
- /// <summary>Calls <see cref="PlayableGraph.Evaluate(float)"/>.</summary>
- public void Evaluate(float deltaTime)
- {
- if (_Graph.IsValid())
- _Graph.Evaluate(deltaTime);
- }
- /************************************************************************************************************************/
- /// <summary>Plays the <see cref="Clip"/> on the target <see cref="Animator"/>.</summary>
- protected virtual void OnEnable()
- {
- if (!_IsPlaying)
- Play();
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Checks if the animation is done
- /// so it can pause the <see cref="PlayableGraph"/> to improve performance.
- /// </summary>
- protected virtual void LateUpdate()
- {
- if (!IsPlaying ||
- IsLooping ||
- !_Playable.IsValid())
- return;
- var time = (float)_Playable.GetTime();
- if (_Speed >= 0)
- {
- if (time >= Length)
- {
- IsPlaying = false;
- Time = Length;
- }
- }
- else
- {
- if (time <= 0)
- {
- IsPlaying = false;
- Time = 0;
- }
- }
- }
- /************************************************************************************************************************/
- /// <summary>Stops playing and rewinds if <see cref="StopOnDisable"/>.</summary>
- protected virtual void OnDisable()
- {
- if (!IsInitialized)
- return;
- _IsPlaying = false;
- _Graph.Stop();
- if (StopOnDisable)
- SetTime(0);
- }
- /************************************************************************************************************************/
- /// <summary>Ensures that the <see cref="PlayableGraph"/> is properly cleaned up.</summary>
- protected virtual void OnDestroy()
- {
- if (IsInitialized)
- _Graph.Destroy();
- }
- /************************************************************************************************************************/
- #if UNITY_EDITOR
- /// <summary>[Editor-Only] Ensures that the <see cref="PlayableGraph"/> is destroyed.</summary>
- ~SoloAnimation()
- {
- UnityEditor.EditorApplication.delayCall += OnDestroy;
- }
- #endif
- /************************************************************************************************************************/
- /// <summary>[<see cref="IAnimationClipSource"/>] Adds the <see cref="Clip"/> to the list.</summary>
- public void GetAnimationClips(List<AnimationClip> clips)
- {
- if (_Clip != null)
- clips.Add(_Clip);
- }
- /************************************************************************************************************************/
- }
- }
|