// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // using System.Collections.Generic; using UnityEngine; namespace Animancer { /// /// A system for synchronizing the /// of animations within the same "group". /// /// /// /// /// Store a in a field. /// Call any of the methods before playing a new animation. /// Then call any of the methods after playing the animation. /// /// Sample: /// /// Character Controller -> Synchronization /// /// // 1. Define your group type. /// // You could use strings or ints or whatever you want, but enums are often best. /// public enum AnimationGroup /// { /// None, /// Movement, /// } /// /// [SerializeField] private AnimancerComponent _Animancer; /// /// // 2. Store a TimeSynchronizer in a field. /// private readonly TimeSynchronizer<AnimationGroup> /// TimeSynchronizer = new(); /// /// public AnimancerState Play(AnimationClip clip, AnimationGroup group) /// { /// // 3. Call one of the StoreTime methods before playing a new animation. /// TimeSynchronizer.StoreTime(_Animancer); /// /// // 4. Play an animation. /// var state = _Animancer.Play(clip); /// /// // 5. Call one of the SyncTime methods after playing the animation. /// // If the `group` was the same as the value from last time you called it, /// // then the state's NormalizedTime will be set to the stored value. /// TimeSynchronizer.SyncTime(state, group); /// /// return state; /// } /// /// /// https://kybernetik.com.au/animancer/api/Animancer/TimeSynchronizer_1 /// public class TimeSynchronizer { /************************************************************************************************************************/ /// The group that the current animation is in. public T CurrentGroup { get; set; } /// Should synchronization be applied when the is at its default value? /// This is false by default so that the default group represents "ungrouped". public bool SynchronizeDefaultGroup { get; set; } /// The state which the came from (to avoid syncing with itself). public AnimancerState State { get; set; } /// The stored . public double NormalizedTime { get; set; } = double.NaN; /************************************************************************************************************************/ /// Creates a new . public TimeSynchronizer() { } /// Creates a new . public TimeSynchronizer(T group, bool synchronizeDefaultGroup = false) { CurrentGroup = group; SynchronizeDefaultGroup = synchronizeDefaultGroup; } /************************************************************************************************************************/ /// /// Stores the of the . /// public void StoreTime(AnimancerLayer layer) => StoreTime(layer.CurrentState); /// Stores the of the `state`. public void StoreTime(AnimancerState state) => StoreTime(state, state != null ? state.NormalizedTimeD : double.NaN); /************************************************************************************************************************/ /// Sets the and . public void StoreTime(AnimancerState state, double normalizedTime) { State = state; NormalizedTime = normalizedTime; } /************************************************************************************************************************/ /// /// Applies the to the /// if the `group` matches the . /// public bool SyncTime(AnimancerLayer layer, T group) => SyncTime(layer.CurrentState, group, Time.deltaTime); /// /// Applies the to the /// if the `group` matches the . /// public bool SyncTime(AnimancerLayer layer, T group, float deltaTime) => SyncTime(layer.CurrentState, group, deltaTime); /// /// Applies the to the `state` /// if the `group` matches the . /// public bool SyncTime(AnimancerState state, T group) => SyncTime(state, group, Time.deltaTime); /// /// Applies the to the `state` /// and returns true if the `group` matches the . /// /// /// If the `state` is the same one the time was stored from, this method does nothing and returns false. /// public bool SyncTime(AnimancerState state, T group, float deltaTime) { if (state == null || state == State || double.IsNaN(NormalizedTime) || !EqualityComparer.Default.Equals(CurrentGroup, group) || (!SynchronizeDefaultGroup && EqualityComparer.Default.Equals(default, group))) { CurrentGroup = group; return false; } // Setting the Time forces it to stay at that value after the next animation update. // But we actually want it to keep playing, so we need to add deltaTime manually. state.MoveTime(NormalizedTime * state.Length + deltaTime * state.EffectiveSpeed, false); return true; } /************************************************************************************************************************/ } }