1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207 |
- // 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.Playables;
- using Object = UnityEngine.Object;
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
- namespace Animancer
- {
- /// <summary>
- /// Base class for all states in an <see cref="AnimancerGraph"/> graph which manages one or more
- /// <see cref="Playable"/>s.
- /// </summary>
- ///
- /// <remarks>
- /// This class can be used as a custom yield instruction to wait until the animation either stops playing or
- /// reaches its end.
- /// <para></para>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/states">
- /// States</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerState
- ///
- public abstract partial class AnimancerState : AnimancerNode,
- IAnimationClipCollection,
- ICloneable<AnimancerState>,
- ICopyable<AnimancerState>
- {
- /************************************************************************************************************************/
- #region Graph
- /************************************************************************************************************************/
- /// <summary>Sets the <see cref="AnimancerNodeBase.Graph"/>.</summary>
- /// <exception cref="InvalidOperationException">
- /// The <see cref="AnimancerNodeBase.Parent"/> has a different <see cref="AnimancerNodeBase.Graph"/>.
- /// Setting the <see cref="AnimancerNodeBase.Parent"/>'s <see cref="AnimancerNodeBase.Graph"/>
- /// will apply to its children recursively because they must always match.
- /// </exception>
- public virtual void SetGraph(AnimancerGraph graph)
- {
- if (Graph == graph)
- return;
- RemoveFromOldGraph(graph);
- Graph = graph;
- AddToNewGraph();
- FadeGroup?.ChangeGraph(graph);
- }
- private void RemoveFromOldGraph(AnimancerGraph newGraph)
- {
- if (Graph == null)
- {
- #if UNITY_ASSERTIONS
- if (Parent != null && Parent.Graph != newGraph)
- throw new InvalidOperationException(
- "Unable to set the Graph of a state which has a Parent." +
- " Setting the Parent's Graph will apply to its children recursively" +
- " because they must always match.");
- #endif
- return;
- }
- Graph.States.Unregister(this);
- if (Parent != null && Parent.Graph != newGraph)
- {
- Parent.OnRemoveChild(this);
- Parent = null;
- Index = -1;
- }
- _Time = TimeD;
- DestroyPlayable();
- }
- private void AddToNewGraph()
- {
- if (Graph != null)
- {
- Graph.States.Register(this);
- CreatePlayable();
- }
- for (int i = ChildCount - 1; i >= 0; i--)
- GetChild(i)?.SetGraph(Graph);
- if (Parent != null)
- CopyIKFlags(Parent);
- }
- /************************************************************************************************************************/
- /// <summary>Connects this state to the `parent` at its next available child index.</summary>
- /// <remarks>If the `parent` is <c>null</c>, this state will be disconnected from everything.</remarks>
- public void SetParent(AnimancerNode parent)
- {
- #if UNITY_ASSERTIONS
- if (Parent == parent)
- Debug.LogWarning(
- $"{nameof(Parent)} is already set to {AnimancerUtilities.ToStringOrNull(parent)}.",
- Graph?.Component as Object);
- #endif
- if (Parent != null)
- {
- Parent.OnRemoveChild(this);
- Parent = null;
- }
- if (parent == null)
- {
- FadeGroup?.ChangeParent(this);
- Index = -1;
- return;
- }
- SetGraph(parent.Graph);
- Parent = parent;
- parent.OnAddChild(this);
- CopyIKFlags(parent);
- FadeGroup?.ChangeParent(this);
- }
- /// <summary>[Internal]
- /// Directly sets the <see cref="AnimancerNodeBase.Parent"/> and <see cref="AnimancerNode.Index"/>
- /// without triggering any other connection methods.
- /// </summary>
- internal void SetParentInternal(AnimancerNode parent, int index = -1)
- {
- Parent = parent;
- Index = index;
- }
- /************************************************************************************************************************/
- // Layer.
- /************************************************************************************************************************/
- /// <summary>
- /// The index of the <see cref="AnimancerLayer"/> this state is connected to
- /// (determined by the <see cref="AnimancerNodeBase.Parent"/>).
- /// </summary>
- /// <returns><c>-1</c> if this state isn't connected to a layer.</returns>
- public int LayerIndex
- {
- get
- {
- if (Parent == null)
- return -1;
- var layer = Parent.Layer;
- if (layer == null)
- return -1;
- return layer.Index;
- }
- set => SetParent(value >= 0
- ? Graph.Layers[value]
- : null);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Key and Clip
- /************************************************************************************************************************/
- internal object _Key;
- /// <summary>
- /// The object used to identify this state in the graph <see cref="AnimancerGraph.States"/> dictionary.
- /// Can be null.
- /// </summary>
- public object Key
- {
- get => _Key;
- set
- {
- if (Graph == null)
- {
- _Key = value;
- }
- else
- {
- Graph.States.Unregister(this);
- _Key = value;
- Graph.States.Register(this);
- }
- }
- }
- /************************************************************************************************************************/
- /// <summary>The <see cref="AnimationClip"/> which this state plays (if any).</summary>
- /// <exception cref="NotSupportedException">This state type doesn't have a clip and you try to set it.</exception>
- public virtual AnimationClip Clip
- {
- get => null;
- set
- {
- MarkAsUsed(this);
- throw new NotSupportedException($"{GetType()} doesn't support setting the {nameof(Clip)}.");
- }
- }
- /// <summary>The main object to show in the Inspector for this state (if any).</summary>
- /// <exception cref="NotSupportedException">This state type doesn't have a main object and you try to set it.</exception>
- /// <exception cref="InvalidCastException">This state can't use the assigned value.</exception>
- public virtual Object MainObject
- {
- get => null;
- set
- {
- MarkAsUsed(this);
- throw new NotSupportedException($"{GetType()} doesn't support setting the {nameof(MainObject)}.");
- }
- }
- #if UNITY_EDITOR
- /// <summary>[Editor-Only] The base type which can be assigned to the <see cref="MainObject"/>.</summary>
- public virtual Type MainObjectType
- => null;
- #endif
- /************************************************************************************************************************/
- /// <summary>
- /// Sets the `currentObject` and calls <see cref="AnimancerNode.RecreatePlayable"/>.
- /// If the `currentObject` was being used as the <see cref="Key"/> then it is changed as well.
- /// </summary>
- /// <exception cref="ArgumentNullException">The `newObject` is null.</exception>
- protected bool ChangeMainObject<T>(ref T currentObject, T newObject)
- where T : Object
- {
- if (newObject == null)
- {
- MarkAsUsed(this);
- throw new ArgumentNullException(nameof(newObject));
- }
- if (ReferenceEquals(currentObject, newObject))
- return false;
- if (ReferenceEquals(_Key, currentObject))
- Key = newObject;
- currentObject = newObject;
- if (Graph != null)
- RecreatePlayable();
- return true;
- }
- /************************************************************************************************************************/
- /// <summary>The average velocity of the Root Motion caused by this state.</summary>
- public virtual Vector3 AverageVelocity => default;
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Playing
- /************************************************************************************************************************/
- /// <summary>Is the <see cref="Time"/> automatically advancing?</summary>
- private bool _IsPlaying;
- /************************************************************************************************************************/
- /// <summary>Is the <see cref="Time"/> automatically advancing?</summary>
- ///
- /// <remarks>
- /// <strong>Example:</strong><code>
- /// void IsPlayingExample(AnimancerComponent animancer, AnimationClip clip)
- /// {
- /// var state = animancer.States.GetOrCreate(clip);
- ///
- /// if (state.IsPlaying)
- /// Debug.Log(clip + " is playing");
- /// else
- /// Debug.Log(clip + " is paused");
- ///
- /// state.IsPlaying = false;// Pause the animation.
- ///
- /// state.IsPlaying = true;// Unpause the animation.
- /// }
- /// </code></remarks>
- public bool IsPlaying
- {
- get => _IsPlaying;
- set
- {
- SetIsPlaying(value);
- UpdateIsActive();
- }
- }
- /// <summary>
- /// Sets <see cref="IsPlaying"/> and applies it to the <see cref="Playable"/>
- /// without calling <see cref="UpdateIsActive"/>.
- /// </summary>
- protected internal void SetIsPlaying(bool isPlaying)
- {
- if (_IsPlaying == isPlaying)
- return;
- _IsPlaying = isPlaying;
- if (_Playable.IsValid())
- {
- if (_IsPlaying)
- _Playable.Play();
- else
- _Playable.Pause();
- }
- OnSetIsPlaying();
- }
- /// <summary>Called when the value of <see cref="IsPlaying"/> is changed.</summary>
- protected virtual void OnSetIsPlaying() { }
- /************************************************************************************************************************/
- /// <summary>Creates and assigns the <see cref="Playable"/> managed by this state.</summary>
- /// <remarks>This method also applies the <see cref="AnimancerNodeBase.Speed"/> and <see cref="IsPlaying"/>.</remarks>
- protected sealed override void CreatePlayable()
- {
- base.CreatePlayable();
- if (Parent != null && (IsActive || Parent.KeepChildrenConnected))
- Graph._PlayableGraph.Connect(Parent.Playable, Playable, Index, Weight);
- if (!_IsPlaying)
- _Playable.Pause();
- RawTime = _Time;
- }
- /************************************************************************************************************************/
- /// <summary>Is this state playing and not fading out?</summary>
- /// <remarks>
- /// If true, this state will usually be the <see cref="AnimancerLayer.CurrentState"/> but that is not always
- /// the case.
- /// </remarks>
- public bool IsCurrent
- => _IsPlaying
- && TargetWeight > 0;
- /// <summary>Is this state not playing and at 0 <see cref="AnimancerNode.Weight"/>?</summary>
- public bool IsStopped
- => !_IsPlaying
- && Weight == 0;
- /************************************************************************************************************************/
- /// <summary>
- /// Plays this state immediately, without any blending and without affecting any other states.
- /// </summary>
- /// <remarks>
- /// Unlike <see cref="AnimancerLayer.Play(AnimancerState)"/>,
- /// this method only affects this state and won't stop any others that are playing.
- /// <para></para>
- /// Sets <see cref="IsPlaying"/> = true and <see cref="AnimancerNode.Weight"/> = 1.
- /// <para></para>
- /// Doesn't change the <see cref="Time"/> so it will continue from its current value.
- /// </remarks>
- public void Play()
- {
- SetIsPlaying(true);
- Weight = 1;
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected internal override void StopWithoutWeight()
- {
- SetIsPlaying(false);
- TimeD = 0;
- UpdateIsActive();
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected internal override void OnStartFade()
- {
- UpdateIsActive();
- }
- /// <inheritdoc/>
- protected internal override void InternalClearFade()
- {
- base.InternalClearFade();
- UpdateIsActive();
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Active
- /************************************************************************************************************************/
- /// <summary>
- /// The index of this state in its parent <see cref="AnimancerLayer.ActiveStates"/> (or -1 if inactive).
- /// </summary>
- /// <remarks>
- /// If this state's direct parent isn't a layer (such as a child of a mixer), this value simply uses 0 to
- /// indicate active.
- /// </remarks>
- internal int _ActiveIndex = ActiveList.NotInList;
- /************************************************************************************************************************/
- /// <summary>Is this state currently updating or affecting the animation output?</summary>
- /// <remarks>
- /// This property is true when <see cref="IsPlaying"/> or the <see cref="AnimancerNode.Weight"/> or
- /// <see cref="AnimancerNode.TargetWeight"/> are above 0.
- /// </remarks>
- public bool IsActive
- => _ActiveIndex >= 0;
- /// <summary>[Internal] Should <see cref="IsActive"/> be true based on the current details of this state?</summary>
- internal bool ShouldBeActive
- {
- get => IsPlaying
- || Weight > 0
- || FadeGroup != null;
- set => _ActiveIndex = value ? 0 : -1;
- }
- /// <summary>[Internal] If <see cref="IsActive"/> this method sets it to false and returns true.</summary>
- internal bool TryDeactivate()
- {
- if (_ActiveIndex < 0)
- return false;
- _ActiveIndex = ActiveList.NotInList;
- return true;
- }
- /// <summary>Called when <see cref="IsActive"/> might change.</summary>
- internal void UpdateIsActive()
- {
- var shouldBeActive = ShouldBeActive;
- if (IsActive == shouldBeActive)
- return;
- var parent = Parent;
- if (parent != null)
- parent.ApplyChildActive(this, shouldBeActive);
- else
- ShouldBeActive = ShouldBeActive;
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public sealed override void SetWeight(float value)
- {
- base.SetWeight(value);
- UpdateIsActive();
- }
- /************************************************************************************************************************/
- /// <summary>[Internal] An <see cref="IIndexer{T}"/> based on <see cref="_ActiveIndex"/>.</summary>
- internal readonly struct Indexer : IIndexer<AnimancerState>
- {
- /************************************************************************************************************************/
- /// <summary>The <see cref="AnimancerNodeBase.Graph"/>.</summary>
- public readonly AnimancerGraph Graph;
- /// <summary>The <see cref="Playable"/> of the <see cref="AnimancerNodeBase.Parent"/>.</summary>
- public readonly Playable ParentPlayable;
- /************************************************************************************************************************/
- /// <summary>Creates a new <see cref="Indexer"/>.</summary>
- public Indexer(AnimancerGraph graph, Playable parentPlayable)
- {
- Graph = graph;
- ParentPlayable = parentPlayable;
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public readonly int GetIndex(AnimancerState state)
- => state._ActiveIndex;
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public readonly void SetIndex(AnimancerState state, int index)
- {
- if (!Graph.KeepChildrenConnected && state._ActiveIndex < 0)
- {
- Validate.AssertPlayable(state);
- Graph._PlayableGraph.Connect(ParentPlayable, state._Playable, state.Index, state.Weight);
- }
- state._ActiveIndex = index;
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public readonly void ClearIndex(AnimancerState state)
- {
- if (!Graph.KeepChildrenConnected)
- Graph._PlayableGraph.Disconnect(ParentPlayable, state.Index);
- state._ActiveIndex = ActiveList.NotInList;
- }
- /************************************************************************************************************************/
- }
- /************************************************************************************************************************/
- /// <summary>[Internal]
- /// An <see cref="IndexedList{TItem, TAccessor}"/> of <see cref="AnimancerState"/>s
- /// which tracks <see cref="IsActive"/>.
- /// </summary>
- internal class ActiveList : IndexedList<AnimancerState, Indexer>
- {
- /// <summary>The default <see cref="IndexedList{TItem, TIndexer}.Capacity"/> for newly created lists.</summary>
- /// <remarks>Default value is 4.</remarks>
- public static new int DefaultCapacity { get; set; } = 4;
- /// <summary>Creates a new <see cref="ActiveList"/> with the <see cref="DefaultCapacity"/>.</summary>
- public ActiveList(Indexer accessor)
- : base(DefaultCapacity, accessor)
- { }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Timing
- /************************************************************************************************************************/
- // Time.
- /************************************************************************************************************************/
- /// <summary>
- /// The current time of the <see cref="Playable"/>, retrieved by <see cref="Time"/> whenever the
- /// <see cref="_TimeFrameID"/> is different from the <see cref="AnimancerGraph.FrameID"/>.
- /// </summary>
- private double _Time;
- /// <summary>
- /// The <see cref="AnimancerGraph.FrameID"/> from when the <see cref="Time"/> was last retrieved from the
- /// <see cref="Playable"/>.
- /// </summary>
- private ulong _TimeFrameID;
- /************************************************************************************************************************/
- /// <summary>The number of seconds that have passed since the start of this animation.</summary>
- ///
- /// <remarks>
- /// This value continues increasing after the animation passes the end of its
- /// <see cref="Length"/>, regardless of whether it <see cref="IsLooping"/> or not.
- /// <para></para>
- /// The underlying <see cref="double"/> can be accessed via <see cref="TimeD"/>.
- /// <para></para>
- /// Setting this value will skip Events and Root Motion between the old and new time.
- /// Use <see cref="MoveTime(float, bool)"/> instead if you don't want that behaviour.
- /// <para></para>
- /// <em>Animancer Lite doesn't allow this value to be changed in runtime builds (except resetting it to 0).</em>
- /// <para></para>
- /// <strong>Example:</strong><code>
- /// void TimeExample(AnimancerComponent animancer, AnimationClip clip)
- /// {
- /// var state = animancer.Play(clip);
- ///
- /// // Skip 0.5 seconds into the animation:
- /// state.Time = 0.5f;
- ///
- /// // Skip 50% of the way through the animation (0.5 in a range of 0 to 1):
- /// state.NormalizedTime = 0.5f;
- ///
- /// // Skip to the end of the animation and play backwards:
- /// state.NormalizedTime = 1;
- /// state.Speed = -1;
- /// }
- /// </code></remarks>
- public float Time
- {
- get => (float)TimeD;
- set => TimeD = value;
- }
- /// <summary>The underlying <see cref="double"/> value of <see cref="Time"/>.</summary>
- public double TimeD
- {
- get
- {
- var graph = Graph;
- if (graph == null)
- return _Time;
- var frameID = graph.FrameID;
- if (_TimeFrameID != frameID)
- {
- _TimeFrameID = frameID;
- _Time = RawTime;
- }
- return _Time;
- }
- set
- {
- #if UNITY_ASSERTIONS
- if (!value.IsFinite())
- {
- MarkAsUsed(this);
- throw new ArgumentOutOfRangeException(
- nameof(value),
- value,
- $"{nameof(Time)} {Strings.MustBeFinite}");
- }
- #endif
- _Time = value;
- var graph = Graph;
- if (graph != null)
- {
- _TimeFrameID = graph.FrameID;
- RawTime = value;
- }
- _EventDispatcher?.OnSetTime();
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// The internal implementation of <see cref="Time"/> which directly gets and sets the underlying value.
- /// </summary>
- /// <remarks>
- /// This property should generally not be accessed directly.
- /// <para></para>
- /// Setting this value will skip Events and Root Motion between the old and new time.
- /// Use <see cref="MoveTime(float, bool)"/> instead if you don't want that behaviour.
- /// </remarks>
- public virtual double RawTime
- {
- get
- {
- Validate.AssertPlayable(this);
- return _Playable.GetTime();
- }
- set
- {
- Validate.AssertPlayable(this);
- var time = value;
- _Playable.SetTime(time);
- _Playable.SetTime(time);
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// The <see cref="Time"/> of this state as a portion of the animation's <see cref="Length"/>, meaning the
- /// value goes from 0 to 1 as it plays from start to end, regardless of how long that actually takes.
- /// </summary>
- ///
- /// <remarks>
- /// This value continues increasing after the animation passes the end of its
- /// <see cref="Length"/>, regardless of whether it <see cref="IsLooping"/> or not.
- /// <para></para>
- /// The fractional part of the value (<c>NormalizedTime % 1</c>)
- /// is the percentage (0-1) of progress in the current loop
- /// while the integer part (<c>(int)NormalizedTime</c>)
- /// is the number of times the animation has been looped.
- /// <para></para>
- /// Setting this value will skip Events and Root Motion between the old and new time.
- /// Use <see cref="MoveTime(float, bool)"/> instead if you don't want that behaviour.
- /// <para></para>
- /// <em>Animancer Lite doesn't allow this value to be changed in runtime builds (except resetting it to 0).</em>
- /// <para></para>
- /// <strong>Example:</strong><code>
- /// void TimeExample(AnimancerComponent animancer, AnimationClip clip)
- /// {
- /// var state = animancer.Play(clip);
- ///
- /// // Skip 0.5 seconds into the animation:
- /// state.Time = 0.5f;
- ///
- /// // Skip 50% of the way through the animation (0.5 in a range of 0 to 1):
- /// state.NormalizedTime = 0.5f;
- ///
- /// // Skip to the end of the animation and play backwards:
- /// state.NormalizedTime = 1;
- /// state.Speed = -1;
- /// }
- /// </code></remarks>
- public float NormalizedTime
- {
- get => (float)NormalizedTimeD;
- set => NormalizedTimeD = value;
- }
- /// <summary>The underlying <see cref="double"/> value of <see cref="NormalizedTime"/>.</summary>
- public double NormalizedTimeD
- {
- get
- {
- var length = Length;
- if (length != 0)
- return TimeD / length;
- else
- return 0;
- }
- set => TimeD = value * Length;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Sets the <see cref="Time"/> or <see cref="NormalizedTime"/>, but unlike those properties
- /// this method doesn't skip Events or Root Motion between the old and new time.
- /// </summary>
- /// <remarks>
- /// The Events and Root Motion will be applied during the next animation update.
- /// If you want to apply them immediately you can call <see cref="AnimancerGraph.Evaluate()"/>.
- /// <para></para>
- /// Events are triggered where <c>old time <= event time < new time</c>.
- /// <para></para>
- /// Avoid calling this method more than once per frame because doing so will cause
- /// Animation Events and Root Motion to be skipped due to an unfortunate design
- /// decision in the Playables API. Animancer Events would still be triggered,
- /// but only between the old time and the last new time you set
- /// (any other values would be ignored).
- /// </remarks>
- public void MoveTime(float time, bool normalized)
- => MoveTime((double)time, normalized);
- /// <summary>
- /// Sets the <see cref="Time"/> or <see cref="NormalizedTime"/>, but unlike those properties
- /// this method doesn't skip Events or Root Motion between the old and new time.
- /// </summary>
- /// <remarks>
- /// The Events and Root Motion will be applied during the next animation update.
- /// If you want to apply them immediately you can call <see cref="AnimancerGraph.Evaluate()"/>.
- /// <para></para>
- /// Avoid calling this method more than once per frame because doing so will cause
- /// Animation Events and Root Motion to be skipped due to an unfortunate design
- /// decision in the Playables API. Animancer Events would still be triggered,
- /// but only between the old time and the last new time you set
- /// (any other values would be ignored).
- /// </remarks>
- public virtual void MoveTime(double time, bool normalized)
- {
- #if UNITY_ASSERTIONS
- if (!time.IsFinite())
- {
- MarkAsUsed(this);
- throw new ArgumentOutOfRangeException(nameof(time), time,
- $"{nameof(Time)} {Strings.MustBeFinite}");
- }
- #endif
- var graph = Graph;
- if (graph != null)
- _TimeFrameID = graph.FrameID;
- if (normalized)
- time *= Length;
- _Time = time;
- _Playable.SetTime(time);
- }
- /************************************************************************************************************************/
- // Duration.
- /************************************************************************************************************************/
- /// <summary>
- /// The <see cref="NormalizedTime"/> after which the
- /// <see cref="AnimancerEvent.Sequence.OnEnd"/> callback will be invoked every frame.
- /// </summary>
- /// <remarks>
- /// This is a wrapper around <see cref="AnimancerEvent.Sequence.NormalizedEndTime"/>
- /// so that if the value hasn't been set (<see cref="float.NaN"/>)
- /// it can be determined based on the <see cref="AnimancerNodeBase.EffectiveSpeed"/>:
- /// positive speed ends at 1 and negative speed ends at 0.
- /// </remarks>
- public float NormalizedEndTime
- {
- get
- {
- var events = SharedEvents;
- if (events != null)
- {
- var time = events.NormalizedEndTime;
- if (!float.IsNaN(time))
- return time;
- }
- return AnimancerEvent.Sequence.GetDefaultNormalizedEndTime(EffectiveSpeed);
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// The number of seconds the animation will take to play fully at its current
- /// <see cref="AnimancerNodeBase.EffectiveSpeed"/>.
- /// </summary>
- ///
- /// <remarks>
- /// For the time remaining from now until it reaches the end, use <see cref="RemainingDuration"/> instead.
- /// <para></para>
- /// Setting this value modifies the <see cref="AnimancerNodeBase.Speed"/>, not the <see cref="Length"/>.
- /// <para></para>
- /// <em>Animancer Lite doesn't allow this value to be changed in runtime builds.</em>
- /// <para></para>
- /// <strong>Example:</strong><code>
- /// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
- /// {
- /// var state = animancer.Play(clip);
- ///
- /// state.Duration = 1;// Play fully in 1 second.
- /// state.Duration = 2;// Play fully in 2 seconds.
- /// state.Duration = 0.5f;// Play fully in half a second.
- /// state.Duration = -1;// Play backwards fully in 1 second.
- /// state.NormalizedTime = 1; state.Duration = -1;// Play backwards from the end in 1 second.
- /// }
- /// </code></remarks>
- public float Duration
- {
- get
- {
- var speed = EffectiveSpeed;
- if (speed == 0)
- return float.PositiveInfinity;
- var events = SharedEvents;
- if (events != null)
- {
- var endTime = events.NormalizedEndTime;
- if (!float.IsNaN(endTime))
- {
- if (speed > 0)
- return Length * endTime / speed;
- else
- return Length * (1 - endTime) / -speed;
- }
- }
- return Length / Math.Abs(speed);
- }
- set
- {
- var length = Length;
- var events = SharedEvents;
- if (events != null)
- {
- var endTime = events.NormalizedEndTime;
- if (!float.IsNaN(endTime))
- {
- if (EffectiveSpeed > 0)
- length *= endTime;
- else
- length *= 1 - endTime;
- }
- }
- EffectiveSpeed = length / value;
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// The number of seconds this state will take to go from its current <see cref="NormalizedTime"/> to the
- /// <see cref="NormalizedEndTime"/> at its current <see cref="AnimancerNodeBase.EffectiveSpeed"/>.
- /// </summary>
- ///
- /// <remarks>
- /// For the time it would take to play fully from the start, use the <see cref="Duration"/> instead.
- /// <para></para>
- /// Setting this value modifies the <see cref="AnimancerNodeBase.EffectiveSpeed"/>, not the <see cref="Length"/>.
- /// <para></para>
- /// <em>Animancer Lite doesn't allow this value to be changed in runtime builds.</em>
- /// <para></para>
- /// <strong>Example:</strong><code>
- /// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
- /// {
- /// var state = animancer.Play(clip);
- ///
- /// state.RemainingDuration = 1;// Play from the current time to the end in 1 second.
- /// state.RemainingDuration = 2;// Play from the current time to the end in 2 seconds.
- /// state.RemainingDuration = 0.5f;// Play from the current time to the end in half a second.
- /// state.RemainingDuration = -1;// Play from the current time away from the end.
- /// }
- /// </code></remarks>
- public float RemainingDuration
- {
- get => (Length * NormalizedEndTime - Time) / EffectiveSpeed;
- set => EffectiveSpeed = (Length * NormalizedEndTime - Time) / value;
- }
- /************************************************************************************************************************/
- // Length.
- /************************************************************************************************************************/
- /// <summary>
- /// The total time this state would take to play in seconds when <see cref="AnimancerNodeBase.Speed"/> = 1.
- /// </summary>
- public abstract float Length { get; }
- /// <summary>Will this state loop back to the start when it reaches the end?</summary>
- /// <remarks>
- /// Note that <see cref="Time"/> always continues increasing regardless of this value.
- /// See the comments on <see cref="Time"/> for more information.
- /// </remarks>
- public virtual bool IsLooping => false;
- /************************************************************************************************************************/
- /// <summary>
- /// Gets the details used to trigger <see cref="AnimancerEvent"/>s on this state:
- /// <see cref="Length"/>, <see cref="NormalizedTime"/>, and <see cref="IsLooping"/>.
- /// </summary>
- public virtual void GetEventDispatchInfo(
- out float length,
- out float normalizedTime,
- out bool isLooping)
- {
- length = Length;
- normalizedTime = length != 0
- ? Time / length
- : 0;
- isLooping = IsLooping;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Methods
- /************************************************************************************************************************/
- /// <summary>Destroys the <see cref="Playable"/> and cleans up this state.</summary>
- /// <remarks>
- /// This method is NOT called automatically, so when implementing a custom state type you must use
- /// <see cref="AnimancerGraph.Disposables"/> if you need to guarantee that things will get cleaned up.
- /// </remarks>
- public virtual void Destroy()
- {
- if (Parent != null)
- {
- Parent.OnRemoveChild(this);
- Parent = null;
- }
- FadeGroup = null;
- Index = -1;
- _EventDispatcher = null;
- var graph = Graph;
- if (graph != null)
- {
- graph.States.Unregister(this);
- // This is slightly faster than _Playable.Destroy().
- if (_Playable.IsValid() && graph._PlayableGraph.IsValid())
- graph._PlayableGraph.DestroyPlayable(_Playable);
- }
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public abstract AnimancerState Clone(CloneContext context);
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public sealed override void CopyFrom(AnimancerNode copyFrom, CloneContext context)
- => this.CopyFromBase(copyFrom, context);
- /// <inheritdoc/>
- public virtual void CopyFrom(AnimancerState copyFrom, CloneContext context)
- {
- CopyFirstGraphAndKeyFrom(copyFrom, context);
- TimeD = copyFrom.TimeD;
- IsPlaying = copyFrom.IsPlaying;
- base.CopyFrom(copyFrom, context);
- CopyEvents(copyFrom, context);
- UpdateIsActive();
- }
- /************************************************************************************************************************/
- /// <summary>Sets the <see cref="AnimancerNodeBase.Graph"/> and <see cref="Key"/>.</summary>
- private void CopyFirstGraphAndKeyFrom(AnimancerState copyFrom, CloneContext context)
- {
- if (Graph != null)
- return;
- Graph = context.GetCloneOrOriginal(copyFrom.Graph);
- // If a clone is registered for the key, use it.
- // Otherwise, if the key is a state and we're cloning into a different graph, clone the key state.
- // Otherwise, just use the same key.
- _Key = copyFrom.Key is AnimancerState stateKey && stateKey.Graph != Graph
- ? context.GetOrCreateCloneOrOriginal(stateKey)
- : context.GetCloneOrOriginal(copyFrom.Key);
- // Each key can only be used once per graph,
- // so we can only use it if it's different or we have a different graph.
- if (_Key == copyFrom.Key && Graph == copyFrom.Graph)
- _Key = null;
- AddToNewGraph();
- }
- /************************************************************************************************************************/
- /// <summary>[<see cref="IAnimationClipCollection"/>] Gathers all the animations in this state.</summary>
- public virtual void GatherAnimationClips(ICollection<AnimationClip> clips)
- {
- clips.Gather(Clip);
- for (int i = ChildCount - 1; i >= 0; i--)
- GetChild(i).GatherAnimationClips(clips);
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Returns true if the animation is playing and has not yet passed the
- /// <see cref="AnimancerEvent.Sequence.EndEvent"/>.
- /// </summary>
- /// <remarks>
- /// 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.
- /// </remarks>
- public override bool IsPlayingAndNotEnding()
- {
- if (!IsPlaying || !_Playable.IsValid())
- return false;
- var speed = EffectiveSpeed;
- if (speed > 0)
- {
- float endTime;
- var events = SharedEvents;
- if (events != null)
- {
- endTime = events.NormalizedEndTime;
- if (float.IsNaN(endTime))
- endTime = Length;
- else
- endTime *= Length;
- }
- else endTime = Length;
- return Time <= endTime;
- }
- else if (speed < 0)
- {
- float endTime;
- var events = SharedEvents;
- if (events != null)
- {
- endTime = events.NormalizedEndTime;
- if (float.IsNaN(endTime))
- endTime = 0;
- else
- endTime *= Length;
- }
- else endTime = 0;
- return Time >= endTime;
- }
- else return true;
- }
- /************************************************************************************************************************/
- #if UNITY_ASSERTIONS
- private string _CachedToString;
- #endif
- /// <summary>
- /// Returns the <see cref="AnimancerNode.DebugName"/> if one is set, otherwise a string describing the type of
- /// this state and the name of the <see cref="MainObject"/>.
- /// </summary>
- public override string ToString()
- {
- #if UNITY_ASSERTIONS
- if (NameCache.TryToString(DebugName, out var cachedName))
- return cachedName;
- if (_CachedToString != null)
- return _CachedToString;
- #endif
- string name;
- var type = GetType().Name;
- var mainObject = MainObject;
- if (mainObject != null)
- {
- #if UNITY_ASSERTIONS
- name = mainObject.GetCachedName();
- #else
- name = mainObject.name;
- #endif
- name = $"{name} ({type})";
- }
- else
- {
- name = type;
- }
- #if UNITY_ASSERTIONS
- _CachedToString = name;
- #endif
- return name;
- }
- /************************************************************************************************************************/
- #region Descriptions
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected override void AppendDetails(StringBuilder text, string separator)
- {
- text.AppendField(separator, nameof(Key), _Key);
- text.AppendField(separator, "ActiveIndex", _ActiveIndex);
- var mainObject = MainObject;
- if (mainObject != _Key as Object)
- text.AppendField(separator, nameof(MainObject), mainObject);
- #if UNITY_EDITOR
- if (mainObject != null)
- text.AppendField(separator, "AssetPath", AssetDatabase.GetAssetPath(mainObject));
- #endif
- base.AppendDetails(text, separator);
- text.AppendField(separator, nameof(IsPlaying), IsPlaying);
- try
- {
- text.AppendField(separator, nameof(Time), TimeD)
- .Append("s / ")
- .Append(Length)
- .Append("s = ")
- .Append((NormalizedTime * 100).ToString("0.00"))
- .Append('%');
- text.AppendField(separator, nameof(IsLooping), IsLooping);
- }
- catch (Exception exception)
- {
- text.Append(separator).Append(exception);
- }
- text.AppendField(separator, nameof(Events), SharedEvents?.DeepToString(false));
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
|