356 lines
13 KiB
C#
356 lines
13 KiB
C#
// 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
|
|
{
|
|
/// <summary>[Editor-Only] Manages the selection and instantiation of models for previewing animations.</summary>
|
|
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/AnimancerPreviewObject
|
|
[Serializable]
|
|
public class AnimancerPreviewObject : IDisposable
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Editor-Only] Handles events from an <see cref="AnimancerPreviewObject"/>.</summary>
|
|
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/IEventHandler
|
|
public interface IEventHandler
|
|
{
|
|
/// <summary>Called after the <see cref="InstanceObject"/> is instantiated.</summary>
|
|
void OnInstantiateObject();
|
|
|
|
/// <summary>Called after the <see cref="SelectedInstanceAnimator"/> is changed.</summary>
|
|
void OnSetSelectedAnimator();
|
|
|
|
/// <summary>Called after the <see cref="Graph"/> is initialized.</summary>
|
|
void OnCreateGraph();
|
|
}
|
|
|
|
/// <summary>An optional listener for this object's events.</summary>
|
|
[field: NonSerialized] public IEventHandler EventHandler { get; set; }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
[SerializeField]
|
|
private Transform _OriginalObject;
|
|
|
|
/// <summary>[<see cref="SerializeField"/>]
|
|
/// The original model which was instantiated to create the <see cref="InstanceObject"/>.
|
|
/// </summary>
|
|
public Transform OriginalObject
|
|
{
|
|
get => _OriginalObject;
|
|
set
|
|
{
|
|
_OriginalObject = value;
|
|
InstantiateObject();
|
|
|
|
if (value != null)
|
|
TransitionPreviewSettings.AddModel(value.gameObject);
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The object to instantiate the <see cref="InstanceObject"/> under.</summary>
|
|
[field: NonSerialized]
|
|
public Transform InstanceRoot { get; private set; }
|
|
|
|
/// <summary>The preview copy of the <see cref="OriginalObject"/>.</summary>
|
|
[field: NonSerialized]
|
|
public Transform InstanceObject { get; private set; }
|
|
|
|
/// <summary>The bounds of the <see cref="InstanceObject"/>.</summary>
|
|
[field: NonSerialized]
|
|
public Bounds InstanceBounds { get; private set; }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The <see cref="Animator"/>s on the <see cref="InstanceObject"/> and its children.</summary>
|
|
[field: NonSerialized]
|
|
public Animator[] InstanceAnimators { get; private set; }
|
|
|
|
/// <summary>The type of the <see cref="SelectedInstanceAnimator"/>.</summary>
|
|
[field: NonSerialized]
|
|
public AnimationType SelectedInstanceType { get; private set; }
|
|
|
|
[SerializeField]
|
|
private int _SelectedInstanceAnimator;
|
|
|
|
/// <summary>The <see cref="Animator"/> component currently being used for the preview.</summary>
|
|
public Animator SelectedInstanceAnimator
|
|
{
|
|
get
|
|
{
|
|
if (InstanceAnimators.IsNullOrEmpty())
|
|
return null;
|
|
|
|
if (_SelectedInstanceAnimator >= InstanceAnimators.Length)
|
|
_SelectedInstanceAnimator = InstanceAnimators.Length - 1;
|
|
|
|
return InstanceAnimators[_SelectedInstanceAnimator];
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
[NonSerialized]
|
|
private AnimancerGraph _Graph;
|
|
|
|
/// <summary>The <see cref="AnimancerGraph"/> being used for the preview.</summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="AnimancerPreviewObject"/>
|
|
/// and calls <see cref="Initialize(Transform)"/>.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>Sets the <see cref="InstanceRoot"/> for this preview to work under.</summary>
|
|
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;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Cleans up this preview.</summary>
|
|
public void Dispose()
|
|
{
|
|
EditorSceneManager.sceneOpening -= OnSceneOpening;
|
|
EditorApplication.playModeStateChanged -= OnPlayModeChanged;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Called when entering or exiting Play Mode to destroy the <see cref="InstanceObject"/>.</summary>
|
|
private void OnPlayModeChanged(PlayModeStateChange change)
|
|
{
|
|
switch (change)
|
|
{
|
|
case PlayModeStateChange.ExitingEditMode:
|
|
case PlayModeStateChange.ExitingPlayMode:
|
|
DestroyInstanceObject();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Called when opening a scene to destroy the <see cref="InstanceObject"/>.</summary>
|
|
private void OnSceneOpening(string path, OpenSceneMode mode)
|
|
{
|
|
if (mode == OpenSceneMode.Single)
|
|
DestroyInstanceObject();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Destroys and re-instantiates the <see cref="InstanceObject"/>.</summary>
|
|
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<Animator>();
|
|
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();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Disables all unnecessary components on the `root` or its children.</summary>
|
|
private static void DisableUnnecessaryComponents(GameObject root)
|
|
{
|
|
var behaviours = root.GetComponentsInChildren<Behaviour>();
|
|
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;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Sets the <see cref="SelectedInstanceAnimator"/>.</summary>
|
|
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();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Destroys the <see cref="InstanceObject"/>.</summary>
|
|
public void DestroyInstanceObject()
|
|
{
|
|
DestroyGraph();
|
|
|
|
if (InstanceObject == null)
|
|
return;
|
|
|
|
Object.DestroyImmediate(InstanceObject.gameObject);
|
|
InstanceObject = null;
|
|
InstanceAnimators = null;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Destroys the <see cref="Graph"/>.</summary>
|
|
private void DestroyGraph()
|
|
{
|
|
if (_Graph == null)
|
|
return;
|
|
|
|
_Graph.Destroy();
|
|
_Graph = null;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Calls <see cref="TransitionPreviewSettings.TrySelectBestModel(object, Transform)"/>
|
|
/// if there is no <see cref="OriginalObject"/> yet.
|
|
/// </summary>
|
|
public void TrySelectBestModel(object animationClipSource)
|
|
{
|
|
if (OriginalObject == null)
|
|
OriginalObject = TransitionPreviewSettings.TrySelectBestModel(animationClipSource, InstanceRoot);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Creates an object using <see cref="HideFlags.HideAndDontSave"/>
|
|
/// without <see cref="HideFlags.NotEditable"/>.
|
|
/// </summary>
|
|
public static GameObject CreateEmpty(string name)
|
|
=> EditorUtility.CreateGameObjectWithHideFlags(
|
|
name,
|
|
HideFlags.HideInHierarchy | HideFlags.DontSave);
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|