// Animancer // Copyright 2020 Kybernetik // #if UNITY_EDITOR using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer.Editor { /// [Editor-Only] [Pro-Only] /// An which allows the user to preview animation transitions separately from the rest /// of the scene in Edit Mode or Play Mode. /// public sealed class TransitionPreviewWindow : EditorWindow, IHasCustomMenu { /************************************************************************************************************************/ #region Public API /************************************************************************************************************************/ private static Texture _Icon; /// The icon image used by this window. public static Texture Icon { get { #if UNITY_2019_3_OR_NEWER const string IconName = "ViewToolOrbit"; #else const string IconName = "UnityEditor.LookDevView"; #endif // Possible icons: "UnityEditor.LookDevView", "SoftlockInline", "ViewToolOrbit". if (_Icon == null) _Icon = EditorGUIUtility.IconContent(IconName).image; return _Icon; } } /************************************************************************************************************************/ /// Focusses the or creates one if none exists. public static void Open(SerializedProperty transitionProperty, bool open) { if (open) { GetWindow(typeof(SceneView)) .SetTargetProperty(transitionProperty); } else if (IsPreviewingCurrentProperty()) { EditorApplication.delayCall += _Instance.Close; } } /************************************************************************************************************************/ /// /// Sets the of the current transition if the property being /// previewed matches the . /// public static void SetPreviewNormalizedTime(float normalizedTime) { if (float.IsNaN(normalizedTime) || !IsPreviewingCurrentProperty() || _Instance.InstanceAnimancer == null) return; var transition = _Instance.ShowTransitionPaused(); if (transition == null) return; var animancer = _Instance.InstanceAnimancer; var state = animancer.States.Current; var length = state.Length; var time = normalizedTime * length; var fadeDuration = transition.FadeDuration; var startTime = transition.NormalizedStartTime * length; if (float.IsNaN(startTime)) startTime = 0; if (time < startTime)// Previous animation. { if (_Instance._PreviousAnimation != null) { var fromState = animancer.States.GetOrCreate(PreviousAnimationKey, _Instance._PreviousAnimation, true); animancer.Play(fromState); _Instance.OnPlayAnimation(); fromState.NormalizedTime = normalizedTime; normalizedTime = 0; } } else if (time < startTime + fadeDuration)// Fade from previous animation to the target. { if (_Instance._PreviousAnimation != null) { var fromState = animancer.States.GetOrCreate(PreviousAnimationKey, _Instance._PreviousAnimation, true); animancer.Play(fromState); _Instance.OnPlayAnimation(); fromState.NormalizedTime = normalizedTime; state.IsPlaying = true; state.Weight = (time - startTime) / fadeDuration; fromState.Weight = 1 - state.Weight; } } else if (_Instance._NextAnimation != null)// Fade from the target transition to the next animation. { var normalizedEndTime = state.Events.NormalizedEndTime; if (float.IsNaN(normalizedEndTime)) normalizedEndTime = AnimancerEvent.Sequence.GetDefaultNormalizedEndTime(state.Speed); if (normalizedTime < normalizedEndTime) { // Just the main state. } else { var toState = animancer.States.GetOrCreate(NextAnimationKey, _Instance._NextAnimation, true); animancer.Play(toState); _Instance.OnPlayAnimation(); toState.NormalizedTime = normalizedTime - normalizedEndTime; var endTime = normalizedEndTime * length; var fadeOutEnd = TimeRuler.GetFadeOutEnd(toState.Speed, endTime, transition.MaximumDuration); if (time < fadeOutEnd) { state.IsPlaying = true; toState.Weight = (time - endTime) / (fadeOutEnd - endTime); state.Weight = 1 - toState.Weight; } } } state.NormalizedTime = state.Weight > 0 ? normalizedTime : 0; animancer.Evaluate(); UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); } /************************************************************************************************************************/ /// /// Returns the of the current transition if the property being previewed matches /// the . Otherwise returns null. /// public static AnimancerState GetCurrentState() { if (!IsPreviewingCurrentProperty() || _Instance.InstanceAnimancer == null) return null; var transition = _Instance.GetTransition(); return _Instance.InstanceAnimancer.States[transition]; } /************************************************************************************************************************/ /// /// Indicates whether the current is being previewed /// at the moment. /// public static bool IsPreviewingCurrentProperty() { return _Instance != null && TransitionDrawer.Context != null && _Instance._TransitionProperty.IsValid() && Serialization.AreSameProperty(TransitionDrawer.Context.Property, _Instance._TransitionProperty); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Messages /************************************************************************************************************************/ private static TransitionPreviewWindow _Instance; /************************************************************************************************************************/ private void OnEnable() { _Instance = this; titleContent = new GUIContent("Animancer", Icon); autoRepaintOnSceneChange = true; _InspectorWidth = EditorPrefs.GetFloat(InspectorWidthKey, 300); if (_TransitionProperty.IsValid() && !CanBePreviewed(_TransitionProperty)) { DestroyTransitionProperty(); } InitialisePreview(); UnityEditor.SceneManagement.EditorSceneManager.sceneOpening += OnSceneOpening; #if UNITY_2017_3_OR_NEWER EditorApplication.playModeStateChanged += OnPlayModeChanged; #endif } /************************************************************************************************************************/ [NonSerialized] private bool _IsChangingPlayMode; #if UNITY_2017_3_OR_NEWER private void OnPlayModeChanged(PlayModeStateChange change) { switch (change) { case PlayModeStateChange.ExitingEditMode: case PlayModeStateChange.ExitingPlayMode: DestroyModelInstance(); _IsChangingPlayMode = true; break; case PlayModeStateChange.EnteredEditMode: case PlayModeStateChange.EnteredPlayMode: _IsChangingPlayMode = false; break; } } #endif /************************************************************************************************************************/ private void OnSceneOpening(string path, UnityEditor.SceneManagement.OpenSceneMode mode) { if (mode == UnityEditor.SceneManagement.OpenSceneMode.Single) DestroyModelInstance(); } /************************************************************************************************************************/ private void OnDisable() { UnityEditor.SceneManagement.EditorSceneManager.sceneOpening -= OnSceneOpening; #if UNITY_2017_3_OR_NEWER EditorApplication.playModeStateChanged -= OnPlayModeChanged; #endif EditorPrefs.SetFloat(InspectorWidthKey, _InspectorWidth); DestroyAnimancerInstance(); if (_PreviewRenderUtility != null) { _PreviewRenderUtility.Cleanup(); _PreviewRenderUtility = null; } _Instance = null; } /************************************************************************************************************************/ private void OnDestroy() { if (_PreviewSceneRoot != null) { DestroyImmediate(_PreviewSceneRoot.gameObject); _PreviewSceneRoot = null; } DestroyTransitionProperty(); UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); } /************************************************************************************************************************/ private void OnGUI() { // Maximising then un-maximising the window causes it to lose the _Instance for some reason. _Instance = this; GUILayout.BeginHorizontal(); { DoPreviewGUI(); DoInspectorGUI(); } GUILayout.EndHorizontal(); } /************************************************************************************************************************/ private void Update() { _Instance = this; if (!_IsChangingPlayMode && _InstanceRoot == null) InstantiateModel(); if (AutoClose && !_TransitionProperty.IsValid()) Close(); else if (_InstanceAnimancer != null && _InstanceAnimancer.IsGraphPlaying) Repaint(); } /************************************************************************************************************************/ void IHasCustomMenu.AddItemsToMenu(GenericMenu menu) { ShowTransition.AddToggleFunction(menu); AutoClose.AddToggleFunction(menu); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Transition Property /************************************************************************************************************************/ [SerializeField] private Serialization.PropertyReference _TransitionProperty; /************************************************************************************************************************/ /// Indicates whether the `property` is able to be previewed by this system. public static bool CanBePreviewed(SerializedProperty property) { var type = property.GetAccessor().FieldType; return typeof(ITransitionDetailed).IsAssignableFrom(type); } /************************************************************************************************************************/ private void SetTargetProperty(SerializedProperty property) { if (property.serializedObject.targetObjects.Length != 1) { Close(); throw new ArgumentException("The TransitionPreviewWindow does not support multi-object selection."); } if (!CanBePreviewed(property)) { Close(); throw new ArgumentException("The specified property does not implement IAnimancerTransitionDetailed."); } DestroyTransitionProperty(); _TransitionProperty = property; _SelectedInstanceAnimator = 0; _OriginalRoot = AnimancerEditorUtilities.FindRoot(_TransitionProperty.TargetObject); } /************************************************************************************************************************/ private ITransitionDetailed GetTransition() { if (!_TransitionProperty.IsValid()) return null; return _TransitionProperty.Property.GetValue(); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Preview /************************************************************************************************************************/ [NonSerialized] private PreviewRenderUtility _PreviewRenderUtility; [SerializeField] private Transform _PreviewSceneRoot; [SerializeField] private Transform _OriginalRoot; [SerializeField] private Transform _InstanceRoot; /************************************************************************************************************************/ [SerializeField] private Animator[] _InstanceAnimators; [SerializeField] private int _SelectedInstanceAnimator; [NonSerialized] private AnimationType _SelectedInstanceType; private Animator SelectedInstanceAnimator { get { if (_InstanceAnimators == null || _InstanceAnimators.Length == 0) return null; if (_SelectedInstanceAnimator > _InstanceAnimators.Length) _SelectedInstanceAnimator = _InstanceAnimators.Length; return _InstanceAnimators[_SelectedInstanceAnimator]; } } /************************************************************************************************************************/ [NonSerialized] private AnimancerPlayable _InstanceAnimancer; private AnimancerPlayable InstanceAnimancer { get { if ((_InstanceAnimancer == null || !_InstanceAnimancer.IsValid) && _InstanceRoot != null) { var animator = SelectedInstanceAnimator; if (animator != null) { AnimancerPlayable.SetNextGraphName(_InstanceRoot.name); _InstanceAnimancer = AnimancerPlayable.Create(); _InstanceAnimancer.SetOutput( new AnimancerEditorUtilities.DummyAnimancerComponent(animator, _InstanceAnimancer)); } } return _InstanceAnimancer; } } /************************************************************************************************************************/ private void InitialisePreview() { if (_PreviewRenderUtility == null) _PreviewRenderUtility = new PreviewRenderUtility(); if (_PreviewSceneRoot == null) { _PreviewSceneRoot = EditorUtility.CreateGameObjectWithHideFlags( "Animancer Transition Preview Window", HideFlags.HideAndDontSave).transform; _PreviewRenderUtility.AddSingleGO(_PreviewSceneRoot.gameObject); _PreviewRenderUtility.ambientColor = new Color(0.1f, 0.1f, 0.1f, 0f); } } /************************************************************************************************************************/ private void InstantiateModel() { DestroyModelInstance(); if (_OriginalRoot == null) return; _PreviewSceneRoot.gameObject.SetActive(false); _InstanceRoot = Instantiate(_OriginalRoot, _PreviewSceneRoot); _InstanceRoot.localPosition = Vector3.zero; _InstanceRoot.name = _OriginalRoot.name; DestroyUnnecessaryComponents(_InstanceRoot.gameObject); _InstanceAnimators = _InstanceRoot.GetComponentsInChildren(); for (int i = 0; i < _InstanceAnimators.Length; i++) { var animator = _InstanceAnimators[i]; animator.enabled = false; animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; animator.fireEvents = false; animator.updateMode = AnimatorUpdateMode.Normal; animator.gameObject.AddComponent() .animator = animator; } _PreviewSceneRoot.gameObject.SetActive(true); InitialiseCamera(); SetSelectedAnimator(_SelectedInstanceAnimator); AnimationGatherer.GatherFromGameObject(_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; //defaultClipIsIdle = true; break; } } if (_PreviousAnimation == null) _PreviousAnimation = defaultClip; if (_NextAnimation == null) _NextAnimation = defaultClip; } } /************************************************************************************************************************/ /// /// Destroys all unnecessary components on the preview instance while accounting for any /// attributes. /// private static void DestroyUnnecessaryComponents(GameObject root) { var components = root.GetComponentsInChildren(); var typeToDependencies = new Dictionary>(); var componentToDependencies = new Dictionary>(components.Length); for (int i = 0; i < components.Length; i++) { var component = components[i]; List dependencies; if (!componentToDependencies.TryGetValue(component, out dependencies)) { var type = component.GetType(); List typeDependencies; if (!typeToDependencies.TryGetValue(type, out typeDependencies)) { if (type.IsDefined(typeof(RequireComponent), false)) { var requirements = (RequireComponent[])type.GetCustomAttributes(typeof(RequireComponent), false); for (int j = 0; j < requirements.Length; j++) { var requirement = requirements[j]; GatherRequirement(ref typeDependencies, requirement.m_Type0); GatherRequirement(ref typeDependencies, requirement.m_Type1); GatherRequirement(ref typeDependencies, requirement.m_Type2); } } typeToDependencies.Add(type, typeDependencies); } if (typeDependencies != null) { dependencies = new List(); for (int j = 0; j < typeDependencies.Count; j++) { var requiredComponent = component.GetComponent(typeDependencies[j]); if (requiredComponent != null) dependencies.Add(requiredComponent); } } componentToDependencies.Add(component, dependencies); } } var sortedComponents = AnimancerEditorUtilities.TopologicalSort(components, (component) => componentToDependencies[component]); for (int i = components.Length - 1; i >= 0; i--) { var component = sortedComponents[i]; if (component is Transform || component is MeshFilter || component is Renderer || component is Animator) continue; DestroyImmediate(component); } } /************************************************************************************************************************/ private static void GatherRequirement(ref List requirements, Type type) { if (type == null) return; if (requirements == null) requirements = new List(); requirements.Add(type); } /************************************************************************************************************************/ private void SetSelectedAnimator(int index) { DestroyAnimancerInstance(); var animator = SelectedInstanceAnimator; if (animator != null && animator.enabled) { animator.Rebind(); animator.enabled = false; return; } _SelectedInstanceAnimator = index; animator = SelectedInstanceAnimator; if (animator != null) { animator.enabled = true; _SelectedInstanceType = AnimancerEditorUtilities.GetAnimationType(animator); if (_SelectedInstanceType == AnimationType.Sprite) { Camera.transform.parent.localRotation = Quaternion.identity; } else { CameraEulerAngles = CameraEulerAngles; } } } /************************************************************************************************************************/ [NonSerialized] private bool _IsDraggingCamera; [NonSerialized] private Texture _PreviewTexture; private void DoPreviewGUI() { GUILayout.BeginVertical(); { GUILayout.FlexibleSpace(); } GUILayout.EndVertical(); var area = GUILayoutUtility.GetLastRect(); var inspectorBorder = new Rect(area.xMax, area.y, 0, area.height); DoResizeInspectorGUI(inspectorBorder); var currentEvent = Event.current; switch (currentEvent.type) { case EventType.Repaint: if (area.width <= 0 || area.height <= 0) break; DrawFloor(); // Start the model paused at the beginning of the animation. // For some reason Unity doesn't like having this in OnEnable. if (InstanceAnimancer != null && InstanceAnimancer.Layers.Count == 0) ShowTransitionPaused(); var fog = RenderSettings.fog; Unsupported.SetRenderSettingsUseFogNoDirty(false); if (InstanceAnimancer != null || _PreviewTexture == null || _OriginalRoot == null) { _PreviewRenderUtility.BeginPreview(area, GUIStyle.none); _PreviewRenderUtility.Render(); _PreviewTexture = _PreviewRenderUtility.EndPreview(); } GUI.DrawTexture(area, _PreviewTexture, ScaleMode.StretchToFill, false); Unsupported.SetRenderSettingsUseFogNoDirty(fog); break; // Camera Control. case EventType.MouseDown: _IsDraggingCamera = area.Contains(currentEvent.mousePosition); if (_IsDraggingCamera) currentEvent.Use(); break; case EventType.MouseUp: if (_IsDraggingCamera) { _IsDraggingCamera = false; currentEvent.Use(); } break; case EventType.MouseDrag: if (_IsDraggingCamera) { var sensitivity = Screen.dpi * 0.01f; var euler = CameraEulerAngles; euler.x += currentEvent.delta.y * sensitivity; euler.y += currentEvent.delta.x * sensitivity; CameraEulerAngles = euler; currentEvent.Use(); } break; case EventType.ScrollWheel: if (area.Contains(currentEvent.mousePosition)) { CameraZoom *= 1 + 0.03f * currentEvent.delta.y; currentEvent.Use(); } break; // Drag and Drop. case EventType.DragUpdated: if (area.Contains(currentEvent.mousePosition) && GetDragAndDropRoot() != null) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; GUIUtility.ExitGUI(); } break; case EventType.DragPerform: if (area.Contains(currentEvent.mousePosition)) { var root = GetDragAndDropRoot(); if (root != null) { _OriginalRoot = root; InstantiateModel(); GUIUtility.ExitGUI(); } } break; } } /************************************************************************************************************************/ private ITransitionDetailed ShowTransitionPaused() { var transition = GetTransition(); if (transition != null) { var animancer = InstanceAnimancer; #if UNITY_2018_3_OR_NEWER animancer.States.Destroy(transition); #endif animancer.Play(transition, 0); OnPlayAnimation(); animancer.Evaluate(); animancer.PauseGraph(); } return transition; } /************************************************************************************************************************/ private static Transform GetDragAndDropRoot() { var objects = DragAndDrop.objectReferences; if (objects.Length != 1) return null; return AnimancerEditorUtilities.FindRoot(objects[0]); } /************************************************************************************************************************/ private void OnPlayAnimation() { var animancer = InstanceAnimancer; if (animancer == null || animancer.States.Current == null) return; var state = animancer.States.Current; var normalizedEndTime = state.Events.NormalizedEndTime; state.Events = null; state.Events.NormalizedEndTime = normalizedEndTime; } /************************************************************************************************************************/ #region Camera /************************************************************************************************************************/ private Camera Camera { get { return _PreviewRenderUtility.camera; } } /************************************************************************************************************************/ [SerializeField] private float _CameraZoom; private float CameraZoom { get { return _CameraZoom; } set { _CameraZoom = value; if (Camera != null) Camera.transform.localPosition = new Vector3(0, 0, -_CameraZoom); } } /************************************************************************************************************************/ [SerializeField] private Vector3 _CameraEulerAngles = new Vector3(float.NaN, float.NaN, float.NaN); private Vector3 CameraEulerAngles { get { return _CameraEulerAngles; } set { if (_SelectedInstanceType == AnimationType.Sprite) return; _CameraEulerAngles = value; if (Camera != null && Camera.transform.parent != null) Camera.transform.parent.localEulerAngles = value; } } /************************************************************************************************************************/ private void InitialiseCamera() { var renderers = _InstanceRoot.GetComponentsInChildren(); var bounds = renderers.Length > 0 ? renderers[0].bounds : default(Bounds); for (int i = 1; i < renderers.Length; i++) { bounds.Encapsulate(renderers[i].bounds); } const string CameraParentName = "Animancer Preview Camera Root"; var cameraParent = _PreviewSceneRoot.Find(CameraParentName); if (cameraParent == null) { cameraParent = EditorUtility.CreateGameObjectWithHideFlags( CameraParentName, HideFlags.HideAndDontSave).transform; cameraParent.parent = _PreviewSceneRoot; var lights = _PreviewRenderUtility.lights; for (int i = 0; i < lights.Length; i++) lights[i].transform.parent = cameraParent; Camera.transform.parent = cameraParent; Camera.transform.localRotation = Quaternion.identity; } cameraParent.localPosition = bounds.center; Camera.farClipPlane = 100; if (_CameraZoom == 0) CameraZoom = bounds.extents.magnitude * 2 / Mathf.Tan(Camera.fieldOfView * Mathf.Deg2Rad); else CameraZoom = CameraZoom; if (float.IsNaN(_CameraEulerAngles.x)) CameraEulerAngles = new Vector3(45, 135, 0); else CameraEulerAngles = CameraEulerAngles; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Floor /************************************************************************************************************************/ private const float FloorScale = 5; [NonSerialized] private Vector3 _FloorPosition; private void DrawFloor() { var rotation = _SelectedInstanceType == AnimationType.Sprite ? Quaternion.Euler(-90f, 0f, 0f) : Quaternion.identity; var scale = Vector3.one * FloorScale;// * _AvatarScale; var matrix = Matrix4x4.TRS(_FloorPosition, rotation, scale); var layer = 0; var camera = _PreviewRenderUtility.camera; Graphics.DrawMesh(Floor.Plane, matrix, Floor.Material, layer, camera, 0); } /************************************************************************************************************************/ // Initialisation based on UnityEditor.AvatarPreview. private static class Floor { /************************************************************************************************************************/ public static readonly Material Material; public static readonly Mesh Plane; /************************************************************************************************************************/ static Floor() { var texture = (Texture2D)EditorGUIUtility.Load("Avatar/Textures/AvatarFloor.png"); if (texture == null) return; var shader = EditorGUIUtility.Load("Previews/PreviewPlaneWithShadow.shader") as Shader; if (shader == null) return; Material = new Material(shader) { mainTexture = texture, mainTextureScale = Vector2.one * 5f * 4f, hideFlags = HideFlags.HideAndDontSave }; Material.SetVector("_Alphas", new Vector4(0.5f, 0.3f, 0f, 0f)); //this.m_FloorMaterialSmall = new Material(Material); //this.m_FloorMaterialSmall.mainTextureScale = Vector2.one * 0.2f * 4f; //this.m_FloorMaterialSmall.hideFlags = HideFlags.HideAndDontSave; Plane = Resources.GetBuiltinResource(typeof(Mesh), "New-Plane.fbx") as Mesh; } /************************************************************************************************************************/ } /************************************************************************************************************************/ [AddComponentMenu("")] private sealed class RedirectRootMotion : MonoBehaviour { public Animator animator; private void OnAnimatorMove() { if (animator == null || _Instance == null) return; if (animator == _Instance.SelectedInstanceAnimator) { _Instance._FloorPosition -= animator.deltaPosition; _Instance._FloorPosition.x %= FloorScale; _Instance._FloorPosition.y = 0; _Instance._FloorPosition.z %= FloorScale; } } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Cleanup /************************************************************************************************************************/ private void DestroyModelInstance() { DestroyAnimancerInstance(); if (_InstanceRoot == null) return; DestroyImmediate(_InstanceRoot.gameObject); _InstanceRoot = null; _InstanceAnimators = null; } /************************************************************************************************************************/ private void DestroyAnimancerInstance() { if (_InstanceAnimancer == null) return; _InstanceAnimancer.Destroy(); _InstanceAnimancer = null; } /************************************************************************************************************************/ private void DestroyTransitionProperty() { if (_TransitionProperty == null) return; DestroyModelInstance(); _TransitionProperty.Dispose(); _TransitionProperty = null; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Inspector /************************************************************************************************************************/ private const string KeyPrefix = "TransitionPreviewWindow.", InspectorWidthKey = BoolPref.KeyPrefix + KeyPrefix + "InspectorWidth"; [SerializeField] private float _InspectorWidth; [NonSerialized] private AnimationClip[] _OtherAnimations; [SerializeField] private AnimationClip _PreviousAnimation; [SerializeField] private AnimationClip _NextAnimation; private readonly AnimancerPlayableDrawer PlayableDrawer = new AnimancerPlayableDrawer(); private const string PreviousAnimationKey = "Previous Animation", NextAnimationKey = "Next Animation"; private static readonly BoolPref ShowTransition = new BoolPref(KeyPrefix, "Show Transition", true), AutoClose = new BoolPref(KeyPrefix, "Auto Close", true); /************************************************************************************************************************/ [NonSerialized] private bool _IsResizingInspector; private void DoResizeInspectorGUI(Rect borderArea) { borderArea.width = 10; borderArea.x -= borderArea.width * 0.5f; EditorGUIUtility.AddCursorRect(borderArea, MouseCursor.ResizeHorizontal); var currentEvent = Event.current; switch (currentEvent.type) { case EventType.MouseDown: _IsResizingInspector = borderArea.Contains(currentEvent.mousePosition); if (_IsResizingInspector) currentEvent.Use(); break; case EventType.MouseUp: if (_IsResizingInspector) { _IsResizingInspector = false; currentEvent.Use(); } break; case EventType.MouseDrag: if (_IsResizingInspector) { _InspectorWidth -= currentEvent.delta.x; _InspectorWidth = Mathf.Clamp(_InspectorWidth, 250, position.width - 250); currentEvent.Use(); } break; } } /************************************************************************************************************************/ [SerializeField] private Vector2 _InspectorScroll; private void DoInspectorGUI() { EditorGUIUtility.hierarchyMode = true; EditorGUIUtility.labelWidth = Math.Max(_InspectorWidth * 0.55f - 60, 100); EditorGUIUtility.wideMode = _InspectorWidth > 300; GUILayout.BeginVertical(GUILayout.Width(_InspectorWidth)); { _InspectorScroll = GUILayout.BeginScrollView(_InspectorScroll, GUILayout.Width(_InspectorWidth)); if (!_TransitionProperty.IsValid()) { GUILayout.Label("No target property"); DestroyTransitionProperty(); } else { DoTransitionPropertyGUI(); DoPreviewSettingsGUI(); var animancer = InstanceAnimancer; if (animancer != null) { PlayableDrawer.DoGUI(animancer.Component); if (animancer.IsGraphPlaying) GUI.changed = true; } } GUILayout.EndScrollView(); } GUILayout.EndVertical(); EditorGUIUtility.hierarchyMode = false; } /************************************************************************************************************************/ private void DoTransitionPropertyGUI() { _TransitionProperty.Update(); var enabled = GUI.enabled; GUI.enabled = false; { EditorGUI.showMixedValue = _TransitionProperty.TargetObjects.Length > 1; EditorGUILayout.ObjectField(_TransitionProperty.TargetObject, typeof(Object), true); EditorGUI.showMixedValue = false; GUILayout.Label(_TransitionProperty.Property.GetFriendlyPath()); } GUI.enabled = enabled; if (ShowTransition) { var isExpanded = _TransitionProperty.Property.isExpanded; _TransitionProperty.Property.isExpanded = true; var height = EditorGUI.GetPropertyHeight(_TransitionProperty, true); const float Indent = 12; var padding = GUI.skin.box.padding; var area = GUILayoutUtility.GetRect(0, height + padding.horizontal - padding.bottom); area.x += Indent + padding.left; area.width -= Indent + padding.horizontal; EditorGUI.BeginChangeCheck(); EditorGUI.PropertyField(area, _TransitionProperty, true); _TransitionProperty.Property.isExpanded = isExpanded; if (EditorGUI.EndChangeCheck()) _TransitionProperty.ApplyModifiedProperties(); } } /************************************************************************************************************************/ private void DoPreviewSettingsGUI() { GUILayout.BeginVertical(GUI.skin.box); EditorGUILayout.LabelField("Preview Settings", "(Not Serialized)"); DoAnimatorSelectorGUI(); DoAnimationFieldGUI(AnimancerGUI.TempContent("Previous Animation", "The animation for the preview to play before the target transition"), _PreviousAnimation, (clip) => _PreviousAnimation = clip); var animancer = InstanceAnimancer; DoCurrentAnimationGUI(animancer); DoAnimationFieldGUI(AnimancerGUI.TempContent("Next Animation", "The animation for the preview to play after the target transition"), _NextAnimation, (clip) => _NextAnimation = clip); if (animancer != null) { animancer.Speed = EditorGUILayout.FloatField("Overall Speed", animancer.Speed); if (animancer.IsGraphPlaying) { if (GUILayout.Button("Pause", EditorStyles.miniButton)) animancer.PauseGraph(); } else { if (GUILayout.Button("Play", EditorStyles.miniButton)) { if (_PreviousAnimation != null) { InstanceAnimancer.Stop(); var fromState = animancer.States.GetOrCreate(PreviousAnimationKey, _Instance._PreviousAnimation, true); animancer.Play(fromState); OnPlayAnimation(); fromState.Time = 0; fromState.Events.endEvent = new AnimancerEvent(1 / fromState.Length, PlayTransition); } else { PlayTransition(); } InstanceAnimancer.UnpauseGraph(); } } } #if !UNITY_2018_3_OR_NEWER if (!EditorApplication.isPlayingOrWillChangePlaymode) { EditorGUILayout.HelpBox("Playing multiple animations in Edit Mode does not work properly in this version of Unity." + " It can play one animation, but transitioning to or from others will look wrong" + " so if you want to use this feature you should upgrade to Unity 2018.3 or newer.", MessageType.Warning); } #endif GUILayout.EndVertical(); } /************************************************************************************************************************/ private void DoAnimatorSelectorGUI() { 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, "Animator"); var selectedAnimator = SelectedInstanceAnimator; var label = AnimancerGUI.TempContent(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]; label = new GUIContent(animator.name); var index = i; menu.AddItem(label, animator == selectedAnimator, () => { SetSelectedAnimator(index); }); } menu.ShowAsContext(); } /************************************************************************************************************************/ private void DoAnimationFieldGUI(GUIContent label, AnimationClip clip, Action setClip) { var area = AnimancerGUI.LayoutSingleLineRect(); var labelWidth = EditorGUIUtility.labelWidth; #if UNITY_2019_3_OR_NEWER labelWidth += 2; area.xMin -= 1; #else area.xMin += 1; area.xMax -= 1; #endif var spacing = AnimancerGUI.StandardSpacing; var labelArea = AnimancerGUI.StealFromLeft(ref area, labelWidth - spacing, spacing); if (_OtherAnimations != null && _OtherAnimations.Length > 0) { if (EditorGUI.DropdownButton(labelArea, label, FocusType.Passive)) { 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(); } } else { GUI.Label(labelArea, label); } EditorGUI.BeginChangeCheck(); clip = (AnimationClip)EditorGUI.ObjectField(area, clip, typeof(AnimationClip), true); if (EditorGUI.EndChangeCheck()) setClip(clip); } /************************************************************************************************************************/ private void DoCurrentAnimationGUI(AnimancerPlayable animancer) { const string Label = "Current Animation"; var enabled = GUI.enabled; GUI.enabled = false; string text = null; if (animancer != null) { var transition = GetTransition(); var state = animancer.States[transition]; if (state != null) { var mainObject = state.MainObject; if (mainObject != null) { EditorGUILayout.ObjectField(Label, mainObject, typeof(Object), true); } else { text = state.ToString(); } } else { text = _TransitionProperty.Property.GetFriendlyPath(); } } else { text = _TransitionProperty.Property.GetFriendlyPath(); } if (text != null) EditorGUILayout.LabelField(Label, text); GUI.enabled = enabled; } /************************************************************************************************************************/ private void PlayTransition() { var transition = GetTransition(); #if UNITY_2018_3_OR_NEWER InstanceAnimancer.States.Destroy(transition); #endif var targetState = InstanceAnimancer.Play(transition); OnPlayAnimation(); targetState.Events.OnEnd = () => { if (_NextAnimation != null) { var toState = InstanceAnimancer.States.GetOrCreate(NextAnimationKey, _Instance._NextAnimation, true); InstanceAnimancer.Play(toState, AnimancerPlayable.DefaultFadeDuration); OnPlayAnimation(); } }; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } } #endif