12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
- using System;
- using System.Collections.Generic;
- using System.Text;
- using UnityEngine;
- using UnityEngine.Animations;
- using UnityEngine.Playables;
- using Object = UnityEngine.Object;
- namespace Animancer
- {
- /// <summary>[Pro-Only]
- /// An <see cref="AnimancerState"/> which blends multiple child states
- /// by allowing you to control their <see cref="AnimancerNode.Weight"/> manually.
- /// </summary>
- /// <remarks>
- /// This mixer type is similar to the Direct Blend Type in Mecanim Blend Trees.
- /// The official <see href="https://learn.unity.com/tutorial/5c5152bcedbc2a001fd5c696">Direct Blend Trees</see>
- /// tutorial explains their general concepts and purpose which apply to <see cref="ManualMixerState"/>s as well.
- /// <para></para>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/manual/blending/mixers">
- /// Mixers</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/ManualMixerState
- ///
- public partial class ManualMixerState : AnimancerState,
- ICopyable<ManualMixerState>,
- IParametizedState,
- IUpdatable
- {
- /************************************************************************************************************************/
- #region Properties
- /************************************************************************************************************************/
- /// <summary>The states connected to this mixer.</summary>
- /// <remarks>Only states up to the <see cref="ChildCount"/> should be assigned.</remarks>
- protected AnimancerState[] ChildStates { get; private set; }
- = Array.Empty<AnimancerState>();
- /************************************************************************************************************************/
- private int _ChildCount;
- /// <inheritdoc/>
- public sealed override int ChildCount
- => _ChildCount;
- /************************************************************************************************************************/
- /// <summary>The size of the internal array of <see cref="ChildStates"/>.</summary>
- /// <remarks>This value starts at 0 then expands to <see cref="ChildCapacity"/> when the first child is added.</remarks>
- public int ChildCapacity
- {
- get => ChildStates.Length;
- set
- {
- if (value == ChildStates.Length)
- return;
- #if UNITY_ASSERTIONS
- if (value <= 1 && OptionalWarning.MixerMinChildren.IsEnabled())
- OptionalWarning.MixerMinChildren.Log(
- $"The {nameof(ChildCapacity)} of '{this}' is being set to {value}." +
- $" The purpose of a mixer is to mix multiple child states so this may be a mistake.",
- Graph?.Component);
- #endif
- var newChildStates = new AnimancerState[value];
- if (value > _ChildCount)// Increase size.
- {
- Array.Copy(ChildStates, newChildStates, _ChildCount);
- }
- else// Decrease size.
- {
- for (int i = value; i < _ChildCount; i++)
- ChildStates[i].Destroy();
- Array.Copy(ChildStates, newChildStates, value);
- _ChildCount = value;
- }
- ChildStates = newChildStates;
- if (_Playable.IsValid())
- {
- _Playable.SetInputCount(value);
- }
- else if (Graph != null)
- {
- CreatePlayable();
- }
- OnChildCapacityChanged();
- }
- }
- /// <summary>Called when the <see cref="ChildCapacity"/> is changed.</summary>
- protected virtual void OnChildCapacityChanged() { }
- /// <summary><see cref="ChildCapacity"/> starts at 0 then expands to this value when the first child is added.</summary>
- /// <remarks>Default 8.</remarks>
- public static int DefaultChildCapacity { get; set; } = 8;
- /// <summary>
- /// Ensures that the remaining unused <see cref="ChildCapacity"/>
- /// is greater than or equal to the specified `minimumCapacity`.
- /// </summary>
- public void EnsureRemainingChildCapacity(int minimumCapacity)
- {
- minimumCapacity += _ChildCount;
- if (ChildCapacity < minimumCapacity)
- {
- var capacity = Math.Max(ChildCapacity, DefaultChildCapacity);
- while (capacity < minimumCapacity)
- capacity *= 2;
- ChildCapacity = capacity;
- }
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public sealed override AnimancerState GetChild(int index)
- => ChildStates[index];
- /// <inheritdoc/>
- public sealed override FastEnumerator<AnimancerState> GetEnumerator()
- => new(ChildStates, _ChildCount);
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected override void OnSetIsPlaying()
- {
- var isPlaying = IsPlaying;
- for (int i = _ChildCount - 1; i >= 0; i--)
- ChildStates[i].IsPlaying = isPlaying;
- }
- /************************************************************************************************************************/
- /// <summary>If greater than 0 then <see cref="IsLooping"/> is true.</summary>
- private int _LoopingChildCount;
- /// <summary>Are any child states looping?</summary>
- public override bool IsLooping
- => _LoopingChildCount > 0;
- /// <summary>Sets <see cref="IsLooping"/> and informs the <see cref="AnimancerNodeBase.Parent"/>s.</summary>
- private void AddIsLooping(int offset)
- {
- var wasLooping = IsLooping;
- _LoopingChildCount += offset;
- var isLooping = IsLooping;
- if (wasLooping != isLooping)
- OnIsLoopingChangedRecursive(isLooping);
- }
- /// <inheritdoc/>
- protected override void OnChildIsLoopingChanged(bool value)
- => AddIsLooping(value ? 1 : -1);
- /************************************************************************************************************************/
- /// <summary>The weighted average <see cref="AnimancerState.Time"/> of each child state.</summary>
- /// <remarks>
- /// If there are any <see cref="SynchronizedChildren"/>,
- /// only those states will be included in the getter calculation.
- /// </remarks>
- public override double RawTime
- {
- get
- {
- GetTimeDetails(out var totalWeight, out var normalizedTime, out var length);
- if (totalWeight == 0)
- return base.RawTime;
- totalWeight *= totalWeight;
- return normalizedTime * length / totalWeight;
- }
- set
- {
- if (value == 0)
- goto SetToZero;
- var length = Length;
- if (length == 0)
- goto SetToZero;
- value /= length;// Normalize.
- for (int i = _ChildCount - 1; i >= 0; i--)
- ChildStates[i].NormalizedTimeD = value;
- return;
- // If the value is 0, we can set the child times more efficiently.
- SetToZero:
- for (int i = _ChildCount - 1; i >= 0; i--)
- ChildStates[i].TimeD = 0;
- }
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void MoveTime(double time, bool normalized)
- {
- base.MoveTime(time, normalized);
- for (int i = _ChildCount - 1; i >= 0; i--)
- ChildStates[i].MoveTime(time, normalized);
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void GetEventDispatchInfo(
- out float length,
- out float normalizedTime,
- out bool isLooping)
- {
- GetTimeDetails(out _, out normalizedTime, out length);
- isLooping = _LoopingChildCount > 0;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Gets the time details based on the synchronized child states if any are active,
- /// otherwise recalculates based on all child states.
- /// </summary>
- private void GetTimeDetails(out float totalWeight, out float normalizedTime, out float length)
- {
- if (_SynchronizedChildren != null)
- {
- GetTimeDetails(
- _SynchronizedChildren,
- _SynchronizedChildren.Count,
- out totalWeight,
- out normalizedTime,
- out length);
- if (totalWeight > MinimumSynchronizeChildrenWeight)
- return;
- }
- GetTimeDetails(
- ChildStates,
- _ChildCount,
- out totalWeight,
- out normalizedTime,
- out length);
- }
- /// <summary>Gets the time details based on the `states`.</summary>
- private void GetTimeDetails(
- IList<AnimancerState> states,
- int count,
- out float totalWeight,
- out float normalizedTime,
- out float length)
- {
- totalWeight = 0;
- normalizedTime = 0;
- length = 0;
- for (int i = count - 1; i >= 0; i--)
- {
- var state = states[i];
- var weight = state.Weight;
- if (weight == 0)
- continue;
- var stateLength = state.Length;
- if (stateLength == 0)
- continue;
- totalWeight += weight;
- normalizedTime += state.Time / stateLength * weight;
- length += stateLength * weight;
- }
- }
- /************************************************************************************************************************/
- /// <summary>The weighted average <see cref="AnimancerState.Length"/> of each child state.</summary>
- public override float Length
- {
- get
- {
- var length = 0f;
- var totalChildWeight = 0f;
- if (_SynchronizedChildren != null)
- {
- for (int i = _SynchronizedChildren.Count - 1; i >= 0; i--)
- {
- var state = _SynchronizedChildren[i];
- var weight = state.Weight;
- if (weight == 0)
- continue;
- var stateLength = state.Length;
- if (stateLength == 0)
- continue;
- totalChildWeight += weight;
- length += stateLength * weight;
- }
- }
- if (totalChildWeight > 0)
- return length / totalChildWeight;
- totalChildWeight = CalculateTotalWeight(ChildStates, _ChildCount);
- if (totalChildWeight <= 0)
- return 0;
- for (int i = _ChildCount - 1; i >= 0; i--)
- {
- var state = ChildStates[i];
- length += state.Length * state.Weight;
- }
- return length / totalChildWeight;
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Initialization
- /************************************************************************************************************************/
- /// <summary>Creates and assigns the <see cref="AnimationMixerPlayable"/> managed by this state.</summary>
- protected override void CreatePlayable(out Playable playable)
- {
- playable = AnimationMixerPlayable.Create(Graph._PlayableGraph, ChildCapacity);
- }
- /************************************************************************************************************************/
- /// <summary>Connects the `state` to this mixer at its <see cref="AnimancerNode.Index"/>.</summary>
- protected internal override void OnAddChild(AnimancerState state)
- {
- Validate.AssertGraph(state, Graph);
- var capacity = ChildCapacity;
- if (_ChildCount >= capacity)
- ChildCapacity = Math.Max(DefaultChildCapacity, capacity * 2);
- state.Index = _ChildCount;
- ChildStates[_ChildCount] = state;
- _ChildCount++;
- state.IsPlaying = IsPlaying;
- if (Graph != null)
- ConnectChildUnsafe(state.Index, state);
- if (SynchronizeNewChildren)
- Synchronize(state);
- if (state.IsLooping)
- AddIsLooping(1);
- #if UNITY_ASSERTIONS
- _CachedToString = null;
- #endif
- }
- /************************************************************************************************************************/
- /// <summary>Disconnects the `state` from this mixer at its <see cref="AnimancerNode.Index"/>.</summary>
- protected internal override void OnRemoveChild(AnimancerState state)
- {
- DontSynchronize(state);
- Validate.AssertCanRemoveChild(state, ChildStates, _ChildCount);
- // Shuffle all subsequent children down one place.
- if (Graph == null || !Graph._PlayableGraph.IsValid())
- {
- Array.Copy(
- ChildStates, state.Index + 1,
- ChildStates, state.Index,
- _ChildCount - state.Index - 1);
- for (int i = state.Index; i < _ChildCount - 1; i++)
- ChildStates[i].Index = i;
- }
- else
- {
- Graph._PlayableGraph.Disconnect(_Playable, state.Index);
- for (int i = state.Index + 1; i < _ChildCount; i++)
- {
- var otherChild = ChildStates[i];
- Graph._PlayableGraph.Disconnect(_Playable, otherChild.Index);
- otherChild.Index = i - 1;
- ChildStates[i - 1] = otherChild;
- ConnectChildUnsafe(i - 1, otherChild);
- }
- }
- _ChildCount--;
- ChildStates[_ChildCount] = null;
- if (state.IsLooping)
- AddIsLooping(-1);
- #if UNITY_ASSERTIONS
- _CachedToString = null;
- #endif
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void Destroy()
- {
- DestroyChildren();
- base.Destroy();
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override AnimancerState Clone(CloneContext context)
- {
- var clone = new ManualMixerState();
- clone.CopyFrom(this, context);
- return clone;
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public sealed override void CopyFrom(AnimancerState copyFrom, CloneContext context)
- => this.CopyFromBase(copyFrom, context);
- /// <inheritdoc/>
- public virtual void CopyFrom(ManualMixerState copyFrom, CloneContext context)
- {
- base.CopyFrom(copyFrom, context);
- DestroyChildren();
- var synchronizeNewChildren = SynchronizeNewChildren;
- var childCount = copyFrom.ChildCount;
- EnsureRemainingChildCapacity(childCount);
- for (int i = 0; i < childCount; i++)
- {
- var child = copyFrom.ChildStates[i];
- SynchronizeNewChildren = copyFrom.IsSynchronized(child);
- child = context.Clone(child);
- Add(child);
- }
- SynchronizeNewChildren = synchronizeNewChildren;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Child Configuration
- /************************************************************************************************************************/
- /// <summary>Assigns the `state` as a child of this mixer.</summary>
- /// <remarks>This is the same as calling <see cref="AnimancerState.SetParent"/>.</remarks>
- public void Add(AnimancerState state)
- => state.SetParent(this);
- /// <summary>Creates and returns a new <see cref="ClipState"/> to play the `clip` as a child of this mixer.</summary>
- public ClipState Add(AnimationClip clip)
- {
- var state = new ClipState(clip);
- Add(state);
- return state;
- }
- /// <summary>Calls <see cref="AnimancerUtilities.CreateStateAndApply"/> then <see cref="Add(AnimancerState)"/>.</summary>
- public AnimancerState Add(ITransition transition)
- {
- var state = transition.CreateStateAndApply(Graph);
- Add(state);
- return state;
- }
- /// <summary>Calls one of the other <see cref="Add(object)"/> overloads as appropriate for the `child`.</summary>
- public AnimancerState Add(object child)
- {
- if (child is AnimationClip clip)
- return Add(clip);
- if (child is ITransition transition)
- return Add(transition);
- if (child is AnimancerState state)
- {
- Add(state);
- return state;
- }
- MarkAsUsed(this);
- throw new ArgumentException($"Failed to {nameof(Add)} '{AnimancerUtilities.ToStringOrNull(child)}'" +
- $" as child of '{this}' because it isn't an" +
- $" {nameof(AnimationClip)}, {nameof(ITransition)}, or {nameof(AnimancerState)}.");
- }
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="Add(AnimationClip)"/> for each of the `clips`.</summary>
- public void AddRange(IList<AnimationClip> clips)
- {
- var count = clips.Count;
- EnsureRemainingChildCapacity(count);
- for (int i = 0; i < count; i++)
- Add(clips[i]);
- }
- /// <summary>Calls <see cref="Add(AnimationClip)"/> for each of the `clips`.</summary>
- public void AddRange(params AnimationClip[] clips)
- => AddRange((IList<AnimationClip>)clips);
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="Add(ITransition)"/> for each of the `transitions`.</summary>
- public void AddRange(IList<ITransition> transitions)
- {
- var count = transitions.Count;
- EnsureRemainingChildCapacity(count);
- for (int i = 0; i < count; i++)
- Add(transitions[i]);
- }
- /// <summary>Calls <see cref="Add(ITransition)"/> for each of the `transitions`.</summary>
- public void AddRange(params ITransition[] transitions)
- => AddRange((IList<ITransition>)transitions);
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="Add(object)"/> for each of the `children`.</summary>
- public void AddRange(IList<object> children)
- {
- var count = children.Count;
- EnsureRemainingChildCapacity(count);
- for (int i = 0; i < count; i++)
- Add(children[i]);
- }
- /// <summary>Calls <see cref="Add(object)"/> for each of the `children`.</summary>
- public void AddRange(params object[] children)
- => AddRange((IList<object>)children);
- /************************************************************************************************************************/
- /// <summary>Removes the child at the specified `index`.</summary>
- public void Remove(int index, bool destroy)
- => Remove(ChildStates[index], destroy);
- /// <summary>Removes the specified `child`.</summary>
- public void Remove(AnimancerState child, bool destroy)
- {
- #if UNITY_ASSERTIONS
- if (child.Parent != this)
- Debug.LogWarning($"Attempting to remove a state which is not a child of this {GetType().Name}." +
- $" This will remove the child from its actual parent so you should directly call" +
- $" child.{nameof(child.Destroy)} or child.{nameof(child.SetParent)}(null, -1) instead." +
- $"\n• Child: {child}" +
- $"\n• Removing From: {this}" +
- $"\n• Actual Parent: {child.Parent}",
- Graph?.Component as Object);
- #endif
- if (destroy)
- child.Destroy();
- else
- child.SetParent(null);
- }
- /************************************************************************************************************************/
- /// <summary>Replaces the `child` at the specified `index`.</summary>
- public void Set(int index, AnimancerState child, bool destroyPrevious)
- {
- #if UNITY_ASSERTIONS
- if ((uint)index >= _ChildCount)
- {
- MarkAsUsed(this);
- MarkAsUsed(child);
- throw new IndexOutOfRangeException(
- $"Invalid child index. Must be 0 <= index < {nameof(ChildCount)} ({ChildCount}).");
- }
- #endif
- if (child.Parent != null)
- child.SetParent(null);
- var previousChild = ChildStates[index];
- DontSynchronize(previousChild);
- previousChild.SetParentInternal(null);
- child.SetGraph(Graph);
- ChildStates[index] = child;
- child.SetParentInternal(this, index);
- child.IsPlaying = IsPlaying;
- if (Graph != null)
- {
- Graph._PlayableGraph.Disconnect(_Playable, index);
- ConnectChildUnsafe(index, child);
- }
- var loopingOffset = 0;
- if (previousChild.IsLooping)
- loopingOffset--;
- if (child.IsLooping)
- loopingOffset++;
- if (loopingOffset != 0)
- AddIsLooping(loopingOffset);
- child.CopyIKFlags(this);
- if (SynchronizeNewChildren)
- Synchronize(child);
- if (destroyPrevious)
- previousChild.Destroy();
- #if UNITY_ASSERTIONS
- _CachedToString = null;
- #endif
- }
- /// <summary>Replaces the child at the specified `index` with a new <see cref="ClipState"/>.</summary>
- public ClipState Set(int index, AnimationClip clip, bool destroyPrevious)
- {
- var state = new ClipState(clip);
- Set(index, state, destroyPrevious);
- return state;
- }
- /// <summary>Replaces the child at the specified `index` with a <see cref="ITransition.CreateState"/>.</summary>
- public AnimancerState Set(int index, ITransition transition, bool destroyPrevious)
- {
- var state = transition.CreateStateAndApply(Graph);
- Set(index, state, destroyPrevious);
- return state;
- }
- /// <summary>Calls one of the other <see cref="Set(int, object, bool)"/> overloads as appropriate for the `child`.</summary>
- public AnimancerState Set(int index, object child, bool destroyPrevious)
- {
- if (child is AnimationClip clip)
- return Set(index, clip, destroyPrevious);
- if (child is ITransition transition)
- return Set(index, transition, destroyPrevious);
- if (child is AnimancerState state)
- {
- Set(index, state, destroyPrevious);
- return state;
- }
- MarkAsUsed(this);
- throw new ArgumentException($"Failed to {nameof(Set)} '{AnimancerUtilities.ToStringOrNull(child)}'" +
- $" as child of '{this}' because it isn't an" +
- $" {nameof(AnimationClip)}, {nameof(ITransition)}, or {nameof(AnimancerState)}.");
- }
- /************************************************************************************************************************/
- /// <summary>Returns the index of the specified `child` state.</summary>
- public int IndexOf(AnimancerState child)
- => Array.IndexOf(ChildStates, child, 0, _ChildCount);
- /************************************************************************************************************************/
- /// <summary>
- /// Destroys all <see cref="ChildStates"/> connected to this mixer. This operation cannot be undone.
- /// </summary>
- public void DestroyChildren()
- {
- for (int i = _ChildCount - 1; i >= 0; i--)
- ChildStates[i].Destroy();
- Array.Clear(ChildStates, 0, _ChildCount);
- _ChildCount = 0;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Jobs
- /************************************************************************************************************************/
- /// <summary>
- /// Creates an <see cref="AnimationScriptPlayable"/> to run the specified Animation Job instead of the usual
- /// <see cref="AnimationMixerPlayable"/>.
- /// </summary>
- ///
- /// <remarks>
- /// <strong>Example:</strong><code>
- /// void CreatePlayableExample(AnimancerComponent animancer)
- /// }
- /// var job = new MyJob();// A struct that implements IAnimationJob.
- /// var mixer = new WhateverMixerState();// e.g. LinearMixerState.
- /// mixer.CreatePlayable(animancer, job);
- /// // Use mixer.Initialize, CreateChild, and SetChild to configure the children as normal.
- /// }
- /// </code>
- /// See also: <seealso cref="CreatePlayable{T}(out Playable, T, bool)"/>
- /// </remarks>
- public AnimationScriptPlayable CreatePlayable<T>(
- AnimancerGraph graph,
- T job,
- bool processInputs = false)
- where T : struct, IAnimationJob
- {
- // Can't just use SetGraph normally because it would call the regular CreatePlayable method.
- SetGraph(null);
- Graph = graph;
- graph.States.Register(this);
- var playable = AnimationScriptPlayable.Create(graph._PlayableGraph, job, _ChildCount);
- if (!processInputs)
- playable.SetProcessInputs(false);
- for (int i = _ChildCount - 1; i >= 0; i--)
- ChildStates[i].SetGraph(graph);
- return playable;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Creates an <see cref="AnimationScriptPlayable"/> to run the specified Animation Job instead of the usual
- /// <see cref="AnimationMixerPlayable"/>.
- /// </summary>
- ///
- /// <remarks>
- /// <para></para>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/source/creating-custom-states">
- /// Creating Custom States</see>
- /// <para></para>
- /// <strong>Example:</strong><code>
- /// public class MyMixer : LinearMixerState
- /// {
- /// protected override void CreatePlayable(out Playable playable)
- /// {
- /// CreatePlayable(out playable, new MyJob());
- /// }
- ///
- /// private struct MyJob : IAnimationJob
- /// {
- /// public void ProcessAnimation(AnimationStream stream)
- /// {
- /// }
- ///
- /// public void ProcessRootMotion(AnimationStream stream)
- /// {
- /// }
- /// }
- /// }
- /// </code>
- /// See also: <seealso cref="CreatePlayable{T}(AnimancerGraph, T, bool)"/>
- /// </remarks>
- protected void CreatePlayable<T>(
- out Playable playable,
- T job,
- bool processInputs = false)
- where T : struct, IAnimationJob
- {
- var scriptPlayable = AnimationScriptPlayable.Create(Graph._PlayableGraph, job, ChildCount);
- if (!processInputs)
- scriptPlayable.SetProcessInputs(false);
- playable = scriptPlayable;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Gets the Animation Job data from the <see cref="AnimationScriptPlayable"/>.
- /// </summary>
- /// <exception cref="InvalidCastException">
- /// This mixer was not initialized using <see cref="CreatePlayable{T}(AnimancerGraph, T, bool)"/>
- /// or <see cref="CreatePlayable{T}(out Playable, T, bool)"/>.
- /// </exception>
- public T GetJobData<T>()
- where T : struct, IAnimationJob
- => ((AnimationScriptPlayable)_Playable).GetJobData<T>();
- /// <summary>
- /// Sets the Animation Job data in the <see cref="AnimationScriptPlayable"/>.
- /// </summary>
- /// <exception cref="InvalidCastException">
- /// This mixer was not initialized using <see cref="CreatePlayable{T}(AnimancerGraph, T, bool)"/>
- /// or <see cref="CreatePlayable{T}(out Playable, T, bool)"/>.
- /// </exception>
- public void SetJobData<T>(T value)
- where T : struct, IAnimationJob
- => ((AnimationScriptPlayable)_Playable).SetJobData(value);
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Updates
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public int UpdatableIndex { get; set; } = IUpdatable.List.NotInList;
- /// <summary>Recalculates the weights of child states and synchronizes their times if necessary.</summary>
- public virtual void Update()
- {
- if (!ApplySynchronizeChildren())
- Graph.CancelPreUpdate(this);
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected internal override void UpdateEvents()
- => UpdateEventsRecursive(this);
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void SetGraph(AnimancerGraph graph)
- {
- if (Graph == graph)
- return;
- Graph?.CancelPreUpdate(this);
- base.SetGraph(graph);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Synchronize Children
- /************************************************************************************************************************/
- /// <summary>Should newly added children be automatically added to the synchronization list? Default true.</summary>
- public static bool SynchronizeNewChildren { get; set; } = true;
- /// <summary>The minimum total weight of all children for their times to be synchronized. Default 0.01.</summary>
- public static float MinimumSynchronizeChildrenWeight { get; set; } = 0.01f;
- /************************************************************************************************************************/
- private List<AnimancerState> _SynchronizedChildren;
- /// <summary>A copy of the internal list of child states that will have their times synchronized.</summary>
- /// <remarks>
- /// If this mixer is a child of another mixer, its synchronized children will be managed by the parent.
- /// <para></para>
- /// The getter allocates a new array if <see cref="SynchronizedChildCount"/> is greater than zero.
- /// </remarks>
- public AnimancerState[] SynchronizedChildren
- {
- get => SynchronizedChildCount > 0
- ? _SynchronizedChildren.ToArray()
- : Array.Empty<AnimancerState>();
- set
- {
- if (_SynchronizedChildren == null)
- _SynchronizedChildren = new();
- else
- _SynchronizedChildren.Clear();
- for (int i = 0; i < value.Length; i++)
- Synchronize(value[i]);
- }
- }
- /// <summary>The number of <see cref="SynchronizedChildren"/>.</summary>
- public int SynchronizedChildCount
- => _SynchronizedChildren != null
- ? _SynchronizedChildren.Count
- : 0;
- /************************************************************************************************************************/
- /// <summary>Is the `state` in the <see cref="SynchronizedChildren"/>?</summary>
- public bool IsSynchronized(AnimancerState state)
- {
- var synchronizer = GetParentMixer();
- return
- synchronizer._SynchronizedChildren != null &&
- synchronizer._SynchronizedChildren.Contains(state);
- }
- /************************************************************************************************************************/
- /// <summary>Adds the `state` to the <see cref="SynchronizedChildren"/>.</summary>
- /// <remarks>
- /// The `state` must be a child of this mixer.
- /// <para></para>
- /// If this mixer is a child of another mixer, the `state` will be added to the parent's
- /// <see cref="SynchronizedChildren"/> instead.
- /// </remarks>
- public void Synchronize(AnimancerState state)
- {
- if (state == null)
- return;
- #if UNITY_ASSERTIONS
- if (!IsChildOf(state, this))
- {
- MarkAsUsed(this);
- throw new ArgumentException(
- $"State is not a child of the mixer." +
- $"\n• State: {state}" +
- $"\n• Mixer: {this}",
- nameof(state));
- }
- #endif
- var synchronizer = GetParentMixer();
- synchronizer.SynchronizeDirect(state);
- }
- /// <summary>The internal implementation of <see cref="Synchronize"/>.</summary>
- private void SynchronizeDirect(AnimancerState state)
- {
- if (state == null)
- return;
- // If the state is a mixer, steal all its synchronized children instead of synchronizing the mixer itself.
- if (state is ManualMixerState mixer)
- {
- if (mixer._SynchronizedChildren != null)
- {
- for (int i = 0; i < mixer._SynchronizedChildren.Count; i++)
- Synchronize(mixer._SynchronizedChildren[i]);
- mixer._SynchronizedChildren.Clear();
- }
- return;
- }
- #if UNITY_ASSERTIONS
- if (OptionalWarning.MixerSynchronizeZeroLength.IsEnabled() && state.Length == 0)
- OptionalWarning.MixerSynchronizeZeroLength.Log(
- $"Adding a state with zero {nameof(AnimancerState.Length)} to the synchronization list: '{state}'." +
- $"\n\nSynchronization is based on the {nameof(NormalizedTime)}" +
- $" which can't be calculated if the {nameof(Length)} is 0." +
- $" Some state types can change their {nameof(Length)}, in which case you can just disable this warning." +
- $" But otherwise, the indicated state probably shouldn't be added to the synchronization list.",
- Graph?.Component);
- #endif
- _SynchronizedChildren ??= new();
- #if UNITY_ASSERTIONS
- if (_SynchronizedChildren.Contains(state))
- Debug.LogError($"{state} is already in the {nameof(SynchronizedChildren)} list.");
- #endif
- _SynchronizedChildren.Add(state);
- Graph?.RequirePreUpdate(this);
- }
- /************************************************************************************************************************/
- /// <summary>Removes the `state` from the <see cref="SynchronizedChildren"/>.</summary>
- public void DontSynchronize(AnimancerState state)
- {
- var synchronizer = GetParentMixer();
- if (synchronizer._SynchronizedChildren != null &&
- synchronizer._SynchronizedChildren.Remove(state) &&
- state._Playable.IsValid())
- state._Playable.SetSpeed(state.Speed);
- }
- /************************************************************************************************************************/
- /// <summary>Removes all children of this mixer from the <see cref="SynchronizedChildren"/>.</summary>
- public void DontSynchronizeChildren()
- {
- var synchronizer = GetParentMixer();
- var synchronizedChildren = synchronizer._SynchronizedChildren;
- if (synchronizedChildren == null)
- return;
- if (synchronizer == this)
- {
- for (int i = synchronizedChildren.Count - 1; i >= 0; i--)
- {
- var state = synchronizedChildren[i];
- if (state._Playable.IsValid())
- state._Playable.SetSpeed(state.Speed);
- }
- synchronizedChildren.Clear();
- }
- else
- {
- for (int i = synchronizedChildren.Count - 1; i >= 0; i--)
- {
- var state = synchronizedChildren[i];
- if (IsChildOf(state, this))
- {
- if (state._Playable.IsValid())
- state._Playable.SetSpeed(state.Speed);
- synchronizedChildren.RemoveAt(i);
- }
- }
- }
- }
- /************************************************************************************************************************/
- /// <summary>Initializes the internal <see cref="SynchronizedChildren"/> list.</summary>
- /// <remarks>
- /// The array can be null or empty. Any elements not in the array will be treated as <c>true</c>.
- /// <para></para>
- /// This method can only be called before any <see cref="SynchronizedChildren"/> are added and also before this
- /// mixer is made the child of another mixer.
- /// </remarks>
- public void InitializeSynchronizedChildren(params bool[] synchronizeChildren)
- {
- AnimancerUtilities.Assert(GetParentMixer() == this,
- $"{nameof(InitializeSynchronizedChildren)} cannot be used on a mixer that is a child of another mixer.");
- AnimancerUtilities.Assert(_SynchronizedChildren == null,
- $"{nameof(InitializeSynchronizedChildren)} cannot be used on a mixer already has synchronized children.");
- int flagCount;
- if (synchronizeChildren != null)
- {
- flagCount = synchronizeChildren.Length;
- for (int i = 0; i < flagCount; i++)
- if (synchronizeChildren[i])
- SynchronizeDirect(ChildStates[i]);
- }
- else flagCount = 0;
- for (int i = flagCount; i < _ChildCount; i++)
- SynchronizeDirect(ChildStates[i]);
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Returns the highest <see cref="ManualMixerState"/> in the hierarchy above this mixer
- /// or this mixer itself if there are none above it.
- /// </summary>
- public ManualMixerState GetParentMixer()
- {
- var mixer = this;
- var parent = Parent;
- while (parent != null)
- {
- if (parent is ManualMixerState parentMixer)
- mixer = parentMixer;
- parent = parent.Parent;
- }
- return mixer;
- }
- /// <summary>Returns the highest <see cref="ManualMixerState"/> in the hierarchy above the `state` (inclusive).</summary>
- public static ManualMixerState GetParentMixer(AnimancerNodeBase node)
- {
- ManualMixerState mixer = null;
- while (node != null)
- {
- if (node is ManualMixerState parentMixer)
- mixer = parentMixer;
- node = node.Parent;
- }
- return mixer;
- }
- /************************************************************************************************************************/
- /// <summary>Is the `child` a child of the `parent`?</summary>
- public static bool IsChildOf(AnimancerNodeBase child, AnimancerNodeBase parent)
- {
- while (true)
- {
- child = child.Parent;
- if (child == parent)
- return true;
- else if (child == null)
- return false;
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Synchronizes the <see cref="AnimancerState.NormalizedTime"/>s of the <see cref="SynchronizedChildren"/> by
- /// modifying their internal playable speeds.
- /// </summary>
- private bool ApplySynchronizeChildren()
- {
- if (Weight == 0 ||
- !IsPlaying ||
- _SynchronizedChildren == null ||
- _SynchronizedChildren.Count <= 1)
- return false;
- var deltaTime = AnimancerGraph.DeltaTime * CalculateRealEffectiveSpeed();
- if (deltaTime == 0)
- return true;
- var count = _SynchronizedChildren.Count;
- // Calculate the weighted average normalized time and normalized speed of all children.
- var totalWeight = 0f;
- var weightedNormalizedTime = 0f;
- var weightedNormalizedSpeed = 0f;
- for (int i = 0; i < count; i++)
- {
- var state = _SynchronizedChildren[i];
- var weight = CalculateRelativeWeight(state);
- if (weight == 0)
- continue;
- var length = state.Length;
- if (length == 0)
- continue;
- totalWeight += weight;
- weight /= length;
- weightedNormalizedTime += state.Time * weight;
- weightedNormalizedSpeed += state.Speed * weight;
- }
- #if UNITY_ASSERTIONS
- if (!(totalWeight >= 0) || totalWeight == float.PositiveInfinity)// Reversed comparison includes NaN.
- {
- MarkAsUsed(this);
- throw new ArgumentOutOfRangeException(nameof(totalWeight), totalWeight,
- $"Total weight {Strings.MustBeFinite} and must be positive");
- }
- if (!weightedNormalizedTime.IsFinite())
- {
- MarkAsUsed(this);
- throw new ArgumentOutOfRangeException(nameof(weightedNormalizedTime), weightedNormalizedTime,
- $"Time {Strings.MustBeFinite}");
- }
- if (!weightedNormalizedSpeed.IsFinite())
- {
- MarkAsUsed(this);
- throw new ArgumentOutOfRangeException(nameof(weightedNormalizedSpeed), weightedNormalizedSpeed,
- $"Speed {Strings.MustBeFinite}");
- }
- #endif
- // If the total weight is too small, pretend they are all at Weight = 1.
- if (totalWeight < MinimumSynchronizeChildrenWeight)
- {
- weightedNormalizedTime = 0;
- weightedNormalizedSpeed = 0;
- var nonZeroCount = 0;
- for (int i = 0; i < count; i++)
- {
- var state = _SynchronizedChildren[i];
- var length = state.Length;
- if (length == 0)
- continue;
- length = 1f / length;
- weightedNormalizedTime += state.Time * length;
- weightedNormalizedSpeed += state.Speed * length;
- nonZeroCount++;
- }
- totalWeight = nonZeroCount;
- }
- // Increment that time value according to delta time.
- weightedNormalizedTime += deltaTime * weightedNormalizedSpeed;
- weightedNormalizedTime /= totalWeight;
- var inverseDeltaTime = 1f / deltaTime;
- // Modify the speed of all children to go from their current normalized time to the average in one frame.
- for (int i = 0; i < count; i++)
- {
- var state = _SynchronizedChildren[i];
- var length = state.Length;
- if (length == 0)
- continue;
- var normalizedTime = state.Time / length;
- var speed = (weightedNormalizedTime - normalizedTime) * length * inverseDeltaTime;
- state._Playable.SetSpeed(speed);
- }
- // After this, all the playables will update and advance according to their new speeds this frame.
- return true;
- }
- /************************************************************************************************************************/
- /// <summary>Calculates the weight of the `child` multiplied by its parents up to this mixer.</summary>
- private float CalculateRelativeWeight(AnimancerState child)
- {
- var weight = child.Weight;
- var parent = child.Parent;
- while (parent != this && parent != null)
- {
- weight *= parent.BaseWeight;
- parent = parent.Parent;
- }
- return weight;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// The multiplied <see cref="PlayableExtensions.GetSpeed"/> of this mixer and its parents down the
- /// hierarchy to determine the actual speed its output is being played at.
- /// </summary>
- /// <remarks>
- /// This can be different from the <see cref="AnimancerNodeBase.EffectiveSpeed"/> because the
- /// <see cref="SynchronizedChildren"/> have their playable speed modified without setting their
- /// <see cref="AnimancerNodeBase.Speed"/>.
- /// </remarks>
- public float CalculateRealEffectiveSpeed()
- {
- var speed = _Playable.GetSpeed();
- var parent = Parent;
- while (parent != null)
- {
- speed *= parent.Playable.GetSpeed();
- parent = parent.Parent;
- }
- return (float)speed;
- }
- /************************************************************************************************************************/
- #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 Methods
- /************************************************************************************************************************/
- /// <summary>Calculates the sum of the <see cref="AnimancerNode.Weight"/> of all `states`.</summary>
- public static float CalculateTotalWeight(AnimancerState[] states, int count)
- {
- var total = 0f;
- for (int i = count - 1; i >= 0; i--)
- total += states[i].Weight;
- return total;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Sets <see cref="AnimancerState.Time"/> for all <see cref="ChildStates"/>.
- /// </summary>
- public void SetChildrenTime(float value, bool normalized = false)
- {
- for (int i = _ChildCount - 1; i >= 0; i--)
- {
- var state = ChildStates[i];
- if (normalized)
- state.NormalizedTime = value;
- else
- state.Time = value;
- }
- }
- /************************************************************************************************************************/
- /// <summary>Sets the weight of all states after the `previousIndex` to 0.</summary>
- protected void DisableRemainingStates(int previousIndex)
- {
- for (int i = previousIndex + 1; i < _ChildCount; i++)
- Playable.SetChildWeight(ChildStates[i], 0);
- }
- /************************************************************************************************************************/
- private static float[] _TemporaryWeights = Array.Empty<float>();
- /// <summary>Returns an array at least as large as the `count`.</summary>
- /// <remarks>
- /// The same array is returned by subsequent calls as long as it's large enough
- /// and it isn't cleared between calls so it will contain the previous data.
- /// </remarks>
- public static float[] GetTemporaryFloatArray(int count)
- {
- if (_TemporaryWeights.Length < count)
- {
- if (count <= 16)
- count = 16;
- else
- count = Mathf.NextPowerOfTwo(count);
- _TemporaryWeights = new float[count];
- }
- return _TemporaryWeights;
- }
- /************************************************************************************************************************/
- /// <summary>Divides `weights` by the `totalWeight` and applies them to the child states.</summary>
- public void NormalizeAndApplyWeights(float totalWeight, float[] weights)
- {
- totalWeight = 1f / totalWeight;
- for (int i = _ChildCount - 1; i >= 0; i--)
- {
- var state = ChildStates[i];
- var weight = weights[i] * totalWeight;
- Playable.SetChildWeight(state, weight);
- }
- }
- /************************************************************************************************************************/
- /// <summary>Gets a user-friendly key to identify the `state` in the Inspector.</summary>
- public virtual string GetDisplayKey(AnimancerState state)
- => $"[{state.Index}]";
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override Vector3 AverageVelocity
- {
- get
- {
- var velocity = default(Vector3);
- for (int i = _ChildCount - 1; i >= 0; i--)
- {
- var state = ChildStates[i];
- velocity += state.AverageVelocity * state.Weight;
- }
- return velocity;
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Recalculates the <see cref="AnimancerState.Duration"/> of all child states so that they add up to 1.
- /// </summary>
- public void NormalizeDurations()
- {
- int divideBy = 0;
- float totalDuration = 0f;
- // Count the number of states that exist and their total duration.
- for (int i = 0; i < _ChildCount; i++)
- {
- divideBy++;
- totalDuration += ChildStates[i].Duration;
- }
- // Calculate the average duration.
- totalDuration /= divideBy;
- // Set all states to that duration.
- for (int i = 0; i < _ChildCount; i++)
- {
- ChildStates[i].Duration = totalDuration;
- }
- }
- /************************************************************************************************************************/
- #if UNITY_ASSERTIONS
- /// <summary>[Assert-Only] A string built by <see cref="ToString"/> to describe this mixer.</summary>
- private string _CachedToString;
- #endif
- /// <summary>
- /// Returns a string describing the type of this mixer and the name of states connected to it.
- /// </summary>
- public override string ToString()
- {
- #if UNITY_ASSERTIONS
- if (NameCache.TryToString(DebugName, out var name))
- return name;
- if (_CachedToString != null)
- return _CachedToString;
- #endif
- // Gather child names.
- var childNames = ListPool.Acquire<string>();
- var allSimple = true;
- for (int i = 0; i < _ChildCount; i++)
- {
- var state = ChildStates[i];
- if (state == null)
- continue;
- if (state.MainObject != null)
- {
- childNames.Add(state.MainObject.name);
- }
- else
- {
- childNames.Add(state.ToString());
- allSimple = false;
- }
- }
- // If they all have a main object, check if they all have the same prefix so it doesn't need to be repeated.
- int prefixLength = 0;
- var count = childNames.Count;
- if (count <= 1 || !allSimple)
- {
- prefixLength = 0;
- }
- else
- {
- var prefix = childNames[0];
- var shortest = prefixLength = prefix.Length;
- for (int iName = 0; iName < count; iName++)
- {
- var childName = childNames[iName];
- if (shortest > childName.Length)
- {
- shortest = prefixLength = childName.Length;
- }
- for (int iCharacter = 0; iCharacter < prefixLength; iCharacter++)
- {
- if (childName[iCharacter] != prefix[iCharacter])
- {
- prefixLength = iCharacter;
- break;
- }
- }
- }
- if (prefixLength < 3 ||// Less than 3 characters probably isn't an intentional prefix.
- prefixLength >= shortest)
- prefixLength = 0;
- }
- // Build the mixer name.
- var mixerName = StringBuilderPool.Instance.Acquire();
- var type = GetType().Name;
- if (type.EndsWith("State"))
- mixerName.Append(type, 0, type.Length - 5);
- else
- mixerName.Append(type);
- mixerName.Append('(');
- if (count > 0)
- {
- if (prefixLength > 0)
- mixerName.Append(childNames[0], 0, prefixLength).Append('[');
- for (int i = 0; i < count; i++)
- {
- if (i > 0)
- mixerName.Append(", ");
- var childName = childNames[i];
- mixerName.Append(childName, prefixLength, childName.Length - prefixLength);
- }
- mixerName.Append(']');
- }
- ListPool.Release(childNames);
- mixerName.Append(')');
- var result = mixerName.ReleaseToString();
- #if UNITY_ASSERTIONS
- _CachedToString = result;
- #endif
- return result;
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- protected override void AppendDetails(StringBuilder text, string separator)
- {
- base.AppendDetails(text, separator);
- text.Append(separator)
- .Append("SynchronizedChildren: ");
- if (SynchronizedChildCount == 0)
- {
- text.Append("0");
- }
- else
- {
- text.Append(_SynchronizedChildren.Count);
- separator += Strings.Indent;
- for (int i = 0; i < _SynchronizedChildren.Count; i++)
- {
- text.Append(separator)
- .Append(_SynchronizedChildren[i]);
- }
- }
- }
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override void GatherAnimationClips(ICollection<AnimationClip> clips)
- => clips.GatherFromSource(ChildStates);
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public virtual void GetParameters(List<StateParameterDetails> parameters) { }
- /// <inheritdoc/>
- public virtual void SetParameters(List<StateParameterDetails> parameters) { }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
|