chore: initial commit
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// <summary>[Editor-Only] A minimal <see cref="ITransition"/> to preview an <see cref="AnimationClip"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
|
||||
/// Previews</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/AnimationClipPreview
|
||||
///
|
||||
[AnimancerHelpUrl(typeof(AnimationClipPreview))]
|
||||
internal class AnimationClipPreview : ScriptableObject
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private Transition _Transition;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[Serializable]
|
||||
[Obsolete("Only intended for internal use.")]// Prevent this type from showing up in [SerializeReference] fields.
|
||||
private class Transition : ITransition, IAnimationClipCollection
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private AnimationClip _Clip;
|
||||
public ref AnimationClip Clip => ref _Clip;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public object Key => _Clip;
|
||||
public float FadeDuration => 0;
|
||||
public FadeMode FadeMode => default;
|
||||
public AnimancerState CreateState() => new ClipState(_Clip);
|
||||
public void Apply(AnimancerState state) { }
|
||||
|
||||
public bool IsValid => _Clip != null;
|
||||
public bool IsLooping => _Clip.isLooping;
|
||||
public float NormalizedStartTime { get => float.NaN; set => throw new NotSupportedException(); }
|
||||
public float MaximumLength => _Clip.length;
|
||||
public float Speed { get => 1; set => throw new NotSupportedException(); }
|
||||
|
||||
AnimancerEvent.Sequence IHasEvents.Events => null;
|
||||
AnimancerEvent.Sequence.Serializable IHasEvents.SerializedEvents
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public void GatherAnimationClips(ICollection<AnimationClip> clips) => clips.Add(_Clip);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[MenuItem("CONTEXT/" + nameof(AnimationClip) + "/Preview")]
|
||||
private static void Preview(MenuCommand command)
|
||||
{
|
||||
var preview = FindObjectOfType<AnimationClipPreview>();
|
||||
if (preview == null)
|
||||
{
|
||||
preview = CreateInstance<AnimationClipPreview>();
|
||||
preview.hideFlags = HideFlags.HideInHierarchy | HideFlags.DontSave;
|
||||
}
|
||||
|
||||
preview._Transition = new Transition
|
||||
{
|
||||
Clip = (AnimationClip)command.context
|
||||
};
|
||||
|
||||
var serializedObject = new SerializedObject(preview);
|
||||
var property = serializedObject.FindProperty(nameof(_Transition));
|
||||
|
||||
TransitionPreviewWindow.OpenOrClose(property);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec38ac7d3e85249448cd4a4cdf7a73cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,526 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
|
||||
|
||||
using Animancer.Units;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// <summary>Persistent settings for the <see cref="TransitionPreviewWindow"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
|
||||
/// Previews</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewSettings
|
||||
[Serializable, InternalSerializableType]
|
||||
public class TransitionPreviewSettings : AnimancerSettingsGroup
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DisplayName
|
||||
=> "Transition Previews";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Index
|
||||
=> 3;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static TransitionPreviewSettings Instance
|
||||
=> AnimancerSettingsGroup<TransitionPreviewSettings>.Instance;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Draws the Inspector GUI for these settings.</summary>
|
||||
public static void DoInspectorGUI()
|
||||
{
|
||||
AnimancerSettings.SerializedObject.Update();
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
DoMiscGUI();
|
||||
DoEnvironmentGUI();
|
||||
DoModelsGUI();
|
||||
DoHierarchyGUI();
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
AnimancerSettings.SerializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Misc
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoMiscGUI()
|
||||
{
|
||||
Instance.DoPropertyField(nameof(_AutoClose));
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Should this window automatically close if the target object is destroyed?")]
|
||||
private bool _AutoClose = true;
|
||||
|
||||
/// <summary>Should this window automatically close if the target object is destroyed?</summary>
|
||||
public static bool AutoClose
|
||||
=> Instance._AutoClose;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Should the scene lighting be enabled?")]
|
||||
private bool _SceneLighting = false;
|
||||
|
||||
/// <summary>Should the scene lighting be enabled?</summary>
|
||||
public static bool SceneLighting
|
||||
{
|
||||
get => Instance._SceneLighting;
|
||||
set
|
||||
{
|
||||
if (SceneLighting == value)
|
||||
return;
|
||||
|
||||
var property = Instance.GetSerializedProperty(nameof(_SceneLighting));
|
||||
property.boolValue = value;
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Should the skybox be visible?")]
|
||||
private bool _ShowSkybox = false;
|
||||
|
||||
/// <summary>Should the skybox be visible?</summary>
|
||||
public static bool ShowSkybox
|
||||
{
|
||||
get => Instance._ShowSkybox;
|
||||
set
|
||||
{
|
||||
if (ShowSkybox == value)
|
||||
return;
|
||||
|
||||
var property = Instance.GetSerializedProperty(nameof(_ShowSkybox));
|
||||
property.boolValue = value;
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Seconds(Rule = Validate.Value.IsNotNegative)]
|
||||
[DefaultValue(0.02f)]
|
||||
[Tooltip("The amount of time that will be added by a single frame step")]
|
||||
private float _FrameStep = 0.02f;
|
||||
|
||||
/// <summary>The amount of time that will be added by a single frame step (in seconds).</summary>
|
||||
public static float FrameStep
|
||||
=> AnimancerSettingsGroup<TransitionPreviewSettings>.Instance._FrameStep;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Environment
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("If set, the default preview scene lighting will be replaced with this prefab.")]
|
||||
private GameObject _SceneEnvironment;
|
||||
|
||||
/// <summary>If set, the default preview scene lighting will be replaced with this prefab.</summary>
|
||||
public static GameObject SceneEnvironment
|
||||
=> Instance._SceneEnvironment;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoEnvironmentGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var property = Instance.DoPropertyField(nameof(_SceneEnvironment));
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
TransitionPreviewWindow.InstanceScene.OnEnvironmentPrefabChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Models
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoModelsGUI()
|
||||
{
|
||||
var property = ModelsProperty;
|
||||
|
||||
var area = AnimancerGUI.LayoutSingleLineRect();
|
||||
|
||||
var valueArea = area;
|
||||
valueArea.xMin = EditorGUIUtility.labelWidth - 10;
|
||||
|
||||
area.xMax = valueArea.xMin - AnimancerGUI.StandardSpacing;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var count = property.arraySize = EditorGUI.DelayedIntField(valueArea, property.arraySize);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
property.isExpanded = true;
|
||||
|
||||
HandleModelDragAndDrop(area);
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
property.isExpanded = EditorGUI.Foldout(area, property.isExpanded, nameof(Models), true);
|
||||
if (!property.isExpanded)
|
||||
return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
var model = property.GetArrayElementAtIndex(0);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUILayout.ObjectField(model);
|
||||
|
||||
if (GUILayout.Button(AnimancerIcons.ClearIcon("Remove model"), AnimancerGUI.NoPaddingButtonStyle))
|
||||
{
|
||||
Serialization.RemoveArrayElement(property, i);
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
AnimancerGUI.Deselect();
|
||||
GUIUtility.ExitGUI();
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Space(EditorStyles.objectField.margin.right);
|
||||
GUILayout.EndHorizontal();
|
||||
model.Next(false);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static DragAndDropHandler<GameObject> _ModelDropHandler;
|
||||
|
||||
private static void HandleModelDragAndDrop(Rect area)
|
||||
{
|
||||
_ModelDropHandler ??= (gameObject, isDrop) =>
|
||||
{
|
||||
if (!EditorUtility.IsPersistent(gameObject) ||
|
||||
Models.Contains(gameObject) ||
|
||||
gameObject.GetComponentInChildren<Animator>() == null)
|
||||
return false;
|
||||
|
||||
if (isDrop)
|
||||
{
|
||||
var modelsProperty = ModelsProperty;
|
||||
modelsProperty.serializedObject.Update();
|
||||
|
||||
var i = modelsProperty.arraySize;
|
||||
modelsProperty.arraySize = i + 1;
|
||||
modelsProperty.GetArrayElementAtIndex(i).objectReferenceValue = gameObject;
|
||||
modelsProperty.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
_ModelDropHandler.Handle(area);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private List<GameObject> _Models;
|
||||
|
||||
/// <summary>The models previously used in the <see cref="TransitionPreviewWindow"/>.</summary>
|
||||
/// <remarks>Accessing this property removes missing and duplicate models from the list.</remarks>
|
||||
public static List<GameObject> Models
|
||||
{
|
||||
get
|
||||
{
|
||||
var instance = Instance;
|
||||
AnimancerEditorUtilities.RemoveMissingAndDuplicates(ref instance._Models);
|
||||
return instance._Models;
|
||||
}
|
||||
}
|
||||
|
||||
private static SerializedProperty ModelsProperty
|
||||
=> Instance.GetSerializedProperty(nameof(_Models));
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds a `model` to the list of preview models.</summary>
|
||||
public static void AddModel(GameObject model)
|
||||
{
|
||||
if (model == GetOrCreateDefaultHumanoid(null) ||
|
||||
model == GetOrCreateDefaultSprite(null))
|
||||
return;
|
||||
|
||||
if (EditorUtility.IsPersistent(model))
|
||||
{
|
||||
AddModel(Models, model);
|
||||
AnimancerSettings.SetDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModel(TemporarySettings.PreviewModels, model);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddModel(List<GameObject> models, GameObject model)
|
||||
{
|
||||
// Remove if it was already there so that when we add it, it will be moved to the end.
|
||||
var index = models.LastIndexOf(model);// Search backwards because it's more likely to be near the end.
|
||||
if (index >= 0 && index < models.Count)
|
||||
models.RemoveAt(index);
|
||||
|
||||
models.Add(model);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GameObject _DefaultHumanoid;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default preview object for Humanoid animations
|
||||
/// if it has already been loaded.
|
||||
/// </summary>
|
||||
public static GameObject GetDefaultHumanoidIfAlreadyLoaded()
|
||||
=> _DefaultHumanoid;
|
||||
|
||||
/// <summary>Returns the default preview object for Humanoid animations.</summary>
|
||||
/// <remarks>A `parent` is only required if Animancer's or Unity's default objects fail to load.</remarks>
|
||||
public static GameObject GetOrCreateDefaultHumanoid(Transform parent)
|
||||
{
|
||||
if (_DefaultHumanoid != null)
|
||||
return _DefaultHumanoid;
|
||||
|
||||
// Try to load Animancer Humanoid.
|
||||
var path = AssetDatabase.GUIDToAssetPath("f976ca0fb1329b44a8bc3dcca706751a");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
_DefaultHumanoid = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
if (_DefaultHumanoid != null)
|
||||
return _DefaultHumanoid;
|
||||
}
|
||||
|
||||
// Otherwise try to load Unity's DefaultAvatar.
|
||||
_DefaultHumanoid = EditorGUIUtility.Load("Avatar/DefaultAvatar.fbx") as GameObject;
|
||||
|
||||
if (_DefaultHumanoid != null)
|
||||
return _DefaultHumanoid;
|
||||
|
||||
if (parent == null)
|
||||
return null;
|
||||
|
||||
// Otherwise just create an empty object.
|
||||
_DefaultHumanoid = EditorUtility.CreateGameObjectWithHideFlags(
|
||||
"DummyAvatar",
|
||||
HideFlags.HideAndDontSave,
|
||||
typeof(Animator));
|
||||
_DefaultHumanoid.transform.parent = parent;
|
||||
return _DefaultHumanoid;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GameObject _DefaultSprite;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default preview object for <see cref="Sprite"/> animations
|
||||
/// if it has already been created.
|
||||
/// </summary>
|
||||
public static GameObject GetDefaultSpriteIfAlreadyCreated()
|
||||
=> _DefaultSprite;
|
||||
|
||||
/// <summary>Returns the default preview object for <see cref="Sprite"/> animations.</summary>
|
||||
/// <remarks>A `parent` is required to create the object.</remarks>
|
||||
public static GameObject GetOrCreateDefaultSprite(Transform parent)
|
||||
{
|
||||
if (_DefaultSprite == null && parent != null)
|
||||
{
|
||||
_DefaultSprite = EditorUtility.CreateGameObjectWithHideFlags(
|
||||
"DefaultSprite",
|
||||
HideFlags.HideAndDontSave,
|
||||
typeof(Animator),
|
||||
typeof(SpriteRenderer));
|
||||
_DefaultSprite.transform.parent = parent;
|
||||
}
|
||||
|
||||
return _DefaultSprite;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Tries to choose the most appropriate model to use
|
||||
/// based on the properties animated by the `animationClipSource`.
|
||||
/// </summary>
|
||||
public static Transform TrySelectBestModel(
|
||||
object animationClipSource,
|
||||
Transform parent)
|
||||
{
|
||||
if (animationClipSource.IsNullOrDestroyed())
|
||||
return null;
|
||||
|
||||
using (SetPool<AnimationClip>.Instance.Acquire(out var clips))
|
||||
{
|
||||
clips.GatherFromSource(animationClipSource);
|
||||
if (clips.Count == 0)
|
||||
return null;
|
||||
|
||||
var model = TrySelectBestModel(clips, TemporarySettings.PreviewModels);
|
||||
if (model != null)
|
||||
return model;
|
||||
|
||||
model = TrySelectBestModel(clips, Models);
|
||||
if (model != null)
|
||||
return model;
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var type = AnimationBindings.GetAnimationType(clip);
|
||||
switch (type)
|
||||
{
|
||||
case AnimationType.Humanoid:
|
||||
return GetOrCreateDefaultHumanoid(parent).transform;
|
||||
|
||||
case AnimationType.Sprite:
|
||||
return GetOrCreateDefaultSprite(parent).transform;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static Transform TrySelectBestModel(HashSet<AnimationClip> clips, List<GameObject> models)
|
||||
{
|
||||
var animatableBindings = new HashSet<EditorCurveBinding>[models.Count];
|
||||
|
||||
for (int i = 0; i < models.Count; i++)
|
||||
{
|
||||
animatableBindings[i] = AnimationBindings.GetBindings(models[i]).ObjectBindings;
|
||||
}
|
||||
|
||||
var bestMatchIndex = -1;
|
||||
var bestMatchCount = 0;
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var clipBindings = AnimationBindings.GetBindings(clip);
|
||||
|
||||
for (int iModel = animatableBindings.Length - 1; iModel >= 0; iModel--)
|
||||
{
|
||||
var modelBindings = animatableBindings[iModel];
|
||||
var matches = 0;
|
||||
|
||||
for (int iBinding = 0; iBinding < clipBindings.Length; iBinding++)
|
||||
{
|
||||
if (modelBindings.Contains(clipBindings[iBinding]))
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (bestMatchCount < matches && matches > clipBindings.Length / 2)
|
||||
{
|
||||
bestMatchCount = matches;
|
||||
bestMatchIndex = iModel;
|
||||
|
||||
// If it matches all bindings, use it.
|
||||
if (bestMatchCount == clipBindings.Length)
|
||||
goto FoundBestMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FoundBestMatch:
|
||||
if (bestMatchIndex >= 0)
|
||||
return models[bestMatchIndex].transform;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Scene Hierarchy
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static void DoHierarchyGUI()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box);
|
||||
GUILayout.Label("Preview Scene Hierarchy");
|
||||
DoHierarchyGUI(TransitionPreviewWindow.InstanceScene.PreviewSceneRoot);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static GUIStyle _HierarchyButtonStyle;
|
||||
|
||||
private static void DoHierarchyGUI(Transform root)
|
||||
{
|
||||
var area = AnimancerGUI.LayoutSingleLineRect();
|
||||
|
||||
_HierarchyButtonStyle ??= new(EditorStyles.miniButton)
|
||||
{
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
};
|
||||
|
||||
if (GUI.Button(EditorGUI.IndentedRect(area), root.name, _HierarchyButtonStyle))
|
||||
{
|
||||
Selection.activeTransform = root;
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
var childCount = root.childCount;
|
||||
if (childCount == 0)
|
||||
return;
|
||||
|
||||
var expandedHierarchy = TransitionPreviewWindow.InstanceScene.ExpandedHierarchy;
|
||||
var index = expandedHierarchy != null ? expandedHierarchy.IndexOf(root) : -1;
|
||||
var isExpanded = EditorGUI.Foldout(area, index >= 0, GUIContent.none);
|
||||
if (isExpanded)
|
||||
{
|
||||
if (index < 0)
|
||||
expandedHierarchy.Add(root);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
for (int i = 0; i < childCount; i++)
|
||||
DoHierarchyGUI(root.GetChild(i));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
else if (index >= 0)
|
||||
{
|
||||
expandedHierarchy.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 384a95563a658c94583de0afc8fec816
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,457 @@
|
||||
// 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;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewWindow
|
||||
partial class TransitionPreviewWindow
|
||||
{
|
||||
/// <summary>Animation details for the <see cref="TransitionPreviewWindow"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
|
||||
/// Previews</see>
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
internal class Animations
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public const string
|
||||
PreviousAnimationKey = "Previous Animation",
|
||||
NextAnimationKey = "Next Animation";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[NonSerialized] private AnimationClip[] _OtherAnimations;
|
||||
|
||||
[SerializeField]
|
||||
private AnimationClip _PreviousAnimation;
|
||||
public AnimationClip PreviousAnimation => _PreviousAnimation;
|
||||
|
||||
[SerializeField]
|
||||
private AnimationClip _NextAnimation;
|
||||
public AnimationClip NextAnimation => _NextAnimation;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static AnimancerPreviewObject PreviewObject
|
||||
=> _Instance._Scene.PreviewObject;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public void DoGUI()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box);
|
||||
EditorGUILayout.LabelField("Preview Details", "(Not Serialized)");
|
||||
|
||||
var previewObject = PreviewObject;
|
||||
AnimancerPreviewObjectGUI.DoModelGUI(previewObject);
|
||||
|
||||
using (var label = PooledGUIContent.Acquire("Previous Animation",
|
||||
"The animation for the preview to play before the target transition"))
|
||||
{
|
||||
DoAnimationFieldGUI(label, ref _PreviousAnimation, (clip) => _PreviousAnimation = clip);
|
||||
}
|
||||
|
||||
var graph = previewObject.Graph;
|
||||
DoCurrentAnimationGUI(graph);
|
||||
|
||||
using (var label = PooledGUIContent.Acquire("Next Animation",
|
||||
"The animation for the preview to play after the target transition"))
|
||||
{
|
||||
DoAnimationFieldGUI(label, ref _NextAnimation, (clip) => _NextAnimation = clip);
|
||||
}
|
||||
|
||||
if (graph != null)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(!Transition.IsValid()))
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (graph.IsGraphPlaying)
|
||||
{
|
||||
if (CompactMiniButton(AnimancerIcons.PauseIcon))
|
||||
graph.PauseGraph();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CompactMiniButton(AnimancerIcons.StepBackwardIcon))
|
||||
StepBackward();
|
||||
|
||||
if (CompactMiniButton(AnimancerIcons.PlayIcon))
|
||||
PlaySequence(graph);
|
||||
|
||||
if (CompactMiniButton(AnimancerIcons.StepForwardIcon))
|
||||
StepForward();
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public void GatherAnimations()
|
||||
{
|
||||
AnimationGatherer.GatherFromGameObject(
|
||||
PreviewObject.OriginalObject.gameObject,
|
||||
ref _OtherAnimations,
|
||||
true);
|
||||
|
||||
if (_OtherAnimations.Length > 0 &&
|
||||
(_PreviousAnimation == null || _NextAnimation == null))
|
||||
{
|
||||
var defaultClip = _OtherAnimations[0];
|
||||
var defaultClipIsIdle = false;
|
||||
|
||||
for (int i = 0; i < _OtherAnimations.Length; i++)
|
||||
{
|
||||
var clip = _OtherAnimations[i];
|
||||
|
||||
if (defaultClipIsIdle && clip.name.Length > defaultClip.name.Length)
|
||||
continue;
|
||||
|
||||
if (clip.name.IndexOf("idle", StringComparison.CurrentCultureIgnoreCase) >= 0)
|
||||
{
|
||||
defaultClip = clip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_PreviousAnimation == null)
|
||||
_PreviousAnimation = defaultClip;
|
||||
if (_NextAnimation == null)
|
||||
_NextAnimation = defaultClip;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoAnimationFieldGUI(GUIContent label, ref AnimationClip clip, Action<AnimationClip> setClip)
|
||||
{
|
||||
var showDropdown = !_OtherAnimations.IsNullOrEmpty();
|
||||
|
||||
var area = LayoutSingleLineRect();
|
||||
if (DoDropdownObjectFieldGUI(area, label, showDropdown, ref clip))
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(new("None"), clip == null, () => setClip(null));
|
||||
|
||||
for (int i = 0; i < _OtherAnimations.Length; i++)
|
||||
{
|
||||
var animation = _OtherAnimations[i];
|
||||
menu.AddItem(new(animation.name), animation == clip, () => setClip(animation));
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoCurrentAnimationGUI(AnimancerGraph animancer)
|
||||
{
|
||||
string text;
|
||||
|
||||
if (animancer != null)
|
||||
{
|
||||
var transition = Transition;
|
||||
if (transition.IsValid() && transition.Key != null)
|
||||
text = animancer.States.GetOrCreate(transition).ToString();
|
||||
else
|
||||
text = transition?.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
text = _Instance._TransitionProperty.Property.GetFriendlyPath();
|
||||
}
|
||||
|
||||
if (text != null)
|
||||
EditorGUILayout.LabelField("Current Animation", text);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void PlaySequence(AnimancerGraph animancer)
|
||||
{
|
||||
if (_PreviousAnimation != null && _PreviousAnimation.length > 0)
|
||||
{
|
||||
PreviewObject.Graph.Stop();
|
||||
var fromState = animancer.States.GetOrCreate(PreviousAnimationKey, _PreviousAnimation, true);
|
||||
animancer.Layers[0].Play(fromState);
|
||||
OnPlayAnimation();
|
||||
fromState.TimeD = 0;
|
||||
|
||||
var warnings = OptionalWarning.UnsupportedEvents.DisableTemporarily();
|
||||
fromState.Events(this).EndEvent = new(1 / fromState.Length, PlayTransition);
|
||||
warnings.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayTransition();
|
||||
}
|
||||
|
||||
PreviewObject.Graph.UnpauseGraph();
|
||||
}
|
||||
|
||||
private void PlayTransition()
|
||||
{
|
||||
var transition = Transition;
|
||||
var animancer = PreviewObject.Graph;
|
||||
animancer.States.TryGet(transition, out var oldState);
|
||||
|
||||
var targetState = animancer.Layers[0].Play(transition);
|
||||
OnPlayAnimation();
|
||||
|
||||
if (oldState != null && oldState != targetState)
|
||||
oldState.Destroy();
|
||||
|
||||
var warnings = OptionalWarning.UnsupportedEvents.DisableTemporarily();
|
||||
targetState.Events(this).OnEnd = () =>
|
||||
{
|
||||
if (_NextAnimation != null)
|
||||
{
|
||||
var fadeDuration = AnimancerEvent.GetFadeOutDuration(
|
||||
targetState,
|
||||
AnimancerGraph.DefaultFadeDuration);
|
||||
PlayOther(NextAnimationKey, _NextAnimation, 0, fadeDuration);
|
||||
OnPlayAnimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
animancer.Layers[0].IncrementCommandCount();
|
||||
}
|
||||
};
|
||||
warnings.Enable();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Does nothing.</summary>
|
||||
/// <remarks>
|
||||
/// Doesn't trigger <see cref="OptionalWarning.UselessEvent"/>
|
||||
/// (unlike <see cref="AnimancerEvent.DummyCallback"/>).
|
||||
/// </remarks>
|
||||
public static readonly Action
|
||||
DummyCallback = () => { };
|
||||
|
||||
public void OnPlayAnimation()
|
||||
{
|
||||
var animancer = PreviewObject.Graph;
|
||||
if (animancer == null ||
|
||||
animancer.States.Current == null)
|
||||
return;
|
||||
|
||||
var state = animancer.States.Current;
|
||||
|
||||
state.RecreatePlayableRecursive();
|
||||
|
||||
if (state.HasEvents)
|
||||
{
|
||||
var warnings = OptionalWarning.UnsupportedEvents | OptionalWarning.ProOnly;
|
||||
warnings = warnings.DisableTemporarily();
|
||||
|
||||
var events = state.Events(this);
|
||||
events.OnEnd = DummyCallback;
|
||||
|
||||
for (int i = events.Count - 1; i >= 0; i--)
|
||||
events.SetCallback(i, DummyCallback);
|
||||
|
||||
warnings.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void StepBackward()
|
||||
=> StepTime(-TransitionPreviewSettings.FrameStep);
|
||||
|
||||
private void StepForward()
|
||||
=> StepTime(TransitionPreviewSettings.FrameStep);
|
||||
|
||||
private void StepTime(float timeOffset)
|
||||
{
|
||||
if (!TryShowTransitionPaused(out _, out _, out var state))
|
||||
return;
|
||||
|
||||
var length = state.Length;
|
||||
if (length != 0)
|
||||
timeOffset /= length;
|
||||
|
||||
NormalizedTime += timeOffset;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private float _NormalizedTime;
|
||||
|
||||
public float NormalizedTime
|
||||
{
|
||||
get => _NormalizedTime;
|
||||
set
|
||||
{
|
||||
if (!value.IsFinite())
|
||||
return;
|
||||
|
||||
_NormalizedTime = value;
|
||||
|
||||
if (!TryShowTransitionPaused(out var animancer, out var transition, out var state))
|
||||
return;
|
||||
|
||||
var length = state.Length;
|
||||
var speed = state.Speed;
|
||||
var time = value * length;
|
||||
var fadeDuration = transition.FadeDuration * Math.Abs(speed);
|
||||
|
||||
var startTime = TimelineGUI.GetStartTime(transition.NormalizedStartTime, speed, length);
|
||||
var normalizedEndTime = state.NormalizedEndTime;
|
||||
var endTime = normalizedEndTime * length;
|
||||
var fadeOutEnd = TimelineGUI.GetFadeOutEnd(speed, endTime, length);
|
||||
|
||||
if (speed < 0)
|
||||
{
|
||||
time = length - time;
|
||||
startTime = length - startTime;
|
||||
value = 1 - value;
|
||||
normalizedEndTime = 1 - normalizedEndTime;
|
||||
endTime = length - endTime;
|
||||
fadeOutEnd = length - fadeOutEnd;
|
||||
}
|
||||
|
||||
if (time < startTime)// Previous animation.
|
||||
{
|
||||
if (_PreviousAnimation != null)
|
||||
{
|
||||
PlayOther(PreviousAnimationKey, _PreviousAnimation, value);
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
else if (time < startTime + fadeDuration)// Fade from previous animation to the target.
|
||||
{
|
||||
if (_PreviousAnimation != null)
|
||||
{
|
||||
var fromState = PlayOther(PreviousAnimationKey, _PreviousAnimation, value);
|
||||
|
||||
state.IsPlaying = true;
|
||||
state.Weight = (time - startTime) / fadeDuration;
|
||||
fromState.Weight = 1 - state.Weight;
|
||||
}
|
||||
}
|
||||
else if (_NextAnimation != null)
|
||||
{
|
||||
if (value < normalizedEndTime)
|
||||
{
|
||||
// Just the main state.
|
||||
}
|
||||
else
|
||||
{
|
||||
var toState = PlayOther(NextAnimationKey, _NextAnimation, value - normalizedEndTime);
|
||||
|
||||
if (time < fadeOutEnd)// Fade from the target transition to the next animation.
|
||||
{
|
||||
state.IsPlaying = true;
|
||||
toState.Weight = (time - endTime) / (fadeOutEnd - endTime);
|
||||
state.Weight = 1 - toState.Weight;
|
||||
}
|
||||
// Else just the next animation.
|
||||
}
|
||||
}
|
||||
|
||||
if (speed < 0)
|
||||
value = 1 - value;
|
||||
|
||||
state.MoveTime(state.Weight > 0 ? value : 0, true);
|
||||
animancer.Evaluate();
|
||||
|
||||
RepaintEverything();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private bool TryShowTransitionPaused(
|
||||
out AnimancerGraph animancer, out ITransition transition, out AnimancerState state)
|
||||
{
|
||||
animancer = PreviewObject.Graph;
|
||||
transition = Transition;
|
||||
|
||||
if (animancer == null || !transition.IsValid())
|
||||
{
|
||||
state = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
state = animancer.Layers[0].Play(transition, 0);
|
||||
OnPlayAnimation();
|
||||
animancer.PauseGraph();
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private AnimancerState PlayOther(
|
||||
object key,
|
||||
AnimationClip animation,
|
||||
float normalizedTime,
|
||||
float fadeDuration = 0)
|
||||
{
|
||||
var animancer = PreviewObject.Graph;
|
||||
var state = animancer.States.GetOrCreate(key, animation, true);
|
||||
state = animancer.Layers[0].Play(state, fadeDuration);
|
||||
OnPlayAnimation();
|
||||
|
||||
normalizedTime *= state.Length;
|
||||
state.Time = normalizedTime.IsFinite() ? normalizedTime : 0;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal class WindowMatchStateTime : Updatable
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (_Instance == null ||
|
||||
!AnimancerGraph.Current.IsGraphPlaying)
|
||||
return;
|
||||
|
||||
var transition = Transition;
|
||||
if (transition == null)
|
||||
return;
|
||||
|
||||
if (AnimancerGraph.Current.States.TryGet(transition, out var state))
|
||||
_Instance._Animations._NormalizedTime = state.NormalizedTime;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override string ToString()
|
||||
=> nameof(WindowMatchStateTime);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 662c4f0d4e803a846bef6fa3c0f8f30b
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,159 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewWindow
|
||||
partial class TransitionPreviewWindow
|
||||
{
|
||||
/// <summary>[Internal] Custom Inspector for the <see cref="TransitionPreviewWindow"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
|
||||
/// Previews</see>
|
||||
/// </remarks>
|
||||
[CustomEditor(typeof(TransitionPreviewWindow))]
|
||||
internal class Inspector : UnityEditor.Editor
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static readonly string[]
|
||||
TabNames = { "Preview", "Settings" };
|
||||
|
||||
private const int
|
||||
PreviewTab = 0,
|
||||
SettingsTab = 1;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private int _CurrentTab;
|
||||
|
||||
private readonly AnimancerGraphDrawer
|
||||
PlayableDrawer = new();
|
||||
|
||||
public TransitionPreviewWindow Target { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
=> false;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
GUILayout.Space(AnimancerGUI.StandardSpacing * 2);
|
||||
|
||||
Target = (TransitionPreviewWindow)target;
|
||||
|
||||
if (Event.current.type == EventType.MouseDown)
|
||||
Target.ShowTab();
|
||||
|
||||
_CurrentTab = GUILayout.Toolbar(_CurrentTab, TabNames);
|
||||
_CurrentTab = Mathf.Clamp(_CurrentTab, 0, TabNames.Length - 1);
|
||||
|
||||
switch (_CurrentTab)
|
||||
{
|
||||
case PreviewTab: DoPreviewInspectorGUI(); break;
|
||||
case SettingsTab: TransitionPreviewSettings.DoInspectorGUI(); break;
|
||||
default: GUILayout.Label("Tab index is out of bounds"); break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoPreviewInspectorGUI()
|
||||
{
|
||||
if (!Target._TransitionProperty.IsValid())
|
||||
{
|
||||
EditorGUILayout.HelpBox("No target property", MessageType.Info, true);
|
||||
Target.DestroyTransitionProperty();
|
||||
return;
|
||||
}
|
||||
|
||||
DoTransitionPropertyGUI();
|
||||
DoTransitionGUI();
|
||||
Target._Animations.DoGUI();
|
||||
|
||||
var animancer = Target._Scene.PreviewObject.Graph;
|
||||
if (animancer != null)
|
||||
{
|
||||
PlayableDrawer.DoGUI(animancer.Component);
|
||||
AnimancerGUI.SetGuiChanged(animancer.IsGraphPlaying);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The tooltip used for the Close button.</summary>
|
||||
public const string CloseTooltip = "Close the Transition Preview Window";
|
||||
|
||||
/// <summary>Draws the target object and path of the <see cref="TransitionProperty"/>.</summary>
|
||||
private void DoTransitionPropertyGUI()
|
||||
{
|
||||
var property = Target._TransitionProperty;
|
||||
property.Update();
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUI.showMixedValue = property.TargetObjects.Length > 1;
|
||||
EditorGUILayout.ObjectField(property.TargetObject, typeof(Object), true);
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Label(property.Property.GetFriendlyPath());
|
||||
|
||||
GUI.enabled = true;
|
||||
|
||||
using (var label = PooledGUIContent.Acquire("Close", CloseTooltip))
|
||||
{
|
||||
if (GUILayout.Button(label, EditorStyles.miniButton, AnimancerGUI.DontExpandWidth))
|
||||
{
|
||||
Target.Close();
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoTransitionGUI()
|
||||
{
|
||||
var property = Target._TransitionProperty;
|
||||
|
||||
var isExpanded = property.Property.isExpanded;
|
||||
property.Property.isExpanded = true;
|
||||
var height = EditorGUI.GetPropertyHeight(property, true);
|
||||
|
||||
const float Indent = 12;
|
||||
|
||||
var padding = GUI.skin.box.padding;
|
||||
|
||||
var area = AnimancerGUI.LayoutRect(height + padding.horizontal - padding.bottom);
|
||||
area.x += Indent + padding.left;
|
||||
area.width -= Indent + padding.horizontal;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.PropertyField(area, property, true);
|
||||
property.Property.isExpanded = isExpanded;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
property.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2259cdd4601c9694681cc39a978b15c4
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,323 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
#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.Previews
|
||||
{
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewWindow
|
||||
partial class TransitionPreviewWindow
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="Scene"/> of the current <see cref="TransitionPreviewWindow"/> instance.</summary>
|
||||
public static Scene InstanceScene
|
||||
=> _Instance != null
|
||||
? _Instance._Scene
|
||||
: null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Temporary scene management for the <see cref="TransitionPreviewWindow"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
|
||||
/// Previews</see>
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public class Scene :
|
||||
AnimancerPreviewObject.IEventHandler
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The scene displayed by the <see cref="TransitionPreviewWindow"/>.</summary>
|
||||
[SerializeField]
|
||||
private UnityEngine.SceneManagement.Scene _Scene;
|
||||
|
||||
/// <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; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private AnimancerPreviewObject _PreviewObject;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The object being previewed.</summary>
|
||||
public AnimancerPreviewObject PreviewObject
|
||||
=> AnimancerPreviewObject.Initialize(ref _PreviewObject, this, PreviewSceneRoot);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Vector3 _PreviousPreviewObjectPosition;
|
||||
|
||||
private Vector3 CurrentPreviewObjectPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
var animator = PreviewObject.SelectedInstanceAnimator;
|
||||
return animator != null
|
||||
? animator.transform.position
|
||||
: default;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Initialization
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes this <see cref="Scene"/>.</summary>
|
||||
public void OnEnable()
|
||||
{
|
||||
duringSceneGui += DoCustomGUI;
|
||||
|
||||
CreateScene();
|
||||
|
||||
PreviewObject.TrySelectBestModel(Transition);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void CreateScene()
|
||||
{
|
||||
_Scene = EditorSceneManager.NewPreviewScene();
|
||||
_Scene.name = "Transition Preview";
|
||||
_Instance.customScene = _Scene;
|
||||
|
||||
var root = AnimancerPreviewObject.CreateEmpty(nameof(TransitionPreviewWindow));
|
||||
PreviewSceneRoot = root.transform;
|
||||
SceneManager.MoveGameObjectToScene(root, _Scene);
|
||||
_Instance.customParentForDraggedObjects = PreviewSceneRoot;
|
||||
|
||||
OnEnvironmentPrefabChanged();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
internal void OnEnvironmentPrefabChanged()
|
||||
{
|
||||
DestroyImmediate(EnvironmentInstance);
|
||||
|
||||
var prefab = TransitionPreviewSettings.SceneEnvironment;
|
||||
if (prefab != null)
|
||||
EnvironmentInstance = Instantiate(prefab, PreviewSceneRoot);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
void AnimancerPreviewObject.IEventHandler.OnInstantiateObject()
|
||||
{
|
||||
FocusCamera();
|
||||
_Instance._Animations.GatherAnimations();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
void AnimancerPreviewObject.IEventHandler.OnSetSelectedAnimator()
|
||||
{
|
||||
_Instance.in2DMode = PreviewObject.SelectedInstanceType == AnimationType.Sprite;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void AnimancerPreviewObject.IEventHandler.OnCreateGraph()
|
||||
{
|
||||
PreviewObject.Graph.RequirePostUpdate(new Animations.WindowMatchStateTime());
|
||||
_Instance._Animations.NormalizedTime = _Instance._Animations.NormalizedTime;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called when the target transition property is changed.</summary>
|
||||
public void OnTargetPropertyChanged()
|
||||
{
|
||||
_ExpandedHierarchy?.Clear();
|
||||
|
||||
var previewObject = PreviewObject;
|
||||
|
||||
previewObject.OriginalObject = AnimancerUtilities.FindRoot(_Instance._TransitionProperty.TargetObject);
|
||||
previewObject.TrySelectBestModel(Transition);
|
||||
|
||||
_Instance._Animations.NormalizedTime = 0;
|
||||
|
||||
_Instance.in2DMode = previewObject.SelectedInstanceType == AnimationType.Sprite;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void FocusCamera()
|
||||
{
|
||||
var instance = _PreviewObject.InstanceObject;
|
||||
if (instance == null)
|
||||
return;
|
||||
|
||||
var bounds = CalculateBounds(instance);
|
||||
|
||||
var rotation = _Instance.in2DMode ?
|
||||
Quaternion.identity :
|
||||
Quaternion.Euler(15, 225, 0);
|
||||
|
||||
var size = bounds.extents.magnitude * 1.2f;
|
||||
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)
|
||||
{
|
||||
if (transform == null)
|
||||
return default;
|
||||
|
||||
var renderers = transform.GetComponentsInChildren<Renderer>();
|
||||
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
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called when the window GUI is drawn.</summary>
|
||||
public void OnGUI()
|
||||
{
|
||||
if (_PreviewObject != null &&
|
||||
_PreviewObject.Graph != null &&
|
||||
_PreviewObject.Graph.IsGraphPlaying)
|
||||
AnimancerGUI.RepaintEverything();
|
||||
|
||||
if (Selection.activeObject == _Instance &&
|
||||
Event.current.type == EventType.KeyUp &&
|
||||
Event.current.keyCode == KeyCode.F)
|
||||
FocusCamera();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DoCustomGUI(SceneView sceneView)
|
||||
{
|
||||
FollowPreviewObject(sceneView);
|
||||
|
||||
var animancer = PreviewObject.Graph;
|
||||
if (animancer == null ||
|
||||
sceneView is not TransitionPreviewWindow instance ||
|
||||
!AnimancerUtilities.TryGetWrappedObject(Transition, out ITransitionGUI gui) ||
|
||||
instance._TransitionProperty == null)
|
||||
return;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
using (new TransitionDrawer.DrawerContext(instance._TransitionProperty))
|
||||
{
|
||||
try
|
||||
{
|
||||
gui.OnPreviewSceneGUI(new(animancer));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
AnimancerGUI.RepaintEverything();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void FollowPreviewObject(SceneView sceneView)
|
||||
{
|
||||
var currentPreviewObjectPosition = CurrentPreviewObjectPosition;
|
||||
if (_PreviousPreviewObjectPosition == currentPreviewObjectPosition)
|
||||
return;
|
||||
|
||||
sceneView.pivot += currentPreviewObjectPosition - _PreviousPreviewObjectPosition;
|
||||
|
||||
_PreviousPreviewObjectPosition = currentPreviewObjectPosition;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is the `obj` a <see cref="GameObject"/> in the preview scene?</summary>
|
||||
public bool IsSceneObject(Object obj)
|
||||
=> obj is GameObject gameObject
|
||||
&& gameObject.transform.IsChildOf(PreviewSceneRoot);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private List<Transform> _ExpandedHierarchy;
|
||||
|
||||
/// <summary>A list of all objects with their child hierarchy expanded.</summary>
|
||||
public List<Transform> ExpandedHierarchy
|
||||
=> _ExpandedHierarchy ??= new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Cleanup
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called by <see cref="TransitionPreviewWindow.OnDisable"/>.</summary>
|
||||
public void OnDisable()
|
||||
{
|
||||
duringSceneGui -= DoCustomGUI;
|
||||
|
||||
_PreviewObject?.Dispose();
|
||||
|
||||
EditorSceneManager.ClosePreviewScene(_Scene);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called by <see cref="TransitionPreviewWindow.OnDestroy"/>.</summary>
|
||||
public void OnDestroy()
|
||||
{
|
||||
if (PreviewSceneRoot != null)
|
||||
{
|
||||
DestroyImmediate(PreviewSceneRoot.gameObject);
|
||||
PreviewSceneRoot = null;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee0324674116dbf4eb4f7da9b41ee03f
|
||||
timeCreated: 1516751545
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,386 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#if UNITY_EDITOR && UNITY_IMGUI
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer.Editor.Previews
|
||||
{
|
||||
/// <summary>[Editor-Only]
|
||||
/// An <see cref="EditorWindow"/> which allows the user to preview animation transitions separately from the rest
|
||||
/// of the scene in Edit Mode or Play Mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions#previews">
|
||||
/// Previews</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewWindow
|
||||
///
|
||||
[HelpURL(Strings.DocsURLs.TransitionPreviews)]
|
||||
[EditorWindowTitle]// Prevent the base SceneView from trying to use this type name to find the icon.
|
||||
public partial class TransitionPreviewWindow : SceneView
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Public API
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static Texture _Icon;
|
||||
|
||||
/// <summary>The icon image used by this window.</summary>
|
||||
public static Texture Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Icon == null)
|
||||
{
|
||||
// Possible icons: "UnityEditor.LookDevView", "SoftlockInline", "ViewToolOrbit", "ClothInspector.ViewValue".
|
||||
var name = EditorGUIUtility.isProSkin ? "ViewToolOrbit On" : "ViewToolOrbit";
|
||||
|
||||
_Icon = AnimancerIcons.Load(name);
|
||||
if (_Icon == null)
|
||||
_Icon = EditorGUIUtility.whiteTexture;
|
||||
}
|
||||
|
||||
return _Icon;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Focusses the <see cref="TransitionPreviewWindow"/> or creates one if none exists.
|
||||
/// Or closes the existing window if it was already previewing the `transitionProperty`.
|
||||
/// </summary>
|
||||
public static void OpenOrClose(SerializedProperty transitionProperty)
|
||||
{
|
||||
transitionProperty = transitionProperty.Copy();
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (!IsPreviewing(transitionProperty))
|
||||
{
|
||||
// To avoid Unity giving a warning about camera rotation in 2D Mode:
|
||||
// Set all scene views to not 2D mode and store their previous state.
|
||||
var sceneViews = SceneView.sceneViews.ToArray();
|
||||
var was2D = new bool[sceneViews.Length];
|
||||
for (int i = 0; i < sceneViews.Length; i++)
|
||||
{
|
||||
var sceneView = (SceneView)sceneViews[i];
|
||||
was2D[i] = sceneView.in2DMode;
|
||||
sceneView.in2DMode = false;
|
||||
}
|
||||
|
||||
GetWindow<TransitionPreviewWindow>(typeof(SceneView))
|
||||
.SetTargetProperty(transitionProperty);
|
||||
|
||||
// Then after opening the window immediately return each scene view back to its previous state.
|
||||
for (int i = 0; i < sceneViews.Length; i++)
|
||||
{
|
||||
var sceneView = (SceneView)sceneViews[i];
|
||||
sceneView.in2DMode = was2D[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_Instance.Close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AnimancerState.NormalizedTime"/> of the current transition. Can only be set if the property
|
||||
/// being previewed matches the current <see cref="TransitionDrawer.Context"/>.
|
||||
/// </summary>
|
||||
public static float PreviewNormalizedTime
|
||||
{
|
||||
get => _Instance._Animations.NormalizedTime;
|
||||
set
|
||||
{
|
||||
if (value.IsFinite() &&
|
||||
IsPreviewingCurrentProperty())
|
||||
_Instance._Animations.NormalizedTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="AnimancerState"/> of the current transition if the property being previewed matches
|
||||
/// the <see cref="TransitionDrawer.Context"/>. Otherwise returns null.
|
||||
/// </summary>
|
||||
public static AnimancerState GetCurrentState()
|
||||
{
|
||||
if (!IsPreviewingCurrentProperty())
|
||||
return null;
|
||||
|
||||
var previewObject = _Instance._Scene.PreviewObject;
|
||||
if (previewObject == null || previewObject.Graph == null)
|
||||
return null;
|
||||
|
||||
previewObject.Graph.States.TryGet(Transition, out var state);
|
||||
return state;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Is the current <see cref="TransitionDrawer.DrawerContext.Property"/> being previewed at the moment?
|
||||
/// </summary>
|
||||
public static bool IsPreviewingCurrentProperty()
|
||||
=> IsPreviewing(TransitionDrawer.Context.Property);
|
||||
|
||||
/// <summary>Is the `property` being previewed at the moment?</summary>
|
||||
public static bool IsPreviewing(SerializedProperty property)
|
||||
=> property != null
|
||||
&& _Instance != null
|
||||
&& _Instance._TransitionProperty.IsValid()
|
||||
&& Serialization.AreSameProperty(property, _Instance._TransitionProperty);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Messages
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static TransitionPreviewWindow _Instance;
|
||||
|
||||
[SerializeField] private Object[] _PreviousSelection;
|
||||
[SerializeField] private Animations _Animations;
|
||||
[SerializeField] private Scene _Scene;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnEnable()
|
||||
{
|
||||
_Instance = this;
|
||||
|
||||
base.OnEnable();
|
||||
|
||||
name = "Transition Preview Window";
|
||||
titleContent = new("Transition Preview", Icon);
|
||||
autoRepaintOnSceneChange = true;
|
||||
sceneViewState.showSkybox = TransitionPreviewSettings.ShowSkybox;
|
||||
sceneLighting = TransitionPreviewSettings.SceneLighting;
|
||||
|
||||
_Scene ??= new();
|
||||
_Animations ??= new();
|
||||
|
||||
if (_TransitionProperty.IsValid() &&
|
||||
!CanBePreviewed(_TransitionProperty))
|
||||
{
|
||||
DestroyTransitionProperty();
|
||||
}
|
||||
|
||||
_Scene.OnEnable();
|
||||
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
AssemblyReloadEvents.beforeAssemblyReload += DeselectPreviewSceneObjects;
|
||||
|
||||
// Re-select next frame.
|
||||
// This fixes an issue where the Inspector header displays differently after a domain reload.
|
||||
if (Selection.activeObject == this)
|
||||
{
|
||||
Selection.activeObject = null;
|
||||
EditorApplication.delayCall += () => Selection.activeObject = this;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
_Scene.OnDisable();
|
||||
_Instance = null;
|
||||
Selection.selectionChanged -= OnSelectionChanged;
|
||||
AssemblyReloadEvents.beforeAssemblyReload -= DeselectPreviewSceneObjects;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Cleans up this window.</summary>
|
||||
protected virtual new void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
_Scene.OnDestroy();
|
||||
DestroyTransitionProperty();
|
||||
|
||||
using (ListPool<Object>.Instance.Acquire(out var objects))
|
||||
{
|
||||
for (int i = 0; i < _PreviousSelection.Length; i++)
|
||||
{
|
||||
var obj = _PreviousSelection[i];
|
||||
if (obj != null)
|
||||
objects.Add(obj);
|
||||
}
|
||||
Selection.objects = objects.ToArray();
|
||||
}
|
||||
|
||||
_TransitionProperty = null;
|
||||
|
||||
AnimancerGUI.RepaintEverything();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnSceneGUI()
|
||||
{
|
||||
_Instance = this;
|
||||
|
||||
base.OnSceneGUI();
|
||||
|
||||
_Scene.OnGUI();
|
||||
|
||||
TransitionPreviewSettings.ShowSkybox = sceneViewState.showSkybox;
|
||||
TransitionPreviewSettings.SceneLighting = sceneLighting;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Called multiple times per second while this window is visible.</summary>
|
||||
private void Update()
|
||||
{
|
||||
if (Selection.activeObject == null)
|
||||
Selection.activeObject = this;
|
||||
|
||||
if (TransitionPreviewSettings.AutoClose &&
|
||||
!_TransitionProperty.IsValid())
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns false.</summary>
|
||||
/// <remarks>Returning true makes it draw the main scene instead of the custom scene in Unity 2020.</remarks>
|
||||
protected override bool SupportsStageHandling() => false;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
if (Selection.activeObject == null)
|
||||
EditorApplication.delayCall += () => Selection.activeObject = this;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DeselectPreviewSceneObjects()
|
||||
{
|
||||
using (ListPool<Object>.Instance.Acquire(out var objects))
|
||||
{
|
||||
var selection = Selection.objects;
|
||||
for (int i = 0; i < selection.Length; i++)
|
||||
{
|
||||
var obj = selection[i];
|
||||
if (!_Scene.IsSceneObject(obj))
|
||||
objects.Add(obj);
|
||||
}
|
||||
Selection.objects = objects.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Transition Property
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private Serialization.PropertyReference _TransitionProperty;
|
||||
|
||||
/// <summary>The <see cref="SerializedProperty"/> currently being previewed.</summary>
|
||||
public static SerializedProperty TransitionProperty => _Instance._TransitionProperty;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="ITransition"/> currently being previewed.</summary>
|
||||
public static ITransition Transition
|
||||
{
|
||||
get
|
||||
{
|
||||
var property = _Instance._TransitionProperty;
|
||||
if (!property.IsValid())
|
||||
return null;
|
||||
|
||||
return property.Property.GetValue<ITransition>();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Indicates whether the `property` is able to be previewed by this system.</summary>
|
||||
public static bool CanBePreviewed(SerializedProperty property)
|
||||
{
|
||||
var accessor = property.GetAccessor();
|
||||
if (accessor == null)
|
||||
return false;
|
||||
|
||||
var type = accessor.GetFieldElementType(property);
|
||||
if (typeof(ITransition).IsAssignableFrom(type))
|
||||
return true;
|
||||
|
||||
var value = accessor.GetValue(property);
|
||||
return
|
||||
value != null &&
|
||||
typeof(ITransition).IsAssignableFrom(value.GetType());
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void SetTargetProperty(SerializedProperty property)
|
||||
{
|
||||
if (property.serializedObject.targetObjects.Length != 1)
|
||||
{
|
||||
Close();
|
||||
throw new ArgumentException($"{nameof(TransitionPreviewWindow)} does not support multi-object selection.");
|
||||
}
|
||||
|
||||
if (!CanBePreviewed(property))
|
||||
{
|
||||
Close();
|
||||
throw new ArgumentException($"The specified property does not implement {nameof(ITransition)}.");
|
||||
}
|
||||
|
||||
if (!_TransitionProperty.IsValid())
|
||||
_PreviousSelection = Selection.objects;
|
||||
Selection.activeObject = this;
|
||||
|
||||
DestroyTransitionProperty();
|
||||
|
||||
_TransitionProperty = property;
|
||||
_Scene.OnTargetPropertyChanged();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private void DestroyTransitionProperty()
|
||||
{
|
||||
if (_TransitionProperty == null)
|
||||
return;
|
||||
|
||||
_Scene.PreviewObject.DestroyInstanceObject();
|
||||
|
||||
_TransitionProperty.Dispose();
|
||||
_TransitionProperty = null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cadc451eaffebdb48ad6dc32456ebbd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: d4e06a71fc03595429cac47cd385c4c1, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user