123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
- #if UNITY_EDITOR
- using System;
- using System.Collections.Generic;
- using UnityEditor;
- using UnityEngine;
- using Object = UnityEngine.Object;
- namespace Animancer.Editor
- {
- /// https://kybernetik.com.au/animancer/api/Animancer.Editor/TransitionPreviewWindow
- partial class TransitionPreviewWindow
- {
- /// <summary>Animation details for the <see cref="TransitionPreviewWindow"/>.</summary>
- /// <remarks>
- /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">Previews</see>
- /// </remarks>
- [Serializable]
- private class Animations
- {
- /************************************************************************************************************************/
- public const string
- PreviousAnimationKey = "Previous Animation",
- NextAnimationKey = "Next Animation";
- /************************************************************************************************************************/
- [NonSerialized] private AnimationClip[] _OtherAnimations;
- [SerializeField]
- private AnimationClip _PreviousAnimation;
- public AnimationClip PreviousAnimation => _PreviousAnimation;
- [SerializeField]
- private AnimationClip _NextAnimation;
- public AnimationClip NextAnimation => _NextAnimation;
- /************************************************************************************************************************/
- public void DoGUI()
- {
- GUILayout.BeginVertical(GUI.skin.box);
- EditorGUILayout.LabelField("Preview Details", "(Not Serialized)");
- DoModelGUI();
- DoAnimatorSelectorGUI();
- using (ObjectPool.Disposable.AcquireContent(out var label, "Previous Animation",
- "The animation for the preview to play before the target transition"))
- {
- DoAnimationFieldGUI(label, ref _PreviousAnimation, (clip) => _PreviousAnimation = clip);
- }
- var animancer = _Instance._Scene.Animancer;
- DoCurrentAnimationGUI(animancer);
- using (ObjectPool.Disposable.AcquireContent(out var label, "Next Animation",
- "The animation for the preview to play after the target transition"))
- {
- DoAnimationFieldGUI(label, ref _NextAnimation, (clip) => _NextAnimation = clip);
- }
- if (animancer != null)
- {
- if (animancer.IsGraphPlaying)
- {
- if (GUILayout.Button("Pause", EditorStyles.miniButton))
- animancer.PauseGraph();
- }
- else
- {
- using (new EditorGUI.DisabledScope(!Transition.IsValid()))
- {
- if (GUILayout.Button("Play Transition", EditorStyles.miniButton))
- {
- if (_PreviousAnimation != null && _PreviousAnimation.length > 0)
- {
- _Instance._Scene.Animancer.Stop();
- var fromState = animancer.States.GetOrCreate(PreviousAnimationKey, _PreviousAnimation, true);
- animancer.Play(fromState);
- OnPlayAnimation();
- fromState.Time = 0;
- var warnings = OptionalWarning.UnsupportedEvents.DisableTemporarily();
- fromState.Events.EndEvent = new AnimancerEvent(1 / fromState.Length, PlayTransition);
- warnings.Enable();
- }
- else
- {
- PlayTransition();
- }
- _Instance._Scene.Animancer.UnpauseGraph();
- }
- }
- }
- }
- GUILayout.EndVertical();
- }
- /************************************************************************************************************************/
- private void DoModelGUI()
- {
- var model = _Instance._Scene.OriginalRoot != null ? _Instance._Scene.OriginalRoot.gameObject : null;
- EditorGUI.BeginChangeCheck();
- var warning = GetModelWarning(model);
- var color = GUI.color;
- if (warning != null)
- GUI.color = AnimancerGUI.WarningFieldColor;
- using (ObjectPool.Disposable.AcquireContent(out var label, "Model"))
- {
- if (DoDropdownObjectField(label, true, ref model, AnimancerGUI.SpacingMode.After))
- {
- var menu = new GenericMenu();
- menu.AddItem(new GUIContent("Default Humanoid"), Settings.IsDefaultHumanoid(model),
- () => _Instance._Scene.OriginalRoot = Settings.DefaultHumanoid.transform);
- menu.AddItem(new GUIContent("Default Sprite"), Settings.IsDefaultSprite(model),
- () => _Instance._Scene.OriginalRoot = Settings.DefaultSprite.transform);
- var persistentModels = Settings.Models;
- var temporaryModels = TemporarySettings.PreviewModels;
- if (persistentModels.Count == 0 && temporaryModels.Count == 0)
- {
- menu.AddDisabledItem(new GUIContent("No model prefabs have been used yet"));
- }
- else
- {
- AddModelSelectionFunctions(menu, persistentModels, model);
- AddModelSelectionFunctions(menu, temporaryModels, model);
- }
- menu.ShowAsContext();
- }
- }
- GUI.color = color;
- if (EditorGUI.EndChangeCheck())
- _Instance._Scene.OriginalRoot = model != null ? model.transform : null;
- if (warning != null)
- EditorGUILayout.HelpBox(warning, MessageType.Warning, true);
- }
- /************************************************************************************************************************/
- private static void AddModelSelectionFunctions(GenericMenu menu, List<GameObject> models, GameObject selected)
- {
- for (int i = models.Count - 1; i >= 0; i--)
- {
- var model = models[i];
- var path = AssetDatabase.GetAssetPath(model);
- if (!string.IsNullOrEmpty(path))
- path = path.Replace('/', '\\');
- else
- path = model.name;
- menu.AddItem(new GUIContent(path), model == selected,
- () => _Instance._Scene.OriginalRoot = model.transform);
- }
- }
- /************************************************************************************************************************/
- private string GetModelWarning(GameObject model)
- {
- if (model == null)
- return "No Model is selected so nothing can be previewed.";
- if (_Instance._Scene.Animancer == null)
- return "The selected Model has no Animator component.";
- return null;
- }
- /************************************************************************************************************************/
- private void DoAnimatorSelectorGUI()
- {
- var instanceAnimators = _Instance._Scene.InstanceAnimators;
- if (instanceAnimators == null ||
- instanceAnimators.Length <= 1)
- return;
- var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.After);
- var labelArea = AnimancerGUI.StealFromLeft(ref area, EditorGUIUtility.labelWidth, AnimancerGUI.StandardSpacing);
- GUI.Label(labelArea, nameof(Animator));
- var selectedAnimator = _Instance._Scene.SelectedInstanceAnimator;
- using (ObjectPool.Disposable.AcquireContent(out var label, selectedAnimator != null ? selectedAnimator.name : "None"))
- {
- var clicked = EditorGUI.DropdownButton(area, label, FocusType.Passive);
- if (!clicked)
- return;
- var menu = new GenericMenu();
- for (int i = 0; i < instanceAnimators.Length; i++)
- {
- var animator = instanceAnimators[i];
- var index = i;
- menu.AddItem(new GUIContent(animator.name), animator == selectedAnimator, () =>
- {
- _Instance._Scene.SetSelectedAnimator(index);
- NormalizedTime = 0;
- });
- }
- menu.ShowAsContext();
- }
- }
- /************************************************************************************************************************/
- public void GatherAnimations()
- {
- AnimationGatherer.GatherFromGameObject(_Instance._Scene.OriginalRoot.gameObject, ref _OtherAnimations, true);
- if (_OtherAnimations.Length > 0 &&
- (_PreviousAnimation == null || _NextAnimation == null))
- {
- var defaultClip = _OtherAnimations[0];
- var defaultClipIsIdle = false;
- for (int i = 0; i < _OtherAnimations.Length; i++)
- {
- var clip = _OtherAnimations[i];
- if (defaultClipIsIdle && clip.name.Length > defaultClip.name.Length)
- continue;
- if (clip.name.IndexOf("idle", StringComparison.CurrentCultureIgnoreCase) >= 0)
- {
- defaultClip = clip;
- break;
- }
- }
- if (_PreviousAnimation == null)
- _PreviousAnimation = defaultClip;
- if (_NextAnimation == null)
- _NextAnimation = defaultClip;
- }
- }
- /************************************************************************************************************************/
- private void DoAnimationFieldGUI(GUIContent label, ref AnimationClip clip, Action<AnimationClip> setClip)
- {
- var showDropdown = !_OtherAnimations.IsNullOrEmpty();
- if (DoDropdownObjectField(label, showDropdown, ref clip))
- {
- var menu = new GenericMenu();
- menu.AddItem(new GUIContent("None"), clip == null, () => setClip(null));
- for (int i = 0; i < _OtherAnimations.Length; i++)
- {
- var animation = _OtherAnimations[i];
- menu.AddItem(new GUIContent(animation.name), animation == clip, () => setClip(animation));
- }
- menu.ShowAsContext();
- }
- }
- /************************************************************************************************************************/
- private static bool DoDropdownObjectField<T>(GUIContent label, bool showDropdown, ref T obj,
- AnimancerGUI.SpacingMode spacingMode = AnimancerGUI.SpacingMode.None) where T : Object
- {
- var area = AnimancerGUI.LayoutSingleLineRect(spacingMode);
- var labelWidth = EditorGUIUtility.labelWidth;
- labelWidth += 2;
- area.xMin -= 1;
- var spacing = AnimancerGUI.StandardSpacing;
- var labelArea = AnimancerGUI.StealFromLeft(ref area, labelWidth - spacing, spacing);
- obj = (T)EditorGUI.ObjectField(area, obj, typeof(T), true);
- if (showDropdown)
- {
- return EditorGUI.DropdownButton(labelArea, label, FocusType.Passive);
- }
- else
- {
- GUI.Label(labelArea, label);
- return false;
- }
- }
- /************************************************************************************************************************/
- private void DoCurrentAnimationGUI(AnimancerPlayable animancer)
- {
- string text;
- if (animancer != null)
- {
- var transition = Transition;
- if (transition.IsValid && transition.Key != null)
- text = animancer.States.GetOrCreate(transition).ToString();
- else
- text = transition.ToString();
- }
- else
- {
- text = _Instance._TransitionProperty.Property.GetFriendlyPath();
- }
- if (text != null)
- EditorGUILayout.LabelField("Current Animation", text);
- }
- /************************************************************************************************************************/
- private void PlayTransition()
- {
- var transition = Transition;
- var animancer = _Instance._Scene.Animancer;
- animancer.States.TryGet(transition, out var oldState);
- var targetState = animancer.Play(transition);
- OnPlayAnimation();
- if (oldState != null && oldState != targetState)
- oldState.Destroy();
- var warnings = OptionalWarning.UnsupportedEvents.DisableTemporarily();
- targetState.Events.OnEnd = () =>
- {
- if (_NextAnimation != null)
- {
- var fadeDuration = AnimancerEvent.GetFadeOutDuration(targetState, AnimancerPlayable.DefaultFadeDuration);
- PlayOther(NextAnimationKey, _NextAnimation, 0, fadeDuration);
- OnPlayAnimation();
- }
- else
- {
- animancer.Layers[0].IncrementCommandCount();
- }
- };
- warnings.Enable();
- }
- /************************************************************************************************************************/
- public void OnPlayAnimation()
- {
- var animancer = _Instance._Scene.Animancer;
- if (animancer == null ||
- animancer.States.Current == null)
- return;
- var state = animancer.States.Current;
- state.RecreatePlayableRecursive();
- if (state.HasEvents)
- {
- var warnings = OptionalWarning.UnsupportedEvents.DisableTemporarily();
- var normalizedEndTime = state.Events.NormalizedEndTime;
- state.Events = null;
- state.Events.NormalizedEndTime = normalizedEndTime;
- warnings.Enable();
- }
- }
- /************************************************************************************************************************/
- [SerializeField]
- private float _NormalizedTime;
- public float NormalizedTime
- {
- get => _NormalizedTime;
- set
- {
- if (!value.IsFinite())
- return;
- _NormalizedTime = value;
- if (!TryShowTransitionPaused(out var animancer, out var transition, out var state))
- return;
- var length = state.Length;
- var speed = state.Speed;
- var time = value * length;
- var fadeDuration = transition.FadeDuration * Math.Abs(speed);
- var startTime = TimelineGUI.GetStartTime(transition.NormalizedStartTime, speed, length);
- var normalizedEndTime = state.NormalizedEndTime;
- var endTime = normalizedEndTime * length;
- var fadeOutEnd = TimelineGUI.GetFadeOutEnd(speed, endTime, length);
- if (speed < 0)
- {
- time = length - time;
- startTime = length - startTime;
- value = 1 - value;
- normalizedEndTime = 1 - normalizedEndTime;
- endTime = length - endTime;
- fadeOutEnd = length - fadeOutEnd;
- }
- if (time < startTime)// Previous animation.
- {
- if (_PreviousAnimation != null)
- {
- PlayOther(PreviousAnimationKey, _PreviousAnimation, value);
- value = 0;
- }
- }
- else if (time < startTime + fadeDuration)// Fade from previous animation to the target.
- {
- if (_PreviousAnimation != null)
- {
- var fromState = PlayOther(PreviousAnimationKey, _PreviousAnimation, value);
- state.IsPlaying = true;
- state.Weight = (time - startTime) / fadeDuration;
- fromState.Weight = 1 - state.Weight;
- }
- }
- else if (_NextAnimation != null)
- {
- if (value < normalizedEndTime)
- {
- // Just the main state.
- }
- else
- {
- var toState = PlayOther(NextAnimationKey, _NextAnimation, value - normalizedEndTime);
- if (time < fadeOutEnd)// Fade from the target transition to the next animation.
- {
- state.IsPlaying = true;
- toState.Weight = (time - endTime) / (fadeOutEnd - endTime);
- state.Weight = 1 - toState.Weight;
- }
- // Else just the next animation.
- }
- }
- if (speed < 0)
- value = 1 - value;
- state.NormalizedTime = state.Weight > 0 ? value : 0;
- animancer.Evaluate();
- AnimancerGUI.RepaintEverything();
- }
- }
- /************************************************************************************************************************/
- private bool TryShowTransitionPaused(
- out AnimancerPlayable animancer, out ITransitionDetailed transition, out AnimancerState state)
- {
- animancer = _Instance._Scene.Animancer;
- transition = Transition;
- if (animancer == null || !transition.IsValid())
- {
- state = null;
- return false;
- }
- state = animancer.Play(transition, 0);
- OnPlayAnimation();
- animancer.PauseGraph();
- return true;
- }
- /************************************************************************************************************************/
- private AnimancerState PlayOther(object key, AnimationClip animation, float normalizedTime, float fadeDuration = 0)
- {
- var animancer = _Instance._Scene.Animancer;
- var state = animancer.States.GetOrCreate(key, animation, true);
- state = animancer.Play(state, fadeDuration);
- OnPlayAnimation();
- normalizedTime *= state.Length;
- state.Time = normalizedTime.IsFinite() ? normalizedTime : 0;
- return state;
- }
- /************************************************************************************************************************/
- internal class WindowMatchStateTime : Key, IUpdatable
- {
- /************************************************************************************************************************/
- public static readonly WindowMatchStateTime Instance = new WindowMatchStateTime();
- /************************************************************************************************************************/
- void IUpdatable.Update()
- {
- if (_Instance == null ||
- !AnimancerPlayable.Current.IsGraphPlaying)
- return;
- var transition = Transition;
- if (transition == null)
- return;
- if (AnimancerPlayable.Current.States.TryGet(transition, out var state))
- _Instance._Animations._NormalizedTime = state.NormalizedTime;
- }
- /************************************************************************************************************************/
- }
- /************************************************************************************************************************/
- }
- }
- }
- #endif
|