MixerStateT.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. using System;
  3. using System.Text;
  4. using UnityEngine;
  5. using UnityEngine.Playables;
  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. /// <strong>Documentation:</strong>
  13. /// <see href="https://kybernetik.com.au/animancer/docs/manual/blending/mixers">
  14. /// Mixers</see>
  15. /// </remarks>
  16. /// https://kybernetik.com.au/animancer/api/Animancer/MixerState_1
  17. ///
  18. public abstract class MixerState<TParameter> : ManualMixerState,
  19. ICopyable<MixerState<TParameter>>
  20. {
  21. /************************************************************************************************************************/
  22. #region Thresholds
  23. /************************************************************************************************************************/
  24. /// <summary>The parameter values at which each of the child states are used and blended.</summary>
  25. private TParameter[] _Thresholds = Array.Empty<TParameter>();
  26. /************************************************************************************************************************/
  27. /// <summary>
  28. /// Has the array of thresholds been initialized with a size at least equal to the
  29. /// <see cref="ManualMixerState.ChildCount"/>.
  30. /// </summary>
  31. public bool HasThresholds
  32. => _Thresholds.Length >= ChildCount;
  33. /************************************************************************************************************************/
  34. /// <summary>Returns the value of the threshold associated with the specified `index`.</summary>
  35. public TParameter GetThreshold(int index)
  36. => _Thresholds[index];
  37. /************************************************************************************************************************/
  38. /// <summary>Sets the value of the threshold associated with the specified `index`.</summary>
  39. public void SetThreshold(int index, TParameter threshold)
  40. {
  41. _Thresholds[index] = threshold;
  42. OnThresholdsChanged();
  43. }
  44. /************************************************************************************************************************/
  45. /// <summary>
  46. /// Assigns the specified array as the thresholds to use for blending.
  47. /// <para></para>
  48. /// WARNING: if you keep a reference to the `thresholds` array you must call <see cref="OnThresholdsChanged"/>
  49. /// whenever any changes are made to it, otherwise this mixer may not blend correctly.
  50. /// </summary>
  51. public void SetThresholds(params TParameter[] thresholds)
  52. {
  53. if (thresholds.Length < ChildCount)
  54. {
  55. MarkAsUsed(this);
  56. throw new ArgumentOutOfRangeException(nameof(thresholds),
  57. $"Threshold count ({thresholds.Length}) must not be less than child count ({ChildCount}).");
  58. }
  59. _Thresholds = thresholds;
  60. OnThresholdsChanged();
  61. }
  62. /************************************************************************************************************************/
  63. /// <summary>
  64. /// If the <see cref="Array.Length"/> of the <see cref="_Thresholds"/> is below the
  65. /// <see cref="AnimancerNodeBase.ChildCount"/>, this method assigns a new array with size equal to the
  66. /// <see cref="ManualMixerState.ChildCapacity"/> and returns true.
  67. /// </summary>
  68. public bool ValidateThresholdCount()
  69. {
  70. if (_Thresholds.Length >= ChildCount)
  71. return false;
  72. _Thresholds = new TParameter[ChildCapacity];
  73. return true;
  74. }
  75. /************************************************************************************************************************/
  76. /// <summary>
  77. /// Called whenever the thresholds are changed. By default this method simply indicates that the blend weights
  78. /// need recalculating but it can be overridden by child classes to perform validation checks or optimisations.
  79. /// </summary>
  80. public virtual void OnThresholdsChanged()
  81. {
  82. SetWeightsDirty();
  83. }
  84. /************************************************************************************************************************/
  85. /// <summary>
  86. /// Calls `calculate` for each of the <see cref="ManualMixerState.ChildStates"/> and stores the returned value
  87. /// as the threshold for that state.
  88. /// </summary>
  89. public void CalculateThresholds(Func<AnimancerState, TParameter> calculate)
  90. {
  91. ValidateThresholdCount();
  92. for (int i = ChildCount - 1; i >= 0; i--)
  93. _Thresholds[i] = calculate(GetChild(i));
  94. OnThresholdsChanged();
  95. }
  96. /************************************************************************************************************************/
  97. /// <summary>
  98. /// Stores the values of all parameters, calls <see cref="AnimancerNode.DestroyPlayable"/>, then restores the
  99. /// parameter values.
  100. /// </summary>
  101. public override void RecreatePlayable()
  102. {
  103. base.RecreatePlayable();
  104. SetWeightsDirty();
  105. }
  106. /************************************************************************************************************************/
  107. #endregion
  108. /************************************************************************************************************************/
  109. #region Parameter
  110. /************************************************************************************************************************/
  111. private TParameter _Parameter;
  112. /// <summary>The value used to calculate the weights of the child states.</summary>
  113. /// <remarks>
  114. /// Setting this value takes effect immediately (during the next animation update) without any
  115. /// <see href="https://kybernetik.com.au/animancer/docs/manual/blending/mixers#smoothing">Smoothing</see>.
  116. /// </remarks>
  117. /// <exception cref="ArgumentOutOfRangeException">The value is NaN or Infinity.</exception>
  118. public TParameter Parameter
  119. {
  120. get => _Parameter;
  121. set
  122. {
  123. #if UNITY_ASSERTIONS
  124. if (Graph != null)
  125. Validate.AssertPlayable(this);
  126. var error = GetParameterError(value);
  127. if (error != null)
  128. {
  129. MarkAsUsed(this);
  130. throw new ArgumentOutOfRangeException(nameof(value), error);
  131. }
  132. #endif
  133. _Parameter = value;
  134. SetWeightsDirty();
  135. }
  136. }
  137. /// <summary>
  138. /// Returns an error message if the given `parameter` value can't be assigned to the <see cref="Parameter"/>.
  139. /// Otherwise returns null.
  140. /// </summary>
  141. public abstract string GetParameterError(TParameter parameter);
  142. /************************************************************************************************************************/
  143. /// <summary>The <see cref="Parameter"/> normalized into the range of 0 to 1 across all thresholds.</summary>
  144. public abstract TParameter NormalizedParameter { get; set; }
  145. /************************************************************************************************************************/
  146. #endregion
  147. /************************************************************************************************************************/
  148. #region Weight Calculation
  149. /************************************************************************************************************************/
  150. /// <summary>Should the weights of all child states be recalculated?</summary>
  151. public bool WeightsAreDirty { get; private set; }
  152. /// <summary>Registers this mixer to recalculate its weights during the next animation update.</summary>
  153. public void SetWeightsDirty()
  154. {
  155. if (!WeightsAreDirty)
  156. {
  157. WeightsAreDirty = true;
  158. Graph?.RequirePreUpdate(this);
  159. }
  160. }
  161. /************************************************************************************************************************/
  162. /// <summary>
  163. /// If <see cref="WeightsAreDirty"/> this method recalculates the weights of all child states and returns true.
  164. /// </summary>
  165. public bool RecalculateWeights()
  166. {
  167. if (!WeightsAreDirty)
  168. return false;
  169. ForceRecalculateWeights();
  170. WeightsAreDirty = false;
  171. return true;
  172. }
  173. /************************************************************************************************************************/
  174. /// <summary>
  175. /// Recalculates the weights of all child states based on the current value of the
  176. /// <see cref="MixerState{TParameter}.Parameter"/> and the thresholds.
  177. /// </summary>
  178. protected virtual void ForceRecalculateWeights() { }
  179. /************************************************************************************************************************/
  180. /// <inheritdoc/>
  181. protected override void OnSetIsPlaying()
  182. {
  183. base.OnSetIsPlaying();
  184. if (WeightsAreDirty || SynchronizedChildCount > 0)
  185. Graph?.RequirePreUpdate(this);
  186. }
  187. /************************************************************************************************************************/
  188. /// <inheritdoc/>
  189. public override double RawTime
  190. {
  191. get
  192. {
  193. if (_Playable.IsValid())
  194. RecalculateWeights();
  195. return base.RawTime;
  196. }
  197. }
  198. /************************************************************************************************************************/
  199. /// <inheritdoc/>
  200. public override float Length
  201. {
  202. get
  203. {
  204. if (_Playable.IsValid())
  205. RecalculateWeights();
  206. return base.Length;
  207. }
  208. }
  209. /************************************************************************************************************************/
  210. /// <inheritdoc/>
  211. public override Vector3 AverageVelocity
  212. {
  213. get
  214. {
  215. if (_Playable.IsValid())
  216. RecalculateWeights();
  217. return base.AverageVelocity;
  218. }
  219. }
  220. /************************************************************************************************************************/
  221. /// <inheritdoc/>
  222. protected override void CreatePlayable(out Playable playable)
  223. {
  224. base.CreatePlayable(out playable);
  225. RecalculateWeights();
  226. }
  227. /************************************************************************************************************************/
  228. /// <inheritdoc/>
  229. public override void Update()
  230. {
  231. RecalculateWeights();
  232. base.Update();
  233. }
  234. /************************************************************************************************************************/
  235. #endregion
  236. /************************************************************************************************************************/
  237. #region Initialization
  238. /************************************************************************************************************************/
  239. /// <inheritdoc/>
  240. protected override void OnChildCapacityChanged()
  241. {
  242. Array.Resize(ref _Thresholds, ChildCapacity);
  243. OnThresholdsChanged();
  244. }
  245. /************************************************************************************************************************/
  246. /// <summary>Assigns the `state` as a child of this mixer and assigns the `threshold` for it.</summary>
  247. public void Add(AnimancerState state, TParameter threshold)
  248. {
  249. Add(state);
  250. SetThreshold(state.Index, threshold);
  251. }
  252. /// <summary>
  253. /// Creates and returns a new <see cref="ClipState"/> to play the `clip` as a child of this mixer, and assigns
  254. /// the `threshold` for it.
  255. /// </summary>
  256. public ClipState Add(AnimationClip clip, TParameter threshold)
  257. {
  258. var state = Add(clip);
  259. SetThreshold(state.Index, threshold);
  260. return state;
  261. }
  262. /// <summary>
  263. /// Calls <see cref="AnimancerUtilities.CreateStateAndApply"/> then
  264. /// <see cref="Add(AnimancerState, TParameter)"/>.
  265. /// </summary>
  266. public AnimancerState Add(Animancer.ITransition transition, TParameter threshold)
  267. {
  268. var state = Add(transition);
  269. SetThreshold(state.Index, threshold);
  270. return state;
  271. }
  272. /// <summary>Calls one of the other <see cref="Add(object, TParameter)"/> overloads as appropriate.</summary>
  273. public AnimancerState Add(object child, TParameter threshold)
  274. {
  275. if (child is AnimationClip clip)
  276. return Add(clip, threshold);
  277. if (child is ITransition transition)
  278. return Add(transition, threshold);
  279. if (child is AnimancerState state)
  280. {
  281. Add(state, threshold);
  282. return state;
  283. }
  284. MarkAsUsed(this);
  285. throw new ArgumentException(
  286. $"Unable to add '{AnimancerUtilities.ToStringOrNull(child)}' as child of '{this}'.");
  287. }
  288. /************************************************************************************************************************/
  289. /// <inheritdoc/>
  290. public sealed override void CopyFrom(ManualMixerState copyFrom, CloneContext context)
  291. => this.CopyFromBase(copyFrom, context);
  292. /// <inheritdoc/>
  293. public virtual void CopyFrom(MixerState<TParameter> copyFrom, CloneContext context)
  294. {
  295. base.CopyFrom(copyFrom, context);
  296. var childCount = copyFrom.ChildCount;
  297. if (copyFrom._Thresholds != null)
  298. {
  299. if (_Thresholds == null || _Thresholds.Length != childCount)
  300. _Thresholds = new TParameter[childCount];
  301. var count = Math.Min(childCount, copyFrom._Thresholds.Length);
  302. Array.Copy(copyFrom._Thresholds, _Thresholds, count);
  303. }
  304. Parameter = copyFrom.Parameter;
  305. }
  306. /************************************************************************************************************************/
  307. #endregion
  308. /************************************************************************************************************************/
  309. #region Descriptions
  310. /************************************************************************************************************************/
  311. /// <inheritdoc/>
  312. public override string GetDisplayKey(AnimancerState state)
  313. => $"[{state.Index}] {_Thresholds[state.Index]}";
  314. /************************************************************************************************************************/
  315. /// <inheritdoc/>
  316. protected override void AppendDetails(StringBuilder text, string separator)
  317. {
  318. text.Append(separator)
  319. .Append($"{nameof(Parameter)}: ");
  320. AppendParameter(text, Parameter);
  321. text.Append(separator)
  322. .Append("Thresholds: ");
  323. var thresholdCount = Math.Min(ChildCapacity, _Thresholds.Length);
  324. for (int i = 0; i < thresholdCount; i++)
  325. {
  326. if (i > 0)
  327. text.Append(", ");
  328. AppendParameter(text, _Thresholds[i]);
  329. }
  330. base.AppendDetails(text, separator);
  331. }
  332. /************************************************************************************************************************/
  333. /// <summary>Appends the `parameter` in a viewer-friendly format.</summary>
  334. public virtual void AppendParameter(StringBuilder description, TParameter parameter)
  335. {
  336. description.Append(parameter);
  337. }
  338. /************************************************************************************************************************/
  339. #endregion
  340. /************************************************************************************************************************/
  341. }
  342. }