123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Text;
- using UnityEngine;
- using UnityEngine.Animations;
- using UnityEngine.Playables;
- namespace Animancer
- {
- /// <summary>
- /// A layer on which animations can play with their states managed independantly of other layers while blending the
- /// output with those layers.
- /// </summary>
- ///
- /// <remarks>
- /// This class can be used as a custom yield instruction to wait until all animations finish playing.
- /// <para></para>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/manual/blending/layers">
- /// Layers</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerLayer
- ///
- public class AnimancerLayer : AnimancerNode,
- IAnimationClipCollection,
- ICopyable<AnimancerLayer>
- {
- /************************************************************************************************************************/
- #region Fields and Properties
- /************************************************************************************************************************/
- /// <summary>[Internal] Creates a new <see cref="AnimancerLayer"/>.</summary>
- protected internal AnimancerLayer(AnimancerGraph graph, int index)
- {
- Graph = graph;
- Parent = graph;
- Index = index;
- if (ApplyParentAnimatorIK)
- _ApplyAnimatorIK = graph.ApplyAnimatorIK;
- if (ApplyParentFootIK)
- _ApplyFootIK = graph.ApplyFootIK;
- CreatePlayable();
- ActiveStatesInternal = new(new(Graph, Playable));
- }
- /************************************************************************************************************************/
- /// <summary>Creates and assigns the <see cref="AnimationMixerPlayable"/> managed by this layer.</summary>
- protected override void CreatePlayable(out Playable playable)
- => playable = AnimationMixerPlayable.Create(Graph._PlayableGraph, _Capacity);
- /************************************************************************************************************************/
- /// <summary>A layer is its own root.</summary>
- public override AnimancerLayer Layer
- => this;
- /// <inheritdoc/>
- public override bool KeepChildrenConnected
- => Graph.KeepChildrenConnected;
- /************************************************************************************************************************/
- /// <summary>The animation states connected to this layer.</summary>
- private readonly List<AnimancerState>
- States = new();
- /************************************************************************************************************************/
- private readonly AnimancerState.ActiveList
- ActiveStatesInternal;
- /// <summary>The states connected to this layer which are <see cref="AnimancerState.IsActive"/>.</summary>
- public IReadOnlyIndexedList<AnimancerState> ActiveStates
- => ActiveStatesInternal;
- /************************************************************************************************************************/
- /// <summary>The default <see cref="Capacity"/> is 8 unless changed.</summary>
- /// <remarks>
- /// This value only affects newly created layers.
- /// <para></para>
- /// This value should be set high enough to include all states a layer is likely to have. It's generally not
- /// particularly important though since expanding the capacity is fairly fast.
- /// </remarks>
- public static int DefaultCapacity = 8;
- private int _Capacity = DefaultCapacity;
- /// <summary>
- /// The number of states that can be connected to this layer before its <see cref="Playable"/> needs to
- /// allocate more inputs.
- /// </summary>
- /// <remarks>Starts at the <see cref="DefaultCapacity"/> and doubles each time it needs to expand.</remarks>
- /// <exception cref="ArgumentException">This value cannot be set lower than the <see cref="ChildCount"/>.</exception>
- public int Capacity
- {
- get => _Capacity;
- set
- {
- if (value < ChildCount)
- throw new ArgumentException(
- $"{nameof(Capacity)} ({value}) cannot be smaller than {nameof(ChildCount)} ({ChildCount}).");
- _Capacity = value;
- _Playable.SetInputCount(value);
- }
- }
- /************************************************************************************************************************/
- private AnimancerState _CurrentState;
- /// <summary>The state of the animation currently being played.</summary>
- /// <remarks>
- /// 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 <see cref="AnimancerState"/> itself will
- /// not register in this property.
- /// <para></para>
- /// Each time this property changes, the <see cref="CommandCount"/> is incremented.
- /// </remarks>
- public AnimancerState CurrentState
- {
- get => _CurrentState;
- private set
- {
- _CurrentState = value;
- CommandCount++;
- }
- }
- /// <summary>
- /// The number of times the <see cref="CurrentState"/> 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.
- /// </summary>
- public int CommandCount { get; private set; }
- #if UNITY_EDITOR
- /// <summary>[Editor-Only] [Internal] Increases the <see cref="CommandCount"/> by 1.</summary>
- internal void IncrementCommandCount() => CommandCount++;
- #endif
- /************************************************************************************************************************/
- /// <summary>[Pro-Only]
- /// Determines whether this layer is set to additive blending. Otherwise it will override any earlier layers.
- /// </summary>
- public bool IsAdditive
- {
- get => Graph.Layers.IsAdditive(Index);
- set => Graph.Layers.SetAdditive(Index, value);
- }
- /************************************************************************************************************************/
- /// <summary>[Internal] [Pro-Only] The mask that determines which bones this layer will affect.</summary>
- internal AvatarMask _Mask;
- /// <summary>[Pro-Only] The mask that determines which bones this layer will affect.</summary>
- /// <remarks>
- /// Don't assign the same mask repeatedly unless you have modified it.
- /// This property doesn't check if the mask is the same
- /// so repeatedly assigning the same thing will simply waste performance.
- /// </remarks>
- public AvatarMask Mask
- {
- get => _Mask;
- set => Graph.Layers.SetMask(Index, value);
- }
- /************************************************************************************************************************/
- /// <summary>
- /// The average velocity of the root motion of all currently playing animations, taking their current
- /// <see cref="AnimancerNode.Weight"/> into account.
- /// </summary>
- public Vector3 AverageVelocity
- {
- get
- {
- var velocity = default(Vector3);
- for (int i = ActiveStatesInternal.Count - 1; i >= 0; i--)
- {
- var state = ActiveStatesInternal[i];
- velocity += state.AverageVelocity * state.Weight;
- }
- return velocity;
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Child States
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override int ChildCount
- => States.Count;
- /// <summary>Returns the state connected to the specified `index` as a child of this layer.</summary>
- /// <remarks>This method is identical to <see cref="this[int]"/>.</remarks>
- public override AnimancerState GetChild(int index)
- => States[index];
- /// <summary>Returns the state connected to the specified `index` as a child of this layer.</summary>
- /// <remarks>This indexer is identical to <see cref="GetChild(int)"/>.</remarks>
- public AnimancerState this[int index]
- => States[index];
- /************************************************************************************************************************/
- /// <summary>Connects the `state` to this layer at its <see cref="AnimancerNode.Index"/>.</summary>
- protected internal override void OnAddChild(AnimancerState state)
- {
- Validate.AssertGraph(state, Graph);
- var index = States.Count;
- state.Index = index;
- States.Add(state);
- if (_Capacity <= index)
- {
- _Capacity *= 2;
- _Playable.SetInputCount(_Capacity);
- }
- // If the state should be active, deactivate and properly add it to the active list.
- if (state.TryDeactivate())
- ActiveStatesInternal.Add(state);
- if (Graph.KeepChildrenConnected)
- ConnectChildUnsafe(state.Index, state);
- }
- /************************************************************************************************************************/
- /// <summary>Disconnects the `state` from this layer at its <see cref="AnimancerNode.Index"/>.</summary>
- protected internal override void OnRemoveChild(AnimancerState state)
- {
- var index = state.Index;
- Validate.AssertCanRemoveChild(state, States, States.Count);
- if (ActiveStatesInternal.Remove(state))
- state._ActiveIndex = 0;
- if (Graph._PlayableGraph.IsValid() &&
- _Playable.GetInput(index).IsValid())
- Graph._PlayableGraph.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];
- DisconnectChildSafe(last);
- States[index] = state;
- state.Index = index;
- if (state.IsActive || Graph.KeepChildrenConnected)
- ConnectChildUnsafe(index, state);
- }
- States.RemoveAt(last);
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected internal override void ApplyChildActive(AnimancerState state, bool setActive)
- {
- if (setActive)
- ActiveStatesInternal.Add(state);
- else
- ActiveStatesInternal.Remove(state);
- }
- /************************************************************************************************************************/
- /// <summary>[Internal] Connects all states in this layer.</summary>
- internal void ConnectAllStates()
- {
- for (int i = ChildCount - 1; i >= 0; i--)
- if (!_Playable.GetInput(i).IsValid())
- ConnectChildUnsafe(i, States[i]);
- }
- /// <summary>[Internal] Disconnects all states which are not <see cref="AnimancerState.IsActive"/>.</summary>
- internal void DisconnectInactiveStates()
- {
- for (int i = ChildCount - 1; i >= 0; i--)
- if (!States[i].IsActive)
- DisconnectChildSafe(i);
- }
- /************************************************************************************************************************/
- /// <summary>[Internal]
- /// Checks if any events should be invoked on any of the <see cref="ActiveStates"/>.
- /// </summary>
- internal void UpdateEvents()
- {
- if (Weight <= 0)
- return;
- if (FadeGroup != null &&
- FadeGroup.GetTargetWeight(this) == 0 &&
- !AnimancerState.RaiseEventsDuringFadeOut)
- return;
- for (int i = ActiveStatesInternal.Count - 1; i >= 0; i--)
- ActiveStatesInternal[i].UpdateEvents();
- }
- /************************************************************************************************************************/
- /// <summary>[Internal] Cancels the current <see cref="FadeGroup"/> so it can be object pooled.</summary>
- internal void OnGraphDestroyed()
- {
- CurrentState?.FadeGroup?.Cancel();
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override FastEnumerator<AnimancerState> GetEnumerator()
- => new(States);
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public sealed override void CopyFrom(AnimancerNode copyFrom, CloneContext context)
- => this.CopyFromBase(copyFrom, context);
- /// <summary>Copies the details of `copyFrom` into this layer.</summary>
- /// <remarks>Call <see cref="CopyStatesFrom"/> (as well) if you want to copy the states.</remarks>
- public virtual void CopyFrom(AnimancerLayer copyFrom, CloneContext context)
- {
- base.CopyFrom(copyFrom, context);
- IsAdditive = copyFrom.IsAdditive;
- Mask = copyFrom.Mask;
- }
- /************************************************************************************************************************/
- /// <summary>Copies the details of all states in `copyFrom` to their equivalent states in this layer.</summary>
- /// <remarks>
- /// Any states which do not have an equivalent in this layer will be cloned into this layer.
- /// <para></para>
- /// Call <see cref="CopyFrom(AnimancerLayer, CloneContext)"/> (as well)
- /// if you want to copy the details of the layer itself.
- /// </remarks>
- public void CopyStatesFrom(AnimancerLayer copyFrom, CloneContext context, bool includeInactive = false)
- {
- if (copyFrom == this)
- return;
- Debug.Assert(context.TryGetValue(copyFrom.Graph, out var thisGraph));
- Debug.Assert(thisGraph == Graph);
- for (int i = ActiveStatesInternal.Count - 1; i >= 0; i--)
- ActiveStatesInternal[i].Stop();
- CommandCount++;
- if (!includeInactive && Weight == 0)
- return;
- IReadOnlyList<AnimancerState> copyFromStates = includeInactive
- ? copyFrom.States
- : copyFrom.ActiveStatesInternal;
- var stateCount = copyFromStates.Count;
- for (int i = 0; i < stateCount; i++)
- {
- var state = copyFromStates[i];
- // If the clone already exists, copy over the state details.
- if (context.TryGetClone(state, out var clone))
- clone.CopyFrom(state, context);
- else// Otherwise, create a new clone.
- clone = context.Clone(state);
- if (clone.Parent != this)
- clone.SetParent(this);
- if (copyFrom.CurrentState == state)
- CurrentState = clone;
- // This should prevent the clones from being one frame behind after the next animation update
- // but it seems to only work for some states and not others.
- // clone.Time += clone.EffectiveSpeed * Time.deltaTime;
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Create State
- /************************************************************************************************************************/
- /// <summary>Creates and returns a new <see cref="ClipState"/> to play the `clip`.</summary>
- /// <remarks>
- /// <see cref="AnimancerGraph.GetKey"/> is used to determine the <see cref="AnimancerState.Key"/>.
- /// </remarks>
- public ClipState CreateState(AnimationClip clip)
- => CreateState(Graph.GetKey(clip), clip);
- /// <summary>
- /// Creates and returns a new <see cref="ClipState"/> to play the `clip` and registers it with the `key`.
- /// </summary>
- public ClipState CreateState(object key, AnimationClip clip)
- {
- var state = new ClipState(clip)
- {
- _Key = key,
- };
- state.SetParent(this);
- return state;
- }
- /************************************************************************************************************************/
- /// <summary>Returns a state registered with the `key` and attached to this layer or null if none exist.</summary>
- /// <exception cref="ArgumentNullException">The `key` is null.</exception>
- /// <remarks>
- /// 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.
- /// </remarks>
- 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;
- keyState.SetParent(this);
- 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 (!Graph.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.
- {
- state.SetParent(this);
- 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;
- }
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Calls <see cref="GetOrCreateState(AnimationClip, bool)"/> for each of the specified clips.
- /// <para></para>
- /// If you only want to create a single state, use <see cref="CreateState(AnimationClip)"/>.
- /// </summary>
- public void CreateIfNew(AnimationClip clip0, AnimationClip clip1)
- {
- GetOrCreateState(clip0);
- GetOrCreateState(clip1);
- }
- /// <summary>
- /// Calls <see cref="GetOrCreateState(AnimationClip, bool)"/> for each of the specified clips.
- /// <para></para>
- /// If you only want to create a single state, use <see cref="CreateState(AnimationClip)"/>.
- /// </summary>
- public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2)
- {
- GetOrCreateState(clip0);
- GetOrCreateState(clip1);
- GetOrCreateState(clip2);
- }
- /// <summary>
- /// Calls <see cref="GetOrCreateState(AnimationClip, bool)"/> for each of the specified clips.
- /// <para></para>
- /// If you only want to create a single state, use <see cref="CreateState(AnimationClip)"/>.
- /// </summary>
- public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2, AnimationClip clip3)
- {
- GetOrCreateState(clip0);
- GetOrCreateState(clip1);
- GetOrCreateState(clip2);
- GetOrCreateState(clip3);
- }
- /// <summary>
- /// Calls <see cref="GetOrCreateState(AnimationClip, bool)"/> for each of the specified clips.
- /// <para></para>
- /// If you only want to create a single state, use <see cref="CreateState(AnimationClip)"/>.
- /// </summary>
- 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);
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Calls <see cref="AnimancerGraph.GetKey"/> and returns the state registered with that key or
- /// creates one if it doesn't exist.
- /// <para></para>
- /// If the state already exists but has the wrong <see cref="AnimancerState.Clip"/>, the `allowSetClip`
- /// parameter determines what will happen. False causes it to throw an <see cref="ArgumentException"/> while
- /// true allows it to change the <see cref="AnimancerState.Clip"/>. Note that the change is somewhat costly to
- /// performance to use with caution.
- /// </summary>
- /// <exception cref="ArgumentException"/>
- public AnimancerState GetOrCreateState(AnimationClip clip, bool allowSetClip = false)
- {
- return GetOrCreateState(Graph.GetKey(clip), clip, allowSetClip);
- }
- /// <summary>
- /// Returns the state registered with the <see cref="IHasKey.Key"/> if there is one. Otherwise
- /// this method uses <see cref="ITransition.CreateState"/> to create a new one and registers it with
- /// that key before returning it.
- /// </summary>
- public AnimancerState GetOrCreateState(ITransition transition)
- {
- var key = transition.Key;
- var state = GetState(ref key);
- if (state == null)
- {
- state = transition.CreateState();
- state._Key = key;
- state.SetParent(this);
- }
- return state;
- }
- /// <summary>Returns the state registered with the `key` or creates one if it doesn't exist.</summary>
- /// <exception cref="ArgumentException"/>
- /// <exception cref="ArgumentNullException">The `key` is null.</exception>
- /// <remarks>
- /// If the state already exists but has the wrong <see cref="AnimancerState.Clip"/>, the `allowSetClip`
- /// parameter determines what will happen. False causes it to throw an <see cref="ArgumentException"/> while
- /// true allows it to change the <see cref="AnimancerState.Clip"/>. Note that the change is somewhat costly to
- /// performance to use with caution.
- /// <para></para>
- /// See also: <see cref="AnimancerStateDictionary.GetOrCreate(object, AnimationClip, bool)"/>.
- /// </remarks>
- 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(
- AnimancerStateDictionary.GetClipMismatchError(key, state.Clip, clip));
- }
- }
- return state;
- }
- /// <summary>Returns the `state` if it's a child of this layer. Otherwise makes a clone of it.</summary>
- public AnimancerState GetOrCreateState(AnimancerState state)
- {
- var parent = state.Parent;
- if (parent == this)
- return state;
- if (parent == null)
- {
- state.SetParent(this);
- return state;
- }
- var key = state.Key;
- key ??= state;
- var stateOnThisLayer = GetState(ref key);
- if (stateOnThisLayer == null)
- {
- stateOnThisLayer = state.Clone();
- stateOnThisLayer._Weight = 0;
- stateOnThisLayer.SetParent(this);
- stateOnThisLayer.Key = key;
- }
- return stateOnThisLayer;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// The maximum <see cref="AnimancerNode.Weight"/> that <see cref="GetOrCreateWeightlessState"/>
- /// will treat as being weightless. Default = 0.1.
- /// </summary>
- /// <remarks>This allows states with very small weights to be reused instead of needing to create new ones.</remarks>
- public static float WeightlessThreshold { get; set; } = 0.1f;
- /// <summary>
- /// 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.
- /// </summary>
- public static int MaxCloneCount { get; set; } = 3;
- /// <summary>
- /// If the `state`'s <see cref="AnimancerNode.Weight"/> is not currently low,
- /// this method finds or creates a copy of it which is low.
- /// The returned <see cref="AnimancerState.Time"/> is also set to 0.
- /// </summary>
- /// <remarks>
- /// If this method would exceed the <see cref="MaxCloneCount"/>, it returns the clone with the lowest weight.
- /// <para></para>
- /// "Low" weight is defined as less than or equal to the <see cref="WeightlessThreshold"/>.
- /// <para></para>
- /// The <see href="https://kybernetik.com.au/animancer/docs/manual/blending/fading/modes">Fade Modes</see> page
- /// explains why clones are created.
- /// </remarks>
- 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)
- {
- keyState.SetParent(this);
- 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 (!Graph.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 not ClipState
- ? SimpleTimer.Start()
- : SimpleTimer.Default;
- #endif
- state = keyState.Clone();
- state.SetDebugName($"[{cloneCount + 1}] {keyState}");
- state.Weight = 0;
- state.Key = key;
- if (state.Parent != this)
- state.SetParent(this);
- #if UNITY_ASSERTIONS
- if (cloneTimer.Count() > 0)
- {
- var milliseconds = cloneTimer.TotalTimeSeconds * 1000;
- OptionalWarning.CloneComplexState.Log(
- $"A {keyState.GetType().Name} was cloned in {milliseconds} 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 these clones are created:" +
- $" {Strings.DocsURLs.FadeModes}",
- Graph?.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)
- {
- state.SetParent(this);
- goto GotState;
- }
- cloneCount++;
- }
- GotState:
- state.TimeD = 0;
- return state;
- }
- /************************************************************************************************************************/
- /// <summary>Destroys all states connected to this layer.</summary>
- /// <remarks>This operation cannot be undone.</remarks>
- public void DestroyStates()
- {
- CurrentState?.FadeGroup?.Cancel();
- for (int i = States.Count - 1; i >= 0; i--)
- States[i].Destroy();
- States.Clear();
- CurrentState = null;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Play Management
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected internal override void OnStartFade()
- {
- CommandCount++;
- }
- /************************************************************************************************************************/
- // Play Immediately.
- /************************************************************************************************************************/
- /// <summary>Stops all other animations on this layer, plays the `clip`, and returns its state.</summary>
- /// <remarks>
- /// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
- /// To restart it from the beginning you can use <c>...Play(clip).Time = 0;</c>.
- /// <para></para>
- /// This method is safe to call repeatedly without checking whether the `clip` was already playing.
- /// </remarks>
- public AnimancerState Play(
- AnimationClip clip)
- => Play(GetOrCreateState(clip));
- /// <summary>Stops all other animations on the same layer, plays the `state`, and returns it.</summary>
- /// <remarks>
- /// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
- /// To restart it from the beginning you can use <c>...Play(state).Time = 0;</c>.
- /// <para></para>
- /// This method is safe to call repeatedly without checking whether the `state` was already playing.
- /// </remarks>
- /// <exception cref="InvalidOperationException">
- /// The <see cref="AnimancerNodeBase.Parent"/> is another state (likely a <see cref="ManualMixerState"/>).
- /// It must be either null or a layer.
- /// </exception>
- public AnimancerState Play(
- AnimancerState state)
- {
- #if UNITY_ASSERTIONS
- AnimancerEvent.AssertEventPlayMismatch(Graph);
- AnimancerState.AssertNotExpectingFade(state);
- 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 the layer is at 0 weight and not fading, set it to 1.
- if (Weight == 0 && FadeGroup == null)
- Weight = 1;
- CurrentState?.FadeGroup?.Cancel();
- state = GetOrCreateState(state);
- CurrentState = state;
- for (int i = ActiveStatesInternal.Count - 1; i >= 0; i--)
- {
- var otherState = ActiveStatesInternal[i];
- if (otherState != state)
- otherState.Stop();
- }
- // Similar to state.Play but more optimized.
- state.SetIsPlaying(true);
- state._Weight = 1;
- if (!state.IsActive)
- ActiveStatesInternal.Add(state);
- _Playable.ApplyChildWeight(state);
- return state;
- }
- /************************************************************************************************************************/
- // Cross Fade.
- /************************************************************************************************************************/
- /// <summary>
- /// Starts fading in the `clip` over the course of the `fadeDuration`
- /// while fading out all others in the same layer. Returns its state.
- /// </summary>
- /// <remarks>
- /// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`,
- /// this method will allow it to complete the existing fade rather than starting a slower one.
- /// <para></para>
- /// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>,
- /// this method will fade in the layer itself and simply <see cref="AnimancerState.Play"/> the `state`.
- /// <para></para>
- /// This method is safe to call repeatedly without checking whether the `state` was already playing.
- /// <para></para>
- /// <em>Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
- /// </remarks>
- public AnimancerState Play(
- AnimationClip clip,
- float fadeDuration,
- FadeMode mode = default)
- {
- var key = Graph.GetKey(clip);
- var state = GetOrCreateState(key, clip);
- if (Graph.Transitions != null)
- fadeDuration = Graph.Transitions.GetFadeDuration(this, key, fadeDuration);
- return Play(state, fadeDuration, mode);
- }
- /// <summary>
- /// Starts fading in the `state` over the course of the `fadeDuration` while fading out all others in this
- /// layer. Returns the `state`.
- /// </summary>
- /// <remarks>
- /// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
- /// method will allow it to complete the existing fade rather than starting a slower one.
- /// <para></para>
- /// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
- /// and simply <see cref="AnimancerState.Play"/> the `state`.
- /// <para></para>
- /// This method is safe to call repeatedly without checking whether the `state` was already playing.
- /// <para></para>
- /// <em>Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
- /// </remarks>
- public AnimancerState Play(
- AnimancerState state,
- float fadeDuration,
- FadeMode mode = default)
- {
- // Skip the fade if:
- if (fadeDuration <= 0 ||// There is no duration.
- (Graph.SkipFirstFade && Index == 0 && Weight == 0))// Or this is Layer 0 and it has no weight.
- {
- Weight = 1;
- AnimancerState.SkipNextExpectFade();
- state = Play(state);
- if (mode == FadeMode.FromStart ||
- mode == FadeMode.NormalizedFromStart)
- state.TimeD = 0;
- return state;
- }
- AnimancerEvent.AssertEventPlayMismatch(Graph);
- EvaluateFadeMode(mode, ref state, fadeDuration, out var stateFadeSpeed, out var layerFadeDuration);
- StartFade(1, layerFadeDuration);
- // If the layer has to fade in, play the state immediately.
- if (Weight == 0)
- {
- AnimancerState.SkipNextExpectFade();
- 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.
- if (IsAlreadyFadingIn(state, fadeDuration))
- {
- CommandCount++;// Still pretend the fade was restarted.
- }
- else// Otherwise fade in the target state and fade out all others.
- {
- state.IsPlaying = true;
- var fade = GetFade();
- fade.SetNodes(this, state, ActiveStatesInternal, Graph.KeepChildrenConnected);
- fade.StartFade(1, stateFadeSpeed);
- }
- return state;
- }
- /************************************************************************************************************************/
- /// <summary>Is the `state` already faded in or fading in with shorter than the given `fadeDuration`?</summary>
- private static bool IsAlreadyFadingIn(
- AnimancerState state,
- float fadeDuration)
- {
- if (!state.IsPlaying)
- return false;
- var fadeGroup = state.FadeGroup;
- if (fadeGroup == null)
- return state.Weight == 1;
- return
- fadeGroup.FadeIn.Node == state &&
- fadeGroup.TargetWeight == 1 &&
- fadeGroup.RemainingFadeDuration <= fadeDuration;
- }
- /************************************************************************************************************************/
- /// <summary>Clears and reuses the existing fade if there is one. Otherwise gets one from the object pool.</summary>
- private FadeGroup GetFade()
- {
- if (CurrentState != null)
- {
- var fade = CurrentState.FadeGroup;
- if (fade != null)
- {
- fade.FadeOutInternal.Clear();
- fade.Easing = null;
- return fade;
- }
- }
- return FadeGroup.Pool.Instance.Acquire();
- }
- /************************************************************************************************************************/
- // Transition.
- /************************************************************************************************************************/
- /// <summary>
- /// Creates a state for the `transition` if it didn't already exist, then calls
- /// <see cref="Play(AnimancerState)"/> or <see cref="Play(AnimancerState, float, FadeMode)"/>
- /// depending on the <see cref="ITransition.FadeDuration"/>.
- /// </summary>
- /// <remarks>
- /// This method is safe to call repeatedly without checking whether the `transition` was already playing.
- /// </remarks>
- public AnimancerState Play(
- ITransition transition)
- => Graph.Transitions != null
- ? Graph.Transitions.Play(this, transition)
- : Play(transition, transition.FadeDuration, transition.FadeMode);
- /// <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 the <see cref="ITransition.FadeDuration"/>.
- /// </summary>
- /// <remarks>
- /// This method is safe to call repeatedly without checking whether the `transition` was already playing.
- /// </remarks>
- public AnimancerState Play(
- ITransition transition,
- float fadeDuration,
- FadeMode mode = default)
- {
- var state = GetOrCreateState(transition);
- state = Play(state, fadeDuration, mode);
- transition.Apply(state);
- return state;
- }
- /************************************************************************************************************************/
- // Try Play.
- /************************************************************************************************************************/
- /// <summary>
- /// Stops all other animations on this layer,
- /// plays the animation registered with the `key`,
- /// and returns the animation's state.
- /// </summary>
- /// <remarks>
- /// 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 simply set the returned state's time to 0.
- /// <para></para>
- /// This method is safe to call repeatedly without checking whether the animation was already playing.
- /// </remarks>
- public AnimancerState TryPlay(
- object key)
- {
- if (Graph.Transitions != null)
- {
- var transitionState = Graph.Transitions.TryPlay(this, key);
- if (transitionState != null)
- return transitionState;
- }
- return Graph.States.TryGet(key, out var state)
- ? Play(state)
- : null;
- }
- /// <summary>
- /// Stops all other animations on this layer,
- /// plays the animation registered with the `key`,
- /// and returns the animation's state.
- /// </summary>
- /// <remarks>
- /// 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 simply set the returned state's time to 0.
- /// <para></para>
- /// This method is safe to call repeatedly without checking whether the animation was already playing.
- /// </remarks>
- public AnimancerState TryPlay(
- IHasKey hasKey)
- => TryPlay(hasKey.Key);
- /// <summary>
- /// Starts fading in the animation registered with the `key`
- /// while fading out all others in the same layer over the course of the `fadeDuration`
- /// and returns the animation's state.
- /// </summary>
- /// <remarks>
- /// If no state is registered with the `key`, this method does nothing and returns null.
- /// <para></para>
- /// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`,
- /// this method allows it to continue the existing fade rather than starting a slower one.
- /// <para></para>
- /// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will
- /// fade in the layer itself and simply <see cref="AnimancerState.Play"/> the `state`.
- /// <para></para>
- /// This method is safe to call repeatedly without checking whether the animation was already playing.
- /// <para></para>
- /// <em>Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
- /// </remarks>
- public AnimancerState TryPlay(
- object key,
- float fadeDuration,
- FadeMode mode = default)
- => Graph.States.TryGet(key, out var state)
- ? Play(state, fadeDuration, mode)
- : null;
- /// <summary>
- /// Starts fading in the animation registered with the `key`
- /// while fading out all others in the same layer over the course of the `fadeDuration`
- /// and returns the animation's state.
- /// </summary>
- /// <remarks>
- /// If no state is registered with the `key`, this method does nothing and returns null.
- /// <para></para>
- /// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`,
- /// this method allows it to continue the existing fade rather than starting a slower one.
- /// <para></para>
- /// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will
- /// fade in the layer itself and simply <see cref="AnimancerState.Play"/> the `state`.
- /// <para></para>
- /// This method is safe to call repeatedly without checking whether the animation was already playing.
- /// <para></para>
- /// <em>Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
- /// </remarks>
- public AnimancerState TryPlay(
- IHasKey hasKey,
- float fadeDuration,
- FadeMode mode = default)
- => TryPlay(hasKey.Key, fadeDuration, mode);
- /************************************************************************************************************************/
- /// <summary>Manipulates the other parameters according to the `mode`.</summary>
- /// <exception cref="ArgumentException">
- /// The <see cref="AnimancerState.Clip"/> is null when using <see cref="FadeMode.FromStart"/> or
- /// <see cref="FadeMode.NormalizedFromStart"/>.
- /// </exception>
- private void EvaluateFadeMode(
- FadeMode mode,
- ref AnimancerState state,
- float fadeDuration,
- out float stateFadeSpeed,
- out float layerFadeDuration)
- {
- layerFadeDuration = fadeDuration;
- float fadeDistance;
- switch (mode)
- {
- case FadeMode.FixedSpeed:
- fadeDistance = 1;
- layerFadeDuration *= Math.Abs(1 - Weight);
- break;
- case FadeMode.FixedDuration:
- fadeDistance = Math.Abs(1 - state.Weight);
- break;
- case FadeMode.FromStart:
- state = GetOrCreateWeightlessState(state);
- fadeDistance = 1;
- break;
- case FadeMode.NormalizedSpeed:
- {
- var length = state.Length;
- fadeDistance = 1;
- fadeDuration *= length;
- layerFadeDuration *= Math.Abs(1 - Weight) * length;
- }
- break;
- case FadeMode.NormalizedDuration:
- {
- var length = state.Length;
- fadeDistance = Math.Abs(1 - state.Weight);
- fadeDuration *= length;
- layerFadeDuration *= length;
- }
- break;
- case FadeMode.NormalizedFromStart:
- {
- state = GetOrCreateWeightlessState(state);
- var length = state.Length;
- fadeDistance = 1;
- fadeDuration *= length;
- layerFadeDuration *= length;
- }
- break;
- default:
- throw AnimancerUtilities.CreateUnsupportedArgumentException(mode);
- }
- stateFadeSpeed = fadeDistance / fadeDuration;
- }
- /************************************************************************************************************************/
- // Stopping
- /************************************************************************************************************************/
- /// <summary>
- /// Sets <see cref="AnimancerNode.Weight"/> = 0 and calls <see cref="AnimancerNode.Stop"/>
- /// on all animations to stop them from playing and rewind them to the start.
- /// </summary>
- protected internal override void StopWithoutWeight()
- {
- CurrentState = null;
- for (int i = ActiveStatesInternal.Count - 1; i >= 0; i--)
- ActiveStatesInternal[i].Stop();
- }
- /************************************************************************************************************************/
- // Checking
- /************************************************************************************************************************/
- /// <summary>
- /// Returns true if the `clip` is currently being played by at least one state.
- /// </summary>
- public bool IsPlayingClip(AnimationClip clip)
- {
- for (int i = ActiveStatesInternal.Count - 1; i >= 0; i--)
- {
- var state = ActiveStatesInternal[i];
- if (state.Clip == clip && state.IsPlaying)
- return true;
- }
- return false;
- }
- /// <summary>
- /// Returns true if at least one animation is being played.
- /// </summary>
- public bool IsAnyStatePlaying()
- {
- for (int i = ActiveStatesInternal.Count - 1; i >= 0; i--)
- if (ActiveStatesInternal[i].IsPlaying)
- return true;
- return false;
- }
- /// <summary>
- /// Returns true if the <see cref="CurrentState"/> is playing and hasn't yet reached its end.
- /// <para></para>
- /// This method is called by <see cref="IEnumerator.MoveNext"/> so this object can be used as a custom yield
- /// instruction to wait until it finishes.
- /// </summary>
- public override bool IsPlayingAndNotEnding()
- => _CurrentState != null && _CurrentState.IsPlayingAndNotEnding();
- /************************************************************************************************************************/
- /// <summary>
- /// Calculates the total <see cref="AnimancerNode.Weight"/> of all states in this layer.
- /// </summary>
- public float GetTotalChildWeight()
- {
- float weight = 0;
- for (int i = ActiveStatesInternal.Count - 1; i >= 0; i--)
- {
- weight += ActiveStatesInternal[i].Weight;
- }
- return weight;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Inverse Kinematics
- /************************************************************************************************************************/
- private bool _ApplyAnimatorIK;
- /// <inheritdoc/>
- public override bool ApplyAnimatorIK
- {
- get => _ApplyAnimatorIK;
- set => base.ApplyAnimatorIK = _ApplyAnimatorIK = value;
- }
- /************************************************************************************************************************/
- private bool _ApplyFootIK;
- /// <inheritdoc/>
- public override bool ApplyFootIK
- {
- get => _ApplyFootIK;
- set => base.ApplyFootIK = _ApplyFootIK = value;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Other
- /************************************************************************************************************************/
- /// <summary>[<see cref="IAnimationClipCollection"/>]
- /// Gathers all the animations in this layer.
- /// </summary>
- public void GatherAnimationClips(ICollection<AnimationClip> clips)
- => clips.GatherFromSource(States);
- /************************************************************************************************************************/
- /// <summary>The Inspector display name of this layer.</summary>
- public override string ToString()
- {
- #if UNITY_ASSERTIONS
- if (DebugName == null)
- {
- if (_Mask != null)
- return _Mask.GetCachedName();
- SetDebugName(Index == 0
- ? "Base Layer"
- : "Layer " + Index);
- }
- return base.ToString();
- #else
- return "Layer " + Index;
- #endif
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected override void AppendDetails(StringBuilder text, string separator)
- {
- base.AppendDetails(text, separator);
- text.AppendField(separator, nameof(CurrentState), CurrentState?.GetPath());
- text.AppendField(separator, nameof(CommandCount), CommandCount);
- text.AppendField(separator, nameof(IsAdditive), IsAdditive);
- text.AppendField(separator, nameof(Mask), Mask);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
|