// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System;
using UnityEngine.Playables;
namespace Animancer
{
/// Base class for objects that manage a .
/// This is the base class of and .
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerNodeBase
public abstract class AnimancerNodeBase
{
/************************************************************************************************************************/
/// The containing this node.
public AnimancerGraph Graph { get; internal set; }
/************************************************************************************************************************/
/// The object which receives the output of the .
///
/// This leads from to to
/// to null.
///
public AnimancerNodeBase Parent { get; protected set; }
/************************************************************************************************************************/
/// The root which this node is connected to (if any).
public virtual AnimancerLayer Layer
=> Parent?.Layer;
/************************************************************************************************************************/
/// The number of nodes using this as their .
public virtual int ChildCount
=> 0;
/// Returns the node connected to the specified `index` as a child of this node.
/// When overriding, don't call this base method because it throws an exception.
/// This node can't have children.
protected internal virtual AnimancerNode GetChildNode(int index)
{
MarkAsUsed(this);
throw new NotSupportedException(this + " can't have children.");
}
/// Should child playables stay connected to the graph at all times?
///
/// 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.
///
///
public virtual bool KeepChildrenConnected
=> true;
/************************************************************************************************************************/
/// Called when a child's value changes.
protected virtual void OnChildIsLoopingChanged(bool value) { }
/// [Internal] Calls for each recursively.
protected internal void OnIsLoopingChangedRecursive(bool value)
{
var parent = Parent;
while (parent != null)
{
parent.OnChildIsLoopingChanged(value);
parent = parent.Parent;
}
}
/************************************************************************************************************************/
/// [Internal] Called when a child's is changed from this node.
/// When overriding, don't call this base method because it throws an exception.
/// This node can't have children.
protected internal virtual void OnRemoveChild(AnimancerState state)
{
MarkAsUsed(this);
state.SetParentInternal(null);
throw new NotSupportedException(this + " can't have children.");
}
/************************************************************************************************************************/
/// [Internal] The .
protected internal Playable _Playable;
/// The internal object this node manages in the .
///
/// Must be set by . Failure to do so will throw the following
/// exception throughout the system when using this node: ": The playable passed
/// as an argument is invalid. To create a valid playable, please use the appropriate Create method".
///
public Playable Playable => _Playable;
/************************************************************************************************************************/
/// The current blend weight of this node which determines how much it affects the final output.
protected internal virtual float BaseWeight => 1;
/************************************************************************************************************************/
#region Speed
/************************************************************************************************************************/
private float _Speed = 1;
/// [Pro-Only] How fast the is advancing every frame (default 1).
///
///
/// A negative value will play the animation backwards.
///
/// To pause an animation, consider setting to false instead of setting
/// this value to 0.
///
/// Animancer Lite doesn't allow this value to be changed in runtime builds.
///
/// Example:
/// 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.
/// }
///
///
/// The value is not finite.
public 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);
}
}
/************************************************************************************************************************/
///
/// The of this node multiplied by the of each of its parents to
/// determine the actual speed it's playing at.
///
public float EffectiveSpeed
{
get => Speed * ParentEffectiveSpeed;
set => Speed = value / ParentEffectiveSpeed;
}
/************************************************************************************************************************/
///
/// The multiplied of each of the down the hierarchy,
/// excluding the root .
///
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
/************************************************************************************************************************/
///
/// Should Unity call OnAnimatorIK on the animated object while this object and its children have any
/// ?
///
///
/// This is equivalent to the "IK Pass" toggle in Animator Controller layers, except that due to limitations in
/// the Playables API the layerIndex will always be zero.
///
/// This value starts false by default, but can be automatically changed by
/// when the is set.
///
/// IK only takes effect while at least one has a
/// above zero. Other node types either store the value to apply to their children or don't support IK.
///
/// Documentation:
///
/// IK Pass
///
public abstract bool ApplyAnimatorIK { get; set; }
/************************************************************************************************************************/
/// Should this object and its children apply IK to the character's feet?
///
/// This is equivalent to the "Foot IK" toggle in Animator Controller states.
///
/// This value starts true by default for s (false for others), but can be automatically
/// changed by when the is set.
///
/// IK only takes effect while at least one has a
/// above zero. Other node types either store the value to apply to their children or don't support IK.
///
/// Documentation:
///
/// Foot IK
///
public abstract bool ApplyFootIK { get; set; }
/************************************************************************************************************************/
/// [Internal] Applies a change to a child's .
protected internal virtual void ApplyChildActive(AnimancerState child, bool setActive)
=> child.ShouldBeActive = setActive;
/************************************************************************************************************************/
/// [Assert-Conditional] Prevents the `node` from causing .
[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
/************************************************************************************************************************/
/// [Editor-Only]
/// Adds functions to show and set and
/// .
///
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
/************************************************************************************************************************/
}
}