MixerStateT.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using System;
  3. using System.Text;
  4. using UnityEngine;
  5. using UnityEngine.Animations;
  6. namespace Animancer
  7. {
  8. /// <summary>[Pro-Only]
  9. /// Base class for mixers which blend an array of child states together based on a <see cref="Parameter"/>.
  10. /// </summary>
  11. /// <remarks>
  12. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/blending/mixers">Mixers</see>
  13. /// </remarks>
  14. /// https://kybernetik.com.au/animancer/api/Animancer/MixerState_1
  15. ///
  16. public abstract class MixerState<TParameter> : ManualMixerState
  17. {
  18. /************************************************************************************************************************/
  19. #region Properties
  20. /************************************************************************************************************************/
  21. /// <summary>The parameter values at which each of the child states are used and blended.</summary>
  22. private TParameter[] _Thresholds = Array.Empty<TParameter>();
  23. /************************************************************************************************************************/
  24. private TParameter _Parameter;
  25. /// <summary>The value used to calculate the weights of the child states.</summary>
  26. /// <remarks>
  27. /// Setting this value takes effect immediately (during the next animation update) without any
  28. /// <see href="https://kybernetik.com.au/animancer/docs/manual/blending/mixers#smoothing">Smoothing</see>.
  29. /// </remarks>
  30. /// <exception cref="ArgumentOutOfRangeException">The value is NaN or Infinity.</exception>
  31. public TParameter Parameter
  32. {
  33. get => _Parameter;
  34. set
  35. {
  36. #if UNITY_ASSERTIONS
  37. var error = GetParameterError(value);
  38. if (error != null)
  39. throw new ArgumentOutOfRangeException(nameof(value), error);
  40. #endif
  41. _Parameter = value;
  42. WeightsAreDirty = true;
  43. RequireUpdate();
  44. }
  45. }
  46. /// <summary>
  47. /// Returns an error message if the given `parameter` value can't be assigned to the <see cref="Parameter"/>.
  48. /// Otherwise returns null.
  49. /// </summary>
  50. public abstract string GetParameterError(TParameter parameter);
  51. /************************************************************************************************************************/
  52. #endregion
  53. /************************************************************************************************************************/
  54. #region Thresholds
  55. /************************************************************************************************************************/
  56. /// <summary>
  57. /// Indicates whether the array of thresholds has been initialized with a size at least equal to the
  58. /// <see cref="AnimancerNode.ChildCount"/>.
  59. /// </summary>
  60. public bool HasThresholds => _Thresholds.Length >= ChildCount;
  61. /************************************************************************************************************************/
  62. /// <summary>
  63. /// Returns the value of the threshold associated with the specified index.
  64. /// </summary>
  65. public TParameter GetThreshold(int index) => _Thresholds[index];
  66. /************************************************************************************************************************/
  67. /// <summary>
  68. /// Sets the value of the threshold associated with the specified index.
  69. /// </summary>
  70. public void SetThreshold(int index, TParameter threshold)
  71. {
  72. _Thresholds[index] = threshold;
  73. OnThresholdsChanged();
  74. }
  75. /************************************************************************************************************************/
  76. /// <summary>
  77. /// Assigns the specified array as the thresholds to use for blending.
  78. /// <para></para>
  79. /// WARNING: if you keep a reference to the `thresholds` array you must call <see cref="OnThresholdsChanged"/>
  80. /// whenever any changes are made to it, otherwise this mixer may not blend correctly.
  81. /// </summary>
  82. public void SetThresholds(params TParameter[] thresholds)
  83. {
  84. if (thresholds.Length != ChildCount)
  85. throw new ArgumentOutOfRangeException(nameof(thresholds),
  86. $"Threshold count ({thresholds.Length}) doesn't match child count ({ChildCount}).");
  87. _Thresholds = thresholds;
  88. OnThresholdsChanged();
  89. }
  90. /************************************************************************************************************************/
  91. /// <summary>
  92. /// If the <see cref="Array.Length"/> of the <see cref="_Thresholds"/> is not equal to the
  93. /// <see cref="AnimancerNode.ChildCount"/>, this method assigns a new array of that size and returns true.
  94. /// </summary>
  95. public bool ValidateThresholdCount()
  96. {
  97. var count = ChildCount;
  98. if (_Thresholds.Length != count)
  99. {
  100. _Thresholds = new TParameter[count];
  101. return true;
  102. }
  103. else return false;
  104. }
  105. /************************************************************************************************************************/
  106. /// <summary>
  107. /// Called whenever the thresholds are changed. By default this method simply indicates that the blend weights
  108. /// need recalculating but it can be overridden by child classes to perform validation checks or optimisations.
  109. /// </summary>
  110. public virtual void OnThresholdsChanged()
  111. {
  112. WeightsAreDirty = true;
  113. RequireUpdate();
  114. }
  115. /************************************************************************************************************************/
  116. /// <summary>
  117. /// Calls `calculate` for each of the <see cref="ManualMixerState._Children"/> and stores the returned value as
  118. /// the threshold for that state.
  119. /// </summary>
  120. public void CalculateThresholds(Func<AnimancerState, TParameter> calculate)
  121. {
  122. ValidateThresholdCount();
  123. for (int i = ChildCount - 1; i >= 0; i--)
  124. {
  125. var state = GetChild(i);
  126. if (state == null)
  127. continue;
  128. _Thresholds[i] = calculate(state);
  129. }
  130. OnThresholdsChanged();
  131. }
  132. /************************************************************************************************************************/
  133. /// <summary>
  134. /// Stores the values of all parameters, calls <see cref="AnimancerNode.DestroyPlayable"/>, then restores the
  135. /// parameter values.
  136. /// </summary>
  137. public override void RecreatePlayable()
  138. {
  139. base.RecreatePlayable();
  140. WeightsAreDirty = true;
  141. RequireUpdate();
  142. }
  143. /************************************************************************************************************************/
  144. #endregion
  145. /************************************************************************************************************************/
  146. #region Initialisation
  147. /************************************************************************************************************************/
  148. /// <inheritdoc/>
  149. public override void Initialize(int portCount)
  150. {
  151. base.Initialize(portCount);
  152. _Thresholds = new TParameter[portCount];
  153. OnThresholdsChanged();
  154. }
  155. /************************************************************************************************************************/
  156. /// <summary>
  157. /// Initializes the <see cref="AnimationMixerPlayable"/> and <see cref="ManualMixerState._Children"/> with one
  158. /// state per clip and assigns the `thresholds`.
  159. /// <para></para>
  160. /// WARNING: if you keep a reference to the `thresholds` array, you must call
  161. /// <see cref="OnThresholdsChanged"/> whenever any changes are made to it, otherwise this mixer may not blend
  162. /// correctly.
  163. /// </summary>
  164. public void Initialize(AnimationClip[] clips, TParameter[] thresholds)
  165. {
  166. Initialize(clips);
  167. _Thresholds = thresholds;
  168. OnThresholdsChanged();
  169. }
  170. /// <summary>
  171. /// Initializes the <see cref="AnimationMixerPlayable"/> and <see cref="ManualMixerState._Children"/> with one
  172. /// state per clip and assigns the thresholds by calling `calculateThreshold` for each state.
  173. /// </summary>
  174. public void Initialize(AnimationClip[] clips, Func<AnimancerState, TParameter> calculateThreshold)
  175. {
  176. Initialize(clips);
  177. CalculateThresholds(calculateThreshold);
  178. }
  179. /************************************************************************************************************************/
  180. /// <summary>
  181. /// Creates and returns a new <see cref="ClipState"/> to play the `clip` with this
  182. /// <see cref="MixerState"/> as its parent, connects it to the specified `index`, and assigns the
  183. /// `threshold` for it.
  184. /// </summary>
  185. public ClipState CreateChild(int index, AnimationClip clip, TParameter threshold)
  186. {
  187. SetThreshold(index, threshold);
  188. return CreateChild(index, clip);
  189. }
  190. /// <summary>
  191. /// Calls <see cref="AnimancerUtilities.CreateStateAndApply"/>, sets this mixer as the state's parent, and
  192. /// assigns the `threshold` for it.
  193. /// </summary>
  194. public AnimancerState CreateChild(int index, Animancer.ITransition transition, TParameter threshold)
  195. {
  196. SetThreshold(index, threshold);
  197. return CreateChild(index, transition);
  198. }
  199. /************************************************************************************************************************/
  200. /// <summary>Assigns the `state` as a child of this mixer and assigns the `threshold` for it.</summary>
  201. public void SetChild(int index, AnimancerState state, TParameter threshold)
  202. {
  203. SetChild(index, state);
  204. SetThreshold(index, threshold);
  205. }
  206. /************************************************************************************************************************/
  207. #endregion
  208. /************************************************************************************************************************/
  209. #region Descriptions
  210. /************************************************************************************************************************/
  211. /// <inheritdoc/>
  212. public override string GetDisplayKey(AnimancerState state) => $"[{state.Index}] {_Thresholds[state.Index]}";
  213. /************************************************************************************************************************/
  214. /// <inheritdoc/>
  215. protected override void AppendDetails(StringBuilder text, string separator)
  216. {
  217. text.Append(separator);
  218. text.Append($"{nameof(Parameter)}: ");
  219. AppendParameter(text, Parameter);
  220. text.Append(separator).Append("Thresholds: ");
  221. for (int i = 0; i < _Thresholds.Length; i++)
  222. {
  223. if (i > 0)
  224. text.Append(", ");
  225. AppendParameter(text, _Thresholds[i]);
  226. }
  227. base.AppendDetails(text, separator);
  228. }
  229. /************************************************************************************************************************/
  230. /// <summary>Appends the `parameter` in a viewer-friendly format.</summary>
  231. public virtual void AppendParameter(StringBuilder description, TParameter parameter)
  232. {
  233. description.Append(parameter);
  234. }
  235. /************************************************************************************************************************/
  236. #endregion
  237. /************************************************************************************************************************/
  238. }
  239. }