FadeGroup.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_ASSERTIONS
  3. //#define ANIMANCER_ASSERT_FADE_GRAPH
  4. #endif
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Text;
  8. using UnityEngine;
  9. using UnityEngine.Playables;
  10. using Object = UnityEngine.Object;
  11. namespace Animancer
  12. {
  13. /// <summary>A group of <see cref="AnimancerNode"/>s which are cross-fading.</summary>
  14. ///
  15. /// <remarks>
  16. /// <strong>Documentation:</strong>
  17. /// <see href="https://kybernetik.com.au/animancer/docs/manual/blending/fading/custom">
  18. /// Custom Easing</see>
  19. /// </remarks>
  20. ///
  21. /// https://kybernetik.com.au/animancer/api/Animancer/FadeGroup
  22. ///
  23. public partial class FadeGroup : Updatable,
  24. ICloneable<FadeGroup>,
  25. ICopyable<FadeGroup>,
  26. IHasDescription
  27. {
  28. /************************************************************************************************************************/
  29. #region Fields and Properties
  30. /************************************************************************************************************************/
  31. // Parameters.
  32. /************************************************************************************************************************/
  33. /// <summary>The 0-1 progress of this fade.</summary>
  34. public float NormalizedTime { get; set; }
  35. /// <summary>The <see cref="AnimancerNode.Weight"/> which the <see cref="FadeIn"/> is moving towards.</summary>
  36. public float TargetWeight { get; set; }
  37. /// <summary>The speed at which the <see cref="NormalizedTime"/> increases.</summary>
  38. public float FadeSpeed { get; set; }
  39. /// <summary>The total amount of time this fade will take to complete (in seconds).</summary>
  40. public float FadeDuration
  41. {
  42. get => FadeSpeed != 0
  43. ? 1 / FadeSpeed
  44. : float.PositiveInfinity;
  45. set => FadeSpeed = value != 0
  46. ? 1 / value
  47. : float.PositiveInfinity;
  48. }
  49. /// <summary>The remaining amount of time this fade will take to complete (in seconds).</summary>
  50. public float RemainingFadeDuration
  51. {
  52. get => FadeSpeed != 0
  53. ? (1 - NormalizedTime) / FadeSpeed
  54. : float.PositiveInfinity;
  55. set => FadeSpeed = value != 0
  56. ? (1 - NormalizedTime) / value
  57. : float.PositiveInfinity;
  58. }
  59. /************************************************************************************************************************/
  60. // Parent.
  61. /************************************************************************************************************************/
  62. /// <summary>The <see cref="AnimancerNodeBase.Graph"/>.</summary>
  63. public AnimancerGraph Graph { get; private set; }
  64. /// <summary>The <see cref="AnimancerNodeBase.Graph"/>.</summary>
  65. public AnimancerNodeBase Parent { get; private set; }
  66. /// <summary>The <see cref="AnimancerNodeBase.Playable"/> of the <see cref="Parent"/>.</summary>
  67. public Playable ParentPlayable { get; private set; }
  68. /// <summary>Should the fading nodes always be connected to the <see cref="ParentPlayable"/>?</summary>
  69. public bool KeepChildrenConnected { get; private set; }
  70. /************************************************************************************************************************/
  71. // Nodes.
  72. /************************************************************************************************************************/
  73. /// <summary>The node which is fading towards the <see cref="TargetWeight"/>.</summary>
  74. public NodeWeight FadeIn { get; private set; }
  75. internal readonly List<NodeWeight> FadeOutInternal = new();
  76. /// <summary>The nodes which are fading out.</summary>
  77. public IReadOnlyList<NodeWeight> FadeOut => FadeOutInternal;
  78. /************************************************************************************************************************/
  79. // Custom Fade.
  80. /************************************************************************************************************************/
  81. private Func<float, float> _Easing;
  82. /// <summary>[Pro-Only] An optional function for modifying the fade curve.</summary>
  83. /// <remarks>
  84. /// The <see cref="NormalizedTime"/> is passed in and the return value is multiplied by the
  85. /// <see cref="TargetWeight"/> to set the <see cref="AnimancerNode.Weight"/> of the <see cref="FadeIn"/>.
  86. /// <para></para>
  87. /// <see cref="Animancer.Easing"/> has various common functions that could be used here.
  88. /// <para></para>
  89. /// Note that the <see cref="AnimancerNode.FadeGroup"/> may be <c>null</c>
  90. /// right after playing something if it was already playing, so
  91. /// <see cref="FadeGroupExtensions.SetEasing(FadeGroup, Easing.Function)"/>
  92. /// <see cref="FadeGroupExtensions.SetEasing(FadeGroup, Func{float, float})"/>
  93. /// can be used to avoid needing to null-check it.
  94. /// <para></para>
  95. /// <em>Animancer Lite ignores this property in runtime builds.</em>
  96. /// </remarks>
  97. public Func<float, float> Easing
  98. {
  99. get => _Easing;
  100. set
  101. {
  102. _Easing = value;
  103. AssertNormalizedBounds(value, nameof(Easing));
  104. }
  105. }
  106. /************************************************************************************************************************/
  107. #endregion
  108. /************************************************************************************************************************/
  109. #region Initialization
  110. /************************************************************************************************************************/
  111. /// <summary>Assigns the target nodes that will be faded.</summary>
  112. public void SetNodes(
  113. AnimancerNode parent,
  114. AnimancerNode fadeIn,
  115. IReadOnlyList<AnimancerNode> fadeOut,
  116. bool keepChildrenConnected)
  117. {
  118. Parent = parent;
  119. Graph = parent.Graph;
  120. ParentPlayable = parent.Playable;
  121. KeepChildrenConnected = keepChildrenConnected;
  122. FadeIn = new(fadeIn);
  123. if (fadeIn.FadeGroup != this)
  124. fadeIn.FadeGroup = this;
  125. var count = fadeOut.Count;
  126. for (int i = 0; i < count; i++)
  127. {
  128. var node = fadeOut[i];
  129. if (node != fadeIn)
  130. {
  131. FadeOutInternal.Add(new(node));
  132. if (node.FadeGroup != this)
  133. node.FadeGroup = this;
  134. }
  135. }
  136. }
  137. /************************************************************************************************************************/
  138. /// <summary>Assigns the <see cref="FadeIn"/> with no <see cref="FadeOut"/>.</summary>
  139. public void SetFadeIn(AnimancerNode fadeIn)
  140. {
  141. Parent = fadeIn.Parent;
  142. if (Parent != null)
  143. {
  144. Graph = fadeIn.Graph;
  145. ParentPlayable = Parent.Playable;
  146. KeepChildrenConnected = Parent.KeepChildrenConnected;
  147. }
  148. FadeIn = new(fadeIn);
  149. fadeIn.FadeGroup = this;
  150. }
  151. /************************************************************************************************************************/
  152. /// <summary>Adds a node to the <see cref="FadeOut"/> list.</summary>
  153. public void AddFadeOut(AnimancerNode fadeOut)
  154. {
  155. FadeOutInternal.Add(new(fadeOut));
  156. fadeOut.FadeGroup = this;
  157. }
  158. /************************************************************************************************************************/
  159. /// <summary>Sets the starting values and registers this fade to be updated.</summary>
  160. public void StartFade(
  161. float targetWeight,
  162. float fadeSpeed)
  163. {
  164. NormalizedTime = 0;
  165. TargetWeight = targetWeight;
  166. FadeSpeed = fadeSpeed;
  167. StartFade();
  168. }
  169. /// <summary>Registers this fade to be updated.</summary>
  170. public void StartFade()
  171. {
  172. Graph?.RequirePreUpdate(this);
  173. FadeIn.Node?.OnStartFade();
  174. for (int i = FadeOutInternal.Count - 1; i >= 0; i--)
  175. FadeOutInternal[i].Node.OnStartFade();
  176. }
  177. /************************************************************************************************************************/
  178. #endregion
  179. /************************************************************************************************************************/
  180. #region Queries
  181. /************************************************************************************************************************/
  182. /// <summary>Should this fade continue?</summary>
  183. public bool IsValid
  184. => FadeSpeed > 0;
  185. /************************************************************************************************************************/
  186. /// <summary>Does this fade affect the `node`?</summary>
  187. public bool Contains(AnimancerNode node)
  188. {
  189. if (FadeIn.Node == node)
  190. return true;
  191. for (int i = 0; i < FadeOutInternal.Count; i++)
  192. if (FadeOutInternal[i].Node == node)
  193. return true;
  194. return false;
  195. }
  196. /************************************************************************************************************************/
  197. /// <summary>
  198. /// Returns the <see cref="TargetWeight"/> if the `node` is the <see cref="FadeIn"/>.
  199. /// Otherwise, returns 0.
  200. /// </summary>
  201. public float GetTargetWeight(AnimancerNode node)
  202. {
  203. return FadeIn.Node == node
  204. ? TargetWeight
  205. : 0;
  206. }
  207. /************************************************************************************************************************/
  208. #endregion
  209. /************************************************************************************************************************/
  210. #region Methods
  211. /************************************************************************************************************************/
  212. /// <inheritdoc/>
  213. public override void Update()
  214. {
  215. if (!IsValid)
  216. {
  217. Cancel();
  218. return;
  219. }
  220. AssertGraph();
  221. NormalizedTime += Math.Abs(AnimancerGraph.DeltaTime * Parent.EffectiveSpeed * FadeSpeed);
  222. if (NormalizedTime < 1)// Fade.
  223. {
  224. ApplyWeights();
  225. }
  226. else// End.
  227. {
  228. Finish();
  229. }
  230. }
  231. /************************************************************************************************************************/
  232. private void Finish()
  233. {
  234. NormalizedTime = 1;
  235. if (KeepChildrenConnected)
  236. {
  237. ApplyWeights(1);
  238. for (int i = FadeOutInternal.Count - 1; i >= 0; i--)
  239. FadeOutInternal[i].Node.StopWithoutWeight();
  240. if (TargetWeight == 0)
  241. FadeIn.Node.StopWithoutWeight();
  242. }
  243. else// Disconnect all faded out nodes and only apply the faded in weight.
  244. {
  245. for (int i = FadeOutInternal.Count - 1; i >= 0; i--)
  246. StopAndDisconnect(FadeOutInternal[i].Node);
  247. FadeOutInternal.Clear();
  248. if (TargetWeight > 0)
  249. ApplyWeight(FadeIn.Node, TargetWeight);
  250. else
  251. StopAndDisconnect(FadeIn.Node);
  252. }
  253. Cancel();
  254. }
  255. /************************************************************************************************************************/
  256. /// <summary>
  257. /// Recalculates the node weights based on the <see cref="NormalizedTime"/>.
  258. /// </summary>
  259. public void ApplyWeights()
  260. {
  261. if (NormalizedTime < 1)// Fade.
  262. {
  263. var progress = NormalizedTime;
  264. if (_Easing != null)
  265. progress = _Easing(progress);
  266. ApplyWeights(progress);
  267. }
  268. else// End.
  269. {
  270. Finish();
  271. }
  272. }
  273. private void ApplyWeights(float progress)
  274. {
  275. // Move FadeIn towards target (usually 1 or 0).
  276. ApplyWeight(FadeIn.Node, Mathf.LerpUnclamped(FadeIn.StartingWeight, TargetWeight, progress));
  277. // Move FadeOut towards 0.
  278. progress = 1 - progress;
  279. for (int i = FadeOutInternal.Count - 1; i >= 0; i--)
  280. {
  281. var node = FadeOutInternal[i];
  282. ApplyWeight(node.Node, node.StartingWeight * progress);
  283. }
  284. }
  285. private void ApplyWeight(AnimancerNode node, float weight)
  286. {
  287. node._Weight = weight;
  288. ParentPlayable.ApplyChildWeight(node);
  289. }
  290. private void StopAndDisconnect(AnimancerNode node)
  291. {
  292. // Don't InternalClearFade because it's virtual.
  293. node._FadeGroup = null;
  294. node.Stop();
  295. }
  296. /************************************************************************************************************************/
  297. private void Release()
  298. {
  299. FadeSpeed = 0;
  300. _Easing = null;
  301. Graph = null;
  302. Parent = null;
  303. if (FadeIn.Node != null)
  304. {
  305. FadeIn.Node.InternalClearFade();
  306. FadeIn = default;
  307. }
  308. for (int i = FadeOutInternal.Count - 1; i >= 0; i--)
  309. FadeOutInternal[i].Node.InternalClearFade();
  310. FadeOutInternal.Clear();
  311. Pool.Instance.Release(this);
  312. }
  313. /************************************************************************************************************************/
  314. /// <summary>Interrupts this fade and releases it to the <see cref="ObjectPool{T}"/>.</summary>
  315. public void Cancel()
  316. {
  317. Graph.CancelPreUpdate(this);
  318. Release();
  319. }
  320. /************************************************************************************************************************/
  321. /// <summary>Removes the `node` from this <see cref="FadeGroup"/> and returns true if successful.</summary>
  322. public bool Remove(AnimancerNode node)
  323. {
  324. if (FadeIn.Node == node)
  325. {
  326. FadeIn = default;
  327. if (FadeOutInternal.Count == 0)
  328. FadeSpeed = 0;
  329. node.InternalClearFade();
  330. return true;
  331. }
  332. for (int i = FadeOutInternal.Count - 1; i >= 0; i--)
  333. {
  334. if (FadeOutInternal[i].Node == node)
  335. {
  336. FadeOutInternal.RemoveAt(i);
  337. if (FadeIn.Node == null && FadeOutInternal.Count == 0)
  338. FadeSpeed = 0;
  339. node.InternalClearFade();
  340. return true;
  341. }
  342. }
  343. return false;
  344. }
  345. /************************************************************************************************************************/
  346. /// <inheritdoc/>
  347. public virtual void AppendDescription(StringBuilder text, string separator = "\n")
  348. {
  349. text.Append(GetType().FullName);
  350. if (!IsValid)
  351. {
  352. text.Append("(Cancelled)");
  353. return;
  354. }
  355. if (!separator.StartsWithNewLine())
  356. separator = "\n" + separator;
  357. text.AppendField(separator, nameof(NormalizedTime), NormalizedTime);
  358. text.AppendField(separator, nameof(FadeSpeed), FadeSpeed);
  359. text.AppendField(separator, nameof(Easing), _Easing?.ToStringDetailed());
  360. text.Append(separator).Append($"{nameof(FadeIn)}: ");
  361. FadeIn.AppendDescription(text, TargetWeight);
  362. text.AppendField(separator, nameof(FadeOut), FadeOutInternal.Count);
  363. for (int i = 0; i < FadeOutInternal.Count; i++)
  364. {
  365. text.Append(separator)
  366. .Append(Strings.Indent);
  367. FadeOutInternal[i].AppendDescription(text, 0);
  368. }
  369. }
  370. /************************************************************************************************************************/
  371. /// <summary>[Assert-Conditional] Checks <see cref="OptionalWarning.FadeEasingBounds"/>.</summary>
  372. [System.Diagnostics.Conditional(Strings.Assertions)]
  373. public static void AssertNormalizedBounds(Func<float, float> easing, string name = "function")
  374. {
  375. #if UNITY_ASSERTIONS
  376. if (easing != null && OptionalWarning.FadeEasingBounds.IsEnabled())
  377. {
  378. if (easing(0) != 0)
  379. OptionalWarning.FadeEasingBounds.Log(name + "(0) != 0.");
  380. if (easing(1) != 1)
  381. OptionalWarning.FadeEasingBounds.Log(name + "(1) != 1.");
  382. }
  383. #endif
  384. }
  385. /************************************************************************************************************************/
  386. #endregion
  387. /************************************************************************************************************************/
  388. #region Cloning
  389. /************************************************************************************************************************/
  390. /// <inheritdoc/>
  391. public virtual FadeGroup Clone(CloneContext context)
  392. {
  393. if (!IsValid)
  394. return null;
  395. var clone = new FadeGroup();
  396. clone.CopyFrom(this, context);
  397. return clone;
  398. }
  399. /************************************************************************************************************************/
  400. /// <inheritdoc/>
  401. public virtual void CopyFrom(FadeGroup copyFrom, CloneContext context)
  402. {
  403. CopyNodesFrom(copyFrom, context);
  404. var node = FadeIn.Node;
  405. if (node == null)
  406. {
  407. if (FadeOut.Count == 0)
  408. return;
  409. node = FadeOut[0].Node;
  410. }
  411. ChangeParent(node);
  412. CopyDetailsFrom(copyFrom);
  413. }
  414. /************************************************************************************************************************/
  415. private void CopyNodesFrom(FadeGroup copyFrom, CloneContext context)
  416. {
  417. FadeIn = new(copyFrom.FadeIn, context);
  418. FadeIn.Node.FadeGroup = this;
  419. FadeOutInternal.Clear();
  420. var count = copyFrom.FadeOutInternal.Count;
  421. for (int i = 0; i < count; i++)
  422. {
  423. var nodeWeight = new NodeWeight(copyFrom.FadeOutInternal[i], context);
  424. if (nodeWeight.Node != null)
  425. {
  426. FadeOutInternal.Add(nodeWeight);
  427. nodeWeight.Node.FadeGroup = this;
  428. }
  429. }
  430. }
  431. /************************************************************************************************************************/
  432. internal void ChangeParent(AnimancerNode child)
  433. {
  434. var parent = child.Parent;
  435. if (Parent == parent)
  436. return;
  437. Parent = parent;
  438. if (Parent != null)
  439. {
  440. ParentPlayable = Parent.Playable;
  441. KeepChildrenConnected = Parent.KeepChildrenConnected;
  442. ChangeGraph(child.Graph);
  443. _AssertGraphNextFrame = true;
  444. }
  445. }
  446. /************************************************************************************************************************/
  447. internal void ChangeGraph(AnimancerGraph graph)
  448. {
  449. if (Graph == graph)
  450. return;
  451. Graph?.CancelPreUpdate(this);
  452. Graph = graph;
  453. Graph?.RequirePreUpdate(this);
  454. _AssertGraphNextFrame = true;
  455. }
  456. /************************************************************************************************************************/
  457. private bool _AssertGraphNextFrame;
  458. private void AssertGraph()
  459. {
  460. if (!_AssertGraphNextFrame)
  461. return;
  462. _AssertGraphNextFrame = false;
  463. if (FadeIn.Node != null && !AssertNode(FadeIn.Node))
  464. return;
  465. for (int i = 0; i < FadeOutInternal.Count; i++)
  466. if (!AssertNode(FadeOutInternal[i].Node))
  467. return;
  468. }
  469. private bool AssertNode(AnimancerNode node)
  470. {
  471. string propertyName;
  472. string nodeValue, myValue;
  473. if (node.Graph == Graph)
  474. {
  475. if (node.Parent == Parent)
  476. return true;
  477. propertyName = nameof(node.Parent);
  478. nodeValue = AnimancerUtilities.ToStringOrNull(node.Parent);
  479. myValue = AnimancerUtilities.ToStringOrNull(Parent);
  480. }
  481. else
  482. {
  483. propertyName = nameof(node.Graph);
  484. nodeValue = AnimancerUtilities.ToStringOrNull(node.Graph);
  485. myValue = AnimancerUtilities.ToStringOrNull(Graph);
  486. }
  487. var graph = Graph ?? node.Graph;
  488. Debug.LogWarning(
  489. $"{nameof(AnimancerNode)}.{propertyName} doesn't match {nameof(FadeGroup)}.{propertyName}." +
  490. $"\n• Node: {node.GetPath()}" +
  491. $"\n• Node.{propertyName}: {nodeValue}" +
  492. $"\n• This.{propertyName}: {myValue}" +
  493. $"\n• Graph: {graph?.GetDescription("\n• ")}");
  494. return false;
  495. }
  496. /************************************************************************************************************************/
  497. private void CopyDetailsFrom(FadeGroup copyFrom)
  498. {
  499. NormalizedTime = copyFrom.NormalizedTime;
  500. FadeSpeed = copyFrom.FadeSpeed;
  501. TargetWeight = copyFrom.TargetWeight;
  502. _Easing = copyFrom._Easing;
  503. }
  504. /************************************************************************************************************************/
  505. /// <summary>Creates a clone of this <see cref="FadeGroup"/> for a single target node (`copyTo`).</summary>
  506. public FadeGroup CloneForSingleTarget(AnimancerNode copyFrom, AnimancerNode copyTo)
  507. {
  508. if (!IsValid)
  509. return null;
  510. var clone = Pool.Instance.Acquire();
  511. if (copyFrom == FadeIn.Node)
  512. {
  513. clone.FadeIn = new(copyTo, FadeIn.StartingWeight);
  514. }
  515. else
  516. {
  517. for (int i = 0; i < FadeOutInternal.Count; i++)
  518. {
  519. var fadeOut = FadeOutInternal[i];
  520. if (fadeOut.Node == copyFrom)
  521. {
  522. clone.FadeOutInternal.Add(new(copyTo, fadeOut.StartingWeight));
  523. goto CopyDetails;
  524. }
  525. }
  526. return null;
  527. }
  528. CopyDetails:
  529. clone.ChangeParent(copyTo);
  530. clone.CopyDetailsFrom(this);
  531. clone.StartFade();
  532. return clone;
  533. }
  534. /************************************************************************************************************************/
  535. #endregion
  536. /************************************************************************************************************************/
  537. #region Pooling
  538. /************************************************************************************************************************/
  539. /// <summary>An <see cref="ObjectPool{T}"/> for <see cref="FadeGroup"/>.</summary>
  540. /// https://kybernetik.com.au/animancer/api/Animancer/Pool
  541. public class Pool : ObjectPool<FadeGroup>
  542. {
  543. /************************************************************************************************************************/
  544. /// <summary>Singleton.</summary>
  545. public static Pool Instance = new();
  546. /************************************************************************************************************************/
  547. /// <inheritdoc/>
  548. protected override FadeGroup New()
  549. => new();
  550. /************************************************************************************************************************/
  551. #if UNITY_ASSERTIONS
  552. /************************************************************************************************************************/
  553. /// <inheritdoc/>
  554. public override FadeGroup Acquire()
  555. {
  556. var fade = base.Acquire();
  557. Debug.Assert(fade.FadeIn.Node == null, $"{nameof(fade.FadeIn)} is not null");
  558. Debug.Assert(fade.FadeOutInternal.Count == 0, $"{nameof(fade.FadeOutInternal)} is not empty");
  559. Debug.Assert(fade.Easing == null, $"{nameof(fade.Easing)} is not null");
  560. return fade;
  561. }
  562. /// <inheritdoc/>
  563. public override void Release(FadeGroup item)
  564. {
  565. Debug.Assert(((IUpdatable)item).UpdatableIndex < 0,
  566. $"Releasing {nameof(FadeGroup)} which is still registered for updates.",
  567. item.Graph?.Component as Object);
  568. base.Release(item);
  569. }
  570. /************************************************************************************************************************/
  571. #endif
  572. /************************************************************************************************************************/
  573. }
  574. /************************************************************************************************************************/
  575. #endregion
  576. /************************************************************************************************************************/
  577. }
  578. }