// 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 /************************************************************************************************************************/ } }