// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; namespace Animancer { /// /// A layer on which animations can play with their states managed independantly of other layers while blending the /// output with those layers. /// /// /// /// This class can be used as a custom yield instruction to wait until all animations finish playing. /// /// Documentation: Layers /// /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerLayer /// public sealed class AnimancerLayer : AnimancerNode, IAnimationClipCollection { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ /// [Internal] Creates a new . internal AnimancerLayer(AnimancerPlayable root, int index) { Root = root; Index = index; CreatePlayable(); if (ApplyParentAnimatorIK) _ApplyAnimatorIK = root.ApplyAnimatorIK; if (ApplyParentFootIK) _ApplyFootIK = root.ApplyFootIK; } /************************************************************************************************************************/ /// Creates and assigns the managed by this layer. protected override void CreatePlayable(out Playable playable) => playable = AnimationMixerPlayable.Create(Root._Graph); /************************************************************************************************************************/ /// A layer is its own root. public override AnimancerLayer Layer => this; /// The receives the output of the . public override IPlayableWrapper Parent => Root; /// Indicates whether child playables should stay connected to this layer at all times. public override bool KeepChildrenConnected => Root.KeepChildrenConnected; /************************************************************************************************************************/ /// All of the animation states connected to this layer. private readonly List States = new List(); /************************************************************************************************************************/ private AnimancerState _CurrentState; /// The state of the animation currently being played. /// /// Specifically, this is the state that was most recently started using any of the Play or CrossFade methods /// on this layer. States controlled individually via methods in the itself will /// not register in this property. /// /// Each time this property changes, the is incremented. /// public AnimancerState CurrentState { get => _CurrentState; private set { _CurrentState = value; CommandCount++; } } /// /// The number of times the has changed. By storing this value and later comparing /// the stored value to the current value, you can determine whether the state has been changed since then, /// even it has changed back to the same state. /// public int CommandCount { get; private set; } #if UNITY_EDITOR /// [Editor-Only] [Internal] Increases the by 1. internal void IncrementCommandCount() => CommandCount++; #endif /************************************************************************************************************************/ /// [Pro-Only] /// Determines whether this layer is set to additive blending. Otherwise it will override any earlier layers. /// public bool IsAdditive { get => Root.Layers.IsAdditive(Index); set => Root.Layers.SetAdditive(Index, value); } /************************************************************************************************************************/ /// [Pro-Only] /// Sets an to determine which bones this layer will affect. /// public void SetMask(AvatarMask mask) { Root.Layers.SetMask(Index, mask); } #if UNITY_ASSERTIONS /// [Assert-Only] The that determines which bones this layer will affect. internal AvatarMask _Mask; #endif /************************************************************************************************************************/ /// /// The average velocity of the root motion of all currently playing animations, taking their current /// into account. /// public Vector3 AverageVelocity { get { var velocity = default(Vector3); for (int i = States.Count - 1; i >= 0; i--) { var state = States[i]; velocity += state.AverageVelocity * state.Weight; } return velocity; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Child States /************************************************************************************************************************/ /// public override int ChildCount => States.Count; /// Returns the state connected to the specified `index` as a child of this layer. /// This method is identical to . public override AnimancerState GetChild(int index) => States[index]; /// Returns the state connected to the specified `index` as a child of this layer. /// This indexer is identical to . public AnimancerState this[int index] => States[index]; /************************************************************************************************************************/ /// Adds a new port and uses to connect the `state` to it. public void AddChild(AnimancerState state) { if (state.Parent == this) return; // Set the root before expanding the States list in case it throws an exception. state.SetRoot(Root); var index = States.Count; States.Add(null);// OnAddChild will assign the state. _Playable.SetInputCount(index + 1); state.SetParent(this, index); } /************************************************************************************************************************/ /// Connects the `state` to this layer at its . protected internal override void OnAddChild(AnimancerState state) => OnAddChild(States, state); /************************************************************************************************************************/ /// Disconnects the `state` from this layer at its . protected internal override void OnRemoveChild(AnimancerState state) { var index = state.Index; Validate.AssertCanRemoveChild(state, States, States.Count); if (_Playable.GetInput(index).IsValid()) Root._Graph.Disconnect(_Playable, index); // Swap the last state into the place of the one that was just removed. var last = States.Count - 1; if (index < last) { state = States[last]; state.DisconnectFromGraph(); States[index] = state; state.Index = index; if (state.Weight != 0 || Root.KeepChildrenConnected) state.ConnectToGraph(); } States.RemoveAt(last); _Playable.SetInputCount(last); } /************************************************************************************************************************/ /// public override FastEnumerator GetEnumerator() => new FastEnumerator(States); /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Create State /************************************************************************************************************************/ /// Creates and returns a new to play the `clip`. /// /// is used to determine the . /// public ClipState CreateState(AnimationClip clip) => CreateState(Root.GetKey(clip), clip); /// /// Creates and returns a new to play the `clip` and registers it with the `key`. /// public ClipState CreateState(object key, AnimationClip clip) { var state = new ClipState(clip) { _Key = key, }; AddChild(state); return state; } /************************************************************************************************************************/ /// Returns a state registered with the `key` and attached to this layer or null if none exist. /// The `key` is null. /// /// If a state is registered with the `key` but on a different layer, this method will use that state as the /// key and try to look up another state with it. This allows it to associate multiple states with the same /// original key. /// public AnimancerState GetState(ref object key) { if (key == null) throw new ArgumentNullException(nameof(key)); // Check through any states backwards in the key chain. var earlierKey = key; while (earlierKey is AnimancerState keyState) { if (keyState.Parent == this)// If the state is on this layer, return it. { key = keyState.Key; return keyState; } else if (keyState.Parent == null)// If the state is on no layer, attach it to this one and return it. { key = keyState.Key; AddChild(keyState); return keyState; } else// Otherwise the state is on a different layer. { earlierKey = keyState.Key; } } while (true) { // If no state is registered with the key, return null. if (!Root.States.TryGet(key, out var state)) return null; if (state.Parent == this)// If the state is on this layer, return it. { return state; } else if (state.Parent == null)// If the state is on no layer, attach it to this one and return it. { AddChild(state); return state; } else// Otherwise the state is on a different layer. { // Use it as the key and try to look up the next state in a chain. key = state; } } } /************************************************************************************************************************/ /// /// Calls for each of the specified clips. /// /// If you only want to create a single state, use . /// public void CreateIfNew(AnimationClip clip0, AnimationClip clip1) { GetOrCreateState(clip0); GetOrCreateState(clip1); } /// /// Calls for each of the specified clips. /// /// If you only want to create a single state, use . /// public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2) { GetOrCreateState(clip0); GetOrCreateState(clip1); GetOrCreateState(clip2); } /// /// Calls for each of the specified clips. /// /// If you only want to create a single state, use . /// public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2, AnimationClip clip3) { GetOrCreateState(clip0); GetOrCreateState(clip1); GetOrCreateState(clip2); GetOrCreateState(clip3); } /// /// Calls for each of the specified clips. /// /// If you only want to create a single state, use . /// public void CreateIfNew(params AnimationClip[] clips) { if (clips == null) return; var count = clips.Length; for (int i = 0; i < count; i++) { var clip = clips[i]; if (clip != null) GetOrCreateState(clip); } } /************************************************************************************************************************/ /// /// Calls and returns the state registered with that key or /// creates one if it doesn't exist. /// /// If the state already exists but has the wrong , the `allowSetClip` /// parameter determines what will happen. False causes it to throw an while /// true allows it to change the . Note that the change is somewhat costly to /// performance to use with caution. /// /// public AnimancerState GetOrCreateState(AnimationClip clip, bool allowSetClip = false) { return GetOrCreateState(Root.GetKey(clip), clip, allowSetClip); } /// /// Returns the state registered with the if there is one. Otherwise /// this method uses to create a new one and registers it with /// that key before returning it. /// public AnimancerState GetOrCreateState(ITransition transition) { var key = transition.Key; var state = GetState(ref key); if (state == null) { state = transition.CreateState(); state.Key = key; AddChild(state); } return state; } /// Returns the state registered with the `key` or creates one if it doesn't exist. /// /// The `key` is null. /// /// If the state already exists but has the wrong , the `allowSetClip` /// parameter determines what will happen. False causes it to throw an while /// true allows it to change the . Note that the change is somewhat costly to /// performance to use with caution. /// /// See also: . /// public AnimancerState GetOrCreateState(object key, AnimationClip clip, bool allowSetClip = false) { var state = GetState(ref key); if (state == null) return CreateState(key, clip); // If a state exists but has the wrong clip, either change it or complain. if (!ReferenceEquals(state.Clip, clip)) { if (allowSetClip) { state.Clip = clip; } else { throw new ArgumentException( AnimancerPlayable.StateDictionary.GetClipMismatchError(key, state.Clip, clip)); } } return state; } /// Returns the `state` if it's a child of this layer. Otherwise makes a clone of it. public AnimancerState GetOrCreateState(AnimancerState state) { if (state.Parent == this) return state; if (state.Parent == null) { AddChild(state); return state; } var key = state.Key; if (key == null) key = state; var stateOnThisLayer = GetState(ref key); if (stateOnThisLayer == null) { stateOnThisLayer = state.Clone(Root); stateOnThisLayer.Key = key; AddChild(stateOnThisLayer); } return stateOnThisLayer; } /************************************************************************************************************************/ /// /// The maximum that will treat as /// being weightless. Default = 0.1. /// /// This allows states with very small weights to be reused instead of needing to create new ones. public static float WeightlessThreshold { get; set; } = 0.1f; /// /// The maximum number of duplicate states that can be created for a single clip when trying to get a /// weightless state. Exceeding this limit will cause it to just use the state with the lowest weight. /// Default = 3. /// public static int MaxCloneCount { get; private set; } = 3; /// /// If the `state`'s is not currently low, this method finds or creates a /// copy of it which is low. he returned is also set to 0. /// /// /// If this method would exceed the , it returns the clone with the lowest weight. /// /// "Low" weight is defined as less than or equal to the . /// /// The Fade Modes page /// explains why clones are created. /// public AnimancerState GetOrCreateWeightlessState(AnimancerState state) { if (state.Parent == null) { state.Weight = 0; goto GotState; } if (state.Parent == this && state.Weight <= WeightlessThreshold) goto GotState; float lowestWeight = float.PositiveInfinity; AnimancerState lowestWeightState = null; int cloneCount = 0; // Use any earlier state that is weightless. var keyState = state; while (true) { keyState = keyState.Key as AnimancerState; if (keyState == null) { break; } else if (keyState.Parent == this) { if (keyState.Weight <= WeightlessThreshold) { state = keyState; goto GotState; } else if (lowestWeight > keyState.Weight) { lowestWeight = keyState.Weight; lowestWeightState = keyState; } } else if (keyState.Parent == null) { AddChild(keyState); goto GotState; } cloneCount++; } if (state.Parent == this) { lowestWeight = state.Weight; lowestWeightState = state; } keyState = state; // If that state is not at low weight, get or create another state registered using the previous state as a key. // Keep going through states in this manner until you find one at low weight. while (true) { var key = (object)state; if (!Root.States.TryGet(key, out state)) { if (cloneCount >= MaxCloneCount && lowestWeightState != null) { state = lowestWeightState; goto GotState; } else { #if UNITY_ASSERTIONS var cloneTimer = OptionalWarning.CloneComplexState.IsEnabled() && !(keyState is ClipState) ? SimpleTimer.Start() : default; #endif state = keyState.Clone(Root); state.SetDebugName($"[{cloneCount + 1}] {keyState}"); state.Weight = 0; state._Key = key; Root.States.Register(state); AddChild(state); #if UNITY_ASSERTIONS if (cloneTimer.Stop()) { OptionalWarning.CloneComplexState.Log( $"A {keyState.GetType().Name} was cloned in {cloneTimer.total * 1000} milliseconds." + $" This performance cost may be notable and complex states generally have parameters" + $" that need to be controlled which may result in undesired behaviour if your scripts" + $" are only expecting to have one state to control so you may wish to avoid cloning." + $"\n\nThe Fade Modes page explains why clones are created: {Strings.DocsURLs.FadeModes}", Root?.Component); } #endif goto GotState; } } else if (state.Parent == this) { if (state.Weight <= WeightlessThreshold) { goto GotState; } else if (lowestWeight > state.Weight) { lowestWeight = state.Weight; lowestWeightState = state; } } else if (state.Parent == null) { AddChild(state); goto GotState; } cloneCount++; } GotState: state.TimeD = 0; return state; } /************************************************************************************************************************/ /// Destroys all states connected to this layer. /// This operation cannot be undone. public void DestroyStates() { for (int i = States.Count - 1; i >= 0; i--) { States[i].Destroy(); } States.Clear(); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Play Management /************************************************************************************************************************/ /// protected internal override void OnStartFade() { for (int i = States.Count - 1; i >= 0; i--) States[i].OnStartFade(); } /************************************************************************************************************************/ // Play Immediately. /************************************************************************************************************************/ /// Stops all other animations on this layer, plays the `clip`, and returns its state. /// /// The animation will continue playing from its current . /// To restart it from the beginning you can use ...Play(clip).Time = 0;. /// /// This method is safe to call repeatedly without checking whether the `clip` was already playing. /// public AnimancerState Play(AnimationClip clip) => Play(GetOrCreateState(clip)); /// Stops all other animations on the same layer, plays the `state`, and returns it. /// /// The animation will continue playing from its current . /// To restart it from the beginning you can use ...Play(state).Time = 0;. /// /// This method is safe to call repeatedly without checking whether the `state` was already playing. /// /// /// The is another state (likely a ). /// It must be either null or a layer. /// public AnimancerState Play(AnimancerState state) { #if UNITY_ASSERTIONS if (state.Parent is AnimancerState) throw new InvalidOperationException( $"A layer can't Play a state which is the child of another state." + $"\n- State: {state}" + $"\n- Parent: {state.Parent}" + $"\n- Layer: {this}"); #endif if (Weight == 0 && TargetWeight == 0) Weight = 1; state = GetOrCreateState(state); CurrentState = state; state.Play(); for (int i = States.Count - 1; i >= 0; i--) { var otherState = States[i]; if (otherState != state) otherState.Stop(); } return state; } /************************************************************************************************************************/ // Cross Fade. /************************************************************************************************************************/ /// /// Starts fading in the `clip` over the course of the `fadeDuration` while fading out all others in the same /// layer. Returns its state. /// /// /// 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. /// /// If the layer currently has 0 , this method will fade in the layer itself /// and simply the `state`. /// /// This method is safe to call repeatedly without checking whether the `state` was already playing. /// /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds. /// public AnimancerState Play(AnimationClip clip, float fadeDuration, FadeMode mode = default) => Play(GetOrCreateState(clip), fadeDuration, mode); /// /// Starts fading in the `state` over the course of the `fadeDuration` while fading out all others in this /// layer. Returns the `state`. /// /// /// 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. /// /// If the layer currently has 0 , this method will fade in the layer itself /// and simply the `state`. /// /// This method is safe to call repeatedly without checking whether the `state` was already playing. /// /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds. /// public AnimancerState Play(AnimancerState state, float fadeDuration, FadeMode mode = default) { // Skip the fade if: if (fadeDuration <= 0 ||// There is no duration. (Root.SkipFirstFade && Index == 0 && Weight == 0))// Or this is Layer 0 and it has no weight. { Weight = 1; state = Play(state); if (mode == FadeMode.FromStart || mode == FadeMode.NormalizedFromStart) state.TimeD = 0; return state; } EvaluateFadeMode(mode, ref state, ref fadeDuration, out var layerFadeDuration); StartFade(1, layerFadeDuration); if (Weight == 0) return Play(state); state = GetOrCreateState(state); CurrentState = state; // If the state is already playing or will finish fading in faster than this new fade, // continue the existing fade but still pretend it was restarted. if (state.IsPlaying && state.TargetWeight == 1 && (state.Weight == 1 || state.FadeSpeed * fadeDuration > Math.Abs(1 - state.Weight))) { OnStartFade(); } else// Otherwise fade in the target state and fade out all others. { state.IsPlaying = true; state.StartFade(1, fadeDuration); for (int i = States.Count - 1; i >= 0; i--) { var otherState = States[i]; if (otherState != state) otherState.StartFade(0, fadeDuration); } } return state; } /************************************************************************************************************************/ // Transition. /************************************************************************************************************************/ /// /// Creates a state for the `transition` if it didn't already exist, then calls /// or /// depending on the . /// /// /// This method is safe to call repeatedly without checking whether the `transition` was already playing. /// public AnimancerState Play(ITransition transition) => Play(transition, transition.FadeDuration, transition.FadeMode); /// /// Creates a state for the `transition` if it didn't already exist, then calls /// or /// depending on the . /// /// /// This method is safe to call repeatedly without checking whether the `transition` was already playing. /// public AnimancerState Play(ITransition transition, float fadeDuration, FadeMode mode = default) { var state = GetOrCreateState(transition); state = Play(state, fadeDuration, mode); transition.Apply(state); return state; } /************************************************************************************************************************/ // Try Play. /************************************************************************************************************************/ /// /// 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. /// /// /// The animation will continue playing from its current . /// To restart it from the beginning you can simply set the returned state's time to 0. /// /// This method is safe to call repeatedly without checking whether the animation was already playing. /// public AnimancerState TryPlay(object key) => Root.States.TryGet(key, out var state) ? Play(state) : null; /// /// 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. /// /// /// 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. /// /// If the layer currently has 0 , this method will fade in the layer itself /// and simply the `state`. /// /// This method is safe to call repeatedly without checking whether the animation was already playing. /// /// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds. /// public AnimancerState TryPlay(object key, float fadeDuration, FadeMode mode = default) => Root.States.TryGet(key, out var state) ? Play(state, fadeDuration, mode) : null; /************************************************************************************************************************/ /// Manipulates the other parameters according to the `mode`. /// /// The is null when using or /// . /// private void EvaluateFadeMode(FadeMode mode, ref AnimancerState state, ref float fadeDuration, out float layerFadeDuration) { layerFadeDuration = fadeDuration; switch (mode) { case FadeMode.FixedSpeed: fadeDuration *= Math.Abs(1 - state.Weight); layerFadeDuration *= Math.Abs(1 - Weight); break; case FadeMode.FixedDuration: break; case FadeMode.FromStart: state = GetOrCreateWeightlessState(state); break; case FadeMode.NormalizedSpeed: { var length = state.Length; fadeDuration *= Math.Abs(1 - state.Weight) * length; layerFadeDuration *= Math.Abs(1 - Weight) * length; } break; case FadeMode.NormalizedDuration: { var length = state.Length; fadeDuration *= length; layerFadeDuration *= length; } break; case FadeMode.NormalizedFromStart: { state = GetOrCreateWeightlessState(state); var length = state.Length; fadeDuration *= length; layerFadeDuration *= length; } break; default: throw AnimancerUtilities.CreateUnsupportedArgumentException(mode); } } /************************************************************************************************************************/ // Stopping /************************************************************************************************************************/ /// /// Sets = 0 and calls on all animations /// to stop them from playing and rewind them to the start. /// public override void Stop() { base.Stop(); CurrentState = null; for (int i = States.Count - 1; i >= 0; i--) States[i].Stop(); } /************************************************************************************************************************/ // Checking /************************************************************************************************************************/ /// /// Returns true if the `clip` is currently being played by at least one state. /// public bool IsPlayingClip(AnimationClip clip) { for (int i = States.Count - 1; i >= 0; i--) { var state = States[i]; if (state.Clip == clip && state.IsPlaying) return true; } return false; } /// /// Returns true if at least one animation is being played. /// public bool IsAnyStatePlaying() { for (int i = States.Count - 1; i >= 0; i--) if (States[i].IsPlaying) return true; return false; } /// /// Returns true if the is playing and hasn't yet reached its end. /// /// This method is called by so this object can be used as a custom yield /// instruction to wait until it finishes. /// public override bool IsPlayingAndNotEnding() => _CurrentState != null && _CurrentState.IsPlayingAndNotEnding(); /************************************************************************************************************************/ /// /// Calculates the total of all states in this layer. /// public float GetTotalWeight() { float weight = 0; for (int i = States.Count - 1; i >= 0; i--) { weight += States[i].Weight; } return weight; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Inverse Kinematics /************************************************************************************************************************/ private bool _ApplyAnimatorIK; /// public override bool ApplyAnimatorIK { get => _ApplyAnimatorIK; set => base.ApplyAnimatorIK = _ApplyAnimatorIK = value; } /************************************************************************************************************************/ private bool _ApplyFootIK; /// public override bool ApplyFootIK { get => _ApplyFootIK; set => base.ApplyFootIK = _ApplyFootIK = value; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Inspector /************************************************************************************************************************/ /// [] /// Gathers all the animations in this layer. /// public void GatherAnimationClips(ICollection clips) => clips.GatherFromSource(States); /************************************************************************************************************************/ /// The Inspector display name of this layer. public override string ToString() { #if UNITY_ASSERTIONS if (string.IsNullOrEmpty(DebugName)) { if (_Mask != null) return _Mask.name; SetDebugName(Index == 0 ? "Base Layer" : "Layer " + Index); } return base.ToString(); #else return "Layer " + Index; #endif } /************************************************************************************************************************/ /// protected override void AppendDetails(StringBuilder text, string separator) { base.AppendDetails(text, separator); text.Append(separator).Append($"{nameof(CurrentState)}: ").Append(CurrentState); text.Append(separator).Append($"{nameof(CommandCount)}: ").Append(CommandCount); text.Append(separator).Append($"{nameof(IsAdditive)}: ").Append(IsAdditive); #if UNITY_ASSERTIONS text.Append(separator).Append($"{nameof(AvatarMask)}: ").Append(AnimancerUtilities.ToStringOrNull(_Mask)); #endif } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }