// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // using System.Collections.Generic; using UnityEngine; namespace Animancer { /// [Pro-Only] /// A system which fades animation weights animations using a custom calculation rather than linear interpolation. /// /// /// /// Documentation: Custom Fade /// /// /// /// [SerializeField] private AnimancerComponent _Animancer; /// [SerializeField] private AnimationClip _Clip; /// /// private void Awake() /// { /// // Start fading the animation normally. /// var state = _Animancer.Play(_Clip, 0.25f); /// /// // Then apply the custom fade to modify it. /// CustomFade.Apply(state, Easing.Sine.InOut);// Use a delegate. /// CustomFade.Apply(state, Easing.Function.SineInOut);// Or use the Function enum. /// /// // Or apply it to whatever the current state happens to be. /// CustomFade.Apply(_Animancer, Easing.Sine.InOut); /// /// // Anything else you play after that will automatically cancel the custom fade. /// } /// /// /// https://kybernetik.com.au/animancer/api/Animancer/CustomFade /// public abstract partial class CustomFade : Key, IUpdatable { /************************************************************************************************************************/ private float _Time; private float _FadeSpeed; private NodeWeight _Target; private AnimancerLayer _Layer; private int _CommandCount; private readonly List FadeOutNodes = new List(); /************************************************************************************************************************/ private readonly struct NodeWeight { public readonly AnimancerNode Node; public readonly float StartingWeight; public NodeWeight(AnimancerNode node) { Node = node; StartingWeight = node.Weight; } } /************************************************************************************************************************/ /// /// Gathers the current details of the and register this /// to be updated by it so that it can replace the regular fade behaviour. /// protected void Apply(AnimancerState state) { AnimancerUtilities.Assert(state.Parent != null, "Node is not connected to a layer."); Apply((AnimancerNode)state); var parent = state.Parent; for (int i = parent.ChildCount - 1; i >= 0; i--) { var other = parent.GetChild(i); if (other != state && other.FadeSpeed != 0) { other.FadeSpeed = 0; FadeOutNodes.Add(new NodeWeight(other)); } } } /// /// Gathers the current details of the and register this /// to be updated by it so that it can replace the regular fade behaviour. /// protected void Apply(AnimancerNode node) { #if UNITY_ASSERTIONS AnimancerUtilities.Assert(node != null, "Node is null."); AnimancerUtilities.Assert(node.IsValid, "Node is not valid."); AnimancerUtilities.Assert(node.FadeSpeed != 0, $"Node is not fading ({nameof(node.FadeSpeed)} is 0)."); var animancer = node.Root; AnimancerUtilities.Assert(animancer != null, $"{nameof(node)}.{nameof(node.Root)} is null."); #if UNITY_EDITOR if (OptionalWarning.CustomFadeBounds.IsEnabled()) { if (CalculateWeight(0) != 0) OptionalWarning.CustomFadeBounds.Log("CalculateWeight(0) != 0.", animancer.Component); if (CalculateWeight(1) != 1) OptionalWarning.CustomFadeBounds.Log("CalculateWeight(1) != 1.", animancer.Component); } #endif #endif _Time = 0; _Target = new NodeWeight(node); _FadeSpeed = node.FadeSpeed; _Layer = node.Layer; _CommandCount = _Layer.CommandCount; node.FadeSpeed = 0; FadeOutNodes.Clear(); node.Root.RequirePreUpdate(this); } /************************************************************************************************************************/ /// /// Returns the desired weight for the target state at the specified `progress` (ranging from 0 to 1). /// /// /// This method should return 0 when the `progress` is 0 and 1 when the `progress` is 1. It can do anything you /// want with other values, but violating that guideline will trigger /// . /// protected abstract float CalculateWeight(float progress); /// Called when this fade is cancelled (or ends). /// Can be used to return it to an . protected abstract void Release(); /************************************************************************************************************************/ void IUpdatable.Update() { // Stop fading if the state was destroyed or something else was played. if (!_Target.Node.IsValid() || _Layer != _Target.Node.Layer || _CommandCount != _Layer.CommandCount) { FadeOutNodes.Clear(); _Layer.Root.CancelPreUpdate(this); Release(); return; } _Time += AnimancerPlayable.DeltaTime * _Layer.Speed * _FadeSpeed; if (_Time < 1)// Fade. { var weight = CalculateWeight(_Time); _Target.Node.SetWeight(Mathf.LerpUnclamped(_Target.StartingWeight, _Target.Node.TargetWeight, weight)); _Target.Node.ApplyWeight(); weight = 1 - weight; for (int i = FadeOutNodes.Count - 1; i >= 0; i--) { var node = FadeOutNodes[i]; node.Node.SetWeight(node.StartingWeight * weight); node.Node.ApplyWeight(); } } else// End. { _Time = 1; ForceFinishFade(_Target.Node); for (int i = FadeOutNodes.Count - 1; i >= 0; i--) ForceFinishFade(FadeOutNodes[i].Node); FadeOutNodes.Clear(); _Layer.Root.CancelPreUpdate(this); Release(); } } /************************************************************************************************************************/ private static void ForceFinishFade(AnimancerNode node) { var weight = node.TargetWeight; node.SetWeight(weight); node.ApplyWeight(); if (weight == 0) node.Stop(); } /************************************************************************************************************************/ } }