// 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