AnimancerUtilities.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using System;
  3. using Unity.Collections;
  4. using UnityEngine;
  5. using UnityEngine.Animations;
  6. using UnityEngine.Playables;
  7. using Object = UnityEngine.Object;
  8. namespace Animancer
  9. {
  10. /// <summary>Various extension methods and utilities.</summary>
  11. /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities
  12. ///
  13. public static partial class AnimancerUtilities
  14. {
  15. /************************************************************************************************************************/
  16. #region Misc
  17. /************************************************************************************************************************/
  18. /// <summary>This is Animancer Pro.</summary>
  19. public const bool IsAnimancerPro = true;
  20. /************************************************************************************************************************/
  21. /// <summary>Loops the `value` so that <c>0 &lt;= value &lt; 1</c>.</summary>
  22. /// <remarks>This is more efficient than using <see cref="Wrap"/> with a <c>length</c> of 1.</remarks>
  23. public static float Wrap01(float value)
  24. {
  25. var valueAsDouble = (double)value;
  26. value = (float)(valueAsDouble - Math.Floor(valueAsDouble));
  27. return value < 1 ? value : 0;
  28. }
  29. /// <summary>Loops the `value` so that <c>0 &lt;= value &lt; length</c>.</summary>
  30. /// <remarks>Unike <see cref="Mathf.Repeat"/>, this method will never return the `length`.</remarks>
  31. public static float Wrap(float value, float length)
  32. {
  33. var valueAsDouble = (double)value;
  34. var lengthAsDouble = (double)length;
  35. value = (float)(valueAsDouble - Math.Floor(valueAsDouble / lengthAsDouble) * lengthAsDouble);
  36. return value < length ? value : 0;
  37. }
  38. /************************************************************************************************************************/
  39. /// <summary>
  40. /// Rounds the `value` to the nearest integer using <see cref="MidpointRounding.AwayFromZero"/>.
  41. /// </summary>
  42. public static float Round(float value)
  43. => (float)Math.Round(value, MidpointRounding.AwayFromZero);
  44. /// <summary>
  45. /// Rounds the `value` to be a multiple of the `multiple` using <see cref="MidpointRounding.AwayFromZero"/>.
  46. /// </summary>
  47. public static float Round(float value, float multiple)
  48. => Round(value / multiple) * multiple;
  49. /************************************************************************************************************************/
  50. /// <summary>[Animancer Extension] Is the `value` not NaN or Infinity?</summary>
  51. /// <remarks>Newer versions of the .NET framework apparently have a <c>float.IsFinite</c> method.</remarks>
  52. public static bool IsFinite(this float value) => !float.IsNaN(value) && !float.IsInfinity(value);
  53. /// <summary>[Animancer Extension] Are all components of the `value` not NaN or Infinity?</summary>
  54. public static bool IsFinite(this Vector2 value) => value.x.IsFinite() && value.y.IsFinite();
  55. /************************************************************************************************************************/
  56. /// <summary>
  57. /// If `obj` exists, this method returns <see cref="object.ToString"/>.
  58. /// Or if it is <c>null</c>, this method returns <c>"Null"</c>.
  59. /// Or if it is an <see cref="Object"/> that has been destroyed, this method returns <c>"Null (ObjectType)"</c>.
  60. /// </summary>
  61. public static string ToStringOrNull(object obj)
  62. {
  63. if (obj is null)
  64. return "Null";
  65. if (obj is Object unityObject && unityObject == null)
  66. return $"Null ({obj.GetType()})";
  67. return obj.ToString();
  68. }
  69. /************************************************************************************************************************/
  70. /// <summary>Ensures that the length and contents of `copyTo` match `copyFrom`.</summary>
  71. public static void CopyExactArray<T>(T[] copyFrom, ref T[] copyTo)
  72. {
  73. if (copyFrom == null)
  74. {
  75. copyTo = null;
  76. return;
  77. }
  78. var length = copyFrom.Length;
  79. SetLength(ref copyTo, length);
  80. Array.Copy(copyFrom, copyTo, length);
  81. }
  82. /************************************************************************************************************************/
  83. /// <summary>[Animancer Extension] Swaps <c>array[a]</c> with <c>array[b]</c>.</summary>
  84. public static void Swap<T>(this T[] array, int a, int b)
  85. {
  86. var temp = array[a];
  87. array[a] = array[b];
  88. array[b] = temp;
  89. }
  90. /************************************************************************************************************************/
  91. /// <summary>[Animancer Extension]
  92. /// Is the `array` <c>null</c> or its <see cref="Array.Length"/> <c>0</c>?
  93. /// </summary>
  94. public static bool IsNullOrEmpty<T>(this T[] array) => array == null || array.Length == 0;
  95. /************************************************************************************************************************/
  96. /// <summary>
  97. /// If the `array` is <c>null</c> or its <see cref="Array.Length"/> isn't equal to the specified `length`, this
  98. /// method creates a new array with that `length` and returns <c>true</c>.
  99. /// </summary>
  100. /// <remarks>
  101. /// Unlike <see cref="Array.Resize{T}(ref T[], int)"/>, this method doesn't copy over the contents of the old
  102. /// `array` into the new one.
  103. /// </remarks>
  104. public static bool SetLength<T>(ref T[] array, int length)
  105. {
  106. if (array == null || array.Length != length)
  107. {
  108. array = new T[length];
  109. return true;
  110. }
  111. else return false;
  112. }
  113. /************************************************************************************************************************/
  114. /// <summary>[Animancer Extension] Is the `node` is not null and <see cref="AnimancerNode.IsValid"/>?</summary>
  115. public static bool IsValid(this AnimancerNode node) => node != null && node.IsValid;
  116. /// <summary>[Animancer Extension] Is the `transition` not null and <see cref="ITransitionDetailed.IsValid"/>?</summary>
  117. public static bool IsValid(this ITransitionDetailed transition) => transition != null && transition.IsValid;
  118. /************************************************************************************************************************/
  119. /// <summary>[Animancer Extension] Calls <see cref="ITransition.CreateState"/> and <see cref="ITransition.Apply"/>.</summary>
  120. public static AnimancerState CreateStateAndApply(this ITransition transition, AnimancerPlayable root = null)
  121. {
  122. var state = transition.CreateState();
  123. state.SetRoot(root);
  124. transition.Apply(state);
  125. return state;
  126. }
  127. /************************************************************************************************************************/
  128. /// <summary>[Pro-Only] Reconnects the input of the specified `playable` to its output.</summary>
  129. public static void RemovePlayable(Playable playable, bool destroy = true)
  130. {
  131. if (!playable.IsValid())
  132. return;
  133. Assert(playable.GetInputCount() == 1,
  134. $"{nameof(RemovePlayable)} can only be used on playables with 1 input.");
  135. Assert(playable.GetOutputCount() == 1,
  136. $"{nameof(RemovePlayable)} can only be used on playables with 1 output.");
  137. var input = playable.GetInput(0);
  138. if (!input.IsValid())
  139. {
  140. if (destroy)
  141. playable.Destroy();
  142. return;
  143. }
  144. var graph = playable.GetGraph();
  145. var output = playable.GetOutput(0);
  146. if (output.IsValid())// Connected to another Playable.
  147. {
  148. if (destroy)
  149. {
  150. playable.Destroy();
  151. }
  152. else
  153. {
  154. Assert(output.GetInputCount() == 1,
  155. $"{nameof(RemovePlayable)} can only be used on playables connected to a playable with 1 input.");
  156. graph.Disconnect(output, 0);
  157. graph.Disconnect(playable, 0);
  158. }
  159. graph.Connect(input, 0, output, 0);
  160. }
  161. else// Connected to the graph output.
  162. {
  163. Assert(graph.GetOutput(0).GetSourcePlayable().Equals(playable),
  164. $"{nameof(RemovePlayable)} can only be used on playables connected to another playable or to the graph output.");
  165. if (destroy)
  166. playable.Destroy();
  167. else
  168. graph.Disconnect(playable, 0);
  169. graph.GetOutput(0).SetSourcePlayable(input);
  170. }
  171. }
  172. /************************************************************************************************************************/
  173. /// <summary>
  174. /// Checks if any <see cref="AnimationClip"/> in the `source` has an animation event with the specified
  175. /// `functionName`.
  176. /// </summary>
  177. public static bool HasEvent(IAnimationClipCollection source, string functionName)
  178. {
  179. var clips = ObjectPool.AcquireSet<AnimationClip>();
  180. source.GatherAnimationClips(clips);
  181. foreach (var clip in clips)
  182. {
  183. if (HasEvent(clip, functionName))
  184. {
  185. ObjectPool.Release(clips);
  186. return true;
  187. }
  188. }
  189. ObjectPool.Release(clips);
  190. return false;
  191. }
  192. /// <summary>Checks if the `clip` has an animation event with the specified `functionName`.</summary>
  193. public static bool HasEvent(AnimationClip clip, string functionName)
  194. {
  195. var events = clip.events;
  196. for (int i = events.Length - 1; i >= 0; i--)
  197. {
  198. if (events[i].functionName == functionName)
  199. return true;
  200. }
  201. return false;
  202. }
  203. /************************************************************************************************************************/
  204. /// <summary>[Animancer Extension] [Pro-Only]
  205. /// Calculates all thresholds in the `mixer` using the <see cref="AnimancerState.AverageVelocity"/> of each
  206. /// state on the X and Z axes.
  207. /// <para></para>
  208. /// Note that this method requires the <c>Root Transform Position (XZ) -> Bake Into Pose</c> toggle to be
  209. /// disabled in the Import Settings of each <see cref="AnimationClip"/> in the mixer.
  210. /// </summary>
  211. public static void CalculateThresholdsFromAverageVelocityXZ(this MixerState<Vector2> mixer)
  212. {
  213. mixer.ValidateThresholdCount();
  214. for (int i = mixer.ChildCount - 1; i >= 0; i--)
  215. {
  216. var state = mixer.GetChild(i);
  217. if (state == null)
  218. continue;
  219. var averageVelocity = state.AverageVelocity;
  220. mixer.SetThreshold(i, new Vector2(averageVelocity.x, averageVelocity.z));
  221. }
  222. }
  223. /************************************************************************************************************************/
  224. /// <summary>Gets the value of the `parameter` in the `animator`.</summary>
  225. public static object GetParameterValue(Animator animator, AnimatorControllerParameter parameter)
  226. {
  227. switch (parameter.type)
  228. {
  229. case AnimatorControllerParameterType.Float:
  230. return animator.GetFloat(parameter.nameHash);
  231. case AnimatorControllerParameterType.Int:
  232. return animator.GetInteger(parameter.nameHash);
  233. case AnimatorControllerParameterType.Bool:
  234. case AnimatorControllerParameterType.Trigger:
  235. return animator.GetBool(parameter.nameHash);
  236. default:
  237. throw CreateUnsupportedArgumentException(parameter.type);
  238. }
  239. }
  240. /// <summary>Gets the value of the `parameter` in the `playable`.</summary>
  241. public static object GetParameterValue(AnimatorControllerPlayable playable, AnimatorControllerParameter parameter)
  242. {
  243. switch (parameter.type)
  244. {
  245. case AnimatorControllerParameterType.Float:
  246. return playable.GetFloat(parameter.nameHash);
  247. case AnimatorControllerParameterType.Int:
  248. return playable.GetInteger(parameter.nameHash);
  249. case AnimatorControllerParameterType.Bool:
  250. case AnimatorControllerParameterType.Trigger:
  251. return playable.GetBool(parameter.nameHash);
  252. default:
  253. throw CreateUnsupportedArgumentException(parameter.type);
  254. }
  255. }
  256. /************************************************************************************************************************/
  257. /// <summary>Sets the `value` of the `parameter` in the `animator`.</summary>
  258. public static void SetParameterValue(Animator animator, AnimatorControllerParameter parameter, object value)
  259. {
  260. switch (parameter.type)
  261. {
  262. case AnimatorControllerParameterType.Float:
  263. animator.SetFloat(parameter.nameHash, (float)value);
  264. break;
  265. case AnimatorControllerParameterType.Int:
  266. animator.SetInteger(parameter.nameHash, (int)value);
  267. break;
  268. case AnimatorControllerParameterType.Bool:
  269. animator.SetBool(parameter.nameHash, (bool)value);
  270. break;
  271. case AnimatorControllerParameterType.Trigger:
  272. if ((bool)value)
  273. animator.SetTrigger(parameter.nameHash);
  274. else
  275. animator.ResetTrigger(parameter.nameHash);
  276. break;
  277. default:
  278. throw CreateUnsupportedArgumentException(parameter.type);
  279. }
  280. }
  281. /// <summary>Sets the `value` of the `parameter` in the `playable`.</summary>
  282. public static void SetParameterValue(AnimatorControllerPlayable playable, AnimatorControllerParameter parameter, object value)
  283. {
  284. switch (parameter.type)
  285. {
  286. case AnimatorControllerParameterType.Float:
  287. playable.SetFloat(parameter.nameHash, (float)value);
  288. break;
  289. case AnimatorControllerParameterType.Int:
  290. playable.SetInteger(parameter.nameHash, (int)value);
  291. break;
  292. case AnimatorControllerParameterType.Bool:
  293. playable.SetBool(parameter.nameHash, (bool)value);
  294. break;
  295. case AnimatorControllerParameterType.Trigger:
  296. if ((bool)value)
  297. playable.SetTrigger(parameter.nameHash);
  298. else
  299. playable.ResetTrigger(parameter.nameHash);
  300. break;
  301. default:
  302. throw CreateUnsupportedArgumentException(parameter.type);
  303. }
  304. }
  305. /************************************************************************************************************************/
  306. /// <summary>
  307. /// Creates a <see cref="NativeArray{T}"/> containing a single element so that it can be used like a reference
  308. /// in Unity's C# Job system which does not allow regular reference types.
  309. /// <para></para>
  310. /// Note that you must call <see cref="NativeArray{T}.Dispose()"/> when you are done with the array.
  311. /// </summary>
  312. public static NativeArray<T> CreateNativeReference<T>() where T : struct
  313. {
  314. return new NativeArray<T>(1, Allocator.Persistent, NativeArrayOptions.ClearMemory);
  315. }
  316. /************************************************************************************************************************/
  317. /// <summary>Returns a string stating that the `value` is unsupported.</summary>
  318. public static string GetUnsupportedMessage<T>(T value)
  319. => $"Unsupported {typeof(T).FullName}: {value}";
  320. /// <summary>Returns an exception stating that the `value` is unsupported.</summary>
  321. public static ArgumentException CreateUnsupportedArgumentException<T>(T value)
  322. => new ArgumentException(GetUnsupportedMessage(value));
  323. /************************************************************************************************************************/
  324. #endregion
  325. /************************************************************************************************************************/
  326. #region Components
  327. /************************************************************************************************************************/
  328. /// <summary>[Animancer Extension]
  329. /// Adds the specified type of <see cref="IAnimancerComponent"/>, links it to the `animator`, and returns it.
  330. /// </summary>
  331. public static T AddAnimancerComponent<T>(this Animator animator) where T : Component, IAnimancerComponent
  332. {
  333. var animancer = animator.gameObject.AddComponent<T>();
  334. animancer.Animator = animator;
  335. return animancer;
  336. }
  337. /************************************************************************************************************************/
  338. /// <summary>[Animancer Extension]
  339. /// Returns the <see cref="IAnimancerComponent"/> on the same <see cref="GameObject"/> as the `animator` if
  340. /// there is one. Otherwise this method adds a new one and returns it.
  341. /// </summary>
  342. public static T GetOrAddAnimancerComponent<T>(this Animator animator) where T : Component, IAnimancerComponent
  343. {
  344. if (animator.TryGetComponent<T>(out var component))
  345. return component;
  346. else
  347. return animator.AddAnimancerComponent<T>();
  348. }
  349. /************************************************************************************************************************/
  350. /// <summary>
  351. /// Returns the first <typeparamref name="T"/> component on the `gameObject` or its parents or children (in
  352. /// that order).
  353. /// </summary>
  354. public static T GetComponentInParentOrChildren<T>(this GameObject gameObject) where T : class
  355. {
  356. var component = gameObject.GetComponentInParent<T>();
  357. if (component != null)
  358. return component;
  359. return gameObject.GetComponentInChildren<T>();
  360. }
  361. /// <summary>
  362. /// If the `component` is <c>null</c>, this method tries to find one on the `gameObject` or its parents or
  363. /// children (in that order).
  364. /// </summary>
  365. public static bool GetComponentInParentOrChildren<T>(this GameObject gameObject, ref T component) where T : class
  366. {
  367. if (component != null &&
  368. (!(component is Object obj) || obj != null))
  369. return false;
  370. component = gameObject.GetComponentInParentOrChildren<T>();
  371. return !(component is null);
  372. }
  373. /************************************************************************************************************************/
  374. #endregion
  375. /************************************************************************************************************************/
  376. #region Editor
  377. /************************************************************************************************************************/
  378. /// <summary>[Assert-Conditional]
  379. /// Throws an <see cref="UnityEngine.Assertions.AssertionException"/> if the `condition` is false.
  380. /// </summary>
  381. /// <remarks>
  382. /// This method is similar to <see cref="Debug.Assert(bool, object)"/>, but it throws an exception instead of
  383. /// just logging the `message`.
  384. /// </remarks>
  385. [System.Diagnostics.Conditional(Strings.Assertions)]
  386. public static void Assert(bool condition, object message)
  387. {
  388. #if UNITY_ASSERTIONS
  389. if (!condition)
  390. throw new UnityEngine.Assertions.AssertionException(message != null ? message.ToString() : "Assertion failed.", null);
  391. #endif
  392. }
  393. /************************************************************************************************************************/
  394. /// <summary>[Editor-Conditional] Indicates that the `target` needs to be re-serialized.</summary>
  395. [System.Diagnostics.Conditional(Strings.UnityEditor)]
  396. public static void SetDirty(Object target)
  397. {
  398. #if UNITY_EDITOR
  399. UnityEditor.EditorUtility.SetDirty(target);
  400. #endif
  401. }
  402. /************************************************************************************************************************/
  403. /// <summary>[Editor-Conditional]
  404. /// Applies the effects of the animation `clip` to the <see cref="Component.gameObject"/>.
  405. /// </summary>
  406. /// <remarks>This method is safe to call during <see cref="MonoBehaviour"/><c>.OnValidate</c>.</remarks>
  407. /// <param name="clip">The animation to apply. If <c>null</c>, this method does nothing.</param>
  408. /// <param name="component">
  409. /// The animation will be applied to an <see cref="Animator"/> or <see cref="Animation"/> component on the same
  410. /// object as this or on any of its parents or children. If <c>null</c>, this method does nothing.
  411. /// </param>
  412. /// <param name="time">Determines which part of the animation to apply (in seconds).</param>
  413. /// <seealso cref="EditModePlay"/>
  414. [System.Diagnostics.Conditional(Strings.UnityEditor)]
  415. public static void EditModeSampleAnimation(this AnimationClip clip, Component component, float time = 0)
  416. {
  417. #if UNITY_EDITOR
  418. if (!ShouldEditModeSample(clip, component))
  419. return;
  420. var gameObject = component.gameObject;
  421. component = gameObject.GetComponentInParentOrChildren<Animator>();
  422. if (component == null)
  423. {
  424. component = gameObject.GetComponentInParentOrChildren<Animation>();
  425. if (component == null)
  426. return;
  427. }
  428. UnityEditor.EditorApplication.delayCall += () =>
  429. {
  430. if (!ShouldEditModeSample(clip, component))
  431. return;
  432. clip.SampleAnimation(component.gameObject, time);
  433. };
  434. }
  435. private static bool ShouldEditModeSample(AnimationClip clip, Component component)
  436. {
  437. return
  438. !UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode &&
  439. clip != null &&
  440. component != null &&
  441. !UnityEditor.EditorUtility.IsPersistent(component);
  442. #endif
  443. }
  444. /************************************************************************************************************************/
  445. /// <summary>[Editor-Conditional] Plays the specified `clip` if called in Edit Mode.</summary>
  446. /// <remarks>This method is safe to call during <see cref="MonoBehaviour"/><c>.OnValidate</c>.</remarks>
  447. /// <param name="clip">The animation to apply. If <c>null</c>, this method does nothing.</param>
  448. /// <param name="component">
  449. /// The animation will be played on an <see cref="IAnimancerComponent"/> on the same object as this or on any
  450. /// of its parents or children. If <c>null</c>, this method does nothing.
  451. /// </param>
  452. /// <seealso cref="EditModeSampleAnimation"/>
  453. [System.Diagnostics.Conditional(Strings.UnityEditor)]
  454. public static void EditModePlay(this AnimationClip clip, Component component)
  455. {
  456. #if UNITY_EDITOR
  457. if (!ShouldEditModeSample(clip, component))
  458. return;
  459. var animancer = component as IAnimancerComponent;
  460. if (animancer == null)
  461. animancer = component.gameObject.GetComponentInParentOrChildren<IAnimancerComponent>();
  462. if (!ShouldEditModePlay(animancer, clip))
  463. return;
  464. // If it's already initialized, play immediately.
  465. if (animancer.IsPlayableInitialized)
  466. {
  467. animancer.Playable.Play(clip);
  468. return;
  469. }
  470. // Otherwise, delay it in case this was called at a bad time (such as during OnValidate).
  471. UnityEditor.EditorApplication.delayCall += () =>
  472. {
  473. if (ShouldEditModePlay(animancer, clip))
  474. animancer.Playable.Play(clip);
  475. };
  476. }
  477. private static bool ShouldEditModePlay(IAnimancerComponent animancer, AnimationClip clip)
  478. {
  479. return
  480. ShouldEditModeSample(clip, animancer?.Animator) &&
  481. (!(animancer is Object obj) || obj != null);
  482. #endif
  483. }
  484. /************************************************************************************************************************/
  485. #if UNITY_ASSERTIONS
  486. /************************************************************************************************************************/
  487. private static System.Reflection.FieldInfo _DelegatesField;
  488. private static bool _GotDelegatesField;
  489. /// <summary>[Assert-Only]
  490. /// Uses reflection to achieve the same as <see cref="Delegate.GetInvocationList"/> without allocating
  491. /// garbage every time.
  492. /// <list type="bullet">
  493. /// <item>If the delegate is <c>null</c> or , this method returns <c>false</c> and outputs <c>null</c>.</item>
  494. /// <item>If the underlying <c>delegate</c> field was not found, this method returns <c>false</c> and outputs <c>null</c>.</item>
  495. /// <item>If the delegate is not multicast, this method this method returns <c>true</c> and outputs <c>null</c>.</item>
  496. /// <item>If the delegate is multicast, this method this method returns <c>true</c> and outputs its invocation list.</item>
  497. /// </list>
  498. /// </summary>
  499. public static bool TryGetInvocationListNonAlloc(MulticastDelegate multicast, out Delegate[] delegates)
  500. {
  501. if (multicast == null)
  502. {
  503. delegates = null;
  504. return false;
  505. }
  506. if (!_GotDelegatesField)
  507. {
  508. const string FieldName = "delegates";
  509. _GotDelegatesField = true;
  510. _DelegatesField = typeof(MulticastDelegate).GetField("delegates",
  511. System.Reflection.BindingFlags.Public |
  512. System.Reflection.BindingFlags.NonPublic |
  513. System.Reflection.BindingFlags.Instance);
  514. if (_DelegatesField != null && _DelegatesField.FieldType != typeof(Delegate[]))
  515. _DelegatesField = null;
  516. if (_DelegatesField == null)
  517. Debug.LogError($"Unable to find {nameof(MulticastDelegate)}.{FieldName} field.");
  518. }
  519. if (_DelegatesField == null)
  520. {
  521. delegates = null;
  522. return false;
  523. }
  524. else
  525. {
  526. delegates = (Delegate[])_DelegatesField.GetValue(multicast);
  527. return true;
  528. }
  529. }
  530. /************************************************************************************************************************/
  531. #endif
  532. /************************************************************************************************************************/
  533. #endregion
  534. /************************************************************************************************************************/
  535. }
  536. }