// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // #if UNITY_EDITOR && UNITY_IMGUI using System; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer.Editor.Previews { /// [Editor-Only] Manages the selection and instantiation of models for previewing animations. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/AnimancerPreviewObject [Serializable] public class AnimancerPreviewObject : IDisposable { /************************************************************************************************************************/ /// [Editor-Only] Handles events from an . /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/IEventHandler public interface IEventHandler { /// Called after the is instantiated. void OnInstantiateObject(); /// Called after the is changed. void OnSetSelectedAnimator(); /// Called after the is initialized. void OnCreateGraph(); } /// An optional listener for this object's events. [field: NonSerialized] public IEventHandler EventHandler { get; set; } /************************************************************************************************************************/ [SerializeField] private Transform _OriginalObject; /// [] /// The original model which was instantiated to create the . /// public Transform OriginalObject { get => _OriginalObject; set { _OriginalObject = value; InstantiateObject(); if (value != null) TransitionPreviewSettings.AddModel(value.gameObject); } } /************************************************************************************************************************/ /// The object to instantiate the under. [field: NonSerialized] public Transform InstanceRoot { get; private set; } /// The preview copy of the . [field: NonSerialized] public Transform InstanceObject { get; private set; } /// The bounds of the . [field: NonSerialized] public Bounds InstanceBounds { get; private set; } /************************************************************************************************************************/ /// The s on the and its children. [field: NonSerialized] public Animator[] InstanceAnimators { get; private set; } /// The type of the . [field: NonSerialized] public AnimationType SelectedInstanceType { get; private set; } [SerializeField] private int _SelectedInstanceAnimator; /// The component currently being used for the preview. public Animator SelectedInstanceAnimator { get { if (InstanceAnimators.IsNullOrEmpty()) return null; if (_SelectedInstanceAnimator >= InstanceAnimators.Length) _SelectedInstanceAnimator = InstanceAnimators.Length - 1; return InstanceAnimators[_SelectedInstanceAnimator]; } } /************************************************************************************************************************/ [NonSerialized] private AnimancerGraph _Graph; /// The being used for the preview. public AnimancerGraph Graph { get { if ((_Graph == null || !_Graph.IsValidOrDispose()) && InstanceObject != null) { _Graph = null; var animator = SelectedInstanceAnimator; if (animator != null) { AnimancerGraph.SetNextGraphName($"{animator.name} (Animancer Preview)"); _Graph = new AnimancerGraph(); _Graph.CreateOutput( new DummyAnimancerComponent(animator, _Graph)); EventHandler?.OnCreateGraph(); } } return _Graph; } } /************************************************************************************************************************/ /// /// Creates a new /// and calls . /// public static AnimancerPreviewObject Initialize( ref AnimancerPreviewObject preview, IEventHandler eventHandler, Transform instanceRoot) { preview ??= new(); preview.EventHandler = eventHandler; preview.Initialize(instanceRoot); return preview; } /************************************************************************************************************************/ [NonSerialized] private bool _HasInitialized; /// Sets the for this preview to work under. public void Initialize(Transform instanceRoot) { if (InstanceRoot != instanceRoot) { DestroyInstanceObject(); InstanceRoot = instanceRoot; } if (InstanceObject == null) InstantiateObject(); if (_HasInitialized) return; _HasInitialized = true; EditorSceneManager.sceneOpening += OnSceneOpening; EditorApplication.playModeStateChanged += OnPlayModeChanged; } /************************************************************************************************************************/ /// Cleans up this preview. public void Dispose() { EditorSceneManager.sceneOpening -= OnSceneOpening; EditorApplication.playModeStateChanged -= OnPlayModeChanged; } /************************************************************************************************************************/ /// Called when entering or exiting Play Mode to destroy the . private void OnPlayModeChanged(PlayModeStateChange change) { switch (change) { case PlayModeStateChange.ExitingEditMode: case PlayModeStateChange.ExitingPlayMode: DestroyInstanceObject(); break; } } /************************************************************************************************************************/ /// Called when opening a scene to destroy the . private void OnSceneOpening(string path, OpenSceneMode mode) { if (mode == OpenSceneMode.Single) DestroyInstanceObject(); } /************************************************************************************************************************/ /// Destroys and re-instantiates the . private void InstantiateObject() { if (AnimancerEditorUtilities.IsChangingPlayMode) return; DestroyInstanceObject(); if (_OriginalObject == null || InstanceRoot == null) return; InstanceRoot.gameObject.SetActive(false); InstanceObject = Object.Instantiate(_OriginalObject, InstanceRoot); InstanceObject.localPosition = default; InstanceObject.name = _OriginalObject.name; InstanceBounds = AnimancerEditorUtilities.CalculateBounds(InstanceObject); DisableUnnecessaryComponents(InstanceObject.gameObject); InstanceAnimators = InstanceObject.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.applyRootMotion = true; } InstanceRoot.gameObject.SetActive(true); SetSelectedAnimator(_SelectedInstanceAnimator); EventHandler?.OnInstantiateObject(); } /************************************************************************************************************************/ /// 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) { DestroyGraph(); 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); } else { SelectedInstanceType = default; } EventHandler?.OnSetSelectedAnimator(); } /************************************************************************************************************************/ /// Destroys the . public void DestroyInstanceObject() { DestroyGraph(); if (InstanceObject == null) return; Object.DestroyImmediate(InstanceObject.gameObject); InstanceObject = null; InstanceAnimators = null; } /************************************************************************************************************************/ /// Destroys the . private void DestroyGraph() { if (_Graph == null) return; _Graph.Destroy(); _Graph = null; } /************************************************************************************************************************/ /// /// Calls /// if there is no yet. /// public void TrySelectBestModel(object animationClipSource) { if (OriginalObject == null) OriginalObject = TransitionPreviewSettings.TrySelectBestModel(animationClipSource, InstanceRoot); } /************************************************************************************************************************/ /// /// Creates an object using /// without . /// public static GameObject CreateEmpty(string name) => EditorUtility.CreateGameObjectWithHideFlags( name, HideFlags.HideInHierarchy | HideFlags.DontSave); /************************************************************************************************************************/ } } #endif