123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #if UNITY_EDITOR && UNITY_IMGUI
- #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
- using Animancer.Units;
- using System;
- using System.Collections.Generic;
- using UnityEditor;
- using UnityEngine;
- namespace Animancer.Editor.Previews
- {
- /// <summary>Persistent settings for the <see cref="TransitionPreviewWindow"/>.</summary>
- /// <remarks>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
- /// Previews</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewSettings
- [Serializable, InternalSerializableType]
- public class TransitionPreviewSettings : AnimancerSettingsGroup
- {
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public override string DisplayName
- => "Transition Previews";
- /// <inheritdoc/>
- public override int Index
- => 3;
- /************************************************************************************************************************/
- private static TransitionPreviewSettings Instance
- => AnimancerSettingsGroup<TransitionPreviewSettings>.Instance;
- /************************************************************************************************************************/
- /// <summary>Draws the Inspector GUI for these settings.</summary>
- public static void DoInspectorGUI()
- {
- AnimancerSettings.SerializedObject.Update();
- EditorGUI.indentLevel++;
- DoMiscGUI();
- DoEnvironmentGUI();
- DoModelsGUI();
- DoHierarchyGUI();
- EditorGUI.indentLevel--;
- AnimancerSettings.SerializedObject.ApplyModifiedProperties();
- }
- /************************************************************************************************************************/
- #region Misc
- /************************************************************************************************************************/
- private static void DoMiscGUI()
- {
- Instance.DoPropertyField(nameof(_AutoClose));
- }
- /************************************************************************************************************************/
- [SerializeField]
- [Tooltip("Should this window automatically close if the target object is destroyed?")]
- private bool _AutoClose = true;
- /// <summary>Should this window automatically close if the target object is destroyed?</summary>
- public static bool AutoClose
- => Instance._AutoClose;
- /************************************************************************************************************************/
- [SerializeField]
- [Tooltip("Should the scene lighting be enabled?")]
- private bool _SceneLighting = false;
- /// <summary>Should the scene lighting be enabled?</summary>
- public static bool SceneLighting
- {
- get => Instance._SceneLighting;
- set
- {
- if (SceneLighting == value)
- return;
- var property = Instance.GetSerializedProperty(nameof(_SceneLighting));
- property.boolValue = value;
- property.serializedObject.ApplyModifiedProperties();
- }
- }
- /************************************************************************************************************************/
- [SerializeField]
- [Tooltip("Should the skybox be visible?")]
- private bool _ShowSkybox = false;
- /// <summary>Should the skybox be visible?</summary>
- public static bool ShowSkybox
- {
- get => Instance._ShowSkybox;
- set
- {
- if (ShowSkybox == value)
- return;
- var property = Instance.GetSerializedProperty(nameof(_ShowSkybox));
- property.boolValue = value;
- property.serializedObject.ApplyModifiedProperties();
- }
- }
- /************************************************************************************************************************/
- [SerializeField]
- [Seconds(Rule = Validate.Value.IsNotNegative)]
- [DefaultValue(0.02f)]
- [Tooltip("The amount of time that will be added by a single frame step")]
- private float _FrameStep = 0.02f;
- /// <summary>The amount of time that will be added by a single frame step (in seconds).</summary>
- public static float FrameStep
- => AnimancerSettingsGroup<TransitionPreviewSettings>.Instance._FrameStep;
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Environment
- /************************************************************************************************************************/
- [SerializeField]
- [Tooltip("If set, the default preview scene lighting will be replaced with this prefab.")]
- private GameObject _SceneEnvironment;
- /// <summary>If set, the default preview scene lighting will be replaced with this prefab.</summary>
- public static GameObject SceneEnvironment
- => Instance._SceneEnvironment;
- /************************************************************************************************************************/
- private static void DoEnvironmentGUI()
- {
- EditorGUI.BeginChangeCheck();
- var property = Instance.DoPropertyField(nameof(_SceneEnvironment));
- if (EditorGUI.EndChangeCheck())
- {
- property.serializedObject.ApplyModifiedProperties();
- TransitionPreviewWindow.InstanceScene.OnEnvironmentPrefabChanged();
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Models
- /************************************************************************************************************************/
- private static void DoModelsGUI()
- {
- var property = ModelsProperty;
- var count = property.arraySize = EditorGUILayout.DelayedIntField(nameof(Models), property.arraySize);
- // Drag and Drop to add model.
- var area = GUILayoutUtility.GetLastRect();
- HandleModelDragAndDrop(area);
- if (count == 0)
- return;
- property.isExpanded = EditorGUI.Foldout(area, property.isExpanded, GUIContent.none, true);
- if (!property.isExpanded)
- return;
- EditorGUI.indentLevel++;
- var model = property.GetArrayElementAtIndex(0);
- for (int i = 0; i < count; i++)
- {
- GUILayout.BeginHorizontal();
- EditorGUILayout.ObjectField(model);
- if (GUILayout.Button(AnimancerIcons.ClearIcon("Remove model"), AnimancerGUI.NoPaddingButtonStyle))
- {
- Serialization.RemoveArrayElement(property, i);
- property.serializedObject.ApplyModifiedProperties();
- AnimancerGUI.Deselect();
- GUIUtility.ExitGUI();
- return;
- }
- GUILayout.Space(EditorStyles.objectField.margin.right);
- GUILayout.EndHorizontal();
- model.Next(false);
- }
- EditorGUI.indentLevel--;
- }
- /************************************************************************************************************************/
- private static DragAndDropHandler<GameObject> _ModelDropHandler;
- private static void HandleModelDragAndDrop(Rect area)
- {
- _ModelDropHandler ??= (gameObject, isDrop) =>
- {
- if (!EditorUtility.IsPersistent(gameObject) ||
- Models.Contains(gameObject) ||
- gameObject.GetComponentInChildren<Animator>() == null)
- return false;
- if (isDrop)
- {
- var modelsProperty = ModelsProperty;
- modelsProperty.serializedObject.Update();
- var i = modelsProperty.arraySize;
- modelsProperty.arraySize = i + 1;
- modelsProperty.GetArrayElementAtIndex(i).objectReferenceValue = gameObject;
- modelsProperty.serializedObject.ApplyModifiedProperties();
- }
- return true;
- };
- _ModelDropHandler.Handle(area);
- }
- /************************************************************************************************************************/
- [SerializeField]
- private List<GameObject> _Models;
- /// <summary>The models previously used in the <see cref="TransitionPreviewWindow"/>.</summary>
- /// <remarks>Accessing this property removes missing and duplicate models from the list.</remarks>
- public static List<GameObject> Models
- {
- get
- {
- var instance = Instance;
- AnimancerEditorUtilities.RemoveMissingAndDuplicates(ref instance._Models);
- return instance._Models;
- }
- }
- private static SerializedProperty ModelsProperty
- => Instance.GetSerializedProperty(nameof(_Models));
- /************************************************************************************************************************/
- /// <summary>Adds a `model` to the list of preview models.</summary>
- public static void AddModel(GameObject model)
- {
- if (model == GetOrCreateDefaultHumanoid(null) ||
- model == GetOrCreateDefaultSprite(null))
- return;
- if (EditorUtility.IsPersistent(model))
- {
- AddModel(Models, model);
- AnimancerSettings.SetDirty();
- }
- else
- {
- AddModel(TemporarySettings.PreviewModels, model);
- }
- }
- private static void AddModel(List<GameObject> models, GameObject model)
- {
- // Remove if it was already there so that when we add it, it will be moved to the end.
- var index = models.LastIndexOf(model);// Search backwards because it's more likely to be near the end.
- if (index >= 0 && index < models.Count)
- models.RemoveAt(index);
- models.Add(model);
- }
- /************************************************************************************************************************/
- private static GameObject _DefaultHumanoid;
- /// <summary>
- /// Returns the default preview object for Humanoid animations
- /// if it has already been loaded.
- /// </summary>
- public static GameObject GetDefaultHumanoidIfAlreadyLoaded()
- => _DefaultHumanoid;
- /// <summary>Returns the default preview object for Humanoid animations.</summary>
- /// <remarks>A `parent` is only required if Animancer's or Unity's default objects fail to load.</remarks>
- public static GameObject GetOrCreateDefaultHumanoid(Transform parent)
- {
- if (_DefaultHumanoid != null)
- return _DefaultHumanoid;
- // Try to load Animancer Humanoid.
- var path = AssetDatabase.GUIDToAssetPath("f976ca0fb1329b44a8bc3dcca706751a");
- if (!string.IsNullOrEmpty(path))
- {
- _DefaultHumanoid = AssetDatabase.LoadAssetAtPath<GameObject>(path);
- if (_DefaultHumanoid != null)
- return _DefaultHumanoid;
- }
- // Otherwise try to load Unity's DefaultAvatar.
- _DefaultHumanoid = EditorGUIUtility.Load("Avatar/DefaultAvatar.fbx") as GameObject;
- if (_DefaultHumanoid != null)
- return _DefaultHumanoid;
- if (parent == null)
- return null;
- // Otherwise just create an empty object.
- _DefaultHumanoid = EditorUtility.CreateGameObjectWithHideFlags(
- "DummyAvatar",
- HideFlags.HideAndDontSave,
- typeof(Animator));
- _DefaultHumanoid.transform.parent = parent;
- return _DefaultHumanoid;
- }
- /************************************************************************************************************************/
- private static GameObject _DefaultSprite;
- /// <summary>
- /// Returns the default preview object for <see cref="Sprite"/> animations
- /// if it has already been created.
- /// </summary>
- public static GameObject GetDefaultSpriteIfAlreadyCreated()
- => _DefaultSprite;
- /// <summary>Returns the default preview object for <see cref="Sprite"/> animations.</summary>
- /// <remarks>A `parent` is required to create the object.</remarks>
- public static GameObject GetOrCreateDefaultSprite(Transform parent)
- {
- if (_DefaultSprite == null && parent != null)
- {
- _DefaultSprite = EditorUtility.CreateGameObjectWithHideFlags(
- "DefaultSprite",
- HideFlags.HideAndDontSave,
- typeof(Animator),
- typeof(SpriteRenderer));
- _DefaultSprite.transform.parent = parent;
- }
- return _DefaultSprite;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Tries to choose the most appropriate model to use
- /// based on the properties animated by the `animationClipSource`.
- /// </summary>
- public static Transform TrySelectBestModel(
- object animationClipSource,
- Transform parent)
- {
- if (animationClipSource.IsNullOrDestroyed())
- return null;
- using (SetPool<AnimationClip>.Instance.Acquire(out var clips))
- {
- clips.GatherFromSource(animationClipSource);
- if (clips.Count == 0)
- return null;
- var model = TrySelectBestModel(clips, TemporarySettings.PreviewModels);
- if (model != null)
- return model;
- model = TrySelectBestModel(clips, Models);
- if (model != null)
- return model;
- foreach (var clip in clips)
- {
- var type = AnimationBindings.GetAnimationType(clip);
- switch (type)
- {
- case AnimationType.Humanoid:
- return GetOrCreateDefaultHumanoid(parent).transform;
- case AnimationType.Sprite:
- return GetOrCreateDefaultSprite(parent).transform;
- }
- }
- return null;
- }
- }
- /************************************************************************************************************************/
- private static Transform TrySelectBestModel(HashSet<AnimationClip> clips, List<GameObject> models)
- {
- var animatableBindings = new HashSet<EditorCurveBinding>[models.Count];
- for (int i = 0; i < models.Count; i++)
- {
- animatableBindings[i] = AnimationBindings.GetBindings(models[i]).ObjectBindings;
- }
- var bestMatchIndex = -1;
- var bestMatchCount = 0;
- foreach (var clip in clips)
- {
- var clipBindings = AnimationBindings.GetBindings(clip);
- for (int iModel = animatableBindings.Length - 1; iModel >= 0; iModel--)
- {
- var modelBindings = animatableBindings[iModel];
- var matches = 0;
- for (int iBinding = 0; iBinding < clipBindings.Length; iBinding++)
- {
- if (modelBindings.Contains(clipBindings[iBinding]))
- matches++;
- }
- if (bestMatchCount < matches && matches > clipBindings.Length / 2)
- {
- bestMatchCount = matches;
- bestMatchIndex = iModel;
- // If it matches all bindings, use it.
- if (bestMatchCount == clipBindings.Length)
- goto FoundBestMatch;
- }
- }
- }
- FoundBestMatch:
- if (bestMatchIndex >= 0)
- return models[bestMatchIndex].transform;
- else
- return null;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Scene Hierarchy
- /************************************************************************************************************************/
- private static void DoHierarchyGUI()
- {
- GUILayout.BeginVertical(GUI.skin.box);
- GUILayout.Label("Preview Scene Hierarchy");
- DoHierarchyGUI(TransitionPreviewWindow.InstanceScene.PreviewSceneRoot);
- GUILayout.EndVertical();
- }
- /************************************************************************************************************************/
- private static GUIStyle _HierarchyButtonStyle;
- private static void DoHierarchyGUI(Transform root)
- {
- var area = AnimancerGUI.LayoutSingleLineRect();
- _HierarchyButtonStyle ??= new(EditorStyles.miniButton)
- {
- alignment = TextAnchor.MiddleLeft,
- };
- if (GUI.Button(EditorGUI.IndentedRect(area), root.name, _HierarchyButtonStyle))
- {
- Selection.activeTransform = root;
- GUIUtility.ExitGUI();
- }
- var childCount = root.childCount;
- if (childCount == 0)
- return;
- var expandedHierarchy = TransitionPreviewWindow.InstanceScene.ExpandedHierarchy;
- var index = expandedHierarchy != null ? expandedHierarchy.IndexOf(root) : -1;
- var isExpanded = EditorGUI.Foldout(area, index >= 0, GUIContent.none);
- if (isExpanded)
- {
- if (index < 0)
- expandedHierarchy.Add(root);
- EditorGUI.indentLevel++;
- for (int i = 0; i < childCount; i++)
- DoHierarchyGUI(root.GetChild(i));
- EditorGUI.indentLevel--;
- }
- else if (index >= 0)
- {
- expandedHierarchy.RemoveAt(index);
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
- #endif
|