// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // using System; using System.Collections.Generic; using UnityEngine; namespace Animancer.TransitionLibraries { /// [Pro-Only] /// A library of s which allows specific /// transition combinations to be overridden without needing to be hard coded. /// /// /// Documentation: /// /// Transition Libraries /// /// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionLibrary public class TransitionLibrary : IAnimationClipSource, ICopyable { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ /// [Pro-Only] Modifiers in the order they are created. /// The of each item corresponds to its position in this list. private readonly List TransitionModifiers = new(); /// [Pro-Only] Modifiers registered by their as well as any custom aliases. private readonly Dictionary KeyedTransitionModifiers = new(); /************************************************************************************************************************/ /// [Pro-Only] The number of transitions in this library. public int Count => TransitionModifiers.Count; /// [Pro-Only] The number of transitions in this library plus any additional aliases. public int AliasCount => KeyedTransitionModifiers.Count; /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Queries /************************************************************************************************************************/ /// [Pro-Only] /// Does this library contain a transition registered with the `key`? /// public bool ContainsKey(object key) => KeyedTransitionModifiers.ContainsKey(key); /// [Pro-Only] /// Does this library contain a transition registered with the ? /// public bool ContainsKey(IHasKey hasKey) => ContainsKey(hasKey.Key); /************************************************************************************************************************/ /// [Pro-Only] /// Tries to find a registered with the `key`. /// public bool TryGetTransition(object key, out TransitionModifierGroup transition) { #if UNITY_ASSERTIONS if (KeyedTransitionModifiers.TryGetValue(key, out transition)) return true; AssertStringReference(key); return false; #else return KeyedTransitionModifiers.TryGetValue(key, out transition); #endif } /// [Pro-Only] /// Tries to find a registered with the . /// public bool TryGetTransition(IHasKey hasKey, out TransitionModifierGroup transition) => TryGetTransition(hasKey.Key, out transition); /// [Pro-Only] /// Tries to find a /// via its . /// public bool TryGetTransition(int index, out TransitionModifierGroup transition) => TransitionModifiers.TryGet(index, out transition) && transition != null; /************************************************************************************************************************/ /// [Pro-Only] /// Finds the of the group registered with the `key` /// or returns -1. /// public int IndexOf(object key) => TryGetTransition(key, out var group) ? group.Index : -1; /// [Pro-Only] /// Finds the of the group registered with the `key` /// or returns -1. /// public int IndexOf(IHasKey hasKey) => IndexOf(hasKey.Key); /************************************************************************************************************************/ /// [Pro-Only] /// Returns the fade duration to use when transitioning from `from` to the `transition`. /// public float GetFadeDuration( object from, ITransition to) { if (from != null && TryGetTransition(to.Key, out var group)) return group.GetFadeDuration(from); return to.FadeDuration; } /// [Pro-Only] /// Returns the fade duration to use when transitioning from `from` to the `transition`. /// public float GetFadeDuration( IHasKey from, ITransition to) => GetFadeDuration(from?.Key, to); /// [Pro-Only] /// Returns the fade duration to use when transitioning from the /// to the `transition`. /// public float GetFadeDuration( AnimancerLayer layer, ITransition transition) => GetFadeDuration(layer.CurrentState?.Key, transition); /// [Pro-Only] /// Returns the fade duration to use when transitioning from the /// to the `key`. /// public float GetFadeDuration( AnimancerLayer layer, object key, float fadeDuration) { AssertStringReference(key); var from = layer.CurrentState?.Key; if (from != null && TryGetTransition(key, out var group)) return group.GetFadeDuration(from); return fadeDuration; } /************************************************************************************************************************/ /// [Pro-Only] Gathers all the animations in this library. public void GetAnimationClips(List results) { for (int i = TransitionModifiers.Count - 1; i >= 0; i--) results.GatherFromSource(TransitionModifiers[i].Transition); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Add /************************************************************************************************************************/ /// [Pro-Only] Adds the contents of the `definition` to this library. /// Existing values will be completely replaced. public void Initialize(TransitionLibraryDefinition definition) { Clear(); if (definition == null) return; var count = definition.Transitions.Length; if (TransitionModifiers.Capacity < count) { var capacity = Math.Max(count, 16); TransitionModifiers.Capacity = capacity; KeyedTransitionModifiers.EnsureCapacity(capacity); } for (int i = 0; i < count; i++) { var transition = definition.Transitions[i]; if (transition != null) SetTransition(transition); } for (int i = 0; i < definition.Modifiers.Length; i++) SetFadeDuration(definition.Modifiers[i]); if (definition.AliasAllTransitions) { for (int i = 0; i < count; i++) { var transition = definition.Transitions[i]; var modifier = TransitionModifiers[i]; KeyedTransitionModifiers[StringReference.Get(transition.name)] = modifier; } } for (int i = 0; i < definition.Aliases.Length; i++) { var alias = definition.Aliases[i]; if (alias.Name != null && TransitionModifiers.TryGet(alias.Index, out var group)) KeyedTransitionModifiers[alias.Name.Name] = group; } } /************************************************************************************************************************/ /// [Pro-Only] Adds the `transition` to this library. /// A transition is already registered with the `key`. public TransitionModifierGroup AddTransition( object key, ITransition transition) { AssertStringReference(key); var modifier = new TransitionModifierGroup(TransitionModifiers.Count, transition); KeyedTransitionModifiers.Add(key, modifier); TransitionModifiers.Add(modifier); return modifier; } /// [Pro-Only] Adds the `transition` to this library. /// A transition is already registered with the `key`. public TransitionModifierGroup AddTransition( IHasKey hasKey, ITransition transition) => AddTransition(hasKey.Key, transition); /// [Pro-Only] Adds the `transition` to this library. /// A transition is already registered with the `key`. public TransitionModifierGroup AddTransition( ITransition transition) => AddTransition(transition, transition); /************************************************************************************************************************/ /// [Pro-Only] /// Adds the `transition` to this library or replaces the existing one registered with the `key`. /// public TransitionModifierGroup SetTransition( object key, ITransition transition) { if (TryGetTransition(key, out var oldModifier)) { oldModifier.Transition = transition; return oldModifier; } return AddTransition(key, transition); } /// [Pro-Only] /// Adds the `transition` to this library or replaces the existing one registered with the `key`. /// public TransitionModifierGroup SetTransition( IHasKey hasKey, ITransition transition) => SetTransition(hasKey.Key, transition); /// [Pro-Only] /// Adds the `transition` to this library or replaces the existing one registered with the `key`. /// public TransitionModifierGroup SetTransition( ITransition transition) => SetTransition(transition, transition); /************************************************************************************************************************/ /// [Pro-Only] /// Sets the to use when transitioning from `from` to `to`. /// public void SetFadeDuration( object from, ITransition to, float fadeDuration) { var group = SetTransition(to.Key, to); group.SetFadeDuration( from, fadeDuration); } /// [Pro-Only] /// Sets the to use when transitioning from `from` to `to`. /// public void SetFadeDuration( IHasKey from, ITransition to, float fadeDuration) => SetFadeDuration(from.Key, to, fadeDuration); /// [Pro-Only] /// Sets the to use when transitioning from /// to . /// public bool SetFadeDuration( TransitionModifierDefinition modifier) { if (!TransitionModifiers.TryGet(modifier.FromIndex, out var from) || !TransitionModifiers.TryGet(modifier.ToIndex, out var to)) return false; to.SetFadeDuration( from.Transition.Key, modifier.FadeDuration); return true; } /************************************************************************************************************************/ /// [Pro-Only] Registers the `group` with another `key`. public void AddAlias( object key, TransitionModifierGroup group) { AssertStringReference(key); AssertGroup(group); KeyedTransitionModifiers.Add(key, group); } /// [Pro-Only] Registers the `transition` with the `key`. /// Also registers it with its if it wasn't already. public TransitionModifierGroup AddAlias( object key, ITransition transition) { var group = SetTransition(transition); AddAlias(key, group); return group; } /************************************************************************************************************************/ /// [Pro-Only] Adds the contents of `copyFrom` into this library. /// /// This method adds and replaces values, but does not remove any /// (unlike . /// public void AddLibrary(TransitionLibrary library, CloneContext context) { if (library == null) return; for (int i = 0; i < TransitionModifiers.Count; i++) { var group = TransitionModifiers[i]; context[group.Transition] = group; } foreach (var group in library.KeyedTransitionModifiers) { var transition = group.Value.Transition; if (context.TryGetClone(transition, out var clone) && clone is TransitionModifierGroup cloneGroup) { AssertGroup(cloneGroup); KeyedTransitionModifiers[group.Key] = cloneGroup; } else { cloneGroup = SetTransition(group.Key, group.Value.Transition); cloneGroup.CopyFrom(group.Value); context[transition] = cloneGroup; } } } /// [Pro-Only] Adds the contents of `copyFrom` into this library. /// /// This method adds and replaces values, but does not remove any /// (unlike . /// public void AddLibrary(TransitionLibrary library) { var context = CloneContext.Pool.Instance.Acquire(); AddLibrary(library, context); CloneContext.Pool.Instance.Release(context); } /************************************************************************************************************************/ /// /// See also . public void CopyFrom(TransitionLibrary copyFrom, CloneContext context) { Clear(); if (copyFrom == null) return; var count = copyFrom.TransitionModifiers.Count; for (int i = 0; i < count; i++) TransitionModifiers.Add(copyFrom.TransitionModifiers[i].Clone(context)); foreach (var group in copyFrom.KeyedTransitionModifiers) { var clone = TransitionModifiers[group.Value.Index]; KeyedTransitionModifiers.Add(group.Key, clone); } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Remove /************************************************************************************************************************/ // Remove from the dictionary but not the list because there might be multiple aliases for that index. /// [Pro-Only] Removes the transition registered with the `key`. public bool RemoveTransition(object key) => KeyedTransitionModifiers.Remove(key); /// [Pro-Only] Removes the transition registered with the . public bool RemoveTransition(IHasKey hasKey) => RemoveTransition(hasKey.Key); /************************************************************************************************************************/ /// [Pro-Only] Removes a modified fade duration for transitioning from `from` to `to`. public bool RemoveFadeDuration(object from, object to) => TryGetTransition(to, out var group) && group.FromKeyToFadeDuration != null && group.FromKeyToFadeDuration.Remove(from); /// [Pro-Only] Removes a modified fade duration for transitioning from `from` to `to`. public bool RemoveFadeDuration(IHasKey from, IHasKey to) => RemoveFadeDuration(from.Key, to.Key); /************************************************************************************************************************/ /// [Pro-Only] Removes everything from this library, leaving it empty. public void Clear() { TransitionModifiers.Clear(); KeyedTransitionModifiers.Clear(); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Play /************************************************************************************************************************/ /// /// Calls /// with the fade duration potentially modified by this library. /// public AnimancerState Play( AnimancerLayer layer, ITransition transition) => layer.Play( transition, GetFadeDuration(layer, transition), transition.FadeMode); /// /// Calls /// with the fade duration potentially modified by this library. /// public AnimancerState Play( AnimancerLayer layer, TransitionModifierGroup transition) { var from = layer.CurrentState?.Key; var to = transition.Transition; var fadeDuration = from != null ? transition.GetFadeDuration(from) : to.FadeDuration; return layer.Play( to, fadeDuration, to.FadeMode); } /************************************************************************************************************************/ /// /// Plays the transition registered with the specified `key` if there is one. /// Otherwise, returns null. /// public AnimancerState TryPlay( AnimancerLayer layer, object key) => TryGetTransition(key, out var transition) ? Play(layer, transition) : null; /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Assertions /************************************************************************************************************************/ /// [Assert-Conditional] /// Logs if the `key` is a . /// [System.Diagnostics.Conditional(Strings.Assertions)] private void AssertStringReference(object key) { #if UNITY_ASSERTIONS if (key is string keyString) { if (StringReference.TryGet(keyString, out var keyReference) && KeyedTransitionModifiers.ContainsKey(keyReference)) Debug.LogError( $"{nameof(TransitionLibrary)} key type mismatch:" + $" attempted to use string '{keyString}'," + $" but that value is registered as a {nameof(StringReference)}." + $" Use a {nameof(StringReference)} to ensure the correct lookup."); else OptionalWarning.StringReference.Log( $"A string '{keyString}' is being used as a key in a {nameof(TransitionLibrary)}." + $" {nameof(StringReference)}s should be used instead of strings because they are more efficient" + $" and to avoid mismatches with aliases in a {nameof(TransitionLibraryDefinition)}."); } #endif } /************************************************************************************************************************/ /// [Assert-Conditional] /// Asserts that the /// corresponds to the . /// [System.Diagnostics.Conditional(Strings.Assertions)] [HideInCallstack] internal void AssertGroup(TransitionModifierGroup group) { #if UNITY_ASSERTIONS if (!TransitionModifiers.TryGet(group.Index, out var registered) || registered != group) Debug.LogError( $"{nameof(CloneContext)} contains an {nameof(TransitionModifierGroup)}" + $" which isn't part of this {nameof(TransitionLibrary)}." + $" It must have been added to the context manually."); #endif } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }