// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // #if UNITY_EDITOR #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value. using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using Object = UnityEngine.Object; namespace Animancer.Editor { /// https://kybernetik.com.au/animancer/api/Animancer.Editor/TransitionPreviewWindow partial class TransitionPreviewWindow { /************************************************************************************************************************/ /// The of the current instance. public static Scene InstanceScene => _Instance != null ? _Instance._Scene : null; /************************************************************************************************************************/ /// Temporary scene management for the . /// /// Documentation: Previews /// [Serializable] public class Scene { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ /// without . private const HideFlags HideAndDontSave = HideFlags.HideInHierarchy | HideFlags.DontSave; /// The scene displayed by the . [SerializeField] private UnityEngine.SceneManagement.Scene _Scene; /// The root object in the preview scene. public Transform PreviewSceneRoot { get; private set; } /// The root of the model in the preview scene. A child of the . public Transform InstanceRoot { get; private set; } /// /// An instance of the . /// A child of the . /// public GameObject EnvironmentInstance { get; private set; } /************************************************************************************************************************/ [SerializeField] private Transform _OriginalRoot; /// The original model which was instantiated to create the . public Transform OriginalRoot { get => _OriginalRoot; set { _OriginalRoot = value; InstantiateModel(); if (value != null) Settings.AddModel(value.gameObject); } } /************************************************************************************************************************/ /// The components attached to the and its children. public Animator[] InstanceAnimators { get; private set; } [SerializeField] private int _SelectedInstanceAnimator; [NonSerialized] private AnimationType _SelectedInstanceType; /// The component currently being used for the preview. public Animator SelectedInstanceAnimator { get { if (InstanceAnimators == null || InstanceAnimators.Length == 0) return null; if (_SelectedInstanceAnimator > InstanceAnimators.Length) _SelectedInstanceAnimator = InstanceAnimators.Length; return InstanceAnimators[_SelectedInstanceAnimator]; } } /************************************************************************************************************************/ [NonSerialized] private AnimancerPlayable _Animancer; /// The being used for the preview. public AnimancerPlayable Animancer { get { if ((_Animancer == null || !_Animancer.IsValid) && InstanceRoot != null) { var animator = SelectedInstanceAnimator; if (animator != null) { AnimancerPlayable.SetNextGraphName($"{animator.name} (Animancer Preview)"); _Animancer = AnimancerPlayable.Create(); _Animancer.CreateOutput( new AnimancerEditorUtilities.DummyAnimancerComponent(animator, _Animancer)); _Animancer.RequirePostUpdate(Animations.WindowMatchStateTime.Instance); _Instance._Animations.NormalizedTime = _Instance._Animations.NormalizedTime; } } return _Animancer; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Initialization /************************************************************************************************************************/ /// Initializes this . public void OnEnable() { EditorSceneManager.sceneOpening += OnSceneOpening; EditorApplication.playModeStateChanged += OnPlayModeChanged; duringSceneGui += DoCustomGUI; CreateScene(); if (OriginalRoot == null) OriginalRoot = Settings.TrySelectBestModel(); } /************************************************************************************************************************/ private void CreateScene() { _Scene = EditorSceneManager.NewPreviewScene(); _Scene.name = "Transition Preview"; _Instance.customScene = _Scene; PreviewSceneRoot = EditorUtility.CreateGameObjectWithHideFlags( $"{nameof(Animancer)}.{nameof(TransitionPreviewWindow)}", HideAndDontSave).transform; SceneManager.MoveGameObjectToScene(PreviewSceneRoot.gameObject, _Scene); _Instance.customParentForDraggedObjects = PreviewSceneRoot; OnEnvironmentPrefabChanged(); } /************************************************************************************************************************/ internal void OnEnvironmentPrefabChanged() { DestroyImmediate(EnvironmentInstance); var prefab = Settings.SceneEnvironment; if (prefab != null) EnvironmentInstance = Instantiate(prefab, PreviewSceneRoot); } /************************************************************************************************************************/ private void InstantiateModel() { DestroyModelInstance(); if (_OriginalRoot == null) return; PreviewSceneRoot.gameObject.SetActive(false); InstanceRoot = Instantiate(_OriginalRoot, PreviewSceneRoot); InstanceRoot.localPosition = default; InstanceRoot.name = _OriginalRoot.name; DisableUnnecessaryComponents(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; } PreviewSceneRoot.gameObject.SetActive(true); SetSelectedAnimator(_SelectedInstanceAnimator); FocusCamera(); _Instance._Animations.GatherAnimations(); } /************************************************************************************************************************/ /// Disables all unnecessary components on the `root` or its children. private static void DisableUnnecessaryComponents(GameObject root) { var behaviours = root.GetComponentsInChildren(); for (int i = 0; i < behaviours.Length; i++) { var behaviour = behaviours[i]; // Other undesirable components aren't Behaviours anyway: Transform, MeshFilter, Renderer if (behaviour is Animator) continue; var type = behaviour.GetType(); if (type.IsDefined(typeof(ExecuteAlways), true) || type.IsDefined(typeof(ExecuteInEditMode), true)) continue; behaviour.enabled = false; behaviour.hideFlags |= HideFlags.NotEditable; } } /************************************************************************************************************************/ /// Sets the . public 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 = AnimationBindings.GetAnimationType(animator); _Instance.in2DMode = _SelectedInstanceType == AnimationType.Sprite; } } /************************************************************************************************************************/ /// Called when the target transition property is changed. public void OnTargetPropertyChanged() { _SelectedInstanceAnimator = 0; if (_ExpandedHierarchy != null) _ExpandedHierarchy.Clear(); OriginalRoot = AnimancerEditorUtilities.FindRoot(_Instance._TransitionProperty.TargetObject); if (OriginalRoot == null) OriginalRoot = Settings.TrySelectBestModel(); _Instance._Animations.NormalizedTime = 0; _Instance.in2DMode = _SelectedInstanceType == AnimationType.Sprite; } /************************************************************************************************************************/ private void FocusCamera() { var bounds = CalculateBounds(InstanceRoot); var rotation = _Instance.in2DMode ? Quaternion.identity : Quaternion.Euler(35, 135, 0); var size = bounds.extents.magnitude * 1.5f; if (size == float.PositiveInfinity) return; else if (size == 0) size = 10; _Instance.LookAt(bounds.center, rotation, size, _Instance.in2DMode, true); } /************************************************************************************************************************/ private static Bounds CalculateBounds(Transform transform) { var renderers = transform.GetComponentsInChildren(); if (renderers.Length == 0) return default; var bounds = renderers[0].bounds; for (int i = 1; i < renderers.Length; i++) { bounds.Encapsulate(renderers[i].bounds); } return bounds; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Execution /************************************************************************************************************************/ /// Called when the window GUI is drawn. public void OnGUI() { if (!AnimancerEditorUtilities.IsChangingPlayMode && InstanceRoot == null) InstantiateModel(); if (_Animancer != null && _Animancer.IsGraphPlaying) AnimancerGUI.RepaintEverything(); if (Selection.activeObject == _Instance && Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.F) FocusCamera(); } /************************************************************************************************************************/ private void OnPlayModeChanged(PlayModeStateChange change) { switch (change) { case PlayModeStateChange.ExitingEditMode: case PlayModeStateChange.ExitingPlayMode: DestroyModelInstance(); break; } } /************************************************************************************************************************/ private void OnSceneOpening(string path, OpenSceneMode mode) { if (mode == OpenSceneMode.Single) DestroyModelInstance(); } /************************************************************************************************************************/ private void DoCustomGUI(SceneView sceneView) { var animancer = Animancer; if (animancer != null && sceneView is TransitionPreviewWindow instance && AnimancerUtilities.TryGetWrappedObject(Transition, out ITransitionGUI gui) && instance._TransitionProperty != null) { EditorGUI.BeginChangeCheck(); using (TransitionDrawer.DrawerContext.Get(instance._TransitionProperty)) { try { gui.OnPreviewSceneGUI(new TransitionPreviewDetails(animancer)); } catch (Exception exception) { Debug.LogException(exception); } } if (EditorGUI.EndChangeCheck()) AnimancerGUI.RepaintEverything(); } } /************************************************************************************************************************/ /// Is the `obj` a in the preview scene? public bool IsSceneObject(Object obj) { return obj is GameObject gameObject && gameObject.transform.IsChildOf(PreviewSceneRoot); } /************************************************************************************************************************/ [SerializeField] private List _ExpandedHierarchy; /// A list of all objects with their child hierarchy expanded. public List ExpandedHierarchy { get { if (_ExpandedHierarchy == null) _ExpandedHierarchy = new List(); return _ExpandedHierarchy; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Cleanup /************************************************************************************************************************/ /// Called by . public void OnDisable() { EditorSceneManager.sceneOpening -= OnSceneOpening; EditorApplication.playModeStateChanged -= OnPlayModeChanged; duringSceneGui -= DoCustomGUI; DestroyAnimancerInstance(); EditorSceneManager.ClosePreviewScene(_Scene); } /************************************************************************************************************************/ /// Called by . public void OnDestroy() { if (PreviewSceneRoot != null) { DestroyImmediate(PreviewSceneRoot.gameObject); PreviewSceneRoot = null; } } /************************************************************************************************************************/ /// Destroys the . public void DestroyModelInstance() { DestroyAnimancerInstance(); if (InstanceRoot == null) return; DestroyImmediate(InstanceRoot.gameObject); InstanceRoot = null; InstanceAnimators = null; } /************************************************************************************************************************/ private void DestroyAnimancerInstance() { if (_Animancer == null) return; _Animancer.CancelPostUpdate(Animations.WindowMatchStateTime.Instance); _Animancer.DestroyGraph(); _Animancer = null; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } } } #endif