TransitionPreviewWindow.Settings.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
  4. using System;
  5. using System.Collections.Generic;
  6. using UnityEditor;
  7. using UnityEngine;
  8. namespace Animancer.Editor
  9. {
  10. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/TransitionPreviewWindow
  11. partial class TransitionPreviewWindow
  12. {
  13. /// <summary>Persistent settings for the <see cref="TransitionPreviewWindow"/>.</summary>
  14. /// <remarks>
  15. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">Previews</see>
  16. /// </remarks>
  17. [Serializable]
  18. internal class Settings : AnimancerSettings.Group
  19. {
  20. /************************************************************************************************************************/
  21. private static Settings Instance => AnimancerSettings.TransitionPreviewWindow;
  22. /************************************************************************************************************************/
  23. public static void DoInspectorGUI()
  24. {
  25. AnimancerSettings.SerializedObject.Update();
  26. EditorGUI.indentLevel++;
  27. DoMiscGUI();
  28. DoEnvironmentGUI();
  29. DoModelsGUI();
  30. DoHierarchyGUI();
  31. EditorGUI.indentLevel--;
  32. AnimancerSettings.SerializedObject.ApplyModifiedProperties();
  33. }
  34. /************************************************************************************************************************/
  35. #region Misc
  36. /************************************************************************************************************************/
  37. private static void DoMiscGUI()
  38. {
  39. Instance.DoPropertyField(nameof(_AutoClose));
  40. }
  41. /************************************************************************************************************************/
  42. [SerializeField]
  43. [Tooltip("Should this window automatically close if the target object is destroyed?")]
  44. private bool _AutoClose = true;
  45. public static bool AutoClose => Instance._AutoClose;
  46. /************************************************************************************************************************/
  47. [SerializeField]
  48. [Tooltip("Should the scene lighting be enabled?")]
  49. private bool _SceneLighting = false;
  50. public static bool SceneLighting
  51. {
  52. get => Instance._SceneLighting;
  53. set
  54. {
  55. if (SceneLighting == value)
  56. return;
  57. var property = Instance.GetSerializedProperty(nameof(_SceneLighting));
  58. property.boolValue = value;
  59. AnimancerSettings.SerializedObject.ApplyModifiedProperties();
  60. }
  61. }
  62. /************************************************************************************************************************/
  63. [SerializeField]
  64. [Tooltip("Should the skybox be visible?")]
  65. private bool _ShowSkybox = false;
  66. public static bool ShowSkybox
  67. {
  68. get => Instance._ShowSkybox;
  69. set
  70. {
  71. if (ShowSkybox == value)
  72. return;
  73. var property = Instance.GetSerializedProperty(nameof(_ShowSkybox));
  74. property.boolValue = value;
  75. AnimancerSettings.SerializedObject.ApplyModifiedProperties();
  76. }
  77. }
  78. /************************************************************************************************************************/
  79. #endregion
  80. /************************************************************************************************************************/
  81. #region Environment
  82. /************************************************************************************************************************/
  83. [SerializeField]
  84. [Tooltip("If set, the default preview scene lighting will be replaced with this prefab.")]
  85. private GameObject _SceneEnvironment;
  86. public static GameObject SceneEnvironment => Instance._SceneEnvironment;
  87. /************************************************************************************************************************/
  88. private static void DoEnvironmentGUI()
  89. {
  90. EditorGUI.BeginChangeCheck();
  91. Instance.DoPropertyField(nameof(_SceneEnvironment));
  92. if (EditorGUI.EndChangeCheck())
  93. {
  94. AnimancerSettings.SerializedObject.ApplyModifiedProperties();
  95. InstanceScene.OnEnvironmentPrefabChanged();
  96. }
  97. }
  98. /************************************************************************************************************************/
  99. #endregion
  100. /************************************************************************************************************************/
  101. #region Models
  102. /************************************************************************************************************************/
  103. private static void DoModelsGUI()
  104. {
  105. var property = ModelsProperty;
  106. var count = property.arraySize = EditorGUILayout.DelayedIntField(nameof(Models), property.arraySize);
  107. // Drag and Drop to add model.
  108. var area = GUILayoutUtility.GetLastRect();
  109. AnimancerGUI.HandleDragAndDrop<GameObject>(area,
  110. (gameObject) =>
  111. {
  112. return
  113. EditorUtility.IsPersistent(gameObject) &&
  114. !Models.Contains(gameObject) &&
  115. gameObject.GetComponentInChildren<Animator>() != null;
  116. },
  117. (gameObject) =>
  118. {
  119. var modelsProperty = ModelsProperty;// Avoid Closure.
  120. modelsProperty.serializedObject.Update();
  121. var i = modelsProperty.arraySize;
  122. modelsProperty.arraySize = i + 1;
  123. modelsProperty.GetArrayElementAtIndex(i).objectReferenceValue = gameObject;
  124. modelsProperty.serializedObject.ApplyModifiedProperties();
  125. });
  126. if (count == 0)
  127. return;
  128. property.isExpanded = EditorGUI.Foldout(area, property.isExpanded, GUIContent.none, true);
  129. if (!property.isExpanded)
  130. return;
  131. EditorGUI.indentLevel++;
  132. var model = property.GetArrayElementAtIndex(0);
  133. for (int i = 0; i < count; i++)
  134. {
  135. GUILayout.BeginHorizontal();
  136. EditorGUILayout.ObjectField(model);
  137. if (GUILayout.Button("x", AnimancerGUI.MiniButton))
  138. {
  139. Serialization.RemoveArrayElement(property, i);
  140. property.serializedObject.ApplyModifiedProperties();
  141. AnimancerGUI.Deselect();
  142. GUIUtility.ExitGUI();
  143. return;
  144. }
  145. GUILayout.Space(EditorStyles.objectField.margin.right);
  146. GUILayout.EndHorizontal();
  147. model.Next(false);
  148. }
  149. EditorGUI.indentLevel--;
  150. }
  151. /************************************************************************************************************************/
  152. [SerializeField]
  153. private List<GameObject> _Models;
  154. /// <summary>The models previously used in the <see cref="TransitionPreviewWindow"/>.</summary>
  155. /// <remarks>Accessing this property removes missing and duplicate models from the list.</remarks>
  156. public static List<GameObject> Models
  157. {
  158. get
  159. {
  160. var instance = Instance;
  161. AnimancerEditorUtilities.RemoveMissingAndDuplicates(ref instance._Models);
  162. return instance._Models;
  163. }
  164. }
  165. private static SerializedProperty ModelsProperty => Instance.GetSerializedProperty(nameof(_Models));
  166. /************************************************************************************************************************/
  167. public static void AddModel(GameObject model)
  168. {
  169. if (model == DefaultHumanoid ||
  170. model == DefaultSprite)
  171. return;
  172. if (EditorUtility.IsPersistent(model))
  173. {
  174. AddModel(Models, model);
  175. AnimancerSettings.SetDirty();
  176. }
  177. else
  178. {
  179. AddModel(TemporarySettings.PreviewModels, model);
  180. }
  181. }
  182. private static void AddModel(List<GameObject> models, GameObject model)
  183. {
  184. // Remove if it was already there so that when we add it, it will be moved to the end.
  185. var index = models.LastIndexOf(model);// Search backwards because it's more likely to be near the end.
  186. if (index >= 0 && index < models.Count)
  187. models.RemoveAt(index);
  188. models.Add(model);
  189. }
  190. /************************************************************************************************************************/
  191. private static GameObject _DefaultHumanoid;
  192. public static GameObject DefaultHumanoid
  193. {
  194. get
  195. {
  196. if (_DefaultHumanoid == null)
  197. {
  198. // Try to load Animancer's DefaultHumanoid.
  199. var path = AssetDatabase.GUIDToAssetPath("c9f3e1113795a054c939de9883b31fed");
  200. if (!string.IsNullOrEmpty(path))
  201. {
  202. _DefaultHumanoid = AssetDatabase.LoadAssetAtPath<GameObject>(path);
  203. if (_DefaultHumanoid != null)
  204. return _DefaultHumanoid;
  205. }
  206. // Otherwise try to load Unity's DefaultAvatar.
  207. _DefaultHumanoid = EditorGUIUtility.Load("Avatar/DefaultAvatar.fbx") as GameObject;
  208. if (_DefaultHumanoid == null)
  209. {
  210. // Otherwise just create an empty object.
  211. _DefaultHumanoid = EditorUtility.CreateGameObjectWithHideFlags(
  212. "DefaultAvatar", HideFlags.HideAndDontSave, typeof(Animator));
  213. _DefaultHumanoid.transform.parent = _Instance._Scene.PreviewSceneRoot;
  214. }
  215. }
  216. return _DefaultHumanoid;
  217. }
  218. }
  219. public static bool IsDefaultHumanoid(GameObject gameObject) => gameObject == DefaultHumanoid;
  220. /************************************************************************************************************************/
  221. private static GameObject _DefaultSprite;
  222. public static GameObject DefaultSprite
  223. {
  224. get
  225. {
  226. if (_DefaultSprite == null)
  227. {
  228. _DefaultSprite = EditorUtility.CreateGameObjectWithHideFlags(
  229. "DefaultSprite", HideFlags.HideAndDontSave, typeof(Animator), typeof(SpriteRenderer));
  230. _DefaultSprite.transform.parent = _Instance._Scene.PreviewSceneRoot;
  231. }
  232. return _DefaultSprite;
  233. }
  234. }
  235. public static bool IsDefaultSprite(GameObject gameObject) => gameObject == DefaultSprite;
  236. /************************************************************************************************************************/
  237. /// <summary>
  238. /// Tries to choose the most appropriate model to use based on the properties animated by the target
  239. /// <see cref="Transition"/>.
  240. /// </summary>
  241. public static Transform TrySelectBestModel()
  242. {
  243. var transition = Transition;
  244. if (transition == null)
  245. return null;
  246. using (ObjectPool.Disposable.AcquireSet<AnimationClip>(out var clips))
  247. {
  248. clips.GatherFromSource(transition);
  249. if (clips.Count == 0)
  250. return null;
  251. var model = TrySelectBestModel(clips, TemporarySettings.PreviewModels);
  252. if (model != null)
  253. return model;
  254. model = TrySelectBestModel(clips, Models);
  255. if (model != null)
  256. return model;
  257. foreach (var clip in clips)
  258. {
  259. var type = AnimationBindings.GetAnimationType(clip);
  260. switch (type)
  261. {
  262. case AnimationType.Humanoid:
  263. return DefaultHumanoid.transform;
  264. case AnimationType.Sprite:
  265. return DefaultSprite.transform;
  266. }
  267. }
  268. return null;
  269. }
  270. }
  271. /************************************************************************************************************************/
  272. private static Transform TrySelectBestModel(HashSet<AnimationClip> clips, List<GameObject> models)
  273. {
  274. var animatableBindings = new HashSet<EditorCurveBinding>[models.Count];
  275. for (int i = 0; i < models.Count; i++)
  276. {
  277. animatableBindings[i] = AnimationBindings.GetBindings(models[i]).ObjectBindings;
  278. }
  279. var bestMatchIndex = -1;
  280. var bestMatchCount = 0;
  281. foreach (var clip in clips)
  282. {
  283. var clipBindings = AnimationBindings.GetBindings(clip);
  284. for (int iModel = animatableBindings.Length - 1; iModel >= 0; iModel--)
  285. {
  286. var modelBindings = animatableBindings[iModel];
  287. var matches = 0;
  288. for (int iBinding = 0; iBinding < clipBindings.Length; iBinding++)
  289. {
  290. if (modelBindings.Contains(clipBindings[iBinding]))
  291. matches++;
  292. }
  293. if (bestMatchCount < matches && matches > clipBindings.Length / 2)
  294. {
  295. bestMatchCount = matches;
  296. bestMatchIndex = iModel;
  297. // If it matches all bindings, use it.
  298. if (bestMatchCount == clipBindings.Length)
  299. goto FoundBestMatch;
  300. }
  301. }
  302. }
  303. FoundBestMatch:
  304. if (bestMatchIndex >= 0)
  305. return models[bestMatchIndex].transform;
  306. else
  307. return null;
  308. }
  309. /************************************************************************************************************************/
  310. #endregion
  311. /************************************************************************************************************************/
  312. #region Scene Hierarchy
  313. /************************************************************************************************************************/
  314. private static void DoHierarchyGUI()
  315. {
  316. GUILayout.BeginVertical(GUI.skin.box);
  317. GUILayout.Label("Preview Scene Hierarchy");
  318. DoHierarchyGUI(_Instance._Scene.PreviewSceneRoot);
  319. GUILayout.EndVertical();
  320. }
  321. /************************************************************************************************************************/
  322. private static GUIStyle _HierarchyButtonStyle;
  323. private static void DoHierarchyGUI(Transform root)
  324. {
  325. var area = AnimancerGUI.LayoutSingleLineRect();
  326. if (_HierarchyButtonStyle == null)
  327. _HierarchyButtonStyle = new GUIStyle(EditorStyles.miniButton)
  328. {
  329. alignment = TextAnchor.MiddleLeft,
  330. };
  331. if (GUI.Button(EditorGUI.IndentedRect(area), root.name, _HierarchyButtonStyle))
  332. {
  333. Selection.activeTransform = root;
  334. GUIUtility.ExitGUI();
  335. }
  336. var childCount = root.childCount;
  337. if (childCount == 0)
  338. return;
  339. var expandedHierarchy = _Instance._Scene.ExpandedHierarchy;
  340. var index = expandedHierarchy != null ? expandedHierarchy.IndexOf(root) : -1;
  341. var isExpanded = EditorGUI.Foldout(area, index >= 0, GUIContent.none);
  342. if (isExpanded)
  343. {
  344. if (index < 0)
  345. expandedHierarchy.Add(root);
  346. EditorGUI.indentLevel++;
  347. for (int i = 0; i < childCount; i++)
  348. DoHierarchyGUI(root.GetChild(i));
  349. EditorGUI.indentLevel--;
  350. }
  351. else if (index >= 0)
  352. {
  353. expandedHierarchy.RemoveAt(index);
  354. }
  355. }
  356. /************************************************************************************************************************/
  357. #endregion
  358. /************************************************************************************************************************/
  359. }
  360. }
  361. }
  362. #endif