chore: initial commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cae3088bc435d2942827b0a25e688903
|
||||
timeCreated: 1515048758
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,819 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>Base class for <see cref="Playable"/> wrapper objects in an <see cref="AnimancerGraph"/>.</summary>
|
||||
/// <remarks>This is the base class of <see cref="AnimancerLayer"/> and <see cref="AnimancerState"/>.</remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerNode
|
||||
public abstract class AnimancerNode : AnimancerNodeBase,
|
||||
ICopyable<AnimancerNode>,
|
||||
IEnumerable<AnimancerState>,
|
||||
IEnumerator,
|
||||
IHasDescription
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Playable
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] [Internal] Indicates whether the Inspector details for this node are expanded.</summary>
|
||||
internal bool _IsInspectorExpanded;
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates and assigns the <see cref="Playable"/> managed by this node.</summary>
|
||||
/// <remarks>This method also applies the <see cref="AnimancerNodeBase.Speed"/> if it was set beforehand.</remarks>
|
||||
protected virtual void CreatePlayable()
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (Graph == null)
|
||||
{
|
||||
MarkAsUsed(this);
|
||||
throw new InvalidOperationException($"{nameof(AnimancerNode)}.{nameof(Graph)}" +
|
||||
$" is null when attempting to create its {nameof(Playable)}: {this}" +
|
||||
$"\nThe {nameof(Graph)} is generally set when you first play a state," +
|
||||
$" so you probably just need to play it before trying to access it.");
|
||||
}
|
||||
|
||||
if (_Playable.IsValid())
|
||||
Debug.LogWarning($"{nameof(AnimancerNode)}.{nameof(CreatePlayable)}" +
|
||||
$" was called before destroying the previous {nameof(Playable)}: {this}", Graph?.Component as Object);
|
||||
#endif
|
||||
|
||||
CreatePlayable(out _Playable);
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
if (!_Playable.IsValid())
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(AnimancerNode)}.{nameof(CreatePlayable)}" +
|
||||
$" did not create a valid {nameof(Playable)} for {this}");
|
||||
#endif
|
||||
|
||||
if (Speed != 1)
|
||||
_Playable.SetSpeed(Speed);
|
||||
}
|
||||
|
||||
/// <summary>Creates and assigns the <see cref="Playable"/> managed by this node.</summary>
|
||||
protected abstract void CreatePlayable(out Playable playable);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Destroys the <see cref="Playable"/>.</summary>
|
||||
public void DestroyPlayable()
|
||||
{
|
||||
if (_Playable.IsValid())
|
||||
Graph._PlayableGraph.DestroyPlayable(_Playable);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calls <see cref="DestroyPlayable"/> and <see cref="CreatePlayable()"/>.</summary>
|
||||
public virtual void RecreatePlayable()
|
||||
{
|
||||
DestroyPlayable();
|
||||
CreatePlayable();
|
||||
}
|
||||
|
||||
/// <summary>Calls <see cref="RecreatePlayable"/> on this node and all its children recursively.</summary>
|
||||
public void RecreatePlayableRecursive()
|
||||
{
|
||||
RecreatePlayable();
|
||||
|
||||
for (int i = ChildCount - 1; i >= 0; i--)
|
||||
GetChild(i)?.RecreatePlayableRecursive();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Copies the details of `copyFrom` into this node, replacing its previous contents.</summary>
|
||||
public virtual void CopyFrom(AnimancerNode copyFrom, CloneContext context)
|
||||
{
|
||||
SetWeight(copyFrom._Weight);
|
||||
|
||||
FadeGroup = context.WillCloneUpdatables
|
||||
? null
|
||||
: copyFrom.FadeGroup?.CloneForSingleTarget(copyFrom, this);
|
||||
|
||||
Speed = copyFrom.Speed;
|
||||
|
||||
CopyIKFlags(copyFrom);
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
DebugName = context.GetCloneOrOriginal(copyFrom.DebugName);
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Graph
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The index of the port this node is connected to on the parent's <see cref="Playable"/>.</summary>
|
||||
/// <remarks>
|
||||
/// A negative value indicates that it is not assigned to a port.
|
||||
/// <para></para>
|
||||
/// Indices are generally assigned starting from 0, ascending in the order they are connected to their layer.
|
||||
/// They will not usually change unless the <see cref="AnimancerNodeBase.Parent"/> changes or another state on
|
||||
/// the same layer is destroyed so the last state is swapped into its place to avoid shuffling everything down
|
||||
/// to cover the gap.
|
||||
/// <para></para>
|
||||
/// The setter is internal so user defined states cannot set it incorrectly. Ideally,
|
||||
/// <see cref="AnimancerLayer"/> should be able to set the port in its constructor and
|
||||
/// <see cref="AnimancerState.SetParent"/> should also be able to set it, but classes that further inherit from
|
||||
/// there should not be able to change it without properly calling that method.
|
||||
/// </remarks>
|
||||
public int Index { get; internal set; } = int.MinValue;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="AnimancerNode"/>.</summary>
|
||||
protected AnimancerNode()
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (TraceConstructor)
|
||||
_ConstructorStackTrace = new(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_ASSERTIONS
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Only]
|
||||
/// Should a <see cref="System.Diagnostics.StackTrace"/> be captured in the constructor of all new nodes so
|
||||
/// <see cref="OptionalWarning.UnusedNode"/> can include it in the warning if that node ends up being unused?
|
||||
/// </summary>
|
||||
/// <remarks>This has a notable performance cost so it should only be used when trying to identify a problem.</remarks>
|
||||
public static bool TraceConstructor { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Only]
|
||||
/// The stack trace of the constructor (or null if <see cref="TraceConstructor"/> was false).
|
||||
/// </summary>
|
||||
private System.Diagnostics.StackTrace _ConstructorStackTrace;
|
||||
|
||||
/// <summary>[Assert-Only]
|
||||
/// Returns the stack trace of the constructor (or null if <see cref="TraceConstructor"/> was false).
|
||||
/// </summary>
|
||||
public static System.Diagnostics.StackTrace GetConstructorStackTrace(AnimancerNode node)
|
||||
=> node._ConstructorStackTrace;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Only] Checks <see cref="OptionalWarning.UnusedNode"/>.</summary>
|
||||
~AnimancerNode()
|
||||
{
|
||||
if (Graph != null ||
|
||||
Parent != null ||
|
||||
OptionalWarning.UnusedNode.IsDisabled())
|
||||
return;
|
||||
|
||||
// ToString might throw an exception since finalizers arn't run on the main thread.
|
||||
string name = null;
|
||||
try { name = ToString(); }
|
||||
catch { name = GetType().FullName; }
|
||||
|
||||
var message = $"The {nameof(Graph)} of '{name}'" +
|
||||
$" is null during finalization (garbage collection)." +
|
||||
$" This may have been caused by earlier exceptions, but otherwise it probably means" +
|
||||
$" that this node was never used for anything and should not have been created.";
|
||||
|
||||
if (_ConstructorStackTrace != null)
|
||||
message += "\n\nThis node was created at:\n" + _ConstructorStackTrace;
|
||||
else
|
||||
message += $"\n\nEnable {nameof(AnimancerNode)}.{nameof(TraceConstructor)} on startup" +
|
||||
$" to allow this warning to include the {nameof(System.Diagnostics.StackTrace)}" +
|
||||
$" of when the node was constructed.";
|
||||
|
||||
OptionalWarning.UnusedNode.Log(message);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Connects the `child`'s <see cref="Playable"/> to this node.</summary>
|
||||
/// <remarks>This method is NOT safe to call if the child was already connected.</remarks>
|
||||
protected internal void ConnectChildUnsafe(int index, AnimancerNode child)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (index < 0)
|
||||
{
|
||||
MarkAsUsed(this);
|
||||
throw new InvalidOperationException(
|
||||
$"Invalid {nameof(index)} when attempting to connect to its parent:" +
|
||||
"\n• Child: " + child +
|
||||
"\n• Parent: " + this);
|
||||
}
|
||||
|
||||
Validate.AssertPlayable(child);
|
||||
#endif
|
||||
|
||||
Graph._PlayableGraph.Connect(_Playable, child._Playable, index, child._Weight);
|
||||
}
|
||||
|
||||
/// <summary>Disconnects the <see cref="Playable"/> of the child at the specified `index` from this node.</summary>
|
||||
/// <remarks>This method is safe to call if the child was already disconnected.</remarks>
|
||||
protected void DisconnectChildSafe(int index)
|
||||
{
|
||||
if (_Playable.GetInput(index).IsValid())
|
||||
Graph._PlayableGraph.Disconnect(_Playable, index);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
// IEnumerator for yielding in a coroutine to wait until animations have stopped.
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is this node playing and not yet at its end?</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 abstract bool IsPlayingAndNotEnding();
|
||||
|
||||
bool IEnumerator.MoveNext()
|
||||
=> IsPlayingAndNotEnding();
|
||||
|
||||
object IEnumerator.Current
|
||||
=> null;
|
||||
|
||||
void IEnumerator.Reset() { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Children
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected internal override AnimancerNode GetChildNode(int index)
|
||||
=> GetChild(index);
|
||||
|
||||
/// <summary>Returns the state connected to the specified `index` as a child of this node.</summary>
|
||||
/// <remarks>When overriding, don't call this base method because it throws an exception.</remarks>
|
||||
/// <exception cref="NotSupportedException">This node can't have children.</exception>
|
||||
public virtual AnimancerState GetChild(int index)
|
||||
{
|
||||
MarkAsUsed(this);
|
||||
throw new NotSupportedException(this + " can't have children.");
|
||||
}
|
||||
|
||||
/// <summary>Called when a child is connected with this node as its <see cref="AnimancerNodeBase.Parent"/>.</summary>
|
||||
/// <remarks>When overriding, don't call this base method because it throws an exception.</remarks>
|
||||
/// <exception cref="NotSupportedException">This node can't have children.</exception>
|
||||
protected internal virtual void OnAddChild(AnimancerState child)
|
||||
{
|
||||
MarkAsUsed(this);
|
||||
child.SetParentInternal(null);
|
||||
throw new NotSupportedException(this + " can't have children.");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
// IEnumerable for 'foreach' statements.
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gets an enumerator for all of this node's child states.</summary>
|
||||
public virtual FastEnumerator<AnimancerState> GetEnumerator()
|
||||
=> default;
|
||||
|
||||
IEnumerator<AnimancerState> IEnumerable<AnimancerState>.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Weight
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Internal] The current blend weight of this node. Accessed via <see cref="Weight"/>.</summary>
|
||||
internal float _Weight;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The current blend weight of this node which determines how much it affects the final output.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// 0 has no effect while 1 applies the full effect and values inbetween apply a proportional effect.
|
||||
/// <para></para>
|
||||
/// Setting this property cancels any fade currently in progress. If you don't wish to do that, you can use
|
||||
/// <see cref="SetWeight"/> instead.
|
||||
/// <para></para>
|
||||
/// <em>Animancer Lite only allows this value to be set to 0 or 1 in runtime builds.</em>
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// Calling <see cref="AnimancerLayer.Play(AnimationClip)"/> immediately sets the weight of all states to 0
|
||||
/// and the new state to 1. Note that this is separate from other values like
|
||||
/// <see cref="AnimancerState.IsPlaying"/> so a state can be paused at any point and still show its pose on the
|
||||
/// character or it could be still playing at 0 weight if you want it to still trigger events (though states
|
||||
/// are normally stopped when they reach 0 weight so you would need to explicitly set it to playing again).
|
||||
/// <para></para>
|
||||
/// Calling <see cref="AnimancerLayer.Play(AnimationClip, float, FadeMode)"/> doesn't immediately change
|
||||
/// the weights, but instead calls <see cref="StartFade(float, float)"/> on every state to set their
|
||||
/// <see cref="TargetWeight"/> and <see cref="FadeSpeed"/>. Then every update each state's weight will move
|
||||
/// towards that target value at that speed.
|
||||
/// </example>
|
||||
public float Weight
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _Weight;
|
||||
set
|
||||
{
|
||||
FadeGroup = null;
|
||||
SetWeight(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current blend weight of this node which determines how much it affects the final output.
|
||||
/// 0 has no effect while 1 applies the full effect of this node.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method allows any fade currently in progress to continue. If you don't wish to do that, you can set
|
||||
/// the <see cref="Weight"/> property instead.
|
||||
/// <para></para>
|
||||
/// <em>Animancer Lite only allows this value to be set to 0 or 1 in runtime builds.</em>
|
||||
/// </remarks>
|
||||
public virtual void SetWeight(float value)
|
||||
=> SetWeightInternal(value);
|
||||
|
||||
/// <summary>The internal non-<c>virtual</c> implementation of <see cref="SetWeight"/>.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetWeightInternal(float value)
|
||||
{
|
||||
if (_Weight == value)
|
||||
return;
|
||||
|
||||
Validate.AssertSetWeight(this, value);
|
||||
|
||||
_Weight = value;
|
||||
|
||||
if (Graph != null)
|
||||
Parent?.Playable.ApplyChildWeight(this);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected internal override float BaseWeight
|
||||
=> Weight;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Weight"/> of this state multiplied by the <see cref="Weight"/> of each of its parents down
|
||||
/// the hierarchy to determine how much this state affects the final output.
|
||||
/// </summary>
|
||||
public float EffectiveWeight
|
||||
{
|
||||
get
|
||||
{
|
||||
var weight = Weight;
|
||||
|
||||
var parent = Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
weight *= parent.BaseWeight;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
return weight;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Fading
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal FadeGroup _FadeGroup;
|
||||
|
||||
/// <summary>The current fade being applied to this node (if any).</summary>
|
||||
public FadeGroup FadeGroup
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _FadeGroup;
|
||||
internal set
|
||||
{
|
||||
_FadeGroup?.Remove(this);
|
||||
_FadeGroup = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The desired <see cref="Weight"/> which this node is fading towards according to the
|
||||
/// <see cref="FadeSpeed"/>.
|
||||
/// </summary>
|
||||
public float TargetWeight
|
||||
=> FadeGroup != null
|
||||
? FadeGroup.GetTargetWeight(this)
|
||||
: Weight;
|
||||
|
||||
/// <summary>The speed at which this node is fading towards the <see cref="TargetWeight"/>.</summary>
|
||||
/// <remarks>
|
||||
/// This value isn't affected by this node's <see cref="AnimancerNodeBase.Speed"/>,
|
||||
/// but is affected by its parents.
|
||||
/// </remarks>
|
||||
public float FadeSpeed
|
||||
=> FadeGroup != null
|
||||
? FadeGroup.FadeSpeed
|
||||
: 0;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="OnStartFade"/> and starts fading the <see cref="Weight"/> over the course
|
||||
/// of the <see cref="AnimancerGraph.DefaultFadeDuration"/> (in seconds).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the `targetWeight` is 0 then <see cref="Stop"/> will be called when the fade is complete.
|
||||
/// <para></para>
|
||||
/// If the <see cref="Weight"/> is already equal to the `targetWeight` then the fade will end
|
||||
/// immediately.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void StartFade(float targetWeight)
|
||||
=> StartFade(targetWeight, AnimancerGraph.DefaultFadeDuration);
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="OnStartFade"/> and starts fading the <see cref="Weight"/>
|
||||
/// over the course of the `fadeDuration` (in seconds).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the `targetWeight` is 0 then <see cref="Stop"/> will be called when the fade is complete.
|
||||
/// <para></para>
|
||||
/// If the <see cref="Weight"/> is already equal to the `targetWeight`
|
||||
/// then the fade will end immediately.
|
||||
/// <para></para>
|
||||
/// <em>Animancer Lite only allows a `targetWeight` of 0 or 1
|
||||
/// and the default `fadeDuration` (0.25 seconds) in runtime builds.</em>
|
||||
/// </remarks>
|
||||
public void StartFade(float targetWeight, float fadeDuration)
|
||||
{
|
||||
if (Weight == targetWeight && FadeGroup == null)
|
||||
{
|
||||
OnStartFade();
|
||||
}
|
||||
else if (fadeDuration > 0)
|
||||
{
|
||||
var fade = FadeGroup.Pool.Instance.Acquire();
|
||||
fade.SetFadeIn(this);
|
||||
fade.StartFade(targetWeight, 1 / fadeDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
Weight = targetWeight;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called by <see cref="StartFade(float, float)"/>.</summary>
|
||||
protected internal abstract void OnStartFade();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Removes this node from the <see cref="FadeGroup"/>.</summary>
|
||||
public void CancelFade()
|
||||
=> _FadeGroup?.Remove(this);
|
||||
|
||||
/// <summary>[Internal] Called by <see cref="FadeGroup.Remove"/>.</summary>
|
||||
/// <remarks>Not called when a fade fully completes.</remarks>
|
||||
protected internal virtual void InternalClearFade()
|
||||
{
|
||||
_FadeGroup = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Stops the animation and makes it inactive immediately so it no longer affects the output.</summary>
|
||||
/// <remarks>
|
||||
/// Sets <see cref="Weight"/> = 0 by default unless overridden.
|
||||
/// <para></para>
|
||||
/// Note that playing something new will automatically stop the old animation.
|
||||
/// </remarks>
|
||||
public void Stop()
|
||||
{
|
||||
FadeGroup = null;
|
||||
SetWeightInternal(0);
|
||||
StopWithoutWeight();
|
||||
}
|
||||
|
||||
/// <summary>[Internal] Stops this node without setting its <see cref="Weight"/>.</summary>
|
||||
protected internal virtual void StopWithoutWeight() { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Inverse Kinematics
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Should setting the <see cref="AnimancerNodeBase.Parent"/>
|
||||
/// also set this node's <see cref="ApplyAnimatorIK"/> to match it?
|
||||
/// Default is true.
|
||||
/// </summary>
|
||||
public static bool ApplyParentAnimatorIK { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should setting the <see cref="AnimancerNodeBase.Parent"/>
|
||||
/// also set this node's <see cref="ApplyFootIK"/> to match it?
|
||||
/// Default is true.
|
||||
/// </summary>
|
||||
public static bool ApplyParentFootIK { get; set; } = true;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Copies the IK settings from `copyFrom` into this node:
|
||||
/// <list type="bullet">
|
||||
/// <item>If <see cref="ApplyParentAnimatorIK"/> is true, copy <see cref="ApplyAnimatorIK"/>.</item>
|
||||
/// <item>If <see cref="ApplyParentFootIK"/> is true, copy <see cref="ApplyFootIK"/>.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public virtual void CopyIKFlags(AnimancerNodeBase copyFrom)
|
||||
{
|
||||
if (Graph == null)
|
||||
return;
|
||||
|
||||
if (ApplyParentAnimatorIK)
|
||||
ApplyAnimatorIK = copyFrom.ApplyAnimatorIK;
|
||||
|
||||
if (ApplyParentFootIK)
|
||||
ApplyFootIK = copyFrom.ApplyFootIK;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ApplyAnimatorIK
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = ChildCount - 1; i >= 0; i--)
|
||||
{
|
||||
var state = GetChild(i);
|
||||
if (state.ApplyAnimatorIK)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
set
|
||||
{
|
||||
for (int i = ChildCount - 1; i >= 0; i--)
|
||||
{
|
||||
var state = GetChild(i);
|
||||
state.ApplyAnimatorIK = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ApplyFootIK
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = ChildCount - 1; i >= 0; i--)
|
||||
{
|
||||
var state = GetChild(i);
|
||||
if (state.ApplyFootIK)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
set
|
||||
{
|
||||
for (int i = ChildCount - 1; i >= 0; i--)
|
||||
{
|
||||
var state = GetChild(i);
|
||||
state.ApplyFootIK = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Descriptions
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
/// <summary>[Assert-Only] The Inspector display name of this node.</summary>
|
||||
/// <remarks>Set using <see cref="SetDebugName"/>.</remarks>
|
||||
public object DebugName { get; private set; }
|
||||
#endif
|
||||
|
||||
/// <summary>[Assert-Conditional] Sets the <see cref="DebugName"/> to display in the Inspector.</summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public void SetDebugName(object name)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
DebugName = name;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>The Inspector display name of this node.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (NameCache.TryToString(DebugName, out var name))
|
||||
return name;
|
||||
#endif
|
||||
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AppendDescription(StringBuilder text, string separator = "\n")
|
||||
{
|
||||
|
||||
text.Append(ToString());
|
||||
|
||||
AppendDetails(text, separator);
|
||||
|
||||
if (ChildCount > 0)
|
||||
{
|
||||
text.AppendField(separator, nameof(ChildCount), ChildCount);
|
||||
var indentedSeparator = separator + Strings.Indent;
|
||||
|
||||
var i = 0;
|
||||
foreach (var child in this)
|
||||
{
|
||||
text.Append(separator)
|
||||
.Append('[')
|
||||
.Append(i++)
|
||||
.Append("] ")
|
||||
.AppendDescription(child, indentedSeparator, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called by <see cref="AppendDescription"/> to append the details of this node.</summary>
|
||||
protected virtual void AppendDetails(StringBuilder text, string separator)
|
||||
{
|
||||
text.AppendField(separator, "Playable", _Playable.IsValid()
|
||||
? _Playable.GetPlayableType().ToString()
|
||||
: "Invalid");
|
||||
|
||||
var parent = Parent;
|
||||
var isConnected =
|
||||
parent != null &&
|
||||
parent.Playable.GetInput(Index).IsValid();
|
||||
|
||||
text.AppendField(separator, "Connected", isConnected);
|
||||
|
||||
text.AppendField(separator, nameof(Index), Index);
|
||||
if (Index < 0)
|
||||
text.Append(" (No Parent)");
|
||||
|
||||
text.AppendField(separator, nameof(Speed), Speed);
|
||||
|
||||
var realSpeed = _Playable.IsValid()
|
||||
? _Playable.GetSpeed()
|
||||
: Speed;
|
||||
|
||||
if (realSpeed != Speed)
|
||||
text.Append(" (Real ").Append(realSpeed).Append(')');
|
||||
|
||||
text.AppendField(separator, nameof(Weight), Weight);
|
||||
|
||||
if (Weight != TargetWeight)
|
||||
{
|
||||
text.AppendField(separator, nameof(TargetWeight), TargetWeight);
|
||||
text.AppendField(separator, nameof(FadeSpeed), FadeSpeed);
|
||||
}
|
||||
|
||||
AppendIKDetails(text, separator, this);
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
if (_ConstructorStackTrace != null)
|
||||
text.AppendField(separator, "ConstructorStackTrace", _ConstructorStackTrace);
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Appends the details of <see cref="AnimancerNodeBase.ApplyAnimatorIK"/> and
|
||||
/// <see cref="AnimancerNodeBase.ApplyFootIK"/>.
|
||||
/// </summary>
|
||||
public static void AppendIKDetails(StringBuilder text, string separator, AnimancerNodeBase node)
|
||||
{
|
||||
if (!node.Playable.IsValid())
|
||||
return;
|
||||
|
||||
text.Append(separator)
|
||||
.Append("InverseKinematics: ");
|
||||
|
||||
if (node.ApplyAnimatorIK)
|
||||
{
|
||||
text.Append("OnAnimatorIK");
|
||||
if (node.ApplyFootIK)
|
||||
text.Append(", FootIK");
|
||||
}
|
||||
else if (node.ApplyFootIK)
|
||||
{
|
||||
text.Append("FootIK");
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Append("None");
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the hierarchy path of this node through its <see cref="AnimancerNodeBase.Parent"/>s.</summary>
|
||||
public string GetPath()
|
||||
{
|
||||
var path = StringBuilderPool.Instance.Acquire();
|
||||
|
||||
if (Parent is AnimancerNode parent)
|
||||
{
|
||||
AppendPath(path, parent);
|
||||
AppendPortAndType(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendPortAndType(path);
|
||||
}
|
||||
|
||||
return path.ReleaseToString();
|
||||
}
|
||||
|
||||
/// <summary>Appends the hierarchy path of this state through its <see cref="AnimancerNodeBase.Parent"/>s.</summary>
|
||||
private static void AppendPath(StringBuilder path, AnimancerNode parent)
|
||||
{
|
||||
if (parent != null)
|
||||
{
|
||||
if (parent.Parent is AnimancerNode grandParent)
|
||||
{
|
||||
AppendPath(path, grandParent);
|
||||
}
|
||||
else
|
||||
{
|
||||
var layer = parent.Layer;
|
||||
if (layer != null)
|
||||
{
|
||||
path.Append("Layers[")
|
||||
.Append(parent.Layer.Index)
|
||||
.Append("].States");
|
||||
}
|
||||
else
|
||||
{
|
||||
path.Append("NoLayer -> ")
|
||||
.Append(parent.ToString());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent is AnimancerState state)
|
||||
{
|
||||
state.AppendPortAndType(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
path.Append(" -> ")
|
||||
.Append(parent.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Appends "[Index] -> ToString()".</summary>
|
||||
private void AppendPortAndType(StringBuilder path)
|
||||
{
|
||||
path.Append('[')
|
||||
.Append(Index)
|
||||
.Append("] -> ")
|
||||
.Append(ToString());
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54f55b421ce0c584ab6bcb47947126e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,282 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>Base class for objects that manage a <see cref="UnityEngine.Playables.Playable"/>.</summary>
|
||||
/// <remarks>This is the base class of <see cref="AnimancerGraph"/> and <see cref="AnimancerNode"/>.</remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerNodeBase
|
||||
public abstract class AnimancerNodeBase
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="AnimancerGraph"/> containing this node.</summary>
|
||||
public AnimancerGraph Graph { get; internal set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The object which receives the output of the <see cref="Playable"/>.</summary>
|
||||
/// <remarks>
|
||||
/// This leads from <see cref="AnimancerState"/> to <see cref="AnimancerLayer"/> to
|
||||
/// <see cref="AnimancerGraph"/> to <c>null</c>.
|
||||
/// </remarks>
|
||||
public AnimancerNodeBase Parent { get; protected set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The root <see cref="AnimancerLayer"/> which this node is connected to (if any).</summary>
|
||||
public virtual AnimancerLayer Layer
|
||||
=> Parent?.Layer;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The number of nodes using this as their <see cref="Parent"/>.</summary>
|
||||
public virtual int ChildCount
|
||||
=> 0;
|
||||
|
||||
/// <summary>Returns the node connected to the specified `index` as a child of this node.</summary>
|
||||
/// <remarks>When overriding, don't call this base method because it throws an exception.</remarks>
|
||||
/// <exception cref="NotSupportedException">This node can't have children.</exception>
|
||||
protected internal virtual AnimancerNode GetChildNode(int index)
|
||||
{
|
||||
MarkAsUsed(this);
|
||||
throw new NotSupportedException(this + " can't have children.");
|
||||
}
|
||||
|
||||
/// <summary>Should child playables stay connected to the graph at all times?</summary>
|
||||
/// <remarks>
|
||||
/// If false, playables will be disconnected from the graph while they are inactive to stop it from
|
||||
/// evaluating them every frame which usually improves performance.
|
||||
/// </remarks>
|
||||
/// <seealso cref="AnimancerGraph.KeepChildrenConnected"/>
|
||||
public virtual bool KeepChildrenConnected
|
||||
=> true;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called when a child's <see cref="AnimancerState.IsLooping"/> value changes.</summary>
|
||||
protected virtual void OnChildIsLoopingChanged(bool value) { }
|
||||
|
||||
/// <summary>[Internal] Calls <see cref="OnChildIsLoopingChanged"/> for each <see cref="Parent"/> recursively.</summary>
|
||||
protected internal void OnIsLoopingChangedRecursive(bool value)
|
||||
{
|
||||
var parent = Parent;
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
parent.OnChildIsLoopingChanged(value);
|
||||
|
||||
parent = parent.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Internal] Called when a child's <see cref="Parent"/> is changed from this node.</summary>
|
||||
/// <remarks>When overriding, don't call this base method because it throws an exception.</remarks>
|
||||
/// <exception cref="NotSupportedException">This node can't have children.</exception>
|
||||
protected internal virtual void OnRemoveChild(AnimancerState child)
|
||||
{
|
||||
MarkAsUsed(this);
|
||||
child.SetParentInternal(null);
|
||||
throw new NotSupportedException(this + " can't have children.");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Internal] The <see cref="Playable"/>.</summary>
|
||||
protected internal Playable _Playable;
|
||||
|
||||
/// <summary>The internal object this node manages in the <see cref="PlayableGraph"/>.</summary>
|
||||
/// <remarks>
|
||||
/// Must be set by <see cref="AnimancerNode.CreatePlayable()"/>. Failure to do so will throw the following
|
||||
/// exception throughout the system when using this node: "<see cref="ArgumentException"/>: The playable passed
|
||||
/// as an argument is invalid. To create a valid playable, please use the appropriate Create method".
|
||||
/// </remarks>
|
||||
public Playable Playable => _Playable;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The current blend weight of this node which determines how much it affects the final output.</summary>
|
||||
protected internal virtual float BaseWeight => 1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Speed
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private float _Speed = 1;
|
||||
|
||||
/// <summary>[Pro-Only] How fast the <see cref="AnimancerState.Time"/> is advancing every frame (default 1).</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// A negative value will play the animation backwards.
|
||||
/// <para></para>
|
||||
/// To pause an animation, consider setting <see cref="AnimancerState.IsPlaying"/> to false instead of setting
|
||||
/// this value to 0.
|
||||
/// <para></para>
|
||||
/// <em>Animancer Lite doesn't allow this value to be changed in runtime builds.</em>
|
||||
/// <para></para>
|
||||
/// <strong>Example:</strong><code>
|
||||
/// void SpeedExample(AnimancerComponent animancer, AnimationClip clip)
|
||||
/// {
|
||||
/// var state = animancer.Play(clip);
|
||||
///
|
||||
/// state.Speed = 1;// Normal speed.
|
||||
/// state.Speed = 2;// Double speed.
|
||||
/// state.Speed = 0.5f;// Half speed.
|
||||
/// state.Speed = -1;// Normal speed playing backwards.
|
||||
/// state.NormalizedTime = 1;// Start at the end to play backwards from there.
|
||||
/// }
|
||||
/// </code></remarks>
|
||||
///
|
||||
/// <exception cref="ArgumentOutOfRangeException">The value is not finite.</exception>
|
||||
public virtual float Speed
|
||||
{
|
||||
get => _Speed;
|
||||
set
|
||||
{
|
||||
if (_Speed == value)
|
||||
return;
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
if (!value.IsFinite())
|
||||
{
|
||||
MarkAsUsed(this);
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(value),
|
||||
value,
|
||||
$"{nameof(Speed)} {Strings.MustBeFinite}");
|
||||
}
|
||||
#endif
|
||||
_Speed = value;
|
||||
|
||||
if (_Playable.IsValid())
|
||||
_Playable.SetSpeed(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Speed"/> of this node multiplied by the <see cref="Speed"/> of each of its parents to
|
||||
/// determine the actual speed it's playing at.
|
||||
/// </summary>
|
||||
public float EffectiveSpeed
|
||||
{
|
||||
get => Speed * ParentEffectiveSpeed;
|
||||
set => Speed = value / ParentEffectiveSpeed;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The multiplied <see cref="Speed"/> of each of the <see cref="Parent"/> down the hierarchy,
|
||||
/// excluding the root <see cref="Speed"/>.
|
||||
/// </summary>
|
||||
private float ParentEffectiveSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
var parent = Parent;
|
||||
if (parent == null)
|
||||
return 1;
|
||||
|
||||
var speed = parent.Speed;
|
||||
|
||||
while ((parent = parent.Parent) != null)
|
||||
{
|
||||
speed *= parent.Speed;
|
||||
}
|
||||
|
||||
return speed;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Should Unity call <c>OnAnimatorIK</c> on the animated object while this object and its children have any
|
||||
/// <see cref="AnimancerNode.Weight"/>?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is equivalent to the "IK Pass" toggle in Animator Controller layers, except that due to limitations in
|
||||
/// the Playables API the <c>layerIndex</c> will always be zero.
|
||||
/// <para></para>
|
||||
/// This value starts false by default, but can be automatically changed by
|
||||
/// <see cref="AnimancerNode.CopyIKFlags"/> when the <see cref="Parent"/> is set.
|
||||
/// <para></para>
|
||||
/// IK only takes effect while at least one <see cref="ClipState"/> has a <see cref="AnimancerNode.Weight"/>
|
||||
/// above zero. Other node types either store the value to apply to their children or don't support IK.
|
||||
/// <para></para>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/ik#ik-pass">
|
||||
/// IK Pass</see>
|
||||
/// </remarks>
|
||||
public abstract bool ApplyAnimatorIK { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Should this object and its children apply IK to the character's feet?</summary>
|
||||
/// <remarks>
|
||||
/// This is equivalent to the "Foot IK" toggle in Animator Controller states.
|
||||
/// <para></para>
|
||||
/// This value starts true by default for <see cref="ClipState"/>s (false for others), but can be automatically
|
||||
/// changed by <see cref="AnimancerNode.CopyIKFlags"/> when the <see cref="Parent"/> is set.
|
||||
/// <para></para>
|
||||
/// IK only takes effect while at least one <see cref="ClipState"/> has a <see cref="AnimancerNode.Weight"/>
|
||||
/// above zero. Other node types either store the value to apply to their children or don't support IK.
|
||||
/// <para></para>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/ik#foot-ik">
|
||||
/// Foot IK</see>
|
||||
/// </remarks>
|
||||
public abstract bool ApplyFootIK { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Internal] Applies a change to a child's <see cref="AnimancerState.IsActive"/>.</summary>
|
||||
protected internal virtual void ApplyChildActive(AnimancerState child, bool setActive)
|
||||
=> child.ShouldBeActive = setActive;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional] Prevents the `node` from causing <see cref="OptionalWarning.UnusedNode"/>.</summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void MarkAsUsed(AnimancerNodeBase node)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (node.Graph == null)
|
||||
GC.SuppressFinalize(node);
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only]
|
||||
/// Adds functions to show and set <see cref="ApplyAnimatorIK"/> and
|
||||
/// <see cref="ApplyFootIK"/>.
|
||||
/// </summary>
|
||||
public static void AddContextMenuIK(UnityEditor.GenericMenu menu, AnimancerNodeBase ik)
|
||||
{
|
||||
#if UNITY_IMGUI
|
||||
menu.AddItem(new("Inverse Kinematics/Apply Animator IK ?"),
|
||||
ik.ApplyAnimatorIK,
|
||||
() => ik.ApplyAnimatorIK = !ik.ApplyAnimatorIK);
|
||||
menu.AddItem(new("Inverse Kinematics/Apply Foot IK ?"),
|
||||
ik.ApplyFootIK,
|
||||
() => ik.ApplyFootIK = !ik.ApplyFootIK);
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8abfd735619067c40b7e6e10bcc0b559
|
||||
labels:
|
||||
- Interface
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,67 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerState
|
||||
public abstract partial class AnimancerState
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
private static bool _SkipNextExpectFade;
|
||||
|
||||
private bool _ExpectFade;
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Internal] Sets a flag for <see cref="OptionalWarning.ExpectFade"/>.</summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
public static void SetExpectFade(AnimancerState state, float fadeDuration)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
state._ExpectFade = fadeDuration > 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Internal] Sets the next <see cref="AssertNotExpectingFade"/> call to be skipped.</summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
internal static void SkipNextExpectFade()
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
_SkipNextExpectFade = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Internal] Call when playing a `state` without a fade to check <see cref="OptionalWarning.ExpectFade"/>.</summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
internal static void AssertNotExpectingFade(AnimancerState state)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (_SkipNextExpectFade)
|
||||
{
|
||||
_SkipNextExpectFade = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (state._ExpectFade)
|
||||
{
|
||||
state._ExpectFade = false;// Don't log again for the same state.
|
||||
OptionalWarning.ExpectFade.Log(
|
||||
"A state was created by a transition with a non-zero Fade Duration" +
|
||||
" but is now being played without a fade, which may be unintentional." +
|
||||
" In most cases, the transition should be played so that it can properly" +
|
||||
" apply its settings, unlike if the state is played directly.",
|
||||
state.Graph?.Component);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04e4439666601f0439582e16fc1572eb
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 887b4c2f23914df4085099d24992df1c
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,217 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Pro-Only] A scripted animation for an <see cref="AnimationJobState{T}"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Sample:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/samples/jobs/job-states">
|
||||
/// Job States</see>
|
||||
/// </remarks>
|
||||
public interface IAnimancerStateJob : IDisposable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The total time this job would take to play in seconds at normal speed.</summary>
|
||||
float Length { get; }
|
||||
|
||||
/// <summary>Does this job loop back to the start when its time passes its <see cref="Length"/>?</summary>
|
||||
bool IsLooping { get; }
|
||||
|
||||
/// <summary>Defines what do to when processing the root motion.</summary>
|
||||
/// <remarks>
|
||||
/// This is called by <see cref="IAnimationJob.ProcessRootMotion"/>
|
||||
/// and receives the <see cref="AnimancerState.TimeD"/>.
|
||||
/// </remarks>
|
||||
void ProcessRootMotion(AnimationStream stream, double time);
|
||||
|
||||
/// <summary>Defines what do to when processing the animation.</summary>
|
||||
/// <remarks>
|
||||
/// This is called by <see cref="IAnimationJob.ProcessAnimation"/>
|
||||
/// and receives the <see cref="AnimancerState.TimeD"/>.
|
||||
/// </remarks>
|
||||
void ProcessAnimation(AnimationStream stream, double time);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only] An <see cref="AnimancerState"/> which plays an <see cref="IAnimancerStateJob"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Sample:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/samples/jobs/job-states">
|
||||
/// Job States</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/AnimationJobState_1
|
||||
///
|
||||
public class AnimationJobState<T> : AnimancerState, IUpdatable, IDisposable
|
||||
where T : struct, IAnimancerStateJob
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IAnimationJob"/> which wraps an <see cref="IAnimancerStateJob"/>
|
||||
/// to provide its <see cref="Time"/> value.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/TimedJob
|
||||
public struct TimedJob : IAnimationJob, IDisposable
|
||||
{
|
||||
/// <summary>The <see cref="IAnimancerStateJob"/> data.</summary>
|
||||
public T Job;
|
||||
|
||||
/// <summary>The <see cref="AnimancerState.TimeD"/> to be passed to the job.</summary>
|
||||
public NativeArray<double> Time;
|
||||
|
||||
/// <summary>Cleans up the unmanaged resources used by this job.</summary>
|
||||
public readonly void Dispose()
|
||||
{
|
||||
if (Time.IsCreated)
|
||||
Time.Dispose();
|
||||
|
||||
Job.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>Defines what do to when processing the root motion.</summary>
|
||||
public readonly void ProcessRootMotion(AnimationStream stream)
|
||||
=> Job.ProcessRootMotion(stream, Time[0]);
|
||||
|
||||
/// <summary>Defines what do to when processing the animation.</summary>
|
||||
public readonly void ProcessAnimation(AnimationStream stream)
|
||||
=> Job.ProcessAnimation(stream, Time[0]);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private new AnimationScriptPlayable _Playable;
|
||||
private TimedJob _Job;
|
||||
|
||||
/// <summary>The data of the job to be executed by this state.</summary>
|
||||
/// <remarks>
|
||||
/// Setting this value has a minor performance cost. If it needs to be changed frequently,
|
||||
/// consider using a single-item <c>NativeArray</c> in your job as demonstrated in the
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/samples/jobs/hit-impacts#angle">
|
||||
/// Hit Impacts</see> sample.
|
||||
/// </remarks>
|
||||
public T Job
|
||||
{
|
||||
get => _Job.Job;
|
||||
set
|
||||
{
|
||||
_Job.Job = value;
|
||||
_Playable.SetJobData(_Job);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float Length
|
||||
=> _Job.Job.Length;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsLooping
|
||||
=> _Job.Job.IsLooping;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
int IUpdatable.UpdatableIndex { get; set; } = IUpdatable.List.NotInList;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="AnimationJobState{T}"/>.</summary>
|
||||
public AnimationJobState(T job)
|
||||
{
|
||||
_Job.Job = job;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetGraph(AnimancerGraph graph)
|
||||
{
|
||||
Graph?.Disposables.Remove(this);
|
||||
base.SetGraph(graph);
|
||||
Graph?.Disposables.Add(this);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void CreatePlayable(out Playable playable)
|
||||
{
|
||||
if (!_Job.Time.IsCreated)
|
||||
_Job.Time = AnimancerUtilities.CreateNativeReference<double>();
|
||||
|
||||
playable = _Playable = AnimationScriptPlayable.Create(Graph.PlayableGraph, _Job);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnSetIsPlaying()
|
||||
{
|
||||
base.OnSetIsPlaying();
|
||||
|
||||
if (IsPlaying)
|
||||
Graph.RequirePreUpdate(this);
|
||||
else
|
||||
Graph.CancelPreUpdate(this);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame before the job is applied to send the
|
||||
/// <see cref="AnimancerState.Time"/> to the <see cref="Job"/>.
|
||||
/// </summary>
|
||||
public virtual void Update()
|
||||
{
|
||||
var time = _Job.Time;
|
||||
time[0] = TimeD;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
=> _Job.Dispose();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Destroy()
|
||||
{
|
||||
base.Destroy();
|
||||
|
||||
if (Graph != null)
|
||||
{
|
||||
Graph.CancelPreUpdate(this);
|
||||
Graph.Disposables.Remove(this);
|
||||
}
|
||||
|
||||
_Job.Dispose();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
=> _Job.Job.ToString();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerState Clone(CloneContext context)
|
||||
{
|
||||
var clone = new AnimationJobState<T>(_Job.Job);
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a383b34eda0fe0408c12bc3a77108b5
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,190 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>An <see cref="AnimancerState"/> which plays an <see cref="AnimationClip"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/states">
|
||||
/// States</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ClipState
|
||||
///
|
||||
public class ClipState : AnimancerState
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private AnimationClip _Clip;
|
||||
|
||||
/// <summary>The <see cref="AnimationClip"/> which this state plays.</summary>
|
||||
public override AnimationClip Clip
|
||||
{
|
||||
get => _Clip;
|
||||
set
|
||||
{
|
||||
Validate.AssertAnimationClip(value, true, $"set {nameof(ClipState)}.{nameof(Clip)}");
|
||||
if (ChangeMainObject(ref _Clip, value))
|
||||
{
|
||||
_Length = value.length;
|
||||
|
||||
var isLooping = value.isLooping;
|
||||
if (_IsLooping != isLooping)
|
||||
{
|
||||
_IsLooping = isLooping;
|
||||
OnIsLoopingChangedRecursive(isLooping);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The <see cref="AnimationClip"/> which this state plays.</summary>
|
||||
public override Object MainObject
|
||||
{
|
||||
get => _Clip;
|
||||
set => Clip = (AnimationClip)value;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <inheritdoc/>
|
||||
public override Type MainObjectType
|
||||
=> typeof(AnimationClip);
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private float _Length;
|
||||
|
||||
/// <summary>The <see cref="AnimationClip.length"/>.</summary>
|
||||
public override float Length
|
||||
=> _Length;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private bool _IsLooping;
|
||||
|
||||
/// <summary>The <see cref="Motion.isLooping"/>.</summary>
|
||||
public override bool IsLooping
|
||||
=> _IsLooping;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerEvent.DispatchInfo GetEventDispatchInfo()
|
||||
{
|
||||
var length = _Length;
|
||||
return new(
|
||||
length,
|
||||
length != 0 ? Time / length : 0,
|
||||
_IsLooping);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Vector3 AverageVelocity
|
||||
=> _Clip.averageSpeed;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Inverse Kinematics
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ApplyAnimatorIK
|
||||
{
|
||||
get => _Playable.IsValid() && ((AnimationClipPlayable)_Playable).GetApplyPlayableIK();
|
||||
set
|
||||
{
|
||||
Validate.AssertPlayable(this);
|
||||
((AnimationClipPlayable)_Playable).SetApplyPlayableIK(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool ApplyFootIK
|
||||
{
|
||||
get => _Playable.IsValid() && ((AnimationClipPlayable)_Playable).GetApplyFootIK();
|
||||
set
|
||||
{
|
||||
Validate.AssertPlayable(this);
|
||||
((AnimationClipPlayable)_Playable).SetApplyFootIK(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Methods
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="ClipState"/> and sets its <see cref="Clip"/>.</summary>
|
||||
/// <exception cref="ArgumentNullException">The `clip` is null.</exception>
|
||||
public ClipState(AnimationClip clip)
|
||||
{
|
||||
Validate.AssertAnimationClip(clip, true, $"create {nameof(ClipState)}");
|
||||
_Clip = clip;
|
||||
_Length = clip.length;
|
||||
_IsLooping = clip.isLooping;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates and assigns the <see cref="AnimationClipPlayable"/> managed by this node.</summary>
|
||||
protected override void CreatePlayable(out Playable playable)
|
||||
{
|
||||
playable = AnimationClipPlayable.Create(Graph._PlayableGraph, _Clip);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void RecreatePlayable()
|
||||
{
|
||||
var playable = (AnimationClipPlayable)_Playable;
|
||||
var footIK = playable.GetApplyFootIK();
|
||||
var playableIK = playable.GetApplyPlayableIK();
|
||||
|
||||
base.RecreatePlayable();
|
||||
|
||||
playable = (AnimationClipPlayable)_Playable;
|
||||
playable.SetApplyFootIK(footIK);
|
||||
playable.SetApplyPlayableIK(playableIK);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Destroy()
|
||||
{
|
||||
_Clip = null;
|
||||
base.Destroy();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerState Clone(CloneContext context)
|
||||
{
|
||||
var clip = context.GetCloneOrOriginal(_Clip);
|
||||
var clone = new ClipState(clip);
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cba070da561e2f4fa6d618d4701bce3
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,626 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Pro-Only]
|
||||
/// An <see cref="AnimancerState"/> which contains other states.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ParentState
|
||||
///
|
||||
public abstract partial class ParentState : AnimancerState,
|
||||
ICopyable<ParentState>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The children contained within this state.</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);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#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 `child` to this mixer at its <see cref="AnimancerNode.Index"/>.</summary>
|
||||
protected internal override void OnAddChild(AnimancerState child)
|
||||
{
|
||||
Validate.AssertGraph(child, Graph);
|
||||
|
||||
var capacity = ChildCapacity;
|
||||
if (_ChildCount >= capacity)
|
||||
ChildCapacity = Math.Max(DefaultChildCapacity, capacity * 2);
|
||||
|
||||
child.Index = _ChildCount;
|
||||
ChildStates[_ChildCount] = child;
|
||||
_ChildCount++;
|
||||
|
||||
child.IsPlaying = IsPlaying;
|
||||
|
||||
if (Graph != null)
|
||||
ConnectChildUnsafe(child.Index, child);
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
_CachedToString = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Disconnects the `child` from this mixer at its <see cref="AnimancerNode.Index"/>.</summary>
|
||||
protected internal override void OnRemoveChild(AnimancerState child)
|
||||
{
|
||||
Validate.AssertCanRemoveChild(child, ChildStates, _ChildCount);
|
||||
|
||||
// Shuffle all subsequent children down one place.
|
||||
if (Graph == null || !Graph._PlayableGraph.IsValid())
|
||||
{
|
||||
Array.Copy(
|
||||
ChildStates, child.Index + 1,
|
||||
ChildStates, child.Index,
|
||||
_ChildCount - child.Index - 1);
|
||||
|
||||
for (int i = child.Index; i < _ChildCount - 1; i++)
|
||||
ChildStates[i].Index = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
Graph._PlayableGraph.Disconnect(_Playable, child.Index);
|
||||
|
||||
for (int i = child.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 UNITY_ASSERTIONS
|
||||
_CachedToString = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Destroy()
|
||||
{
|
||||
DestroyChildren();
|
||||
base.Destroy();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(AnimancerState copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(ParentState copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
DestroyChildren();
|
||||
|
||||
var childCount = copyFrom.ChildCount;
|
||||
EnsureRemainingChildCapacity(childCount);
|
||||
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
var child = copyFrom.ChildStates[i];
|
||||
child = context.Clone(child);
|
||||
Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#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 virtual void Add(AnimancerState child)
|
||||
=> child.SetParent(this);
|
||||
|
||||
/// <summary>Creates and returns a new <see cref="ClipState"/> to play the `clip` as a child of this mixer.</summary>
|
||||
public virtual ClipState Add(AnimationClip clip)
|
||||
{
|
||||
var child = new ClipState(clip);
|
||||
Add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
/// <summary>Calls <see cref="AnimancerUtilities.CreateStateAndApply"/> then <see cref="Add(AnimancerState)"/>.</summary>
|
||||
public virtual AnimancerState Add(ITransition transition)
|
||||
{
|
||||
var child = transition.CreateStateAndApply(Graph);
|
||||
Add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
/// <summary>Calls one of the other <see cref="Add(object)"/> overloads as appropriate for the `child`.</summary>
|
||||
public virtual 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 virtual 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];
|
||||
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);
|
||||
}
|
||||
|
||||
child.CopyIKFlags(this);
|
||||
|
||||
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 child = new ClipState(clip);
|
||||
Set(index, child, destroyPrevious);
|
||||
return child;
|
||||
}
|
||||
|
||||
/// <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 child = transition.CreateStateAndApply(Graph);
|
||||
Set(index, child, destroyPrevious);
|
||||
return child;
|
||||
}
|
||||
|
||||
/// <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 Updates
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected internal override void UpdateEvents()
|
||||
=> UpdateEventsRecursive(this);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#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
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#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 parent name.
|
||||
var parentName = StringBuilderPool.Instance.Acquire();
|
||||
|
||||
var type = GetType().Name;
|
||||
if (type.EndsWith("State"))
|
||||
parentName.Append(type, 0, type.Length - 5);
|
||||
else
|
||||
parentName.Append(type);
|
||||
|
||||
parentName.Append('(');
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
if (prefixLength > 0)
|
||||
parentName.Append(childNames[0], 0, prefixLength).Append('[');
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
parentName.Append(", ");
|
||||
|
||||
var childName = childNames[i];
|
||||
parentName.Append(childName, prefixLength, childName.Length - prefixLength);
|
||||
}
|
||||
|
||||
parentName.Append(']');
|
||||
}
|
||||
ListPool.Release(childNames);
|
||||
|
||||
parentName.Append(')');
|
||||
|
||||
var result = parentName.ReleaseToString();
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
_CachedToString = result;
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
=> clips.GatherFromSource(ChildStates);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 053b822d8f58e2d4e977ce274eb61e6f
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,442 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Audio;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Pro-Only] An <see cref="AnimancerState"/> which plays a <see cref="PlayableAsset"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/timeline">
|
||||
/// Timeline</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/PlayableAssetState
|
||||
public class PlayableAssetState : AnimancerState
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="PlayableAsset"/> which this state plays.</summary>
|
||||
private PlayableAsset _Asset;
|
||||
|
||||
/// <summary>The <see cref="PlayableAsset"/> which this state plays.</summary>
|
||||
public PlayableAsset Asset
|
||||
{
|
||||
get => _Asset;
|
||||
set => ChangeMainObject(ref _Asset, value);
|
||||
}
|
||||
|
||||
/// <summary>The <see cref="PlayableAsset"/> which this state plays.</summary>
|
||||
public override Object MainObject
|
||||
{
|
||||
get => _Asset;
|
||||
set => _Asset = (PlayableAsset)value;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <inheritdoc/>
|
||||
public override Type MainObjectType
|
||||
=> typeof(PlayableAsset);
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private float _Length;
|
||||
|
||||
/// <summary>The <see cref="PlayableAsset.duration"/> (cached on initialization).</summary>
|
||||
public override float Length
|
||||
=> _Length;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float Speed
|
||||
{
|
||||
get => base.Speed;
|
||||
set
|
||||
{
|
||||
base.Speed = value;
|
||||
|
||||
for (int i = Outputs.Count - 1; i >= 0; i--)
|
||||
Outputs[i].GetSourcePlayable().SetSpeed(value);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerEvent.DispatchInfo GetEventDispatchInfo()
|
||||
{
|
||||
var length = _Length;
|
||||
return new(
|
||||
length,
|
||||
length != 0 ? Time / length : 0,
|
||||
false);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnSetIsPlaying()
|
||||
{
|
||||
if (!_Playable.IsValid())
|
||||
return;
|
||||
|
||||
var inputCount = _Playable.GetInputCount();
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
var playable = _Playable.GetInput(i);
|
||||
if (!playable.IsValid())
|
||||
continue;
|
||||
|
||||
if (IsPlaying)
|
||||
playable.Play();
|
||||
else
|
||||
playable.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>IK cannot be dynamically enabled on a <see cref="PlayableAssetState"/>.</summary>
|
||||
public override void CopyIKFlags(AnimancerNodeBase copyFrom) { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>IK cannot be dynamically enabled on a <see cref="PlayableAssetState"/>.</summary>
|
||||
public override bool ApplyAnimatorIK
|
||||
{
|
||||
get => false;
|
||||
set
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (value)
|
||||
OptionalWarning.UnsupportedIK.Log(
|
||||
$"IK cannot be dynamically enabled on a {nameof(PlayableAssetState)}.", Graph?.Component);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>IK cannot be dynamically enabled on a <see cref="PlayableAssetState"/>.</summary>
|
||||
public override bool ApplyFootIK
|
||||
{
|
||||
get => false;
|
||||
set
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (value)
|
||||
OptionalWarning.UnsupportedIK.Log(
|
||||
$"IK cannot be dynamically enabled on a {nameof(PlayableAssetState)}.", Graph?.Component);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Methods
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="PlayableAssetState"/> to play the `asset`.</summary>
|
||||
/// <exception cref="ArgumentNullException">The `asset` is null.</exception>
|
||||
public PlayableAssetState(PlayableAsset asset)
|
||||
{
|
||||
if (asset == null)
|
||||
throw new ArgumentNullException(nameof(asset));
|
||||
|
||||
_Asset = asset;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void CreatePlayable(out Playable playable)
|
||||
{
|
||||
playable = _Asset.CreatePlayable(Graph._PlayableGraph, Graph.Component.gameObject);
|
||||
playable.SetDuration(9223372.03685477);// https://github.com/KybernetikGames/animancer/issues/111
|
||||
|
||||
_Length = (float)_Asset.duration;
|
||||
|
||||
if (!_HasInitializedBindings)
|
||||
InitializeBindings();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private readonly List<PlayableOutput>
|
||||
Outputs = new();
|
||||
|
||||
private IList<Object> _Bindings;
|
||||
private bool _HasInitializedBindings;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The objects controlled by each track in the asset.</summary>
|
||||
public IList<Object> Bindings
|
||||
{
|
||||
get => _Bindings;
|
||||
set
|
||||
{
|
||||
DestroyBoundOutputs(false);
|
||||
_Bindings = value;
|
||||
InitializeBindings();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sets the <see cref="Bindings"/>.</summary>
|
||||
public void SetBindings(params Object[] bindings)
|
||||
{
|
||||
Bindings = bindings;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void InitializeBindings()
|
||||
{
|
||||
if (Graph == null)
|
||||
return;
|
||||
|
||||
_HasInitializedBindings = true;
|
||||
|
||||
Validate.AssertPlayable(this);
|
||||
|
||||
var graph = Graph._PlayableGraph;
|
||||
|
||||
var bindableIndex = 0;
|
||||
var bindableCount = _Bindings != null
|
||||
? _Bindings.Count
|
||||
: 0;
|
||||
|
||||
var speed = Speed;
|
||||
|
||||
foreach (var binding in _Asset.outputs)
|
||||
{
|
||||
GetBindingDetails(binding, out var trackName, out var trackType, out var isMarkers);
|
||||
|
||||
var bindable = bindableIndex < bindableCount
|
||||
? _Bindings[bindableIndex]
|
||||
: null;
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
if (!isMarkers &&
|
||||
trackType != null &&
|
||||
bindable != null &&
|
||||
!trackType.IsAssignableFrom(bindable.GetType()))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Binding Type Mismatch: bindings[{bindableIndex}] is '{bindable}'" +
|
||||
$" but should be a {trackType.FullName} for {trackName}",
|
||||
Graph.Component as Object);
|
||||
bindableIndex++;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
var playable = _Playable.GetInput(bindableIndex);
|
||||
|
||||
if (speed != 1)
|
||||
playable.SetSpeed(speed);
|
||||
|
||||
if (trackType == typeof(Animator))// AnimationTrack.
|
||||
{
|
||||
if (bindable != null)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (bindable == Graph.Component?.Animator)
|
||||
Debug.LogError(
|
||||
$"{nameof(PlayableAsset)} tracks should not be bound to the same {nameof(Animator)} as" +
|
||||
$" Animancer. Leaving the binding of the first Animation Track empty will automatically" +
|
||||
$" apply its animation to the object being controlled by Animancer.",
|
||||
Graph.Component as Object);
|
||||
#endif
|
||||
|
||||
var playableOutput = AnimationPlayableOutput.Create(graph, trackName, (Animator)bindable);
|
||||
playableOutput.SetReferenceObject(binding.sourceObject);
|
||||
playableOutput.SetSourcePlayable(playable);
|
||||
playableOutput.SetWeight(1);
|
||||
Outputs.Add(playableOutput);
|
||||
}
|
||||
}
|
||||
#if UNITY_AUDIO
|
||||
else if (trackType == typeof(AudioSource))// AudioTrack.
|
||||
{
|
||||
if (bindable != null)
|
||||
{
|
||||
var playableOutput = AudioPlayableOutput.Create(graph, trackName, (AudioSource)bindable);
|
||||
playableOutput.SetReferenceObject(binding.sourceObject);
|
||||
playableOutput.SetSourcePlayable(playable);
|
||||
playableOutput.SetWeight(1);
|
||||
Outputs.Add(playableOutput);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (isMarkers)// Markers.
|
||||
{
|
||||
var animancer = Graph.Component as Component;
|
||||
var playableOutput = ScriptPlayableOutput.Create(graph, trackName);
|
||||
playableOutput.SetReferenceObject(binding.sourceObject);
|
||||
playableOutput.SetSourcePlayable(playable);
|
||||
playableOutput.SetWeight(1);
|
||||
playableOutput.SetUserData(animancer);
|
||||
Outputs.Add(playableOutput);
|
||||
|
||||
var receivers = ListPool.Acquire<INotificationReceiver>();
|
||||
animancer.GetComponents(receivers);
|
||||
for (int i = 0; i < receivers.Count; i++)
|
||||
playableOutput.AddNotificationReceiver(receivers[i]);
|
||||
ListPool.Release(receivers);
|
||||
|
||||
continue;// Don't increment the bindingIndex.
|
||||
}
|
||||
else// ActivationTrack, ControlTrack, PlayableTrack, SignalTrack.
|
||||
{
|
||||
var playableOutput = ScriptPlayableOutput.Create(graph, trackName);
|
||||
playableOutput.SetReferenceObject(binding.sourceObject);
|
||||
playableOutput.SetSourcePlayable(playable);
|
||||
playableOutput.SetWeight(1);
|
||||
playableOutput.SetUserData(bindable);
|
||||
if (bindable is INotificationReceiver receiver)
|
||||
playableOutput.AddNotificationReceiver(receiver);
|
||||
Outputs.Add(playableOutput);
|
||||
}
|
||||
|
||||
bindableIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers details about the `binding`.</summary>
|
||||
public static void GetBindingDetails(
|
||||
PlayableBinding binding,
|
||||
out string name,
|
||||
out Type type,
|
||||
out bool isMarkers)
|
||||
{
|
||||
name = binding.streamName;
|
||||
type = binding.outputTargetType;
|
||||
isMarkers = type == typeof(GameObject) && name == "Markers";
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetWeight(float value)
|
||||
{
|
||||
base.SetWeight(value);
|
||||
|
||||
for (int i = Outputs.Count - 1; i >= 0; i--)
|
||||
Outputs[i].SetWeight(value);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Destroy()
|
||||
{
|
||||
_Asset = null;
|
||||
|
||||
DestroyBoundOutputs(true);
|
||||
|
||||
base.Destroy();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Destroys all of the outputs created for the <see cref="Bindings"/>
|
||||
/// and optionally the state playable itself.
|
||||
/// </summary>
|
||||
public void DestroyBoundOutputs(bool destroyStatePlayable)
|
||||
{
|
||||
if (Graph == null)
|
||||
return;
|
||||
|
||||
var graph = Graph._PlayableGraph;
|
||||
if (!graph.IsValid())
|
||||
return;
|
||||
|
||||
for (int i = Outputs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var output = Outputs[i];
|
||||
|
||||
var playable = output.GetSourcePlayable();
|
||||
if (playable.IsValid())
|
||||
graph.DestroySubgraph(playable);
|
||||
|
||||
graph.DestroyOutput(output);
|
||||
}
|
||||
|
||||
Outputs.Clear();
|
||||
|
||||
if (destroyStatePlayable && _Playable.IsValid())
|
||||
graph.DestroySubgraph(_Playable);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerState Clone(CloneContext context)
|
||||
{
|
||||
var asset = context.GetCloneOrOriginal(_Asset);
|
||||
var clone = new PlayableAssetState(asset);
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void AppendDetails(StringBuilder text, string separator)
|
||||
{
|
||||
base.AppendDetails(text, separator);
|
||||
|
||||
text.Append(separator)
|
||||
.Append($"{nameof(Bindings)}: ");
|
||||
|
||||
int count;
|
||||
if (_Bindings == null)
|
||||
{
|
||||
text.Append("Null");
|
||||
count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = _Bindings.Count;
|
||||
text.Append('[')
|
||||
.Append(count)
|
||||
.Append(']');
|
||||
}
|
||||
|
||||
text.Append(_HasInitializedBindings
|
||||
? " (Initialized)"
|
||||
: " (Not Initialized)");
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
text.Append(separator)
|
||||
.Append($"{nameof(Bindings)}[")
|
||||
.Append(i)
|
||||
.Append("] = ")
|
||||
.Append(AnimancerUtilities.ToStringOrNull(_Bindings[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8771e9c8ab67844b8e5cf90e14bf90e
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,375 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>[Pro-Only]
|
||||
/// An <see cref="AnimancerState"/> which plays a sequence of other states.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/SequenceState
|
||||
///
|
||||
public partial class SequenceState : ParentState,
|
||||
ICopyable<SequenceState>,
|
||||
IUpdatable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private double[] _TimeOffsets = Array.Empty<double>();
|
||||
private double[] _FadeEndTimes = Array.Empty<double>();
|
||||
private double[] _StateEndTimes = Array.Empty<double>();
|
||||
|
||||
/// <summary>The index of the child state which is active at the current time.</summary>
|
||||
private int _ActiveChildIndex;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float Length
|
||||
=> ChildCount > 0
|
||||
? (float)_StateEndTimes[ChildCount - 1]
|
||||
: 0;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override double RawTime
|
||||
{
|
||||
get => base.RawTime;
|
||||
set
|
||||
{
|
||||
base.RawTime = value;
|
||||
|
||||
if (ChildCount == 0)
|
||||
return;
|
||||
|
||||
var activeChildIndex = GetActiveChildIndex(value);
|
||||
SetActiveChildIndex(activeChildIndex);
|
||||
|
||||
for (int i = 0; i < ChildCount; i++)
|
||||
{
|
||||
var child = ChildStates[i];
|
||||
|
||||
var childTime = value;
|
||||
if (i > 0)
|
||||
childTime -= _StateEndTimes[i - 1];
|
||||
|
||||
childTime *= child.Speed;
|
||||
|
||||
child.TimeD = childTime + _TimeOffsets[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void MoveTime(double time, bool normalized)
|
||||
{
|
||||
base.MoveTime(time, normalized);
|
||||
|
||||
for (int i = 0; i < ChildCount; i++)
|
||||
{
|
||||
var child = ChildStates[i];
|
||||
|
||||
var childTime = time;
|
||||
if (i > 0)
|
||||
childTime -= _StateEndTimes[i - 1];
|
||||
|
||||
childTime *= child.Speed;
|
||||
|
||||
child.MoveTime(childTime + _TimeOffsets[i], normalized);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sequences don't loop.</summary>
|
||||
/// <remarks>
|
||||
/// If the last state in the sequence is set to loop it will do so,
|
||||
/// but the rest of the sequence won't replay automatically.
|
||||
/// </remarks>
|
||||
public override bool IsLooping
|
||||
=> false;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Initialisation
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Replaces the child states with new ones created from the `transitions`.</summary>
|
||||
public void Set(params ITransition[] transitions)
|
||||
=> Set((IList<ITransition>)transitions);
|
||||
|
||||
/// <summary>Replaces the child states with new ones created from the `transitions`.</summary>
|
||||
public void Set(IList<ITransition> transitions)
|
||||
{
|
||||
var oldChildCount = ChildCount;
|
||||
var newChildCount = transitions.Count;
|
||||
|
||||
ChildCapacity = newChildCount;
|
||||
|
||||
for (int i = 0; i < newChildCount; i++)
|
||||
{
|
||||
var transition = transitions[i];
|
||||
var state = transition.CreateStateAndApply(Graph);
|
||||
state.IsPlaying = IsPlaying;
|
||||
|
||||
if (i < oldChildCount)
|
||||
Set(i, state, true);
|
||||
else
|
||||
Add(state);
|
||||
|
||||
_FadeEndTimes[i] += transition.FadeDuration;
|
||||
}
|
||||
|
||||
while (oldChildCount > newChildCount)
|
||||
Remove(--oldChildCount, true);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected internal override void OnAddChild(AnimancerState child)
|
||||
{
|
||||
base.OnAddChild(child);
|
||||
GatherChildDetails(child);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers the timing details of a newly added `child` state.</summary>
|
||||
private void GatherChildDetails(AnimancerState child)
|
||||
{
|
||||
var index = child.Index;
|
||||
|
||||
if (index == 0)
|
||||
child.Weight = 1;
|
||||
|
||||
_TimeOffsets[index] = child.TimeD;
|
||||
|
||||
var startTime = GetStartTime(index);
|
||||
|
||||
_FadeEndTimes[index] = startTime;
|
||||
|
||||
var length = child.RemainingDuration;
|
||||
if (length < 0)
|
||||
length = 0;
|
||||
|
||||
startTime += length;
|
||||
|
||||
_StateEndTimes[index] = startTime;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerState Add(ITransition transition)
|
||||
{
|
||||
var child = base.Add(transition);
|
||||
|
||||
_FadeEndTimes[child.Index] += transition.FadeDuration;
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds the `child` to the end of this sequence.</summary>
|
||||
public void Add(AnimancerState child, float fadeDuration)
|
||||
{
|
||||
Add(child);
|
||||
|
||||
_FadeEndTimes[child.Index] += fadeDuration;
|
||||
}
|
||||
|
||||
/// <summary>Adds the `clip` to the end of this sequence.</summary>
|
||||
public void Add(AnimationClip clip, float fadeDuration)
|
||||
{
|
||||
var child = Add(clip);
|
||||
|
||||
_FadeEndTimes[child.Index] += fadeDuration;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Execution
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnChildCapacityChanged()
|
||||
{
|
||||
base.OnChildCapacityChanged();
|
||||
|
||||
var capacity = ChildCapacity;
|
||||
Array.Resize(ref _TimeOffsets, capacity);
|
||||
Array.Resize(ref _FadeEndTimes, capacity);
|
||||
Array.Resize(ref _StateEndTimes, capacity);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
int IUpdatable.UpdatableIndex { get; set; } = IUpdatable.List.NotInList;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnSetIsPlaying()
|
||||
{
|
||||
base.OnSetIsPlaying();
|
||||
|
||||
var isPlaying = IsPlaying;
|
||||
var childStates = ChildStates;
|
||||
for (int i = 0; i < ChildCount; i++)
|
||||
childStates[i].IsPlaying = isPlaying;
|
||||
|
||||
if (IsPlaying)
|
||||
Graph.RequirePreUpdate(this);
|
||||
else
|
||||
Graph.CancelPreUpdate(this);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Destroy()
|
||||
{
|
||||
base.Destroy();
|
||||
Graph.CancelPreUpdate(this);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame while this state is playing to control its children.
|
||||
/// </summary>
|
||||
public virtual void Update()
|
||||
{
|
||||
var time = Time;
|
||||
|
||||
var activeChildIndex = _ActiveChildIndex;
|
||||
if (Speed >= 0)
|
||||
{
|
||||
while (activeChildIndex < ChildCount - 1 && time > _StateEndTimes[activeChildIndex])
|
||||
activeChildIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (activeChildIndex > 0 && time > _StateEndTimes[activeChildIndex - 1])
|
||||
activeChildIndex--;
|
||||
}
|
||||
|
||||
SetActiveChildIndex(activeChildIndex);
|
||||
|
||||
var startTime = GetStartTime(activeChildIndex);
|
||||
|
||||
var endFadeTime = _FadeEndTimes[activeChildIndex];
|
||||
|
||||
if (activeChildIndex == 0 || time > endFadeTime)
|
||||
{
|
||||
ChildStates[activeChildIndex].Weight = 1;
|
||||
|
||||
if (activeChildIndex > 0)
|
||||
ChildStates[activeChildIndex - 1].Weight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var weight = Mathf.InverseLerp((float)startTime, (float)endFadeTime, time);
|
||||
|
||||
ChildStates[activeChildIndex].Weight = weight;
|
||||
ChildStates[activeChildIndex - 1].Weight = 1 - weight;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the indices of the `first` and `last` child states
|
||||
/// which should be active at the specified `time`.
|
||||
/// </summary>
|
||||
public int GetActiveChildIndex(double time)
|
||||
{
|
||||
var index = Array.BinarySearch(_StateEndTimes, time);
|
||||
|
||||
if (index < 0)
|
||||
index = ~index;
|
||||
|
||||
if (index >= ChildCount)
|
||||
index = ChildCount - 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Clears the weights of any active chhildren and sets the newly active child.</summary>
|
||||
private void SetActiveChildIndex(int index)
|
||||
{
|
||||
if (_ActiveChildIndex == index)
|
||||
return;
|
||||
|
||||
ChildStates[_ActiveChildIndex].Weight = 0;
|
||||
|
||||
if (_ActiveChildIndex > 0)
|
||||
ChildStates[_ActiveChildIndex - 1].Weight = 0;
|
||||
|
||||
_ActiveChildIndex = index;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gets the time when the specified child starts relative to the start of this sequence.</summary>
|
||||
public double GetStartTime(int childIndex)
|
||||
=> childIndex > 0
|
||||
? _StateEndTimes[childIndex - 1]
|
||||
: 0;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Copying
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerState Clone(CloneContext context)
|
||||
{
|
||||
var clone = new SequenceState();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(ParentState copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(SequenceState copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
var childCount = Math.Min(copyFrom.ChildCount, ChildCount);
|
||||
|
||||
Array.Copy(copyFrom._TimeOffsets, 0, _TimeOffsets, 0, childCount);
|
||||
Array.Copy(copyFrom._FadeEndTimes, 0, _FadeEndTimes, 0, childCount);
|
||||
Array.Copy(copyFrom._StateEndTimes, 0, _StateEndTimes, 0, childCount);
|
||||
|
||||
_ActiveChildIndex = copyFrom._ActiveChildIndex;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c8e97dddf2a0d246b4cdfe6319d1255
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user