// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // #if UNITY_EDITOR using System; using UnityEditor; using UnityEngine; namespace Animancer.Editor.Previews { /// [Editor-Only] Utility for playing through transition previews. /// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewPlayer [Serializable] public class TransitionPreviewPlayer : IDisposable { /************************************************************************************************************************/ [SerializeReference] private ITransition _FromTransition; [SerializeReference] private ITransition _ToTransition; /// The animation to play first. public ref ITransition FromTransition => ref _FromTransition; /// The animation to transition into. public ref ITransition ToTransition => ref _ToTransition; /************************************************************************************************************************/ private AnimancerGraph _Graph; /// The graph used to play the animations. public AnimancerGraph Graph { get => _Graph; set { _Graph = value; Evaluate(); } } /************************************************************************************************************************/ /// /// The minimum amount of time to play the /// before the starts (in seconds). /// public float FromDuration { get; set; } /// /// The minimum amount of time to continue playing /// after the started (in seconds). /// public float ToDuration { get; set; } /// The speed at which the preview plays. public float Speed { get; set; } = 1; /************************************************************************************************************************/ private float _FadeDuration = float.NaN; /// The . /// uses the value from the . public float FadeDuration { get => float.IsNaN(_FadeDuration) && ToTransition.IsValid() ? ToTransition.FadeDuration : _FadeDuration; set => _FadeDuration = value; } /************************************************************************************************************************/ /// The lowest allowed . /// /// This is the lower of the negative /// or the negative duration of the . /// public float MinTime { get; private set; } /// The highest allowed . /// /// This is the higher of the or /// or the duration of the . /// public float MaxTime { get; private set; } /************************************************************************************************************************/ /// Recalculated the and . 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); } /************************************************************************************************************************/ /// Converts normalized time to seconds. public float LerpTimeUnclamped(float normalizedTime) => Mathf.LerpUnclamped(MinTime, MaxTime, normalizedTime); /// Converts seconds to normalized time. public float InverseLerpTimeUnclamped(float time) => AnimancerUtilities.InverseLerpUnclamped(MinTime, MaxTime, time); /************************************************************************************************************************/ private float _CurrentTime = float.NaN; /// The amount of time that has passed since the started (in seconds). /// This value goes from the to the . public float CurrentTime { get => float.IsNaN(_CurrentTime) ? MinTime : _CurrentTime; set { _CurrentTime = value; Evaluate(); } } /// The amount of time that has passed since the started (normalized). /// 0 is at the and 1 is at the . public float NormalizedTime { get => InverseLerpTimeUnclamped(CurrentTime); set => CurrentTime = LerpTimeUnclamped(value); } /************************************************************************************************************************/ private bool _IsPlaying; /// Is the preview currently playing? public bool IsPlaying { get => _IsPlaying; set { if (_IsPlaying == value) return; _IsPlaying = value; if (_IsPlaying) { _LastUpdateTime = TimeSinceStartup; EditorApplication.update += Update; } else { EditorApplication.update -= Update; } } } /// Cleans up this player. public void Dispose() => IsPlaying = false; /************************************************************************************************************************/ /// private static double TimeSinceStartup => EditorApplication.timeSinceStartup; private double _LastUpdateTime; /// Updates the preview time while playing. 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(); } /************************************************************************************************************************/ /// Applies the animations at the . 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); } } else { if (_ToTransition.IsValid()) Apply(CurrentTime, MaxTime, _ToTransition); else return; } Graph.Evaluate(); } /************************************************************************************************************************/ /// Applies the animations at the `currentTime`. 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); 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); state.Time += currentTime; // Finished. if (currentTime >= maxTime) { _CurrentTime = MinTime; state.Time = _CurrentTime; } } } /************************************************************************************************************************/ /// Applies the animation at the `currentTime`. private void Apply(float currentTime, float endTime, ITransition transition) { var state = Graph.Layers[0].Play(transition); if (currentTime < endTime)// Playing. { state.Time = currentTime; } else// Finished. { _CurrentTime = MinTime; state.Time = _CurrentTime; } } /************************************************************************************************************************/ } } #endif