CustomFade.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace Animancer
  5. {
  6. /// <summary>[Pro-Only]
  7. /// A system which fades animation weights animations using a custom calculation rather than linear interpolation.
  8. /// </summary>
  9. ///
  10. /// <remarks>
  11. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/blending/fading#custom-fade">Custom Fade</see>
  12. /// </remarks>
  13. ///
  14. /// <example><code>
  15. /// [SerializeField] private AnimancerComponent _Animancer;
  16. /// [SerializeField] private AnimationClip _Clip;
  17. ///
  18. /// private void Awake()
  19. /// {
  20. /// // Start fading the animation normally.
  21. /// var state = _Animancer.Play(_Clip, 0.25f);
  22. ///
  23. /// // Then apply the custom fade to modify it.
  24. /// CustomFade.Apply(state, Easing.Sine.InOut);// Use a delegate.
  25. /// CustomFade.Apply(state, Easing.Function.SineInOut);// Or use the Function enum.
  26. ///
  27. /// // Or apply it to whatever the current state happens to be.
  28. /// CustomFade.Apply(_Animancer, Easing.Sine.InOut);
  29. ///
  30. /// // Anything else you play after that will automatically cancel the custom fade.
  31. /// }
  32. /// </code></example>
  33. ///
  34. /// https://kybernetik.com.au/animancer/api/Animancer/CustomFade
  35. ///
  36. public abstract partial class CustomFade : Key, IUpdatable
  37. {
  38. /************************************************************************************************************************/
  39. private float _Time;
  40. private float _FadeSpeed;
  41. private NodeWeight _Target;
  42. private AnimancerLayer _Layer;
  43. private int _CommandCount;
  44. private readonly List<NodeWeight> FadeOutNodes = new List<NodeWeight>();
  45. /************************************************************************************************************************/
  46. private readonly struct NodeWeight
  47. {
  48. public readonly AnimancerNode Node;
  49. public readonly float StartingWeight;
  50. public NodeWeight(AnimancerNode node)
  51. {
  52. Node = node;
  53. StartingWeight = node.Weight;
  54. }
  55. }
  56. /************************************************************************************************************************/
  57. /// <summary>
  58. /// Gathers the current details of the <see cref="AnimancerNode.Root"/> and register this
  59. /// <see cref="CustomFade"/> to be updated by it so that it can replace the regular fade behaviour.
  60. /// </summary>
  61. protected void Apply(AnimancerState state)
  62. {
  63. AnimancerUtilities.Assert(state.Parent != null, "Node is not connected to a layer.");
  64. Apply((AnimancerNode)state);
  65. var parent = state.Parent;
  66. for (int i = parent.ChildCount - 1; i >= 0; i--)
  67. {
  68. var other = parent.GetChild(i);
  69. if (other != state && other.FadeSpeed != 0)
  70. {
  71. other.FadeSpeed = 0;
  72. FadeOutNodes.Add(new NodeWeight(other));
  73. }
  74. }
  75. }
  76. /// <summary>
  77. /// Gathers the current details of the <see cref="AnimancerNode.Root"/> and register this
  78. /// <see cref="CustomFade"/> to be updated by it so that it can replace the regular fade behaviour.
  79. /// </summary>
  80. protected void Apply(AnimancerNode node)
  81. {
  82. #if UNITY_ASSERTIONS
  83. AnimancerUtilities.Assert(node != null, "Node is null.");
  84. AnimancerUtilities.Assert(node.IsValid, "Node is not valid.");
  85. AnimancerUtilities.Assert(node.FadeSpeed != 0, $"Node is not fading ({nameof(node.FadeSpeed)} is 0).");
  86. var animancer = node.Root;
  87. AnimancerUtilities.Assert(animancer != null, $"{nameof(node)}.{nameof(node.Root)} is null.");
  88. if (OptionalWarning.CustomFadeBounds.IsEnabled())
  89. {
  90. if (CalculateWeight(0) != 0)
  91. OptionalWarning.CustomFadeBounds.Log("CalculateWeight(0) != 0.", animancer.Component);
  92. if (CalculateWeight(1) != 1)
  93. OptionalWarning.CustomFadeBounds.Log("CalculateWeight(1) != 1.", animancer.Component);
  94. }
  95. #endif
  96. _Time = 0;
  97. _Target = new NodeWeight(node);
  98. _FadeSpeed = node.FadeSpeed;
  99. _Layer = node.Layer;
  100. _CommandCount = _Layer.CommandCount;
  101. node.FadeSpeed = 0;
  102. FadeOutNodes.Clear();
  103. node.Root.RequirePreUpdate(this);
  104. }
  105. /************************************************************************************************************************/
  106. /// <summary>
  107. /// Returns the desired weight for the target state at the specified `progress` (ranging from 0 to 1).
  108. /// </summary>
  109. /// <remarks>
  110. /// This method should return 0 when the `progress` is 0 and 1 when the `progress` is 1. It can do anything you
  111. /// want with other values, but violating that guideline will trigger
  112. /// <see cref="OptionalWarning.CustomFadeBounds"/>.
  113. /// </remarks>
  114. protected abstract float CalculateWeight(float progress);
  115. /// <summary>Called when this fade is cancelled (or ends).</summary>
  116. /// <remarks>Can be used to return it to an <see cref="ObjectPool"/>.</remarks>
  117. protected abstract void Release();
  118. /************************************************************************************************************************/
  119. void IUpdatable.Update()
  120. {
  121. // Stop fading if the state was destroyed or something else was played.
  122. if (!_Target.Node.IsValid() ||
  123. _Layer != _Target.Node.Layer ||
  124. _CommandCount != _Layer.CommandCount)
  125. {
  126. FadeOutNodes.Clear();
  127. _Layer.Root.CancelPreUpdate(this);
  128. Release();
  129. return;
  130. }
  131. _Time += AnimancerPlayable.DeltaTime * _Layer.Speed * _FadeSpeed;
  132. if (_Time < 1)// Fade.
  133. {
  134. var weight = CalculateWeight(_Time);
  135. _Target.Node.SetWeight(Mathf.LerpUnclamped(_Target.StartingWeight, _Target.Node.TargetWeight, weight));
  136. _Target.Node.ApplyWeight();
  137. weight = 1 - weight;
  138. for (int i = FadeOutNodes.Count - 1; i >= 0; i--)
  139. {
  140. var node = FadeOutNodes[i];
  141. node.Node.SetWeight(node.StartingWeight * weight);
  142. node.Node.ApplyWeight();
  143. }
  144. }
  145. else// End.
  146. {
  147. _Time = 1;
  148. ForceFinishFade(_Target.Node);
  149. for (int i = FadeOutNodes.Count - 1; i >= 0; i--)
  150. ForceFinishFade(FadeOutNodes[i].Node);
  151. FadeOutNodes.Clear();
  152. _Layer.Root.CancelPreUpdate(this);
  153. Release();
  154. }
  155. }
  156. /************************************************************************************************************************/
  157. private static void ForceFinishFade(AnimancerNode node)
  158. {
  159. var weight = node.TargetWeight;
  160. node.SetWeight(weight);
  161. node.ApplyWeight();
  162. if (weight == 0)
  163. node.Stop();
  164. }
  165. /************************************************************************************************************************/
  166. }
  167. }