TransitionPreviewWindow.Animations.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR && UNITY_IMGUI
  3. using System;
  4. using UnityEditor;
  5. using UnityEngine;
  6. using static Animancer.Editor.AnimancerGUI;
  7. namespace Animancer.Editor.Previews
  8. {
  9. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewWindow
  10. partial class TransitionPreviewWindow
  11. {
  12. /// <summary>Animation details for the <see cref="TransitionPreviewWindow"/>.</summary>
  13. /// <remarks>
  14. /// <strong>Documentation:</strong>
  15. /// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
  16. /// Previews</see>
  17. /// </remarks>
  18. [Serializable]
  19. internal class Animations
  20. {
  21. /************************************************************************************************************************/
  22. public const string
  23. PreviousAnimationKey = "Previous Animation",
  24. NextAnimationKey = "Next Animation";
  25. /************************************************************************************************************************/
  26. [NonSerialized] private AnimationClip[] _OtherAnimations;
  27. [SerializeField]
  28. private AnimationClip _PreviousAnimation;
  29. public AnimationClip PreviousAnimation => _PreviousAnimation;
  30. [SerializeField]
  31. private AnimationClip _NextAnimation;
  32. public AnimationClip NextAnimation => _NextAnimation;
  33. /************************************************************************************************************************/
  34. private static AnimancerPreviewObject PreviewObject
  35. => _Instance._Scene.PreviewObject;
  36. /************************************************************************************************************************/
  37. public void DoGUI()
  38. {
  39. GUILayout.BeginVertical(GUI.skin.box);
  40. EditorGUILayout.LabelField("Preview Details", "(Not Serialized)");
  41. var previewObject = PreviewObject;
  42. AnimancerPreviewObjectGUI.DoModelGUI(previewObject);
  43. using (var label = PooledGUIContent.Acquire("Previous Animation",
  44. "The animation for the preview to play before the target transition"))
  45. {
  46. DoAnimationFieldGUI(label, ref _PreviousAnimation, (clip) => _PreviousAnimation = clip);
  47. }
  48. var graph = previewObject.Graph;
  49. DoCurrentAnimationGUI(graph);
  50. using (var label = PooledGUIContent.Acquire("Next Animation",
  51. "The animation for the preview to play after the target transition"))
  52. {
  53. DoAnimationFieldGUI(label, ref _NextAnimation, (clip) => _NextAnimation = clip);
  54. }
  55. if (graph != null)
  56. {
  57. using (new EditorGUI.DisabledScope(!Transition.IsValid()))
  58. {
  59. GUILayout.BeginHorizontal();
  60. GUILayout.FlexibleSpace();
  61. if (graph.IsGraphPlaying)
  62. {
  63. if (CompactMiniButton(AnimancerIcons.PauseIcon))
  64. graph.PauseGraph();
  65. }
  66. else
  67. {
  68. if (CompactMiniButton(AnimancerIcons.StepBackwardIcon))
  69. StepBackward();
  70. if (CompactMiniButton(AnimancerIcons.PlayIcon))
  71. PlaySequence(graph);
  72. if (CompactMiniButton(AnimancerIcons.StepForwardIcon))
  73. StepForward();
  74. }
  75. GUILayout.FlexibleSpace();
  76. GUILayout.EndHorizontal();
  77. }
  78. }
  79. GUILayout.EndVertical();
  80. }
  81. /************************************************************************************************************************/
  82. public void GatherAnimations()
  83. {
  84. AnimationGatherer.GatherFromGameObject(
  85. PreviewObject.OriginalObject.gameObject,
  86. ref _OtherAnimations,
  87. true);
  88. if (_OtherAnimations.Length > 0 &&
  89. (_PreviousAnimation == null || _NextAnimation == null))
  90. {
  91. var defaultClip = _OtherAnimations[0];
  92. var defaultClipIsIdle = false;
  93. for (int i = 0; i < _OtherAnimations.Length; i++)
  94. {
  95. var clip = _OtherAnimations[i];
  96. if (defaultClipIsIdle && clip.name.Length > defaultClip.name.Length)
  97. continue;
  98. if (clip.name.IndexOf("idle", StringComparison.CurrentCultureIgnoreCase) >= 0)
  99. {
  100. defaultClip = clip;
  101. break;
  102. }
  103. }
  104. if (_PreviousAnimation == null)
  105. _PreviousAnimation = defaultClip;
  106. if (_NextAnimation == null)
  107. _NextAnimation = defaultClip;
  108. }
  109. }
  110. /************************************************************************************************************************/
  111. private void DoAnimationFieldGUI(GUIContent label, ref AnimationClip clip, Action<AnimationClip> setClip)
  112. {
  113. var showDropdown = !_OtherAnimations.IsNullOrEmpty();
  114. var area = LayoutSingleLineRect();
  115. if (DoDropdownObjectFieldGUI(area, label, showDropdown, ref clip))
  116. {
  117. var menu = new GenericMenu();
  118. menu.AddItem(new("None"), clip == null, () => setClip(null));
  119. for (int i = 0; i < _OtherAnimations.Length; i++)
  120. {
  121. var animation = _OtherAnimations[i];
  122. menu.AddItem(new(animation.name), animation == clip, () => setClip(animation));
  123. }
  124. menu.ShowAsContext();
  125. }
  126. }
  127. /************************************************************************************************************************/
  128. private void DoCurrentAnimationGUI(AnimancerGraph animancer)
  129. {
  130. string text;
  131. if (animancer != null)
  132. {
  133. var transition = Transition;
  134. if (transition.IsValid() && transition.Key != null)
  135. text = animancer.States.GetOrCreate(transition).ToString();
  136. else
  137. text = transition?.ToString();
  138. }
  139. else
  140. {
  141. text = _Instance._TransitionProperty.Property.GetFriendlyPath();
  142. }
  143. if (text != null)
  144. EditorGUILayout.LabelField("Current Animation", text);
  145. }
  146. /************************************************************************************************************************/
  147. private void PlaySequence(AnimancerGraph animancer)
  148. {
  149. if (_PreviousAnimation != null && _PreviousAnimation.length > 0)
  150. {
  151. PreviewObject.Graph.Stop();
  152. var fromState = animancer.States.GetOrCreate(PreviousAnimationKey, _PreviousAnimation, true);
  153. animancer.Layers[0].Play(fromState);
  154. OnPlayAnimation();
  155. fromState.TimeD = 0;
  156. var warnings = OptionalWarning.UnsupportedEvents.DisableTemporarily();
  157. fromState.Events(this).EndEvent = new(1 / fromState.Length, PlayTransition);
  158. warnings.Enable();
  159. }
  160. else
  161. {
  162. PlayTransition();
  163. }
  164. PreviewObject.Graph.UnpauseGraph();
  165. }
  166. private void PlayTransition()
  167. {
  168. var transition = Transition;
  169. var animancer = PreviewObject.Graph;
  170. animancer.States.TryGet(transition, out var oldState);
  171. var targetState = animancer.Layers[0].Play(transition);
  172. OnPlayAnimation();
  173. if (oldState != null && oldState != targetState)
  174. oldState.Destroy();
  175. var warnings = OptionalWarning.UnsupportedEvents.DisableTemporarily();
  176. targetState.Events(this).OnEnd = () =>
  177. {
  178. if (_NextAnimation != null)
  179. {
  180. var fadeDuration = AnimancerEvent.GetFadeOutDuration(
  181. targetState,
  182. AnimancerGraph.DefaultFadeDuration);
  183. PlayOther(NextAnimationKey, _NextAnimation, 0, fadeDuration);
  184. OnPlayAnimation();
  185. }
  186. else
  187. {
  188. animancer.Layers[0].IncrementCommandCount();
  189. }
  190. };
  191. warnings.Enable();
  192. }
  193. /************************************************************************************************************************/
  194. public void OnPlayAnimation()
  195. {
  196. var animancer = PreviewObject.Graph;
  197. if (animancer == null ||
  198. animancer.States.Current == null)
  199. return;
  200. var state = animancer.States.Current;
  201. state.RecreatePlayableRecursive();
  202. var events = state.SharedEvents;
  203. if (events != null)
  204. {
  205. var warnings = OptionalWarning.UnsupportedEvents | OptionalWarning.ProOnly;
  206. warnings = warnings.DisableTemporarily();
  207. var normalizedEndTime = events.NormalizedEndTime;
  208. state.Events(this).NormalizedEndTime = normalizedEndTime;
  209. warnings.Enable();
  210. }
  211. }
  212. /************************************************************************************************************************/
  213. private void StepBackward()
  214. => StepTime(-TransitionPreviewSettings.FrameStep);
  215. private void StepForward()
  216. => StepTime(TransitionPreviewSettings.FrameStep);
  217. private void StepTime(float timeOffset)
  218. {
  219. if (!TryShowTransitionPaused(out _, out _, out var state))
  220. return;
  221. var length = state.Length;
  222. if (length != 0)
  223. timeOffset /= length;
  224. NormalizedTime += timeOffset;
  225. }
  226. /************************************************************************************************************************/
  227. [SerializeField]
  228. private float _NormalizedTime;
  229. public float NormalizedTime
  230. {
  231. get => _NormalizedTime;
  232. set
  233. {
  234. if (!value.IsFinite())
  235. return;
  236. _NormalizedTime = value;
  237. if (!TryShowTransitionPaused(out var animancer, out var transition, out var state))
  238. return;
  239. var length = state.Length;
  240. var speed = state.Speed;
  241. var time = value * length;
  242. var fadeDuration = transition.FadeDuration * Math.Abs(speed);
  243. var startTime = TimelineGUI.GetStartTime(transition.NormalizedStartTime, speed, length);
  244. var normalizedEndTime = state.NormalizedEndTime;
  245. var endTime = normalizedEndTime * length;
  246. var fadeOutEnd = TimelineGUI.GetFadeOutEnd(speed, endTime, length);
  247. if (speed < 0)
  248. {
  249. time = length - time;
  250. startTime = length - startTime;
  251. value = 1 - value;
  252. normalizedEndTime = 1 - normalizedEndTime;
  253. endTime = length - endTime;
  254. fadeOutEnd = length - fadeOutEnd;
  255. }
  256. if (time < startTime)// Previous animation.
  257. {
  258. if (_PreviousAnimation != null)
  259. {
  260. PlayOther(PreviousAnimationKey, _PreviousAnimation, value);
  261. value = 0;
  262. }
  263. }
  264. else if (time < startTime + fadeDuration)// Fade from previous animation to the target.
  265. {
  266. if (_PreviousAnimation != null)
  267. {
  268. var fromState = PlayOther(PreviousAnimationKey, _PreviousAnimation, value);
  269. state.IsPlaying = true;
  270. state.Weight = (time - startTime) / fadeDuration;
  271. fromState.Weight = 1 - state.Weight;
  272. }
  273. }
  274. else if (_NextAnimation != null)
  275. {
  276. if (value < normalizedEndTime)
  277. {
  278. // Just the main state.
  279. }
  280. else
  281. {
  282. var toState = PlayOther(NextAnimationKey, _NextAnimation, value - normalizedEndTime);
  283. if (time < fadeOutEnd)// Fade from the target transition to the next animation.
  284. {
  285. state.IsPlaying = true;
  286. toState.Weight = (time - endTime) / (fadeOutEnd - endTime);
  287. state.Weight = 1 - toState.Weight;
  288. }
  289. // Else just the next animation.
  290. }
  291. }
  292. if (speed < 0)
  293. value = 1 - value;
  294. state.MoveTime(state.Weight > 0 ? value : 0, true);
  295. animancer.Evaluate();
  296. RepaintEverything();
  297. }
  298. }
  299. /************************************************************************************************************************/
  300. private bool TryShowTransitionPaused(
  301. out AnimancerGraph animancer, out ITransitionDetailed transition, out AnimancerState state)
  302. {
  303. animancer = PreviewObject.Graph;
  304. transition = Transition;
  305. if (animancer == null || !transition.IsValid())
  306. {
  307. state = null;
  308. return false;
  309. }
  310. state = animancer.Layers[0].Play(transition, 0);
  311. OnPlayAnimation();
  312. animancer.PauseGraph();
  313. return true;
  314. }
  315. /************************************************************************************************************************/
  316. private AnimancerState PlayOther(
  317. object key,
  318. AnimationClip animation,
  319. float normalizedTime,
  320. float fadeDuration = 0)
  321. {
  322. var animancer = PreviewObject.Graph;
  323. var state = animancer.States.GetOrCreate(key, animation, true);
  324. state = animancer.Layers[0].Play(state, fadeDuration);
  325. OnPlayAnimation();
  326. normalizedTime *= state.Length;
  327. state.Time = normalizedTime.IsFinite() ? normalizedTime : 0;
  328. return state;
  329. }
  330. /************************************************************************************************************************/
  331. internal class WindowMatchStateTime : Updatable
  332. {
  333. /************************************************************************************************************************/
  334. public override void Update()
  335. {
  336. if (_Instance == null ||
  337. !AnimancerGraph.Current.IsGraphPlaying)
  338. return;
  339. var transition = Transition;
  340. if (transition == null)
  341. return;
  342. if (AnimancerGraph.Current.States.TryGet(transition, out var state))
  343. _Instance._Animations._NormalizedTime = state.NormalizedTime;
  344. }
  345. /************************************************************************************************************************/
  346. public override string ToString()
  347. => nameof(WindowMatchStateTime);
  348. /************************************************************************************************************************/
  349. }
  350. /************************************************************************************************************************/
  351. }
  352. }
  353. }
  354. #endif