// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 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) { #if UNITY_ASSERTIONS GC.SuppressFinalize(this); #endif 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); 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; } /************************************************************************************************************************/ /// /// 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 which 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 state = Root.States.GetOrCreate(transition); state.LayerIndex = Index; return state; } /// /// Returns the state which registered with the `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. /// /// /// /// The `key` is null. /// /// See also: . /// public AnimancerState GetOrCreateState(object key, AnimationClip clip, bool allowSetClip = false) { if (key == null) throw new ArgumentNullException(nameof(key)); if (Root.States.TryGet(key, out var state)) { // If a state exists with the 'key' 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)); } } else// Otherwise make sure it is on the correct layer. { AddChild(state); } } else { state = CreateState(key, clip); } 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. /// public AnimancerState Play(AnimancerState state) { if (Weight == 0 && TargetWeight == 0) Weight = 1; AddChild(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(Root.States.GetOrCreate(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. { if (mode == FadeMode.FromStart || mode == FadeMode.NormalizedFromStart) state.Time = 0; Weight = 1; return Play(state); } EvaluateFadeMode(mode, ref state, ref fadeDuration); StartFade(1, fadeDuration); if (Weight == 0) return Play(state); AddChild(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 = Root.States.GetOrCreate(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) { switch (mode) { case FadeMode.FixedSpeed: fadeDuration *= Math.Abs(1 - state.Weight); break; case FadeMode.FixedDuration: break; case FadeMode.FromStart: #if UNITY_ASSERTIONS if (!(state is ClipState)) throw new ArgumentException( $"{nameof(FadeMode)}.{nameof(FadeMode.FromStart)} can only be used on {nameof(ClipState)}s." + $" State = {state}"); #endif state = GetOrCreateWeightlessState(state); break; case FadeMode.NormalizedSpeed: fadeDuration *= Math.Abs(1 - state.Weight) * state.Length; break; case FadeMode.NormalizedDuration: fadeDuration *= state.Length; break; case FadeMode.NormalizedFromStart: #if UNITY_ASSERTIONS if (!(state is ClipState)) throw new ArgumentException( $"{nameof(FadeMode)}.{nameof(FadeMode.NormalizedFromStart)} can only be used on {nameof(ClipState)}s." + $" State = {state}"); #endif state = GetOrCreateWeightlessState(state); fadeDuration *= state.Length; break; default: throw new ArgumentException($"Invalid {nameof(FadeMode)}: {mode}", nameof(mode)); } } /************************************************************************************************************************/ #if UNITY_ASSERTIONS /// [Assert-Only] /// The maximum number of duplicate states that can be created by for /// a single clip before it will start giving usage warnings. Default = 5. /// public static int MaxStateDepth { get; private set; } = 5; #endif /// [Assert-Conditional] Sets the . /// This would not need to be a separate method if C# supported conditional property setters. [System.Diagnostics.Conditional(Strings.Assertions)] public static void SetMaxStateDepth(int depth) { #if UNITY_ASSERTIONS MaxStateDepth = depth; #endif } /// /// If the `state` is not currently at 0 , this method finds a copy of it /// which is at 0 or creates a new one. /// /// The is null. /// /// More states have been created for this than the /// allows. /// public AnimancerState GetOrCreateWeightlessState(AnimancerState state) { if (state.Weight != 0) { var clip = state.Clip; if (clip == null) { // We could probably support any state type by giving them a Clone method, but that would take a // lot of work for something that might never get used. throw new InvalidOperationException( $"{nameof(GetOrCreateWeightlessState)} can only be used on {nameof(ClipState)}s. State = " + state); } // Use any earlier state that is weightless. var keyState = state; while (true) { keyState = keyState.Key as AnimancerState; if (keyState == null) { break; } else if (keyState.Weight == 0) { state = keyState; goto GotWeightlessState; } } #if UNITY_ASSERTIONS int depth = 0; #endif // If that state is not at 0 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 0 weight. do { // Explicitly cast the state to an object to avoid the overload that warns about using a state as a key. state = Root.States.GetOrCreate((object)state, clip); #if UNITY_ASSERTIONS if (++depth == MaxStateDepth) { throw new ArgumentOutOfRangeException(nameof(depth), $"{nameof(AnimancerLayer)}.{nameof(GetOrCreateWeightlessState)}" + $" has created {MaxStateDepth} states for a single clip." + $" This is most likely a result of calling the method repeatedly on consecutive frames." + $" This can be avoided by using a different {nameof(FadeMode)} or calling" + $" {nameof(AnimancerLayer)}.{nameof(SetMaxStateDepth)} to increase the threshold for this warning."); } #endif } while (state.Weight != 0); } GotWeightlessState: // Make sure it is on this layer and at time 0. AddChild(state); state.Time = 0; return state; } /************************************************************************************************************************/ // 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. /// protected internal 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 /************************************************************************************************************************/ } }