chore: initial commit
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 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.Initialize(
|
||||
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
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c4b63a600a34734e859d9f1bfb96018
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,209 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static Animancer.Editor.AnimancerGUI;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// <summary>[Editor-Only] GUI utilities for <see cref="AnimancerPreviewObject"/>.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/AnimancerPreviewObjectGUI
|
||||
public static class AnimancerPreviewObjectGUI
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calculates the pixel height required to draw the `preview`.</summary>
|
||||
public static float CalculateHeight(AnimancerPreviewObject preview)
|
||||
{
|
||||
var lines = 1;
|
||||
|
||||
var instanceAnimators = preview.InstanceAnimators;
|
||||
if (instanceAnimators != null &&
|
||||
instanceAnimators.Length > 1)
|
||||
lines++;
|
||||
|
||||
return AnimancerGUI.CalculateHeight(lines);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the model selection GUI.</summary>
|
||||
public static void DoModelGUI(AnimancerPreviewObject preview)
|
||||
{
|
||||
var area = LayoutRect(CalculateHeight(preview));
|
||||
DoModelGUI(area, preview);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the model selection GUI.</summary>
|
||||
public static void DoModelGUI(Rect area, AnimancerPreviewObject preview)
|
||||
{
|
||||
var root = preview.OriginalObject;
|
||||
var model = root != null ? root.gameObject : null;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var warning = GetModelWarning(model, preview);
|
||||
var color = GUI.color;
|
||||
if (warning != null)
|
||||
GUI.color = WarningFieldColor;
|
||||
|
||||
using (var label = PooledGUIContent.Acquire("Model"))
|
||||
{
|
||||
var objectFieldArea = StealLineFromTop(ref area);
|
||||
if (DoDropdownObjectFieldGUI(objectFieldArea, label, true, ref model))
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(
|
||||
new("Default Humanoid"),
|
||||
model != null && model == TransitionPreviewSettings.GetOrCreateDefaultHumanoid(null),
|
||||
() => preview.OriginalObject
|
||||
= TransitionPreviewSettings.GetOrCreateDefaultHumanoid(preview.InstanceRoot).transform);
|
||||
menu.AddItem(
|
||||
new("Default Sprite"),
|
||||
model != null && model == TransitionPreviewSettings.GetDefaultSpriteIfAlreadyCreated(),
|
||||
() => preview.OriginalObject
|
||||
= TransitionPreviewSettings.GetOrCreateDefaultSprite(preview.InstanceRoot).transform);
|
||||
|
||||
var persistentModels = TransitionPreviewSettings.Models;
|
||||
var temporaryModels = TemporarySettings.PreviewModels;
|
||||
if (persistentModels.Count == 0 && temporaryModels.Count == 0)
|
||||
{
|
||||
menu.AddDisabledItem(new("No model prefabs have been used yet"));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModelSelectionFunctions(menu, preview, persistentModels, model);
|
||||
AddModelSelectionFunctions(menu, preview, temporaryModels, model);
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
|
||||
GUI.color = color;
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
preview.OriginalObject = model != null ? model.transform : null;
|
||||
|
||||
if (warning != null)
|
||||
EditorGUILayout.HelpBox(warning, MessageType.Warning, true);
|
||||
|
||||
DoAnimatorSelectorGUI(preview);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds menu functions for selecting each of the `models`.</summary>
|
||||
private static void AddModelSelectionFunctions(
|
||||
GenericMenu menu,
|
||||
AnimancerPreviewObject preview,
|
||||
List<GameObject> models,
|
||||
GameObject selected)
|
||||
{
|
||||
for (int i = models.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var model = models[i];
|
||||
var path = AssetDatabase.GetAssetPath(model);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
path = path.Replace('/', '\\');
|
||||
else
|
||||
path = model.name;
|
||||
|
||||
menu.AddItem(new(path), model == selected,
|
||||
() => preview.OriginalObject = model.transform);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a warning about the selected model or <c>null</c>.</summary>
|
||||
private static string GetModelWarning(
|
||||
GameObject model,
|
||||
AnimancerPreviewObject preview)
|
||||
{
|
||||
if (model == null)
|
||||
return "No Model is selected so nothing can be previewed.";
|
||||
|
||||
if (preview.SelectedInstanceAnimator == null)
|
||||
return "The selected Model has no Animator component.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Draws a button for selecting which <see cref="Animator"/> to control if there's more than one.
|
||||
/// </summary>
|
||||
private static void DoAnimatorSelectorGUI(AnimancerPreviewObject preview)
|
||||
{
|
||||
var instanceAnimators = preview.InstanceAnimators;
|
||||
if (instanceAnimators == null ||
|
||||
instanceAnimators.Length <= 1)
|
||||
return;
|
||||
|
||||
var area = LayoutSingleLineRect(SpacingMode.After);
|
||||
var labelArea = StealFromLeft(ref area, EditorGUIUtility.labelWidth, StandardSpacing);
|
||||
GUI.Label(labelArea, nameof(Animator));
|
||||
|
||||
var selectedAnimator = preview.SelectedInstanceAnimator;
|
||||
using (var label = PooledGUIContent.Acquire(
|
||||
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];
|
||||
var index = i;
|
||||
menu.AddItem(new(animator.name), animator == selectedAnimator, () =>
|
||||
{
|
||||
preview.SetSelectedAnimator(index);
|
||||
});
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static DragAndDropHandler<GameObject> _ModelDropHandler;
|
||||
private static AnimancerPreviewObject _ModelDropPreview;
|
||||
|
||||
/// <summary>Handles drag and drop events for preview models.</summary>
|
||||
public static void HandleDragAndDrop(Rect area, AnimancerPreviewObject preview)
|
||||
{
|
||||
_ModelDropPreview = preview;
|
||||
|
||||
_ModelDropHandler ??= (gameObject, isDrop) =>
|
||||
{
|
||||
if (!gameObject.TryGetComponent<Animator>(out _))
|
||||
return false;
|
||||
|
||||
if (isDrop)
|
||||
_ModelDropPreview.OriginalObject = gameObject.transform;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
_ModelDropPreview = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49b17596383f7a44abcf9ba98188ed9d
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,352 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static Animancer.Editor.AnimancerGUI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// <summary>[Editor-Only] Utility for rendering previews of animated objects.</summary>
|
||||
/// <remarks>Parts of this class are based on Unity's <see cref="MeshPreview"/>.</remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/AnimancerPreviewRenderer
|
||||
[Serializable]
|
||||
public class AnimancerPreviewRenderer :
|
||||
AnimancerPreviewObject.IEventHandler,
|
||||
IDisposable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized] private PreviewRenderUtility _PreviewRenderUtility;
|
||||
[NonSerialized] private Light[] _PreviewLights;
|
||||
|
||||
[SerializeField] private AnimancerPreviewObject _PreviewObject;
|
||||
[SerializeField] private Vector3 _OrthographicPosition = new(0.5f, 0.5f, -1);
|
||||
[SerializeField] private Vector2 _PreviewDirection = new(135, -30);
|
||||
[SerializeField] private Vector2 _LightDirection = new(-40, -40);
|
||||
[SerializeField] private Vector3 _PivotPositionOffset;
|
||||
[SerializeField] private float _ZoomFactor = 1f;
|
||||
|
||||
/// <summary>The root object in the preview scene.</summary>
|
||||
public Transform PreviewSceneRoot { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// An instance of the <see cref="TransitionPreviewSettings.SceneEnvironment"/>.
|
||||
/// A child of the <see cref="PreviewSceneRoot"/>.
|
||||
/// </summary>
|
||||
public GameObject EnvironmentInstance { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The object being previewed.</summary>
|
||||
public AnimancerPreviewObject PreviewObject
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializePreviewRenderUtility();
|
||||
return AnimancerPreviewObject.Initialize(ref _PreviewObject, this, PreviewSceneRoot);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Cleans up this renderer.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_PreviewObject?.Dispose();
|
||||
CleanupPreviewRenderUtility();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Calles when the <see cref="TransitionPreviewSettings.SceneEnvironment"/> is changed.</summary>
|
||||
private void OnEnvironmentPrefabChanged()
|
||||
{
|
||||
Object.DestroyImmediate(EnvironmentInstance);
|
||||
|
||||
var prefab = TransitionPreviewSettings.SceneEnvironment;
|
||||
if (prefab != null)
|
||||
EnvironmentInstance = Object.Instantiate(prefab, PreviewSceneRoot);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
void AnimancerPreviewObject.IEventHandler.OnInstantiateObject()
|
||||
{
|
||||
_PivotPositionOffset = _PreviewObject.InstanceBounds.center;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void AnimancerPreviewObject.IEventHandler.OnSetSelectedAnimator() { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
void AnimancerPreviewObject.IEventHandler.OnCreateGraph() { }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region GUI
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the preview.</summary>
|
||||
public void DoGUI(Rect area, GUIStyle background)
|
||||
{
|
||||
if (!DrawIsRenderTextureSupported(area))
|
||||
return;
|
||||
|
||||
var currentEvent = Event.current;
|
||||
|
||||
HandleCameraControlEvent(area, currentEvent);
|
||||
|
||||
if (currentEvent.type == EventType.Repaint)
|
||||
DrawPreview(area, background);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Shows a warning if the current device doesn't support render textures.</summary>
|
||||
private bool DrawIsRenderTextureSupported(Rect area)
|
||||
{
|
||||
if (ShaderUtil.hardwareSupportsRectRenderTexture)
|
||||
return true;
|
||||
|
||||
EditorGUI.DropShadowLabel(
|
||||
area,
|
||||
"Device doesn't support Render Textures\nUnable to render previews");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes the <see cref="_PreviewRenderUtility"/>.</summary>
|
||||
private void InitializePreviewRenderUtility()
|
||||
{
|
||||
if (_PreviewRenderUtility != null)
|
||||
return;
|
||||
|
||||
_PreviewRenderUtility = new();
|
||||
_PreviewRenderUtility.camera.fieldOfView = 30;
|
||||
_PreviewRenderUtility.camera.allowHDR = false;
|
||||
_PreviewRenderUtility.camera.allowMSAA = false;
|
||||
_PreviewRenderUtility.ambientColor = Grey(0.1f, 0);
|
||||
|
||||
_PreviewLights = _PreviewRenderUtility.lights;
|
||||
_PreviewLights[0].intensity = 1.4f;
|
||||
_PreviewLights[0].transform.rotation = Quaternion.Euler(40, 40, 0);
|
||||
_PreviewLights[1].intensity = 1.4f;
|
||||
|
||||
var root = AnimancerPreviewObject.CreateEmpty(nameof(AnimancerPreviewRenderer));
|
||||
_PreviewRenderUtility.AddSingleGO(root);
|
||||
PreviewSceneRoot = root.transform;
|
||||
|
||||
OnEnvironmentPrefabChanged();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Cleans up the <see cref="_PreviewRenderUtility"/>.</summary>
|
||||
private void CleanupPreviewRenderUtility()
|
||||
{
|
||||
_PreviewRenderUtility?.Cleanup();
|
||||
_PreviewRenderUtility = null;
|
||||
_PreviewLights = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Updates and renders the preview.</summary>
|
||||
private void DrawPreview(Rect area, GUIStyle background)
|
||||
{
|
||||
InitializePreviewRenderUtility();
|
||||
_PreviewRenderUtility.BeginPreview(area, background);
|
||||
UpdatePreviewRenderUtility();
|
||||
_PreviewRenderUtility.Render(true, true);
|
||||
_PreviewRenderUtility.EndAndDrawPreview(area);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Updates the preview rendering details.</summary>
|
||||
private void UpdatePreviewRenderUtility()
|
||||
{
|
||||
var previewObject = PreviewObject;
|
||||
if (previewObject.InstanceObject == null)
|
||||
return;
|
||||
|
||||
var rotation = Quaternion.Euler(_PreviewDirection.y, 0, 0) * Quaternion.Euler(0, _PreviewDirection.x, 0);
|
||||
previewObject.InstanceObject.rotation = rotation;
|
||||
|
||||
var size = previewObject.InstanceBounds.extents.magnitude;
|
||||
var position = _ZoomFactor * -4f * size * Vector3.forward + _PivotPositionOffset;
|
||||
|
||||
var camera = _PreviewRenderUtility.camera;
|
||||
camera.transform.SetPositionAndRotation(position, Quaternion.identity);
|
||||
camera.nearClipPlane = 0.0001f;
|
||||
camera.farClipPlane = 1000;
|
||||
camera.orthographic = false;
|
||||
|
||||
var lights = _PreviewLights;
|
||||
lights[0].intensity = 1.1f;
|
||||
lights[0].transform.rotation = Quaternion.Euler(-_LightDirection.y, -_LightDirection.x, 0);
|
||||
lights[1].intensity = 1.1f;
|
||||
lights[1].transform.rotation = Quaternion.Euler(_LightDirection.y, _LightDirection.x, 0);
|
||||
|
||||
_PreviewRenderUtility.ambientColor = Grey(0.1f, 0);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Camera Controls
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly int CameraControlsHint = "CameraControls".GetHashCode();
|
||||
|
||||
/// <summary>Handles GUI events for controlling the preview camera.</summary>
|
||||
private void HandleCameraControlEvent(Rect area, Event currentEvent)
|
||||
{
|
||||
if (currentEvent.button == 1)
|
||||
{
|
||||
if (currentEvent.alt)
|
||||
_LightDirection = Drag2D(_LightDirection, area);// Could draw some lines to show the light directions.
|
||||
else
|
||||
_PreviewDirection = Drag2D(_PreviewDirection, area);
|
||||
}
|
||||
|
||||
var control = new GUIControl(area, currentEvent, CameraControlsHint);
|
||||
|
||||
switch (control.EventType)
|
||||
{
|
||||
case EventType.ScrollWheel:
|
||||
HandleScrollZoomEvent(area, currentEvent);
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (currentEvent.button <= 0 || currentEvent.button == 2)
|
||||
control.TryUseMouseDown();
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
control.TryUseMouseUp();
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (control.TryUseHotControl())
|
||||
HandleDragPanEvent(area, currentEvent);
|
||||
break;
|
||||
|
||||
case EventType.ValidateCommand:
|
||||
case EventType.ExecuteCommand:
|
||||
switch (currentEvent.commandName)
|
||||
{
|
||||
case Commands.FrameSelected:
|
||||
case Commands.FrameSelectedWithLock:
|
||||
FrameTarget();
|
||||
currentEvent.Use();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly int SliderHash = "Slider".GetHashCode();
|
||||
|
||||
/// <summary>Handles drag input within a given `area`.</summary>
|
||||
/// <remarks>Copied from Unity's <see cref="PreviewGUI.Drag2D"/>.</remarks>
|
||||
public static Vector2 Drag2D(Vector2 scrollPosition, Rect area)
|
||||
{
|
||||
var control = new GUIControl(area, SliderHash);
|
||||
|
||||
switch (control.EventType)
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (control.TryUseMouseDown() && area.width > 50)
|
||||
EditorGUIUtility.SetWantsMouseJumping(1);
|
||||
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (control.TryUseMouseUp())
|
||||
EditorGUIUtility.SetWantsMouseJumping(0);
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (control.TryUseHotControl())
|
||||
{
|
||||
var multiplier = control.Event.shift ? 3 : 1;
|
||||
var size = Mathf.Min(area.width, area.height);
|
||||
scrollPosition -= control.Event.delta * multiplier / size * 140;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return scrollPosition;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles a mouse scroll event to zoom the preview camera.</summary>
|
||||
private void HandleScrollZoomEvent(Rect area, Event currentEvent)
|
||||
{
|
||||
var delta = HandleUtility.niceMouseDeltaZoom * -0.025f;
|
||||
var zoom = _ZoomFactor * (1 + delta);
|
||||
zoom = Mathf.Clamp(zoom, 0.1f, 10);
|
||||
var vector = new Vector2(
|
||||
currentEvent.mousePosition.x / area.width,
|
||||
1 - currentEvent.mousePosition.y / area.height);
|
||||
var origin = _PreviewRenderUtility.camera.ViewportToWorldPoint(vector);
|
||||
var direction = _OrthographicPosition - origin;
|
||||
var position = origin + direction * (zoom / _ZoomFactor);
|
||||
_PreviewRenderUtility.camera.transform.position = position;
|
||||
|
||||
_ZoomFactor = zoom;
|
||||
currentEvent.Use();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Handles a mouse drag event to pan the preview camera.</summary>
|
||||
private void HandleDragPanEvent(Rect area, Event currentEvent)
|
||||
{
|
||||
var camera = _PreviewRenderUtility.camera;
|
||||
var direction = new Vector3(
|
||||
-currentEvent.delta.x * camera.pixelWidth / area.width,
|
||||
currentEvent.delta.y * camera.pixelHeight / area.height,
|
||||
0);
|
||||
|
||||
var position = camera.WorldToScreenPoint(_PivotPositionOffset);
|
||||
position += direction;
|
||||
|
||||
direction = camera.ScreenToWorldPoint(position) - _PivotPositionOffset;
|
||||
_PivotPositionOffset += direction;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Frames the preview object in the middle of the camera.</summary>
|
||||
private void FrameTarget()
|
||||
{
|
||||
_ZoomFactor = 1f;
|
||||
_OrthographicPosition = new(0.5f, 0.5f, -1f);
|
||||
_PivotPositionOffset = default;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 837499b597b0bd44592cc3c9f0ec67cd
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// An <see cref="IAnimancerComponent"/> which isn't actually a <see cref="Component"/>.
|
||||
/// </summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/DummyAnimancerComponent
|
||||
public class DummyAnimancerComponent : IAnimancerComponent
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="DummyAnimancerComponent"/>.</summary>
|
||||
public DummyAnimancerComponent(Animator animator, AnimancerGraph playable)
|
||||
{
|
||||
Animator = animator;
|
||||
Graph = playable;
|
||||
InitialUpdateMode = animator.updateMode;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool enabled => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GameObject gameObject => Animator.gameObject;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Animator Animator { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AnimancerGraph Graph { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsGraphInitialized => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ResetOnDisable => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AnimatorUpdateMode UpdateMode
|
||||
{
|
||||
get => Animator.updateMode;
|
||||
set => Animator.updateMode = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object GetKey(AnimationClip clip) => clip;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string AnimatorFieldName => null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ActionOnDisableFieldName => null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AnimatorUpdateMode? InitialUpdateMode { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Describes this and the <see cref="Animator"/>.</summary>
|
||||
public override string ToString()
|
||||
=> $"{nameof(DummyAnimancerComponent)}({(Animator != null ? Animator.name : "Destroyed")})";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab737c58ad615ad41967f853ac3a3e06
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,350 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// <summary>[Editor-Only] Utility for playing through transition previews.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewPlayer
|
||||
[Serializable]
|
||||
public class TransitionPreviewPlayer : IDisposable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeReference] private ITransition _FromTransition;
|
||||
[SerializeReference] private ITransition _ToTransition;
|
||||
|
||||
/// <summary>The animation to play first.</summary>
|
||||
public ref ITransition FromTransition
|
||||
=> ref _FromTransition;
|
||||
|
||||
/// <summary>The animation to transition into.</summary>
|
||||
public ref ITransition ToTransition
|
||||
=> ref _ToTransition;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private AnimancerGraph _Graph;
|
||||
|
||||
/// <summary>The graph used to play the animations.</summary>
|
||||
public AnimancerGraph Graph
|
||||
{
|
||||
get => _Graph;
|
||||
set
|
||||
{
|
||||
_Graph = value;
|
||||
Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The minimum amount of time to play the <see cref="FromTransition"/>
|
||||
/// before the <see cref="ToTransition"/> starts (in seconds).
|
||||
/// </summary>
|
||||
public float FromDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum amount of time to continue playing
|
||||
/// after the <see cref="ToTransition"/> started (in seconds).
|
||||
/// </summary>
|
||||
public float ToDuration { get; set; }
|
||||
|
||||
/// <summary>The speed at which the preview plays.</summary>
|
||||
public float Speed { get; set; } = 1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private float _FadeDuration = float.NaN;
|
||||
|
||||
/// <summary>The <see cref="ITransition.FadeDuration"/>.</summary>
|
||||
/// <remarks><see cref="float.NaN"/> uses the value from the <see cref="ToTransition"/>.</remarks>
|
||||
public float FadeDuration
|
||||
{
|
||||
get => float.IsNaN(_FadeDuration) && ToTransition.IsValid()
|
||||
? ToTransition.FadeDuration
|
||||
: _FadeDuration;
|
||||
set => _FadeDuration = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private float _NormalizedStartTime = float.NaN;
|
||||
|
||||
/// <summary>The <see cref="ITransition.NormalizedStartTime"/>.</summary>
|
||||
/// <remarks><see cref="float.NaN"/> uses the value from the <see cref="ToTransition"/>.</remarks>
|
||||
public float NormalizedStartTime
|
||||
{
|
||||
get => float.IsNaN(_NormalizedStartTime) && ToTransition.IsValid()
|
||||
? ToTransition.NormalizedStartTime
|
||||
: _NormalizedStartTime;
|
||||
set => _NormalizedStartTime = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The lowest allowed <see cref="CurrentTime"/>.</summary>
|
||||
/// <remarks>
|
||||
/// This is the lower of the negative <see cref="FromDuration"/>
|
||||
/// or the negative duration of the <see cref="FromTransition"/>.
|
||||
/// </remarks>
|
||||
public float MinTime { get; private set; }
|
||||
|
||||
/// <summary>The highest allowed <see cref="CurrentTime"/>.</summary>
|
||||
/// <remarks>
|
||||
/// This is the higher of the <see cref="ToDuration"/> or <see cref="FadeDuration"/>
|
||||
/// or the duration of the <see cref="ToTransition"/>.
|
||||
/// </remarks>
|
||||
public float MaxTime { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Recalculated the <see cref="MinTime"/> and <see cref="MaxTime"/>.</summary>
|
||||
public void RecalculateTimeBounds()
|
||||
{
|
||||
MinTime = -FromDuration;
|
||||
|
||||
if (_FromTransition.IsValid() &&
|
||||
AnimancerUtilities.TryCalculateDuration(_FromTransition, out var duration))
|
||||
MinTime = Math.Min(MinTime, -duration);
|
||||
|
||||
MaxTime = ToDuration;
|
||||
|
||||
var fadeDuration = FadeDuration;
|
||||
if (!float.IsNaN(fadeDuration))
|
||||
MaxTime = Math.Max(ToDuration, fadeDuration);
|
||||
|
||||
if (_ToTransition.IsValid() &&
|
||||
AnimancerUtilities.TryCalculateDuration(_ToTransition, out duration))
|
||||
MaxTime = Math.Max(MaxTime, duration);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Converts normalized time to seconds.</summary>
|
||||
public float LerpTimeUnclamped(float normalizedTime)
|
||||
=> Mathf.LerpUnclamped(MinTime, MaxTime, normalizedTime);
|
||||
|
||||
/// <summary>Converts seconds to normalized time.</summary>
|
||||
public float InverseLerpTimeUnclamped(float time)
|
||||
=> AnimancerUtilities.InverseLerpUnclamped(MinTime, MaxTime, time);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private float _CurrentTime = float.NaN;
|
||||
|
||||
/// <summary>The amount of time that has passed since the <see cref="ToTransition"/> started (in seconds).</summary>
|
||||
/// <remarks>This value goes from the <see cref="MinTime"/> to the <see cref="MaxTime"/>.</remarks>
|
||||
public float CurrentTime
|
||||
{
|
||||
get => float.IsNaN(_CurrentTime)
|
||||
? MinTime
|
||||
: _CurrentTime;
|
||||
set
|
||||
{
|
||||
_CurrentTime = value;
|
||||
Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The amount of time that has passed since the <see cref="ToTransition"/> started (normalized).</summary>
|
||||
/// <remarks>0 is at the <see cref="MinTime"/> and 1 is at the <see cref="MaxTime"/>.</remarks>
|
||||
public float NormalizedTime
|
||||
{
|
||||
get => InverseLerpTimeUnclamped(CurrentTime);
|
||||
set => CurrentTime = LerpTimeUnclamped(value);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private bool _IsPlaying;
|
||||
|
||||
/// <summary>Is the preview currently playing?</summary>
|
||||
public bool IsPlaying
|
||||
{
|
||||
get => _IsPlaying;
|
||||
set
|
||||
{
|
||||
if (_IsPlaying == value)
|
||||
return;
|
||||
|
||||
_IsPlaying = value;
|
||||
|
||||
if (_IsPlaying)
|
||||
{
|
||||
_LastUpdateTime = TimeSinceStartup;
|
||||
|
||||
EditorApplication.update += Update;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorApplication.update -= Update;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Cleans up this player.</summary>
|
||||
public void Dispose()
|
||||
=> IsPlaying = false;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary><see cref="EditorApplication.timeSinceStartup"/></summary>
|
||||
private static double TimeSinceStartup
|
||||
=> EditorApplication.timeSinceStartup;
|
||||
|
||||
private double _LastUpdateTime;
|
||||
|
||||
/// <summary>Updates the preview time while playing.</summary>
|
||||
private void Update()
|
||||
{
|
||||
if (Graph == null || !Graph.IsValidOrDispose())
|
||||
{
|
||||
EditorApplication.update -= Update;
|
||||
return;
|
||||
}
|
||||
|
||||
var time = TimeSinceStartup;
|
||||
var deltaTime = time - _LastUpdateTime;
|
||||
_LastUpdateTime = time;
|
||||
|
||||
CurrentTime += ((float)deltaTime) * Speed;
|
||||
|
||||
AnimancerGUI.RepaintEverything();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Applies the animations at the <see cref="CurrentTime"/>.</summary>
|
||||
private void Evaluate()
|
||||
{
|
||||
if (Graph == null)
|
||||
return;
|
||||
|
||||
Graph.PauseGraph();
|
||||
Graph.Stop();
|
||||
|
||||
if (_FromTransition.IsValid())
|
||||
{
|
||||
if (_ToTransition.IsValid())
|
||||
{
|
||||
Apply(CurrentTime, _FromTransition, _ToTransition);
|
||||
}
|
||||
else
|
||||
{
|
||||
var minTime = MinTime;
|
||||
Apply(CurrentTime - minTime, -minTime, _FromTransition, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_ToTransition.IsValid())
|
||||
Apply(CurrentTime, MaxTime, _ToTransition, true);
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
Graph.Evaluate();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Applies the animations at the `currentTime`.</summary>
|
||||
private void Apply(
|
||||
float currentTime,
|
||||
ITransition from,
|
||||
ITransition to)
|
||||
{
|
||||
var layer = Graph.Layers[0];
|
||||
|
||||
// Playing From.
|
||||
if (currentTime < 0)
|
||||
{
|
||||
var state = layer.Play(from);
|
||||
state.Time += state.NormalizedEndTime * state.Length + currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
var maxTime = MaxTime;
|
||||
|
||||
// Fading.
|
||||
if (currentTime < maxTime)
|
||||
{
|
||||
var state = layer.Play(from);
|
||||
state.Time += state.NormalizedEndTime * state.Length + currentTime;
|
||||
|
||||
state = layer.Play(to, FadeDuration, to.FadeMode);
|
||||
|
||||
var normalizedStartTime = NormalizedStartTime;
|
||||
if (!float.IsNaN(normalizedStartTime))
|
||||
state.NormalizedTime = normalizedStartTime;
|
||||
|
||||
state.Time += currentTime;
|
||||
|
||||
var fade = state.FadeGroup;
|
||||
if (fade != null)
|
||||
{
|
||||
fade.NormalizedTime += currentTime * fade.FadeSpeed;
|
||||
fade.ApplyWeights();
|
||||
}
|
||||
}
|
||||
// Playing To.
|
||||
else
|
||||
{
|
||||
var state = layer.Play(to);
|
||||
|
||||
var normalizedStartTime = NormalizedStartTime;
|
||||
if (!float.IsNaN(normalizedStartTime))
|
||||
state.NormalizedTime = normalizedStartTime;
|
||||
|
||||
state.Time += currentTime;
|
||||
|
||||
// Finished.
|
||||
if (currentTime >= maxTime)
|
||||
{
|
||||
_CurrentTime = MinTime;
|
||||
state.Time = _CurrentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Applies the animation at the `currentTime`.</summary>
|
||||
private void Apply(
|
||||
float currentTime,
|
||||
float endTime,
|
||||
ITransition transition,
|
||||
bool applyNormalizedStartTime)
|
||||
{
|
||||
var state = Graph.Layers[0].Play(transition);
|
||||
|
||||
if (currentTime < endTime)// Playing.
|
||||
{
|
||||
state.Time = currentTime;
|
||||
|
||||
if (applyNormalizedStartTime)
|
||||
{
|
||||
var normalizedStartTime = NormalizedStartTime;
|
||||
if (!float.IsNaN(normalizedStartTime))
|
||||
state.NormalizedTime += normalizedStartTime;
|
||||
}
|
||||
}
|
||||
else// Finished.
|
||||
{
|
||||
_CurrentTime = MinTime;
|
||||
state.Time = _CurrentTime;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c00a65de31bb6fe46b447ccb0f346f14
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user