TransitionAssetGenerator.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System.IO;
  4. using UnityEditor;
  5. using UnityEditor.Animations;
  6. using UnityEngine;
  7. using Object = UnityEngine.Object;
  8. namespace Animancer.Editor
  9. {
  10. /// <summary>[Editor-Only]
  11. /// Context menu functions for generating <see cref="TransitionAsset"/>s
  12. /// based on the contents of Animator Controllers.
  13. /// </summary>
  14. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/TransitionAssetGenerator
  15. public static class TransitionAssetGenerator
  16. {
  17. /************************************************************************************************************************/
  18. /// <summary>
  19. /// Creates a <see cref="TransitionAsset"/> from the <see cref="MenuCommand.context"/>.
  20. /// </summary>
  21. [MenuItem("CONTEXT/" + nameof(AnimatorState) + "/Generate Transition")]
  22. [MenuItem("CONTEXT/" + nameof(BlendTree) + "/Generate Transition")]
  23. [MenuItem("CONTEXT/" + nameof(AnimatorStateTransition) + "/Generate Transition")]
  24. [MenuItem("CONTEXT/" + nameof(AnimatorStateMachine) + "/Generate Transitions")]
  25. private static void GenerateTransition(MenuCommand command)
  26. {
  27. var context = command.context;
  28. if (context is AnimatorState state)
  29. {
  30. Selection.activeObject = GenerateTransition(state);
  31. }
  32. else if (context is BlendTree blendTree)
  33. {
  34. Selection.activeObject = GenerateTransition(null, blendTree);
  35. }
  36. else if (context is AnimatorStateTransition transition)
  37. {
  38. Selection.activeObject = GenerateTransition(transition);
  39. }
  40. else if (context is AnimatorStateMachine stateMachine)// Layer or Sub-State Machine.
  41. {
  42. Selection.activeObject = GenerateTransitions(stateMachine);
  43. }
  44. }
  45. /************************************************************************************************************************/
  46. /// <summary>Creates an <see cref="TransitionAssetBase"/> from the `state`.</summary>
  47. public static Object GenerateTransition(AnimatorState state)
  48. => GenerateTransition(state, state.motion);
  49. /************************************************************************************************************************/
  50. /// <summary>Creates an <see cref="TransitionAssetBase"/> from the `motion`.</summary>
  51. public static Object GenerateTransition(Object originalAsset, Motion motion)
  52. {
  53. if (motion is BlendTree blendTree)
  54. {
  55. return GenerateTransition(originalAsset as AnimatorState, blendTree);
  56. }
  57. else if (motion is AnimationClip || motion == null)
  58. {
  59. var asset = ScriptableObject.CreateInstance<TransitionAsset>();
  60. asset.Transition = new ClipTransition
  61. {
  62. Clip = motion as AnimationClip,
  63. };
  64. GetDetailsFromState(
  65. originalAsset as AnimatorState,
  66. asset.Transition as ITransitionDetailed);
  67. SaveTransition(originalAsset, asset);
  68. return asset;
  69. }
  70. else
  71. {
  72. Debug.LogError($"Unsupported {nameof(Motion)} Type: {motion.GetType()}");
  73. return null;
  74. }
  75. }
  76. /************************************************************************************************************************/
  77. /// <summary>Initializes the `transition` based on the `state`.</summary>
  78. public static void GetDetailsFromState(AnimatorState state, ITransitionDetailed transition)
  79. {
  80. if (state == null ||
  81. transition == null)
  82. return;
  83. transition.Speed = state.speed;
  84. var isForwards = state.speed >= 0;
  85. var defaultEndTime = AnimancerEvent.Sequence.GetDefaultNormalizedEndTime(state.speed);
  86. var endTime = defaultEndTime;
  87. var exitTransitions = state.transitions;
  88. for (int i = 0; i < exitTransitions.Length; i++)
  89. {
  90. var exitTransition = exitTransitions[i];
  91. if (exitTransition.hasExitTime)
  92. {
  93. if (isForwards)
  94. {
  95. if (endTime > exitTransition.exitTime)
  96. endTime = exitTransition.exitTime;
  97. }
  98. else
  99. {
  100. if (endTime < exitTransition.exitTime)
  101. endTime = exitTransition.exitTime;
  102. }
  103. }
  104. }
  105. if (endTime != defaultEndTime &&
  106. AnimancerUtilities.TryGetWrappedObject(transition, out IHasEvents events))
  107. {
  108. events.SerializedEvents ??= new();
  109. events.SerializedEvents.SetNormalizedEndTime(endTime);
  110. }
  111. }
  112. /************************************************************************************************************************/
  113. /// <summary>Creates an <see cref="TransitionAssetBase"/> from the `blendTree`.</summary>
  114. public static Object GenerateTransition(AnimatorState state, BlendTree blendTree)
  115. {
  116. var asset = CreateTransition(blendTree);
  117. if (asset == null)
  118. return null;
  119. if (state != null)
  120. asset.name = state.name;
  121. AnimancerUtilities.TryGetWrappedObject(asset, out ITransitionDetailed detailed);
  122. GetDetailsFromState(state, detailed);
  123. SaveTransition(blendTree, asset);
  124. return asset;
  125. }
  126. /************************************************************************************************************************/
  127. /// <summary>Creates an <see cref="TransitionAssetBase"/> from the `transition`.</summary>
  128. public static Object GenerateTransition(AnimatorStateTransition transition)
  129. {
  130. Object animancerTransition = null;
  131. if (transition.destinationStateMachine != null)
  132. animancerTransition = GenerateTransitions(transition.destinationStateMachine);
  133. if (transition.destinationState != null)
  134. animancerTransition = GenerateTransition(transition.destinationState);
  135. return animancerTransition;
  136. }
  137. /************************************************************************************************************************/
  138. /// <summary>Creates <see cref="TransitionAssetBase"/>s from all states in the `stateMachine`.</summary>
  139. public static Object GenerateTransitions(AnimatorStateMachine stateMachine)
  140. {
  141. Object transition = null;
  142. foreach (var child in stateMachine.stateMachines)
  143. transition = GenerateTransitions(child.stateMachine);
  144. foreach (var child in stateMachine.states)
  145. transition = GenerateTransition(child.state);
  146. return transition;
  147. }
  148. /************************************************************************************************************************/
  149. /// <summary>Creates an <see cref="TransitionAssetBase"/> from the `blendTree`.</summary>
  150. public static Object CreateTransition(BlendTree blendTree)
  151. {
  152. switch (blendTree.blendType)
  153. {
  154. case BlendTreeType.Simple1D:
  155. var linearAsset = ScriptableObject.CreateInstance<TransitionAsset>();
  156. linearAsset.Transition = InitializeChildren1D(blendTree);
  157. return linearAsset;
  158. case BlendTreeType.SimpleDirectional2D:
  159. case BlendTreeType.FreeformDirectional2D:
  160. var directionalAsset = ScriptableObject.CreateInstance<TransitionAsset>();
  161. directionalAsset.Transition = new MixerTransition2D
  162. {
  163. Type = MixerTransition2D.MixerType.Directional
  164. };
  165. directionalAsset.Transition = InitializeChildren2D(blendTree);
  166. return directionalAsset;
  167. case BlendTreeType.FreeformCartesian2D:
  168. var cartesianAsset = ScriptableObject.CreateInstance<TransitionAsset>();
  169. cartesianAsset.Transition = new MixerTransition2D
  170. {
  171. Type = MixerTransition2D.MixerType.Cartesian
  172. };
  173. cartesianAsset.Transition = InitializeChildren2D(blendTree);
  174. return cartesianAsset;
  175. case BlendTreeType.Direct:
  176. var manualAsset = ScriptableObject.CreateInstance<TransitionAsset>();
  177. InitializeChildren<ManualMixerTransition, ManualMixerState>(out var transition, blendTree);
  178. manualAsset.Transition = transition;
  179. return manualAsset;
  180. default:
  181. Debug.LogError($"Unsupported {nameof(BlendTreeType)}: {blendTree.blendType}");
  182. return null;
  183. }
  184. }
  185. /************************************************************************************************************************/
  186. /// <summary>Initializes the `transition` based on the <see cref="BlendTree.children"/>.</summary>
  187. public static LinearMixerTransition InitializeChildren1D(BlendTree blendTree)
  188. {
  189. var children = InitializeChildren<LinearMixerTransition, LinearMixerState>(
  190. out var transition,
  191. blendTree);
  192. transition.Thresholds = new float[children.Length];
  193. for (int i = 0; i < children.Length; i++)
  194. transition.Thresholds[i] = children[i].threshold;
  195. return transition;
  196. }
  197. /// <summary>Initializes the `transition` based on the <see cref="BlendTree.children"/>.</summary>
  198. public static MixerTransition2D InitializeChildren2D(BlendTree blendTree)
  199. {
  200. var children = InitializeChildren<MixerTransition2D, Vector2MixerState>(
  201. out var transition,
  202. blendTree);
  203. transition.Thresholds = new Vector2[children.Length];
  204. for (int i = 0; i < children.Length; i++)
  205. transition.Thresholds[i] = children[i].position;
  206. return transition;
  207. }
  208. /// <summary>Initializes the `transition` based on the <see cref="BlendTree.children"/>.</summary>
  209. public static ChildMotion[] InitializeChildren<TTransition, TState>(
  210. out TTransition transition,
  211. BlendTree blendTree)
  212. where TTransition : ManualMixerTransition<TState>, new()
  213. where TState : ManualMixerState
  214. {
  215. transition = new();
  216. var children = blendTree.children;
  217. transition.Animations = new Object[children.Length];
  218. float[] speeds = new float[children.Length];
  219. var hasCustomSpeeds = false;
  220. for (int i = 0; i < children.Length; i++)
  221. {
  222. var child = children[i];
  223. transition.Animations[i] = child.motion is AnimationClip
  224. ? child.motion
  225. : GenerateTransition(blendTree, child.motion);
  226. if ((speeds[i] = child.timeScale) != 1)
  227. hasCustomSpeeds = true;
  228. }
  229. if (hasCustomSpeeds)
  230. transition.Speeds = speeds;
  231. return children;
  232. }
  233. /************************************************************************************************************************/
  234. /// <summary>Saves the `transition` in the same folder as the `originalAsset`.</summary>
  235. public static void SaveTransition(Object originalAsset, Object transition)
  236. {
  237. if (string.IsNullOrEmpty(transition.name))
  238. transition.name = originalAsset.name;
  239. var path = AssetDatabase.GetAssetPath(originalAsset);
  240. path = Path.GetDirectoryName(path);
  241. path = Path.Combine(path, transition.name + ".asset");
  242. path = AssetDatabase.GenerateUniqueAssetPath(path);
  243. AssetDatabase.CreateAsset(transition, path);
  244. Debug.Log($"Saved {path}", transition);
  245. }
  246. /************************************************************************************************************************/
  247. }
  248. }
  249. #endif