// 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
/************************************************************************************************************************/
}
}