// Animancer // Copyright 2020 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"/>. /// <para></para> /// This class can be used as a custom yield instruction to wait until all animations finish playing. /// </summary> /// <remarks> /// This class is mostly just a wrapper that connects an <see cref="AnimancerPlayable"/> to an /// <see cref="UnityEngine.Animator"/>. /// </remarks> [AddComponentMenu(Strings.MenuPrefix + "Animancer Component")] [HelpURL(Strings.APIDocumentationURL + "/AnimancerComponent")] [DefaultExecutionOrder(-5000)]// Initialise before anything else tries to use this component. public class AnimancerComponent : MonoBehaviour, IAnimancerComponent, IEnumerable<AnimancerState>, IEnumerator, IAnimationClipSource, IAnimationClipCollection { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ [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 { return _Animator; } set { #if UNITY_EDITOR Editor.AnimancerEditorUtilities.SetIsInspectorExpanded(_Animator, true); Editor.AnimancerEditorUtilities.SetIsInspectorExpanded(value, false); #endif // It doesn't seem to be possible to stop the old Animator from playing the graph. _Animator = value; if (IsPlayableInitialised) _Playable.SetOutput(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 { get { return "_Animator"; } } #endif /************************************************************************************************************************/ private AnimancerPlayable _Playable; /// <summary> /// The internal system which manages the playing animations. /// Accessing this property will automatically initialise it. /// </summary> public AnimancerPlayable Playable { get { InitialisePlayable(); return _Playable; } } /// <summary>Indicates whether the <see cref="Playable"/> has been initialised.</summary> public bool IsPlayableInitialised { get { return _Playable != null && _Playable.IsValid; } } /************************************************************************************************************************/ /// <summary>The states managed by this component.</summary> public AnimancerPlayable.StateDictionary States { get { return Playable.States; } } /// <summary>The layers which each manage their own set of animations.</summary> public AnimancerPlayable.LayerList Layers { get { return Playable.Layers; } } /// <summary>Returns layer 0.</summary> public static implicit operator AnimancerLayer(AnimancerComponent animancer) { return animancer.Playable.Layers[0]; } /************************************************************************************************************************/ [SerializeField, Tooltip("Determines what happens when this component is disabled" + " or its GameObject becomes inactive (i.e. in OnDisable):" + "\n- Stop all animations" + "\n- Pause all animations" + "\n- Continue playing" + "\n- Reset to the original values" + "\n- Destroy all layers and states")] private DisableAction _ActionOnDisable; /// <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"/>). /// <para></para> /// The default value is <see cref="DisableAction.Stop"/>. /// </summary> public DisableAction ActionOnDisable { get { return _ActionOnDisable; } set { _ActionOnDisable = value; } } /// <summary>Determines whether the object will be reset to its original values when disabled.</summary> bool IAnimancerComponent.ResetOnDisable { get { return _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"/>). /// <para></para> /// Calls <see cref="Stop()"/> and <see cref="AnimancerPlayable.PauseGraph"/>. /// </summary> Stop, /// <summary> /// Pause all animations in their current state so they can resume later. /// <para></para> /// Calls <see cref="AnimancerPlayable.PauseGraph"/>. /// </summary> 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). /// <para></para> /// WARNING: this must occur before the <see cref="UnityEngine.Animator"/> receives its <c>OnDisable</c> /// call, meaning the <see cref="AnimancerComponent"/> must be above it in the Inspector or on a child /// object so that <see cref="OnDisable"/> gets called first. /// <para></para> /// Calls <see cref="Stop()"/>, <see cref="Animator.Rebind"/>, and <see cref="AnimancerPlayable.PauseGraph"/>. /// </summary> 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. /// <para></para> /// Calls <see cref="AnimancerPlayable.Destroy()"/>. /// </summary> 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"/>. /// <para></para> /// Note that changing to or from <see cref="AnimatorUpdateMode.AnimatePhysics"/> at runtime has no effect. /// </summary> /// <exception cref="NullReferenceException">Thrown if no <see cref="Animator"/> is assigned.</exception> public AnimatorUpdateMode UpdateMode { get { return _Animator.updateMode; } set { _Animator.updateMode = value; if (!IsPlayableInitialised) 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 Animator.updateMode to or from AnimatePhysics at runtime will have no effect." + " You must set it in the Unity Editor or on startup."); } #endif } } /************************************************************************************************************************/ #if UNITY_EDITOR /************************************************************************************************************************/ /// <summary>[Editor-Only] /// The <see cref="UpdateMode"/> what was first used when this script initialised. /// This is used to give a warning when changing to or from <see cref="AnimatorUpdateMode.AnimatePhysics"/> at /// runtime since it won't work correctly. /// </summary> public AnimatorUpdateMode? InitialUpdateMode { get; private set; } /************************************************************************************************************************/ #endif /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Animation Events /************************************************************************************************************************/ // These methods are above their regular overloads so Animation Events find them first (because the others can't be used). /************************************************************************************************************************/ /// <summary>Calls <see cref="Play(AnimationClip, int)"/>.</summary> /// <remarks>This method is called by Animation Events.</remarks> private void Play(AnimationEvent animationEvent) { var clip = (AnimationClip)animationEvent.objectReferenceParameter; var layerIndex = animationEvent.intParameter; if (layerIndex < 0) Play(clip); else Layers[layerIndex].Play(clip); } /// <summary> /// Calls <see cref="Play(AnimationClip, int)"/> and sets the <see cref="AnimancerState.Time"/> = 0. /// </summary> /// <remarks>This method is called by Animation Events.</remarks> private void PlayFromStart(AnimationEvent animationEvent) { var clip = (AnimationClip)animationEvent.objectReferenceParameter; var layerIndex = animationEvent.intParameter; if (layerIndex < 0) Play(clip).Time = 0; else Layers[layerIndex].Play(clip).Time = 0; } /// <summary>Calls <see cref="CrossFade(AnimationClip, float, int)"/>.</summary> /// <remarks>This method is called by Animation Events.</remarks> private void CrossFade(AnimationEvent animationEvent) { var clip = (AnimationClip)animationEvent.objectReferenceParameter; var fadeDuration = animationEvent.floatParameter; if (fadeDuration <= 0) fadeDuration = AnimancerPlayable.DefaultFadeDuration; var layerIndex = animationEvent.intParameter; if (layerIndex < 0) Play(clip, fadeDuration); else Layers[layerIndex].Play(clip, fadeDuration); } /// <summary>Calls <see cref="CrossFadeFromStart(AnimationClip, float, int)"/>.</summary> /// <remarks>This method is called by Animation Events.</remarks> private void CrossFadeFromStart(AnimationEvent animationEvent) { var clip = (AnimationClip)animationEvent.objectReferenceParameter; var fadeDuration = animationEvent.floatParameter; if (fadeDuration <= 0) fadeDuration = AnimancerPlayable.DefaultFadeDuration; var layerIndex = animationEvent.intParameter; if (layerIndex < 0) Play(clip, fadeDuration, FadeMode.FromStart); else Layers[layerIndex].Play(clip, fadeDuration, FadeMode.FromStart); } /// <summary>Calls <see cref="Transition(ITransition, int)"/>.</summary> /// <remarks>This method is called by Animation Events.</remarks> private void Transition(AnimationEvent animationEvent) { var transition = (ITransition)animationEvent.objectReferenceParameter; var layerIndex = animationEvent.intParameter; if (layerIndex < 0) Play(transition); else Layers[layerIndex].Play(transition); } /************************************************************************************************************************/ /// <summary> /// Invokes the <see cref="Animancerstate.Events.OnEndCallback"/> event of the /// <see cref="CurrentState"/> if it is playing the <see cref="AnimationClip"/> which triggered the event. /// <para></para> /// Logs a warning if no state is registered for that animation. /// </summary> /// <remarks>This method is called by Animation Events.</remarks> private void End(AnimationEvent animationEvent) { if (_Playable == null) { // This could only happen if another Animator triggers the event on this object somehow. Debug.LogWarning("AnimationEvent 'End' was triggered by " + animationEvent.animatorClipInfo.clip + ", but the AnimancerComponent.Playable hasn't been initialised.", this); return; } if (_Playable.OnEndEventReceived(animationEvent)) return; if (animationEvent.messageOptions == SendMessageOptions.RequireReceiver) { Debug.LogWarning("AnimationEvent 'End' was triggered by " + animationEvent.animatorClipInfo.clip + ", but no state was found with that key.", this); } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Initialisation /************************************************************************************************************************/ #if UNITY_EDITOR /// <summary>[Editor-Only] /// 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. /// <para></para> /// Destroys the playable if one has been initialised. /// Searches for an <see cref="UnityEngine.Animator"/> on this object, or it's children or parents. /// Removes the <see cref="Animator.runtimeAnimatorController"/> if it finds one. /// <para></para> /// This method also prevents you from adding multiple copies of this component to a single object. Doing so /// will destroy the new one immediately and change the old one's type to match the new one, allowing you to /// change the type without losing the values of any serialized fields they share. /// </summary> protected virtual void Reset() { OnDestroy(); _Animator = Editor.AnimancerEditorUtilities.GetComponentInHierarchy<Animator>(gameObject); if (_Animator != null) { _Animator.runtimeAnimatorController = null; Editor.AnimancerEditorUtilities.SetIsInspectorExpanded(_Animator, false); // Collapse the Animator property because the custom Inspector uses that to control whether the // Animator's Inspector is expanded. using (var serializedObject = new UnityEditor.SerializedObject(this)) { var property = serializedObject.FindProperty("_Animator"); property.isExpanded = false; serializedObject.ApplyModifiedProperties(); } } AnimancerUtilities.IfMultiComponentThenChangeType(this); } #endif /************************************************************************************************************************/ /// <summary> /// Called by Unity when this component becomes enabled and active. /// <para></para> /// Ensures that the <see cref="PlayableGraph"/> is playing. /// </summary> protected virtual void OnEnable() { if (IsPlayableInitialised) _Playable.UnpauseGraph(); } /// <summary> /// Called by Unity when this component becomes disabled or inactive. Acts according to the /// <see cref="ActionOnDisable"/>. /// </summary> protected virtual void OnDisable() { if (!IsPlayableInitialised) 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, "DisableAction.Reset failed because the Animator is not enabled." + " This most likely means you are disabling the GameObject and the Animator is above the" + " 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 DisableAction.Stop and call Animator.Rebind" + " manually before disabling the GameObject.", this); Stop(); _Animator.Rebind(); _Playable.PauseGraph(); break; case DisableAction.Destroy: _Playable.Destroy(); _Playable = null; break; default: throw new ArgumentOutOfRangeException("ActionOnDisable"); } } /************************************************************************************************************************/ /// <summary>Creates a new <see cref="AnimancerPlayable"/> if it doesn't already exist.</summary> private void InitialisePlayable() { if (IsPlayableInitialised) return; #if UNITY_EDITOR var currentEvent = Event.current; if (currentEvent != null && (currentEvent.type == EventType.Layout || currentEvent.type == EventType.Repaint)) Debug.LogWarning("Creating an AnimancerPlayable during a " + currentEvent.type + " event is likely undesirable."); #endif if (_Animator == null) _Animator = GetComponent<Animator>(); AnimancerPlayable.SetNextGraphName(name + ".Animancer"); _Playable = AnimancerPlayable.Create(); _Playable.SetOutput(_Animator, this); #if UNITY_EDITOR if (_Animator != null) InitialUpdateMode = UpdateMode; #endif } /************************************************************************************************************************/ /// <summary> /// Called by Unity when this component is destroyed. /// Ensures that the <see cref="Playable"/> is properly cleaned up. /// </summary> protected virtual void OnDestroy() { if (IsPlayableInitialised) { _Playable.Destroy(); _Playable = null; } } /************************************************************************************************************************/ #if UNITY_EDITOR /// <summary>[Editor-Only] /// Ensures that the <see cref="PlayableGraph"/> is destroyed. /// </summary> ~AnimancerComponent() { if (_Playable != null) Editor.AnimancerEditorUtilities.EditModeDelayCall(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) { return clip; } /************************************************************************************************************************/ /// <summary> /// Stops all other animations, plays the `clip`, and returns its state. /// <para></para> /// The animation will continue playing from its current <see cref="AnimancerState.Time"/>. /// To restart it from the beginning you can use <c>...Play(clip, layerIndex).Time = 0;</c>. /// </summary> public AnimancerState Play(AnimationClip clip) { return Play(States.GetOrCreate(clip)); } /// <summary> /// Stops all other animations, plays the `state`, and returns it. /// <para></para> /// 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>. /// </summary> public AnimancerState Play(AnimancerState state) { return Playable.Play(state); } /// <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> public AnimancerState Play(ITransition transition) { return Playable.Play(transition); } /// <summary> /// Stops all other animations, plays the animation registered with the `key`, and returns that /// state. 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 use <c>...Play(key).Time = 0;</c>. /// </summary> public AnimancerState Play(object key) { return Playable.Play(key); } /************************************************************************************************************************/ /// <summary> /// Starts fading in the `clip` over the course of the `fadeDuration` while fading out all others in the same /// layer. Returns its state. /// <para></para> /// 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(AnimationClip)"/> the `clip`. /// <para></para> /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build. /// </summary> public AnimancerState Play(AnimationClip clip, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed) { return Play(States.GetOrCreate(clip), fadeDuration, mode); } /// <summary> /// Starts fading in the `state` over the course of the `fadeDuration` while fading out all others in the same /// layer. Returns the `state`. /// <para></para> /// 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(AnimancerState)"/> the `state`. /// <para></para> /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build. /// </summary> public AnimancerState Play(AnimancerState state, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed) { return Playable.Play(state, fadeDuration, mode); } /// <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> public AnimancerState Play(ITransition transition, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed) { return Playable.Play(transition, fadeDuration, mode); } /// <summary> /// Starts fading in the animation registered with the `key` over the course of the `fadeDuration` while fading /// out all others in the same layer. Returns the animation's state (or null if none was registered). /// <para></para> /// 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(AnimancerState)"/> the state. /// <para></para> /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build. /// </summary> public AnimancerState Play(object key, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed) { return Playable.Play(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) { return 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) { if (_Playable != null) return _Playable.Stop(hasKey); else return null; } /// <summary> /// Gets the state associated with the `key`, stops and rewinds it to the start, then returns it. /// </summary> public AnimancerState Stop(object key) { if (_Playable != null) return _Playable.Stop(key); else return null; } /// <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) { return 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) { if (_Playable != null) return _Playable.IsPlaying(hasKey); else return false; } /// <summary> /// Returns true if a state is registered with the `key` and it is currently playing. /// </summary> public bool IsPlaying(object key) { if (_Playable != null) return _Playable.IsPlaying(key); else return false; } /// <summary> /// Returns true if at least one animation is being played. /// </summary> public bool IsPlaying() { if (_Playable != null) return _Playable.IsPlaying(); else return false; } /************************************************************************************************************************/ /// <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) { if (_Playable != null) return _Playable.IsPlayingClip(clip); else return false; } /************************************************************************************************************************/ /// <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) { return key.IsPlaying; } /************************************************************************************************************************/ #endif #endregion /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Enumeration /************************************************************************************************************************/ // IEnumerable for 'foreach' statements. /************************************************************************************************************************/ /// <summary> /// Returns an enumerator that will iterate through all states in each layer (not states inside mixers). /// </summary> public IEnumerator<AnimancerState> GetEnumerator() { if (!IsPlayableInitialised) yield break; foreach (var state in _Playable.Layers.GetAllStateEnumerable()) yield return state; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /************************************************************************************************************************/ // 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 (!IsPlayableInitialised) return false; return ((IEnumerator)_Playable).MoveNext(); } /// <summary>Returns null.</summary> object IEnumerator.Current { get { return null; } } #pragma warning disable UNT0006 // Incorrect message signature. /// <summary>Does nothing.</summary> void IEnumerator.Reset() { } #pragma warning restore UNT0006 // Incorrect message signature. /************************************************************************************************************************/ /// <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 (IsPlayableInitialised) _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 /************************************************************************************************************************/ } }