PlayableAssetTransitionAsset.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using Animancer.Units;
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using UnityEngine.Playables;
  7. using Object = UnityEngine.Object;
  8. #if UNITY_EDITOR
  9. using Animancer.Editor;
  10. using UnityEditor;
  11. #endif
  12. namespace Animancer
  13. {
  14. /// <inheritdoc/>
  15. /// https://kybernetik.com.au/animancer/api/Animancer/PlayableAssetTransitionAsset
  16. [CreateAssetMenu(menuName = Strings.MenuPrefix + "Playable Asset Transition", order = Strings.AssetMenuOrder + 9)]
  17. [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(PlayableAssetTransitionAsset))]
  18. public class PlayableAssetTransitionAsset : AnimancerTransitionAsset<PlayableAssetTransition>
  19. {
  20. /// <inheritdoc/>
  21. [Serializable]
  22. public new class UnShared :
  23. UnShared<PlayableAssetTransitionAsset, PlayableAssetTransition, PlayableAssetState>,
  24. PlayableAssetState.ITransition
  25. { }
  26. }
  27. /// <inheritdoc/>
  28. /// https://kybernetik.com.au/animancer/api/Animancer/PlayableAssetTransition
  29. [Serializable]
  30. public class PlayableAssetTransition : AnimancerTransition<PlayableAssetState>,
  31. PlayableAssetState.ITransition, IAnimationClipCollection, ICopyable<PlayableAssetTransition>
  32. {
  33. /************************************************************************************************************************/
  34. [SerializeField, Tooltip("The asset to play")]
  35. private PlayableAsset _Asset;
  36. /// <summary>[<see cref="SerializeField"/>] The asset to play.</summary>
  37. public ref PlayableAsset Asset => ref _Asset;
  38. /// <inheritdoc/>
  39. public override Object MainObject => _Asset;
  40. /// <summary>
  41. /// The <see cref="Asset"/> will be used as the <see cref="AnimancerState.Key"/> for the created state to
  42. /// be registered with.
  43. /// </summary>
  44. public override object Key => _Asset;
  45. /************************************************************************************************************************/
  46. [SerializeField]
  47. [Tooltip(Strings.Tooltips.OptionalSpeed)]
  48. [AnimationSpeed]
  49. [DefaultValue(1f, -1f)]
  50. private float _Speed = 1;
  51. /// <summary>[<see cref="SerializeField"/>]
  52. /// Determines how fast the animation plays (1x = normal speed, 2x = double speed).
  53. /// </summary>
  54. public override float Speed
  55. {
  56. get => _Speed;
  57. set => _Speed = value;
  58. }
  59. /************************************************************************************************************************/
  60. [SerializeField]
  61. [Tooltip(Strings.Tooltips.NormalizedStartTime)]
  62. [AnimationTime(AnimationTimeAttribute.Units.Normalized)]
  63. [DefaultValue(float.NaN, 0f)]
  64. private float _NormalizedStartTime = float.NaN;
  65. /// <inheritdoc/>
  66. public override float NormalizedStartTime
  67. {
  68. get => _NormalizedStartTime;
  69. set => _NormalizedStartTime = value;
  70. }
  71. /************************************************************************************************************************/
  72. [SerializeField]
  73. [Tooltip("The objects controlled by each of the tracks in the Asset")]
  74. #if UNITY_2020_2_OR_NEWER
  75. [NonReorderable]
  76. #endif
  77. private Object[] _Bindings;
  78. /// <summary>[<see cref="SerializeField"/>] The objects controlled by each of the tracks in the Asset.</summary>
  79. public ref Object[] Bindings => ref _Bindings;
  80. /************************************************************************************************************************/
  81. /// <inheritdoc/>
  82. public override float MaximumDuration => _Asset != null ? (float)_Asset.duration : 0;
  83. /// <inheritdoc/>
  84. public override bool IsValid => _Asset != null;
  85. /************************************************************************************************************************/
  86. /// <inheritdoc/>
  87. public override PlayableAssetState CreateState()
  88. {
  89. State = new PlayableAssetState(_Asset);
  90. State.SetBindings(_Bindings);
  91. return State;
  92. }
  93. /************************************************************************************************************************/
  94. /// <inheritdoc/>
  95. public override void Apply(AnimancerState state)
  96. {
  97. ApplyDetails(state, _Speed, _NormalizedStartTime);
  98. base.Apply(state);
  99. }
  100. /************************************************************************************************************************/
  101. /// <summary>Gathers all the animations associated with this object.</summary>
  102. void IAnimationClipCollection.GatherAnimationClips(ICollection<AnimationClip> clips)
  103. => clips.GatherFromAsset(_Asset);
  104. /************************************************************************************************************************/
  105. /// <inheritdoc/>
  106. public virtual void CopyFrom(PlayableAssetTransition copyFrom)
  107. {
  108. CopyFrom((AnimancerTransition<PlayableAssetState>)copyFrom);
  109. if (copyFrom == null)
  110. {
  111. _Asset = default;
  112. _Speed = 1;
  113. _NormalizedStartTime = float.NaN;
  114. _Bindings = default;
  115. return;
  116. }
  117. _Asset = copyFrom._Asset;
  118. _Speed = copyFrom._Speed;
  119. _NormalizedStartTime = copyFrom._NormalizedStartTime;
  120. AnimancerUtilities.CopyExactArray(copyFrom._Bindings, ref _Bindings);
  121. }
  122. /************************************************************************************************************************/
  123. #if UNITY_EDITOR
  124. /************************************************************************************************************************/
  125. /// <inheritdoc/>
  126. [CustomPropertyDrawer(typeof(PlayableAssetTransition), true)]
  127. public class Drawer : TransitionDrawer
  128. {
  129. /************************************************************************************************************************/
  130. /// <summary>Creates a new <see cref="Drawer"/>.</summary>
  131. public Drawer() : base(nameof(_Asset)) { }
  132. /************************************************************************************************************************/
  133. /// <inheritdoc/>
  134. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  135. {
  136. _CurrentAsset = null;
  137. var height = base.GetPropertyHeight(property, label);
  138. if (property.isExpanded)
  139. {
  140. var bindings = property.FindPropertyRelative(nameof(_Bindings));
  141. if (bindings != null)
  142. {
  143. bindings.isExpanded = true;
  144. height -= AnimancerGUI.StandardSpacing + AnimancerGUI.LineHeight;
  145. }
  146. }
  147. return height;
  148. }
  149. /************************************************************************************************************************/
  150. private PlayableAsset _CurrentAsset;
  151. /// <inheritdoc/>
  152. protected override void DoMainPropertyGUI(Rect area, out Rect labelArea,
  153. SerializedProperty rootProperty, SerializedProperty mainProperty)
  154. {
  155. _CurrentAsset = mainProperty.objectReferenceValue as PlayableAsset;
  156. base.DoMainPropertyGUI(area, out labelArea, rootProperty, mainProperty);
  157. }
  158. /// <inheritdoc/>
  159. public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
  160. {
  161. base.OnGUI(area, property, label);
  162. _CurrentAsset = null;
  163. }
  164. /// <inheritdoc/>
  165. protected override void DoChildPropertyGUI(ref Rect area, SerializedProperty rootProperty,
  166. SerializedProperty property, GUIContent label)
  167. {
  168. var path = property.propertyPath;
  169. if (path.EndsWith($".{nameof(_Bindings)}"))
  170. {
  171. DoBindingsGUI(ref area, property, label);
  172. return;
  173. }
  174. base.DoChildPropertyGUI(ref area, rootProperty, property, label);
  175. }
  176. /************************************************************************************************************************/
  177. private void DoBindingsGUI(ref Rect area, SerializedProperty property, GUIContent label)
  178. {
  179. var outputCount = GetOutputCount(out var outputEnumerator, out var firstBindingIsAnimation);
  180. // Bindings.
  181. property.Next(true);
  182. // Array.
  183. property.Next(true);
  184. // Array Size.
  185. DoBindingsCountGUI(area, property, label, outputCount, firstBindingIsAnimation, out var bindingCount);
  186. EditorGUI.indentLevel++;
  187. for (int i = 0; i < bindingCount; i++)
  188. {
  189. AnimancerGUI.NextVerticalArea(ref area);
  190. if (!property.Next(false))
  191. {
  192. EditorGUI.LabelField(area, "Binding Count Mismatch");
  193. break;
  194. }
  195. // First Array Item.
  196. if (outputEnumerator != null && outputEnumerator.MoveNext())
  197. {
  198. DoBindingGUI(area, property, label, outputEnumerator, i);
  199. }
  200. else
  201. {
  202. var color = GUI.color;
  203. GUI.color = AnimancerGUI.WarningFieldColor;
  204. EditorGUI.PropertyField(area, property, false);
  205. GUI.color = color;
  206. }
  207. }
  208. EditorGUI.indentLevel--;
  209. }
  210. /************************************************************************************************************************/
  211. private int GetOutputCount(out IEnumerator<PlayableBinding> outputEnumerator, out bool firstBindingIsAnimation)
  212. {
  213. var outputCount = 0;
  214. firstBindingIsAnimation = false;
  215. if (_CurrentAsset != null)
  216. {
  217. var outputs = _CurrentAsset.outputs;
  218. _CurrentAsset = null;
  219. outputEnumerator = outputs.GetEnumerator();
  220. while (outputEnumerator.MoveNext())
  221. {
  222. PlayableAssetState.GetBindingDetails(
  223. outputEnumerator.Current, out var _, out var _, out var isMarkers);
  224. if (isMarkers)
  225. continue;
  226. if (outputCount == 0 && outputEnumerator.Current.outputTargetType == typeof(Animator))
  227. firstBindingIsAnimation = true;
  228. outputCount++;
  229. }
  230. outputEnumerator = outputs.GetEnumerator();
  231. }
  232. else outputEnumerator = null;
  233. return outputCount;
  234. }
  235. /************************************************************************************************************************/
  236. private void DoBindingsCountGUI(Rect area, SerializedProperty property, GUIContent label,
  237. int outputCount, bool firstBindingIsAnimation, out int bindingCount)
  238. {
  239. var color = GUI.color;
  240. var sizeArea = area;
  241. bindingCount = property.intValue;
  242. // Button to fix the number of bindings in the array.
  243. if (bindingCount != outputCount && !(bindingCount == 0 && outputCount == 1 && firstBindingIsAnimation))
  244. {
  245. GUI.color = AnimancerGUI.WarningFieldColor;
  246. var labelText = label.text;
  247. var style = AnimancerGUI.MiniButton;
  248. var countLabel = outputCount.ToString();
  249. var fixSizeWidth = AnimancerGUI.CalculateWidth(style, countLabel);
  250. var fixSizeArea = AnimancerGUI.StealFromRight(
  251. ref sizeArea, fixSizeWidth, AnimancerGUI.StandardSpacing);
  252. if (GUI.Button(fixSizeArea, countLabel, style))
  253. property.intValue = bindingCount = outputCount;
  254. label.text = labelText;
  255. }
  256. EditorGUI.PropertyField(sizeArea, property, label, false);
  257. GUI.color = color;
  258. }
  259. /************************************************************************************************************************/
  260. private void DoBindingGUI(Rect area, SerializedProperty property, GUIContent label,
  261. IEnumerator<PlayableBinding> outputEnumerator, int trackIndex)
  262. {
  263. CheckIfSkip:
  264. PlayableAssetState.GetBindingDetails(
  265. outputEnumerator.Current, out var name, out var bindingType, out var isMarkers);
  266. if (isMarkers)
  267. {
  268. outputEnumerator.MoveNext();
  269. goto CheckIfSkip;
  270. }
  271. label.text = name;
  272. var targetObject = property.serializedObject.targetObject;
  273. var allowSceneObjects =
  274. targetObject != null &&
  275. !EditorUtility.IsPersistent(targetObject);
  276. label = EditorGUI.BeginProperty(area, label, property);
  277. var fieldArea = area;
  278. var obj = property.objectReferenceValue;
  279. var objExists = obj != null;
  280. if (objExists)
  281. DoRemoveButtonIfNecessary(ref fieldArea, label, property, trackIndex, ref bindingType, ref obj);
  282. if (bindingType != null || objExists)
  283. {
  284. property.objectReferenceValue =
  285. EditorGUI.ObjectField(fieldArea, label, obj, bindingType, allowSceneObjects);
  286. }
  287. else
  288. {
  289. EditorGUI.LabelField(fieldArea, label);
  290. }
  291. EditorGUI.EndProperty();
  292. }
  293. /************************************************************************************************************************/
  294. private static void DoRemoveButtonIfNecessary(ref Rect area, GUIContent label, SerializedProperty property,
  295. int trackIndex, ref Type bindingType, ref Object obj)
  296. {
  297. if (trackIndex == 0 && bindingType == typeof(Animator))
  298. {
  299. DoRemoveButton(ref area, label, property, ref obj,
  300. "This Animation Track is the first Track" +
  301. " so it will automatically control the Animancer output" +
  302. " and likely doesn't need a binding.");
  303. }
  304. else if (bindingType == null)
  305. {
  306. DoRemoveButton(ref area, label, property, ref obj,
  307. "This Track doesn't need a binding.");
  308. bindingType = typeof(Object);
  309. }
  310. else if (!bindingType.IsAssignableFrom(obj.GetType()))
  311. {
  312. DoRemoveButton(ref area, label, property, ref obj,
  313. "This binding has the wrong type for this Track.");
  314. }
  315. }
  316. /************************************************************************************************************************/
  317. private static void DoRemoveButton(ref Rect area, GUIContent label, SerializedProperty property,
  318. ref Object obj, string tooltip)
  319. {
  320. label.tooltip = tooltip;
  321. GUI.color = AnimancerGUI.WarningFieldColor;
  322. var miniButton = AnimancerGUI.MiniButton;
  323. var text = label.text;
  324. label.text = "x";
  325. var xWidth = AnimancerGUI.CalculateWidth(miniButton, label);
  326. var xArea = AnimancerGUI.StealFromRight(
  327. ref area, xWidth, AnimancerGUI.StandardSpacing);
  328. if (GUI.Button(xArea, label, miniButton))
  329. property.objectReferenceValue = obj = null;
  330. label.text = text;
  331. }
  332. /************************************************************************************************************************/
  333. }
  334. /************************************************************************************************************************/
  335. #endif
  336. /************************************************************************************************************************/
  337. }
  338. }