chore: initial commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79e7dc2b7dc4516429fdb2fa86e1d380
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.TransitionLibraries
|
||||
{
|
||||
/// <summary>[<see cref="SerializableAttribute"/>] A <see cref="StringAsset"/> and <see cref="int"/> pair.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/libraries">
|
||||
/// Transition Libraries</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/NamedIndex
|
||||
[Serializable]
|
||||
public struct NamedIndex :
|
||||
IComparable<NamedIndex>,
|
||||
IEquatable<NamedIndex>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private StringAsset _Name;
|
||||
|
||||
/// <summary>The name.</summary>
|
||||
public readonly StringAsset Name
|
||||
=> _Name;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private int _Index;
|
||||
|
||||
/// <summary>The index.</summary>
|
||||
public readonly int Index
|
||||
=> _Index;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="NamedIndex"/>.</summary>
|
||||
public NamedIndex(StringAsset name, int index)
|
||||
{
|
||||
_Name = name;
|
||||
_Index = index;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="NamedIndex"/>.</summary>
|
||||
public readonly NamedIndex With(StringAsset name)
|
||||
=> new(name, _Index);
|
||||
|
||||
/// <summary>Creates a new <see cref="NamedIndex"/>.</summary>
|
||||
public readonly NamedIndex With(int index)
|
||||
=> new(_Name, index);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Describes this value.</summary>
|
||||
public override readonly string ToString()
|
||||
=> $"[{_Index}]{_Name}";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Equality
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Compares the <see cref="Index"/> then <see cref="Name"/>.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int CompareTo(NamedIndex other)
|
||||
{
|
||||
var result = _Index.CompareTo(other._Index);
|
||||
if (result != 0)
|
||||
return result;
|
||||
else
|
||||
return StringAsset.Compare(_Name, other._Name);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Are all fields in this object equal to the equivalent in `obj`?</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override readonly bool Equals(object obj)
|
||||
=> obj is NamedIndex value
|
||||
&& Equals(value);
|
||||
|
||||
/// <summary>Are all fields in this object equal to the equivalent fields in `other`?</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Equals(NamedIndex other)
|
||||
=> _Index == other._Index
|
||||
&& _Name == other._Name;
|
||||
|
||||
/// <summary>Are all fields in `a` equal to the equivalent fields in `b`?</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(NamedIndex a, NamedIndex b)
|
||||
=> a.Equals(b);
|
||||
|
||||
/// <summary>Are any fields in `a` not equal to the equivalent fields in `b`?</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(NamedIndex a, NamedIndex b)
|
||||
=> !(a == b);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a hash code based on the values of this object's fields.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override readonly int GetHashCode()
|
||||
=> AnimancerUtilities.Hash(-871379578,
|
||||
_Index.SafeGetHashCode(),
|
||||
_Name.SafeGetHashCode());
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b95ff193221b8141b0f3c98600a8f55
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
namespace Animancer.TransitionLibraries
|
||||
{
|
||||
/// <summary>Values which determine how a transition is played.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/libraries">
|
||||
/// Transition Libraries</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionDetails
|
||||
public struct TransitionDetails
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionDetails"/> with all values as <see cref="float.NaN"/>.</summary>
|
||||
public static TransitionDetails NaN
|
||||
=> new(float.NaN, float.NaN);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary><see cref="ITransition.FadeDuration"/></summary>
|
||||
public float FadeDuration;
|
||||
|
||||
/// <summary><see cref="ITransition.NormalizedStartTime"/></summary>
|
||||
public float NormalizedStartTime;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionDetails"/>.</summary>
|
||||
public TransitionDetails(
|
||||
float fadeDuration,
|
||||
float normalizedStartTime)
|
||||
{
|
||||
FadeDuration = fadeDuration;
|
||||
NormalizedStartTime = normalizedStartTime;
|
||||
}
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionDetails"/>.</summary>
|
||||
public TransitionDetails(
|
||||
ITransition transition)
|
||||
{
|
||||
FadeDuration = transition.FadeDuration;
|
||||
NormalizedStartTime = transition.NormalizedStartTime;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f515e60d52111f4789b11bfc4d1816a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,640 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Pro-Only]
|
||||
/// A library of <see cref="ITransition"/>s which allows specific
|
||||
/// transition combinations to be overridden without needing to be hard coded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/libraries">
|
||||
/// Transition Libraries</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionLibrary
|
||||
public class TransitionLibrary :
|
||||
IAnimationClipCollection,
|
||||
ICopyable<TransitionLibrary>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] Modifiers in the order they are created.</summary>
|
||||
/// <remarks>The <see cref="TransitionModifierGroup.Index"/> of each item corresponds to its position in this list.</remarks>
|
||||
private readonly List<TransitionModifierGroup>
|
||||
TransitionModifiers = new();
|
||||
|
||||
/// <summary>[Pro-Only] Modifiers registered by their <see cref="IHasKey.Key"/> as well as any custom aliases.</summary>
|
||||
private readonly Dictionary<object, TransitionModifierGroup>
|
||||
KeyedTransitionModifiers = new();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] The number of transitions in this library.</summary>
|
||||
public int Count
|
||||
=> TransitionModifiers.Count;
|
||||
|
||||
/// <summary>[Pro-Only] The number of transitions in this library plus any additional aliases.</summary>
|
||||
public int AliasCount
|
||||
=> KeyedTransitionModifiers.Count;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Queries
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Does this library contain a transition registered with the `key`?
|
||||
/// </summary>
|
||||
public bool ContainsKey(object key)
|
||||
=> KeyedTransitionModifiers.ContainsKey(key);
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Does this library contain a transition registered with the <see cref="IHasKey.Key"/>?
|
||||
/// </summary>
|
||||
public bool ContainsKey(IHasKey hasKey)
|
||||
=> ContainsKey(hasKey.Key);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Tries to find a <see cref="TransitionModifierGroup"/> registered with the `key`.
|
||||
/// </summary>
|
||||
public bool TryGetTransition(object key, out TransitionModifierGroup transition)
|
||||
{
|
||||
if (KeyedTransitionModifiers.TryGetValue(key, out transition))
|
||||
return true;
|
||||
|
||||
if (key is AnimancerState state)
|
||||
{
|
||||
key = AnimancerUtilities.GetRootKey(state.Key);
|
||||
|
||||
return
|
||||
key != null &&
|
||||
KeyedTransitionModifiers.TryGetValue(key, out transition);
|
||||
}
|
||||
|
||||
AssertStringReference(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Tries to find a <see cref="TransitionModifierGroup"/> registered with the <see cref="IHasKey.Key"/>.
|
||||
/// </summary>
|
||||
public bool TryGetTransition(IHasKey hasKey, out TransitionModifierGroup transition)
|
||||
=> TryGetTransition(hasKey.Key, out transition);
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Tries to find a <see cref="TransitionModifierGroup"/>
|
||||
/// via its <see cref="TransitionModifierGroup.Index"/>.
|
||||
/// </summary>
|
||||
public bool TryGetTransition(int index, out TransitionModifierGroup transition)
|
||||
=> TransitionModifiers.TryGet(index, out transition)
|
||||
&& transition != null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Finds the <see cref="TransitionModifierGroup.Index"/> of the group registered with the `key`
|
||||
/// or returns <c>-1</c>.
|
||||
/// </summary>
|
||||
public int IndexOf(object key)
|
||||
=> TryGetTransition(key, out var group)
|
||||
? group.Index
|
||||
: -1;
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Finds the <see cref="TransitionModifierGroup.Index"/> of the group registered with the `key`
|
||||
/// or returns <c>-1</c>.
|
||||
/// </summary>
|
||||
public int IndexOf(IHasKey hasKey)
|
||||
=> IndexOf(hasKey.Key);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Returns the fade duration to use when transitioning from `from` to the `transition`.
|
||||
/// </summary>
|
||||
public float GetFadeDuration(
|
||||
object from,
|
||||
ITransition to)
|
||||
{
|
||||
if (from != null &&
|
||||
TryGetTransition(to.Key, out var group))
|
||||
return group.GetFadeDuration(from);
|
||||
|
||||
return to.FadeDuration;
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Returns the fade duration to use when transitioning from `from` to the `transition`.
|
||||
/// </summary>
|
||||
public float GetFadeDuration(
|
||||
IHasKey from,
|
||||
ITransition to)
|
||||
=> GetFadeDuration(from?.Key, to);
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Returns the fade duration to use when transitioning from the
|
||||
/// <see cref="AnimancerLayer.CurrentState"/> to the `transition`.
|
||||
/// </summary>
|
||||
public float GetFadeDuration(
|
||||
AnimancerLayer layer,
|
||||
ITransition transition)
|
||||
=> GetFadeDuration(layer.CurrentState?.Key, transition);
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Returns the fade duration to use when transitioning from the
|
||||
/// <see cref="AnimancerLayer.CurrentState"/> to the `key`.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] Gathers all the animations in this library.</summary>
|
||||
public virtual void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
{
|
||||
for (int i = TransitionModifiers.Count - 1; i >= 0; i--)
|
||||
clips.GatherFromSource(TransitionModifiers[i].Transition);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Add
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] Adds the contents of the `definition` to this library.</summary>
|
||||
/// <remarks>Existing values will be completely replaced.</remarks>
|
||||
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++)
|
||||
SetModifier(definition.Modifiers[i]);
|
||||
|
||||
if (definition.AliasAllTransitions)
|
||||
{
|
||||
var modifierIndex = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var transition = definition.Transitions[i];
|
||||
if (transition == null)
|
||||
continue;
|
||||
|
||||
var modifier = TransitionModifiers[modifierIndex++];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] Adds the `transition` to this library.</summary>
|
||||
/// <exception cref="ArgumentException">A transition is already registered with the `key`.</exception>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only] Adds the `transition` to this library.</summary>
|
||||
/// <exception cref="ArgumentException">A transition is already registered with the `key`.</exception>
|
||||
public TransitionModifierGroup AddTransition(
|
||||
IHasKey hasKey,
|
||||
ITransition transition)
|
||||
=> AddTransition(hasKey.Key, transition);
|
||||
|
||||
/// <summary>[Pro-Only] Adds the `transition` to this library.</summary>
|
||||
/// <exception cref="ArgumentException">A transition is already registered with the `key`.</exception>
|
||||
public TransitionModifierGroup AddTransition(
|
||||
ITransition transition)
|
||||
=> AddTransition(transition, transition);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Adds the `transition` to this library or replaces the existing one registered with the `key`.
|
||||
/// </summary>
|
||||
public TransitionModifierGroup SetTransition(
|
||||
object key,
|
||||
ITransition transition)
|
||||
{
|
||||
if (TryGetTransition(key, out var oldModifier))
|
||||
{
|
||||
oldModifier.Transition = transition;
|
||||
return oldModifier;
|
||||
}
|
||||
|
||||
return AddTransition(key, transition);
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Adds the `transition` to this library or replaces the existing one registered with the `key`.
|
||||
/// </summary>
|
||||
public TransitionModifierGroup SetTransition(
|
||||
IHasKey hasKey,
|
||||
ITransition transition)
|
||||
=> SetTransition(hasKey.Key, transition);
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Adds the `transition` to this library or replaces the existing one registered with the `key`.
|
||||
/// </summary>
|
||||
public TransitionModifierGroup SetTransition(
|
||||
ITransition transition)
|
||||
=> SetTransition(transition, transition);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Sets the <see cref="ITransition.FadeDuration"/>
|
||||
/// to use when transitioning from `from` to `to`.
|
||||
/// </summary>
|
||||
public void SetFadeDuration(
|
||||
object from,
|
||||
ITransition to,
|
||||
float fadeDuration)
|
||||
{
|
||||
var group = SetTransition(to.Key, to);
|
||||
group.SetFadeDuration(from, fadeDuration);
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Sets the <see cref="ITransition.FadeDuration"/>
|
||||
/// to use when transitioning from `from` to `to`.
|
||||
/// </summary>
|
||||
public void SetFadeDuration(
|
||||
IHasKey from,
|
||||
ITransition to,
|
||||
float fadeDuration)
|
||||
=> SetFadeDuration(from.Key, to, fadeDuration);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Sets the <see cref="ITransition.NormalizedStartTime"/>
|
||||
/// to use when transitioning from `from` to `to`.
|
||||
/// </summary>
|
||||
public void SetNormalizedStartTime(
|
||||
object from,
|
||||
ITransition to,
|
||||
float normalizedStartTime)
|
||||
{
|
||||
var group = SetTransition(to.Key, to);
|
||||
group.SetNormalizedStartTime(from, normalizedStartTime);
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Sets the <see cref="ITransition.NormalizedStartTime"/>
|
||||
/// to use when transitioning from `from` to `to`.
|
||||
/// </summary>
|
||||
public void SetNormalizedStartTime(
|
||||
IHasKey from,
|
||||
ITransition to,
|
||||
float normalizedStartTime)
|
||||
=> SetNormalizedStartTime(from.Key, to, normalizedStartTime);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Sets the <see cref="ITransition.FadeDuration"/> and <see cref="ITransition.NormalizedStartTime"/>
|
||||
/// to use when transitioning from `from` to `to`.
|
||||
/// </summary>
|
||||
public void SetModifier(
|
||||
object from,
|
||||
ITransition to,
|
||||
TransitionDetails modifier)
|
||||
{
|
||||
var group = SetTransition(to.Key, to);
|
||||
group.SetModifier(from, modifier);
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Sets the <see cref="ITransition.FadeDuration"/> and <see cref="ITransition.NormalizedStartTime"/>
|
||||
/// to use when transitioning from `from` to `to`.
|
||||
/// </summary>
|
||||
public void SetModifier(
|
||||
IHasKey from,
|
||||
ITransition to,
|
||||
TransitionDetails modifier)
|
||||
=> SetModifier(from.Key, to, modifier);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only]
|
||||
/// Sets the <see cref="ITransition.FadeDuration"/> and <see cref="ITransition.NormalizedStartTime"/>
|
||||
/// to use when transitioning from <see cref="TransitionModifierDefinition.FromIndex"/>
|
||||
/// to <see cref="TransitionModifierDefinition.ToIndex"/>.
|
||||
/// </summary>
|
||||
public bool SetModifier(
|
||||
TransitionModifierDefinition modifier)
|
||||
{
|
||||
if (!TransitionModifiers.TryGet(modifier.FromIndex, out var from) ||
|
||||
!TransitionModifiers.TryGet(modifier.ToIndex, out var to))
|
||||
return false;
|
||||
|
||||
to.SetModifier(
|
||||
from.Transition.Key,
|
||||
modifier.ToTransitionDetails());
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] Registers the `group` with another `key`.</summary>
|
||||
public void AddAlias(
|
||||
object key,
|
||||
TransitionModifierGroup group)
|
||||
{
|
||||
AssertStringReference(key);
|
||||
AssertGroup(group);
|
||||
KeyedTransitionModifiers.Add(key, group);
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only] Registers the `transition` with the `key`.</summary>
|
||||
/// <remarks>Also registers it with its <see cref="IHasKey.Key"/> if it wasn't already.</remarks>
|
||||
public TransitionModifierGroup AddAlias(
|
||||
object key,
|
||||
ITransition transition)
|
||||
{
|
||||
var group = SetTransition(transition);
|
||||
AddAlias(key, group);
|
||||
return group;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] Adds the contents of `copyFrom` into this library.</summary>
|
||||
/// <remarks>
|
||||
/// This method adds and replaces values, but does not remove any
|
||||
/// (unlike <see cref="CopyFrom(TransitionLibrary, CloneContext)"/>.
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>[Pro-Only] Adds the contents of `copyFrom` into this library.</summary>
|
||||
/// <remarks>
|
||||
/// This method adds and replaces values, but does not remove any
|
||||
/// (unlike <see cref="CopyFrom(TransitionLibrary, CloneContext)"/>.
|
||||
/// </remarks>
|
||||
public void AddLibrary(TransitionLibrary library)
|
||||
{
|
||||
var context = CloneContext.Pool.Instance.Acquire();
|
||||
AddLibrary(library, context);
|
||||
CloneContext.Pool.Instance.Release(context);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>See also <see cref="AddLibrary(TransitionLibrary, CloneContext)"/>.</remarks>
|
||||
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.
|
||||
|
||||
/// <summary>[Pro-Only] Removes the transition registered with the `key`.</summary>
|
||||
public bool RemoveTransition(object key)
|
||||
=> KeyedTransitionModifiers.Remove(key);
|
||||
|
||||
/// <summary>[Pro-Only] Removes the transition registered with the <see cref="IHasKey.Key"/>.</summary>
|
||||
public bool RemoveTransition(IHasKey hasKey)
|
||||
=> RemoveTransition(hasKey.Key);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] Removes a modified fade duration for transitioning from `from` to `to`.</summary>
|
||||
public bool RemoveFadeDuration(object from, object to)
|
||||
=> TryGetTransition(to, out var group)
|
||||
&& group.FromKeyToModifier != null
|
||||
&& group.FromKeyToModifier.Remove(from);
|
||||
|
||||
/// <summary>[Pro-Only] Removes a modified fade duration for transitioning from `from` to `to`.</summary>
|
||||
public bool RemoveFadeDuration(IHasKey from, IHasKey to)
|
||||
=> RemoveFadeDuration(from.Key, to.Key);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Pro-Only] Removes everything from this library, leaving it empty.</summary>
|
||||
public void Clear()
|
||||
{
|
||||
TransitionModifiers.Clear();
|
||||
KeyedTransitionModifiers.Clear();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Play
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="AnimancerLayer.Play(ITransition, float, FadeMode)"/>
|
||||
/// with the fade duration potentially modified by this library.
|
||||
/// </summary>
|
||||
public AnimancerState Play(
|
||||
AnimancerLayer layer,
|
||||
ITransition transition)
|
||||
=> TryGetTransition(transition, out var modifier)
|
||||
? Play(layer, modifier)
|
||||
: layer.Play(transition);
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="AnimancerLayer.Play(ITransition, float, FadeMode)"/>
|
||||
/// with the fade duration potentially modified by this library.
|
||||
/// </summary>
|
||||
public AnimancerState Play(
|
||||
AnimancerLayer layer,
|
||||
TransitionModifierGroup transition)
|
||||
{
|
||||
var from = layer.CurrentState?.Key;
|
||||
var to = transition.Transition;
|
||||
var details = transition.GetDetails(from);
|
||||
|
||||
if (float.IsNaN(details.FadeDuration))
|
||||
details.FadeDuration = to.FadeDuration;
|
||||
|
||||
if (float.IsNaN(details.NormalizedStartTime))
|
||||
return layer.Play(
|
||||
to,
|
||||
details.FadeDuration,
|
||||
to.FadeMode);
|
||||
|
||||
var state = layer.Play(
|
||||
to,
|
||||
details.FadeDuration,
|
||||
FadeMode.FromStart);
|
||||
state.NormalizedTime = details.NormalizedStartTime;
|
||||
return state;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Plays the transition registered with the specified `key` if there is one.
|
||||
/// Otherwise, returns <c>null</c>.
|
||||
/// </summary>
|
||||
public AnimancerState TryPlay(
|
||||
AnimancerLayer layer,
|
||||
object key)
|
||||
=> TryGetTransition(key, out var transition)
|
||||
? Play(layer, transition)
|
||||
: null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Assertions
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional]
|
||||
/// Logs <see cref="OptionalWarning.StringReference"/> if the `key` is a <see cref="string"/>.
|
||||
/// </summary>
|
||||
[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
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional]
|
||||
/// Asserts that the <see cref="TransitionModifierGroup.Index"/>
|
||||
/// corresponds to the <see cref="TransitionModifiers"/>.
|
||||
/// </summary>
|
||||
[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
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab6185b55c780f744ada43f1408d8482
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,97 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.TransitionLibraries
|
||||
{
|
||||
/// <summary>[Pro-Only]
|
||||
/// A <see cref="ScriptableObject"/> which serializes a <see cref="TransitionLibraryDefinition"/>
|
||||
/// and creates a <see cref="TransitionLibrary"/> from it at runtime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/libraries">
|
||||
/// Transition Libraries</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionLibraryAsset
|
||||
[CreateAssetMenu(
|
||||
menuName = Strings.MenuPrefix + "Transition Library",
|
||||
order = Strings.AssetMenuOrder + 0)]
|
||||
[AnimancerHelpUrl(typeof(TransitionLibraryAsset))]
|
||||
public class TransitionLibraryAsset : ScriptableObject,
|
||||
IAnimationClipCollection
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TransitionLibraryDefinition _Definition;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// The serialized data which will be used to initialize the <see cref="Library"/> at runtime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you modify the contents of this reference, either re-assign this property
|
||||
/// or call <see cref="OnDefinitionModified"/> to apply any changes to the <see cref="Library"/>.
|
||||
/// </remarks>
|
||||
public TransitionLibraryDefinition Definition
|
||||
{
|
||||
get => _Definition;
|
||||
set
|
||||
{
|
||||
_Definition = value ?? new();
|
||||
OnDefinitionModified();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] [Internal]
|
||||
/// The name of the field which stores the <see cref="Definition"/>.
|
||||
/// </summary>
|
||||
internal const string DefinitionField = nameof(_Definition);
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The runtime <see cref="TransitionLibrary"/> created from the <see cref="Definition"/>.</summary>
|
||||
public TransitionLibrary Library { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes the <see cref="Library"/>.</summary>
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_Definition ??= new();
|
||||
|
||||
Library = new();
|
||||
Library.Initialize(_Definition);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Adds the contents of the <see cref="Definition"/>
|
||||
/// to the <see cref="Library"/> if it was already initialized.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this after modifying the contents of the <see cref="Definition"/>
|
||||
/// to ensure that the <see cref="Library"/> reflects any changes.
|
||||
/// <para></para>
|
||||
/// Note that this doesn't remove anything from the <see cref="Library"/>,
|
||||
/// it only adds or replaces values.
|
||||
/// </remarks>
|
||||
public void OnDefinitionModified()
|
||||
=> Library?.Initialize(_Definition);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers all the animations in the <see cref="Definition"/> and <see cref="Library"/>.</summary>
|
||||
public virtual void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
{
|
||||
clips.GatherFromSource(_Definition);
|
||||
clips.GatherFromSource(Library);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f08fdd50e79d0c4bb235a5c2696516d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,407 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.TransitionLibraries
|
||||
{
|
||||
/// <summary>[<see cref="SerializableAttribute"/>]
|
||||
/// A library of transitions and other details which can create a <see cref="TransitionLibrary"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/libraries">
|
||||
/// Transition Libraries</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionLibraryDefinition
|
||||
[Serializable]
|
||||
public class TransitionLibraryDefinition :
|
||||
IAnimationClipCollection,
|
||||
ICopyable<TransitionLibraryDefinition>,
|
||||
IEquatable<TransitionLibraryDefinition>,
|
||||
IHasDescription
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#region Fields and Properties
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TransitionAssetBase[]
|
||||
_Transitions = Array.Empty<TransitionAssetBase>();
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The transitions in this library.</summary>
|
||||
/// <remarks>This property uses an empty array instead of <c>null</c>.</remarks>
|
||||
public TransitionAssetBase[] Transitions
|
||||
{
|
||||
get => _Transitions;
|
||||
set => _Transitions = value.NullIsEmpty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TransitionModifierDefinition[]
|
||||
_Modifiers = Array.Empty<TransitionModifierDefinition>();
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] Modified fade durations for specific transition combinations.</summary>
|
||||
/// <remarks>This property uses an empty array instead of <c>null</c>.</remarks>
|
||||
public TransitionModifierDefinition[] Modifiers
|
||||
{
|
||||
get => _Modifiers;
|
||||
set => _Modifiers = value.NullIsEmpty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private NamedIndex[]
|
||||
_Aliases = Array.Empty<NamedIndex>();
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] Alternate names that can be used to look up transitions.</summary>
|
||||
/// <remarks>
|
||||
/// This array should always be sorted, use <see cref="SortAliases"/> if necessary.
|
||||
/// <para></para>
|
||||
/// This property uses an empty array instead of <c>null</c>.
|
||||
/// </remarks>
|
||||
public NamedIndex[] Aliases
|
||||
{
|
||||
get => _Aliases;
|
||||
set => _Aliases = value.NullIsEmpty();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(AliasAllTransitionsTooltip)]
|
||||
private bool _AliasAllTransitions;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// Should all Transitions automatically be registered using their name as an Alias?
|
||||
/// </summary>
|
||||
public ref bool AliasAllTransitions
|
||||
=> ref _AliasAllTransitions;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] [Internal]
|
||||
/// The name of the field which stores the <see cref="AliasAllTransitions"/>.
|
||||
/// </summary>
|
||||
internal const string AliasAllTransitionsField = nameof(_AliasAllTransitions);
|
||||
#endif
|
||||
|
||||
/// <summary>Tooltip for the <see cref="AliasAllTransitions"/> field.</summary>
|
||||
public const string AliasAllTransitionsTooltip =
|
||||
"Should all Transitions automatically be registered using their name as an Alias?";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Transitions
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="AnimancerUtilities.TryGet{T}(IList{T}, int, out T)"/> for the <see cref="Transitions"/>.
|
||||
/// </summary>
|
||||
public bool TryGetTransition(
|
||||
int index,
|
||||
out TransitionAssetBase transition)
|
||||
=> _Transitions.TryGet(index, out transition)
|
||||
&& transition != null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds an item to the end of the <see cref="Transitions"/>.</summary>
|
||||
public void AddTransition(
|
||||
TransitionAssetBase transition)
|
||||
=> AnimancerUtilities.InsertAt(
|
||||
ref _Transitions,
|
||||
_Transitions.Length,
|
||||
transition);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the <see cref="Transitions"/>
|
||||
/// and adjusts the other fields to account for the moved indices.
|
||||
/// </summary>
|
||||
public void RemoveTransition(int index)
|
||||
{
|
||||
if ((uint)index >= _Transitions.Length)
|
||||
return;
|
||||
|
||||
AnimancerUtilities.RemoveAt(ref _Transitions, index);
|
||||
|
||||
for (int i = _Modifiers.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var modifier = _Modifiers[i];
|
||||
|
||||
// Remove any modifiers targeting that transition.
|
||||
if (modifier.FromIndex == index ||
|
||||
modifier.ToIndex == index)
|
||||
{
|
||||
AnimancerUtilities.RemoveAt(ref _Modifiers, i);
|
||||
}
|
||||
else// Adjust the indices of any modifiers after it.
|
||||
{
|
||||
var fromIndex = modifier.FromIndex;
|
||||
if (fromIndex > index)
|
||||
fromIndex--;
|
||||
|
||||
var toIndex = modifier.ToIndex;
|
||||
if (toIndex > index)
|
||||
toIndex--;
|
||||
|
||||
_Modifiers[i] = modifier.WithIndices(fromIndex, toIndex);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = _Aliases.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var alias = _Aliases[i];
|
||||
|
||||
// Remove any aliases targeting that transition.
|
||||
if (alias.Index == index)
|
||||
{
|
||||
AnimancerUtilities.RemoveAt(ref _Aliases, i);
|
||||
}
|
||||
else// Adjust the indices of any aliases after it.
|
||||
{
|
||||
if (alias.Index > index)
|
||||
_Aliases[i] = alias.With(alias.Index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Modifiers
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to find an item in the <see cref="Modifiers"/> with the specified indices.</summary>
|
||||
/// <remarks>
|
||||
/// If unsuccessful, the `modifier` is given the details
|
||||
/// from the <see cref="Transitions"/> at the `toIndex`. and this method returns false.
|
||||
/// </remarks>
|
||||
public bool TryGetModifier(
|
||||
int fromIndex,
|
||||
int toIndex,
|
||||
out TransitionModifierDefinition modifier)
|
||||
{
|
||||
var index = IndexOfModifier(fromIndex, toIndex);
|
||||
if (index >= 0)
|
||||
{
|
||||
modifier = _Modifiers[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
float fadeDuration, normalizedStartTime;
|
||||
if (TryGetTransition(toIndex, out var transition))
|
||||
{
|
||||
fadeDuration = transition.TryGetFadeDuration();
|
||||
normalizedStartTime = transition.TryGetNormalizedStartTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
fadeDuration = float.NaN;
|
||||
normalizedStartTime = float.NaN;
|
||||
}
|
||||
|
||||
modifier = new(fromIndex, toIndex, fadeDuration, normalizedStartTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index in the <see cref="Modifiers"/> which matches the given
|
||||
/// <see cref="TransitionModifierDefinition.FromIndex"/> and
|
||||
/// <see cref="TransitionModifierDefinition.ToIndex"/> or -1 if no such item exists.
|
||||
/// </summary>
|
||||
public int IndexOfModifier(int fromIndex, int toIndex)
|
||||
{
|
||||
for (int i = _Modifiers.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var modifier = _Modifiers[i];
|
||||
if (modifier.FromIndex == fromIndex &&
|
||||
modifier.ToIndex == toIndex)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds, replaces, or removes an item in the <see cref="Modifiers"/>.</summary>
|
||||
public void SetModifier(
|
||||
TransitionModifierDefinition modifier)
|
||||
{
|
||||
if (!modifier.Validate())
|
||||
{
|
||||
RemoveModifier(modifier);
|
||||
return;
|
||||
}
|
||||
|
||||
var index = IndexOfModifier(modifier.FromIndex, modifier.ToIndex);
|
||||
if (index >= 0)
|
||||
{
|
||||
_Modifiers[index] = modifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimancerUtilities.InsertAt(ref _Modifiers, _Modifiers.Length, modifier);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Removes an item from the <see cref="Modifiers"/>.</summary>
|
||||
public bool RemoveModifier(
|
||||
TransitionModifierDefinition modifier)
|
||||
=> RemoveModifier(modifier.FromIndex, modifier.ToIndex);
|
||||
|
||||
/// <summary>Removes an item from the <see cref="Modifiers"/>.</summary>
|
||||
public bool RemoveModifier(int fromIndex, int toIndex)
|
||||
{
|
||||
var index = IndexOfModifier(fromIndex, toIndex);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
AnimancerUtilities.RemoveAt(ref _Modifiers, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Aliases
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds an item to the <see cref="Aliases"/>, sorted by its values.</summary>
|
||||
public int AddAlias(NamedIndex alias)
|
||||
{
|
||||
int i = 0;
|
||||
for (; i < _Aliases.Length; i++)
|
||||
if (alias.CompareTo(_Aliases[i]) <= 0)
|
||||
break;
|
||||
|
||||
AnimancerUtilities.InsertAt(ref _Aliases, i, alias);
|
||||
return i;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Removes an item from the <see cref="Aliases"/>.</summary>
|
||||
public bool RemoveAlias(NamedIndex alias)
|
||||
{
|
||||
var index = Array.IndexOf(_Aliases, alias);
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
RemoveAlias(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the <see cref="Aliases"/>.</summary>
|
||||
public void RemoveAlias(int index)
|
||||
=> AnimancerUtilities.RemoveAt(ref _Aliases, index);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Ensures that the <see cref="Aliases"/> are sorted.</summary>
|
||||
/// <remarks>This method shouldn't need to be called manually since aliases are always added in order.</remarks>
|
||||
public void SortAliases()
|
||||
=> Array.Sort(_Aliases, static (a, b) => a.CompareTo(b));
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Equality
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Are all fields in this object equal to the equivalent in `obj`?</summary>
|
||||
public override bool Equals(object obj)
|
||||
=> Equals(obj as TransitionLibraryDefinition);
|
||||
|
||||
/// <summary>Are all fields in this object equal to the equivalent fields in `other`?</summary>
|
||||
public bool Equals(TransitionLibraryDefinition other)
|
||||
=> other != null
|
||||
&& AnimancerUtilities.ContentsAreEqual(_Transitions, other._Transitions)
|
||||
&& AnimancerUtilities.ContentsAreEqual(_Modifiers, other._Modifiers)
|
||||
&& AnimancerUtilities.ContentsAreEqual(_Aliases, other._Aliases)
|
||||
&& _AliasAllTransitions == other._AliasAllTransitions;
|
||||
|
||||
/// <summary>Are all fields in `a` equal to the equivalent fields in `b`?</summary>
|
||||
public static bool operator ==(TransitionLibraryDefinition a, TransitionLibraryDefinition b)
|
||||
=> a is null
|
||||
? b is null
|
||||
: a.Equals(b);
|
||||
|
||||
/// <summary>Are any fields in `a` not equal to the equivalent fields in `b`?</summary>
|
||||
public static bool operator !=(TransitionLibraryDefinition a, TransitionLibraryDefinition b)
|
||||
=> !(a == b);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a hash code based on the values of this object's fields.</summary>
|
||||
public override int GetHashCode()
|
||||
=> AnimancerUtilities.Hash(-871379578,
|
||||
_Transitions.SafeGetHashCode(),
|
||||
_Modifiers.SafeGetHashCode(),
|
||||
_Aliases.SafeGetHashCode(),
|
||||
_AliasAllTransitions.SafeGetHashCode());
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
#region Other
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers all the animations in this definition.</summary>
|
||||
public virtual void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
=> clips.GatherFromSource(_Transitions);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyFrom(TransitionLibraryDefinition copyFrom, CloneContext context)
|
||||
{
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Transitions, ref _Transitions);
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Modifiers, ref _Modifiers);
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Aliases, ref _Aliases);
|
||||
_AliasAllTransitions = copyFrom._AliasAllTransitions;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AppendDescription(StringBuilder text, string separator = "\n")
|
||||
{
|
||||
text.Append(GetType().Name);
|
||||
|
||||
if (!separator.StartsWithNewLine())
|
||||
separator = "\n" + separator;
|
||||
|
||||
var indentedSeparator = separator + Strings.Indent;
|
||||
|
||||
text.AppendField(separator, nameof(Transitions), Transitions.Length);
|
||||
for (int i = 0; i < Transitions.Length; i++)
|
||||
text.AppendField(indentedSeparator, i.ToString(), Transitions[i]);
|
||||
|
||||
text.AppendField(separator, nameof(Modifiers), Modifiers.Length);
|
||||
for (int i = 0; i < Modifiers.Length; i++)
|
||||
text.AppendField(indentedSeparator, i.ToString(), Modifiers[i]);
|
||||
|
||||
text.AppendField(separator, nameof(Aliases), Aliases.Length);
|
||||
for (int i = 0; i < Aliases.Length; i++)
|
||||
text.AppendField(indentedSeparator, i.ToString(), Aliases[i]);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42db79bf33227a542840e18860864e0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,162 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer.TransitionLibraries
|
||||
{
|
||||
/// <summary>[<see cref="SerializableAttribute"/>]
|
||||
/// Details about how to modify a transition when it comes from a specific source.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Multiple of these can be used to build a <see cref="TransitionModifierGroup"/> at runtime.
|
||||
/// <para></para>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/libraries">
|
||||
/// Transition Libraries</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionModifierDefinition
|
||||
[Serializable]
|
||||
public struct TransitionModifierDefinition :
|
||||
IEquatable<TransitionModifierDefinition>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private int _From;
|
||||
|
||||
/// <summary>The index of the source transition in the <see cref="TransitionLibraryDefinition"/>.</summary>
|
||||
public readonly int FromIndex
|
||||
=> _From;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private int _To;
|
||||
|
||||
/// <summary>The index of the destination transition in the <see cref="TransitionLibraryDefinition"/>.</summary>
|
||||
public readonly int ToIndex
|
||||
=> _To;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private float _Fade;
|
||||
|
||||
/// <summary>The fade duration for this modifier to use instead of the transition's default value.</summary>
|
||||
public readonly float FadeDuration
|
||||
=> _Fade;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private float _NormalizedStartTime;
|
||||
|
||||
/// <summary>The normalized start time for this modifier to use instead of the transition's default value.</summary>
|
||||
public readonly float NormalizedStartTime
|
||||
=> _NormalizedStartTime;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionModifierDefinition"/>.</summary>
|
||||
public TransitionModifierDefinition(
|
||||
int fromIndex,
|
||||
int toIndex,
|
||||
float fadeDuration,
|
||||
float normalizedStartTime)
|
||||
{
|
||||
_From = fromIndex;
|
||||
_To = toIndex;
|
||||
_Fade = fadeDuration;
|
||||
_NormalizedStartTime = normalizedStartTime;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Does this modifier contain valid values?</summary>
|
||||
public bool Validate()
|
||||
{
|
||||
var noFade = float.IsNaN(_Fade);
|
||||
var noStart = float.IsNaN(_NormalizedStartTime);
|
||||
if (noFade && noStart)
|
||||
return false;
|
||||
|
||||
if (!noFade)
|
||||
{
|
||||
if (_Fade < 0)
|
||||
_Fade = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a copy of this modifier with the specified <see cref="FadeDuration"/>.</summary>
|
||||
public readonly TransitionModifierDefinition WithFadeDuration(float fadeDuration)
|
||||
=> new(_From, _To, fadeDuration, _NormalizedStartTime);
|
||||
|
||||
/// <summary>Creates a copy of this modifier with the specified <see cref="NormalizedStartTime"/>.</summary>
|
||||
public readonly TransitionModifierDefinition WithNormalizedStartTime(float normalizedStartTime)
|
||||
=> new(_From, _To, _Fade, normalizedStartTime);
|
||||
|
||||
/// <summary>Creates a copy of this modifier with the specified <see cref="FadeDuration"/> and <see cref="NormalizedStartTime"/>.</summary>
|
||||
public readonly TransitionModifierDefinition WithDetails(float fadeDuration, float normalizedStartTime)
|
||||
=> new(_From, _To, fadeDuration, normalizedStartTime);
|
||||
|
||||
/// <summary>Creates a copy of this modifier with the specified <see cref="FromIndex"/> and <see cref="ToIndex"/>.</summary>
|
||||
public readonly TransitionModifierDefinition WithIndices(int fromIndex, int toIndex)
|
||||
=> new(fromIndex, toIndex, _Fade, _NormalizedStartTime);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionDetails"/> from this modifier.</summary>
|
||||
public readonly TransitionDetails ToTransitionDetails()
|
||||
=> new(_Fade, _NormalizedStartTime);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new string describing this modifier.</summary>
|
||||
public override readonly string ToString()
|
||||
=> $"{nameof(TransitionModifierDefinition)}({_From}->{_To}, F={_Fade}, S={_NormalizedStartTime})";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Equality
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Are all fields in this object equal to the equivalent in `obj`?</summary>
|
||||
public override readonly bool Equals(object obj)
|
||||
=> obj is TransitionModifierDefinition value
|
||||
&& Equals(value);
|
||||
|
||||
/// <summary>Are all fields in this object equal to the equivalent fields in `other`?</summary>
|
||||
public readonly bool Equals(TransitionModifierDefinition other)
|
||||
=> _From == other._From
|
||||
&& _To == other._To
|
||||
&& _Fade.IsEqualOrBothNaN(other._Fade)
|
||||
&& _NormalizedStartTime.IsEqualOrBothNaN(other._NormalizedStartTime);
|
||||
|
||||
/// <summary>Are all fields in `a` equal to the equivalent fields in `b`?</summary>
|
||||
public static bool operator ==(TransitionModifierDefinition a, TransitionModifierDefinition b)
|
||||
=> a.Equals(b);
|
||||
|
||||
/// <summary>Are any fields in `a` not equal to the equivalent fields in `b`?</summary>
|
||||
public static bool operator !=(TransitionModifierDefinition a, TransitionModifierDefinition b)
|
||||
=> !(a == b);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns a hash code based on the values of this object's fields.</summary>
|
||||
public override readonly int GetHashCode()
|
||||
=> AnimancerUtilities.Hash(-871379578,
|
||||
_From.GetHashCode(),
|
||||
_To.GetHashCode(),
|
||||
_Fade.GetHashCode(),
|
||||
_NormalizedStartTime.GetHashCode());
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88850ffc4123d1e468ff9c086823689c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,181 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Animancer.TransitionLibraries
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="ITransition"/> and a dictionary to modify it based on the previous state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/libraries">
|
||||
/// Transition Libraries</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer.TransitionLibraries/TransitionModifierGroup
|
||||
public class TransitionModifierGroup :
|
||||
ICloneable<TransitionModifierGroup>,
|
||||
ICopyable<TransitionModifierGroup>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The index at which this group was added to its <see cref="TransitionLibrary"/>.</summary>
|
||||
public readonly int Index;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private ITransition _Transition;
|
||||
|
||||
/// <summary>The target transition of this group.</summary>
|
||||
/// <remarks>Can't be <c>null</c>.</remarks>
|
||||
public ITransition Transition
|
||||
{
|
||||
get => _Transition;
|
||||
set
|
||||
{
|
||||
AnimancerUtilities.Assert(
|
||||
value != null,
|
||||
$"{nameof(TransitionModifierGroup)}.{nameof(Transition)} can't be null.");
|
||||
|
||||
_Transition = value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Custom modifiers to use when playing a <see cref="Transition"/>
|
||||
/// depending on the <see cref="IHasKey.Key"/> of the source state it is coming from.
|
||||
/// </summary>
|
||||
/// <remarks>This is <c>null</c> by default until <see cref="SetModifier"/> adds something.</remarks>
|
||||
public Dictionary<object, TransitionDetails> FromKeyToModifier;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionModifierGroup"/>.</summary>
|
||||
public TransitionModifierGroup(
|
||||
int index,
|
||||
ITransition transition)
|
||||
{
|
||||
Index = index;
|
||||
Transition = transition;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sets the `modifier` to use when transitioning from `from` to the <see cref="Transition"/>.</summary>
|
||||
public void SetModifier(object from, TransitionDetails modifier)
|
||||
{
|
||||
FromKeyToModifier ??= new();
|
||||
FromKeyToModifier[from] = modifier;
|
||||
}
|
||||
|
||||
/// <summary>Removes the fade duration modifier set for transitioning from `from` to the <see cref="Transition"/>.</summary>
|
||||
public void ResetModifier(object from)
|
||||
=> FromKeyToModifier?.Remove(from);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="TransitionDetails.FadeDuration"/>
|
||||
/// to use when transitioning from `from` to the <see cref="Transition"/>.
|
||||
/// </summary>
|
||||
public void SetFadeDuration(object from, float fadeDuration)
|
||||
{
|
||||
FromKeyToModifier ??= new();
|
||||
|
||||
if (!FromKeyToModifier.TryGetValue(from, out var modifier))
|
||||
modifier = TransitionDetails.NaN;
|
||||
|
||||
modifier.FadeDuration = fadeDuration;
|
||||
|
||||
FromKeyToModifier[from] = modifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="TransitionDetails.NormalizedStartTime"/>
|
||||
/// to use when transitioning from `from` to the <see cref="Transition"/>.
|
||||
/// </summary>
|
||||
public void SetNormalizedStartTime(object from, float normalizedStartTime)
|
||||
{
|
||||
FromKeyToModifier ??= new();
|
||||
|
||||
if (!FromKeyToModifier.TryGetValue(from, out var modifier))
|
||||
modifier = TransitionDetails.NaN;
|
||||
|
||||
modifier.NormalizedStartTime = normalizedStartTime;
|
||||
|
||||
FromKeyToModifier[from] = modifier;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the fade duration to use when transitioning from `from` to the <see cref="Transition"/>.</summary>
|
||||
public TransitionDetails GetDetails(object from)
|
||||
{
|
||||
if (FromKeyToModifier != null && from != null)
|
||||
{
|
||||
from = AnimancerUtilities.GetRootKey(from);
|
||||
if (FromKeyToModifier.TryGetValue(from, out var details))
|
||||
{
|
||||
if (float.IsNaN(details.FadeDuration))
|
||||
details.FadeDuration = Transition.FadeDuration;
|
||||
|
||||
if (float.IsNaN(details.NormalizedStartTime))
|
||||
details.NormalizedStartTime = Transition.NormalizedStartTime;
|
||||
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
return new(Transition);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the fade duration to use when transitioning from `from` to the <see cref="Transition"/>.</summary>
|
||||
public float GetFadeDuration(object from)
|
||||
=> FromKeyToModifier != null
|
||||
&& FromKeyToModifier.TryGetValue(AnimancerUtilities.GetRootKey(from), out var modifier)
|
||||
? modifier.FadeDuration
|
||||
: Transition.FadeDuration;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TransitionModifierGroup Clone(CloneContext context)
|
||||
{
|
||||
var clone = new TransitionModifierGroup(Index, null);
|
||||
clone.CopyFrom(this);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyFrom(TransitionModifierGroup copyFrom, CloneContext context)
|
||||
{
|
||||
Transition = copyFrom.Transition;
|
||||
|
||||
if (copyFrom.FromKeyToModifier == null)
|
||||
{
|
||||
FromKeyToModifier?.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
FromKeyToModifier ??= new();
|
||||
foreach (var item in copyFrom.FromKeyToModifier)
|
||||
FromKeyToModifier[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Describes this object.</summary>
|
||||
public override string ToString()
|
||||
=> $"{nameof(TransitionModifierGroup)}([{Index}] {AnimancerUtilities.ToStringOrNull(Transition)})";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 637963091ecbba44baffa2316cf8cd32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4dbf2f76c66425a4697b8e34d3e224f1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,204 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using Animancer.Units;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ClipTransition
|
||||
[Serializable]
|
||||
public class ClipTransition : Transition<ClipState>,
|
||||
IMotion,
|
||||
IAnimationClipCollection,
|
||||
ICopyable<ClipTransition>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Clip"/>.</summary>
|
||||
public const string ClipFieldName = nameof(_Clip);
|
||||
|
||||
[SerializeField, Tooltip("The animation to play")]
|
||||
private AnimationClip _Clip;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The animation to play.</summary>
|
||||
/// <remarks>
|
||||
/// If you set this property and this transition has been played on multiple characters,
|
||||
/// you will need to call <see cref="Transition{T}.ReconcileMainObject(AnimancerGraph)"/>
|
||||
/// for each of them to create new states for the newly assigned object.
|
||||
/// </remarks>
|
||||
public AnimationClip Clip
|
||||
{
|
||||
get => _Clip;
|
||||
set
|
||||
{
|
||||
Validate.AssertAnimationClip(value, false, $"set {nameof(ClipTransition)}.{nameof(Clip)}");
|
||||
_Clip = value;
|
||||
|
||||
if (BaseState != null)
|
||||
ReconcileMainObject(BaseState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Object MainObject => _Clip;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(Strings.Tooltips.NormalizedStartTime)]
|
||||
[DefaultValue(float.NaN, 0f)]
|
||||
[AnimationTime(
|
||||
AnimationTimeAttribute.Units.Normalized,
|
||||
DisabledText = Strings.Tooltips.StartTimeDisabled)]
|
||||
private float _NormalizedStartTime = float.NaN;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float NormalizedStartTime
|
||||
{
|
||||
get => _NormalizedStartTime;
|
||||
set => _NormalizedStartTime = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this transition will set the <see cref="AnimancerState.Time"/>,
|
||||
/// then it needs to use <see cref="FadeMode.FromStart"/>.
|
||||
/// </summary>
|
||||
public override FadeMode FadeMode
|
||||
=> float.IsNaN(_NormalizedStartTime)
|
||||
? default
|
||||
: FadeMode.FromStart;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The length of the <see cref="Clip"/> (in seconds),
|
||||
/// accounting for the <see cref="NormalizedStartTime"/>
|
||||
/// and <see cref="AnimancerEvent.Sequence.NormalizedEndTime"/>
|
||||
/// (but not <see cref="Speed"/>).
|
||||
/// </summary>
|
||||
public virtual float Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsValid)
|
||||
return 0;
|
||||
|
||||
var normalizedStartTime = AnimancerEvent.Sequence.GetNormalizedStartTime(
|
||||
_NormalizedStartTime,
|
||||
Speed);
|
||||
|
||||
var normalizedEndTime = Events.GetRealNormalizedEndTime(Speed);
|
||||
|
||||
return _Clip.length * (normalizedEndTime - normalizedStartTime);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsValid => _Clip != null && !_Clip.legacy;
|
||||
|
||||
/// <summary>[<see cref="ITransition"/>] Is the <see cref="Clip"/> looping?</summary>
|
||||
public override bool IsLooping => _Clip != null && _Clip.isLooping;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float MaximumLength => _Clip != null ? _Clip.length : 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual float AverageAngularSpeed => _Clip != null ? _Clip.averageAngularSpeed : default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual Vector3 AverageVelocity => _Clip != null ? _Clip.averageSpeed : default;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ClipState CreateState()
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (_Clip == null)
|
||||
throw new ArgumentException(
|
||||
$"Unable to create {nameof(ClipState)} because the" +
|
||||
$" {nameof(ClipTransition)}.{nameof(Clip)} is null.");
|
||||
#endif
|
||||
|
||||
return State = new(_Clip);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(AnimancerState state)
|
||||
{
|
||||
base.Apply(state);
|
||||
ApplyNormalizedStartTime(state, _NormalizedStartTime);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[<see cref="IAnimationClipCollection"/>]
|
||||
/// Adds the <see cref="Clip"/> to the collection.
|
||||
/// </summary>
|
||||
public virtual void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
=> clips.Gather(_Clip);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Transition<ClipState> Clone(CloneContext context)
|
||||
{
|
||||
var clone = new ClipTransition();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(Transition<ClipState> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(ClipTransition copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
_Clip = context.GetCloneOrOriginal(copyFrom._Clip);
|
||||
_NormalizedStartTime = copyFrom._NormalizedStartTime;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="ClipTransition"/>
|
||||
/// if the `target` is an <see cref="AnimationClip"/>.
|
||||
/// </summary>
|
||||
[TryCreateTransition(typeof(AnimationClip))]
|
||||
public static ITransition TryCreateTransition(Object target)
|
||||
=> target is not AnimationClip clip
|
||||
? null
|
||||
: new ClipTransition()
|
||||
{
|
||||
Clip = clip,
|
||||
};
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] Validates that the `command` is targeting an asset.</summary>
|
||||
[UnityEditor.MenuItem("CONTEXT/" + nameof(AnimationClip) + "/Create Transition Asset", validate = true)]
|
||||
private static bool ValidateCreateTransitionAsset(UnityEditor.MenuCommand command)
|
||||
=> TryCreateTransitionAttribute.CanCreateAndSave(command.context);
|
||||
|
||||
/// <summary>[Editor-Only] Tries to create an asset containing an appropriate transition for the `command`.</summary>
|
||||
[UnityEditor.MenuItem("CONTEXT/" + nameof(AnimationClip) + "/Create Transition Asset")]
|
||||
private static void CreateTransitionAsset(UnityEditor.MenuCommand command)
|
||||
=> TryCreateTransitionAttribute.TryCreateTransitionAsset(command.context, true);
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bcc2d27b8a6cc084799ce891e5178f6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,355 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>A group of <see cref="ClipTransition"/>s which play one after the other.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ClipTransitionSequence
|
||||
///
|
||||
[Serializable]
|
||||
[Obsolete("ClipTransitionSequence has been replaced by TransitionSequence" +
|
||||
" which is much more powerful and works properly with Animancer Events." +
|
||||
" This script still works the same as it always has so if you want to" +
|
||||
" keep using it you can simply remove this [Obsolete] attribute." +
|
||||
" This script will be removed in a future version of Animancer.")]
|
||||
public class ClipTransitionSequence : ClipTransition,
|
||||
ICopyable<ClipTransitionSequence>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[DrawAfterEvents]
|
||||
[SerializeField]
|
||||
[Tooltip("The other transitions to play in order after the first one.")]
|
||||
private ClipTransition[] _Others = Array.Empty<ClipTransition>();
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The transitions to play in order after the first one.</summary>
|
||||
/// <remarks>
|
||||
/// If you modify any individual items of this array at runtime without actually setting this property, you
|
||||
/// must call <see cref="InitializeEndEventChain"/> afterwards.
|
||||
/// </remarks>
|
||||
public ClipTransition[] Others
|
||||
{
|
||||
get => _Others;
|
||||
set
|
||||
{
|
||||
_Others = value;
|
||||
InitializeEndEventChain();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The last of the <see cref="Others"/> (or <c>this</c> if there are none).</summary>
|
||||
public ClipTransition LastTransition
|
||||
=> _Others.Length > 0
|
||||
? _Others[^1]
|
||||
: this;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Action _OnEnd;
|
||||
|
||||
/// <summary>Initializes the End Events of each of the <see cref="Others"/> to play the next one.</summary>
|
||||
public void InitializeEndEventChain()
|
||||
{
|
||||
if (_Others == null ||
|
||||
_Others.Length <= 0)
|
||||
return;
|
||||
|
||||
_OnEnd ??= () => AnimancerEvent.Current.State.Layer.Play(_Others[0]);
|
||||
InitializeFirstEndEvent();
|
||||
|
||||
// Assign each of the other end events, but this first one will be set by Apply.
|
||||
|
||||
var previous = _Others[0];
|
||||
for (int i = 1; i < _Others.Length; i++)
|
||||
{
|
||||
var next = _Others[i];
|
||||
previous.Events.OnEnd += () => AnimancerEvent.Current.State.Layer.Play(next);
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If an end event is assigned other than the one to play the next transition,
|
||||
/// this method replaces it and move it to be the end event of the last transition instead.
|
||||
/// </summary>
|
||||
private void InitializeFirstEndEvent()
|
||||
{
|
||||
var onEnd = Events.OnEnd;
|
||||
if (onEnd == _OnEnd ||
|
||||
_Others == null ||
|
||||
_Others.Length <= 0)
|
||||
return;
|
||||
|
||||
Events.OnEnd = _OnEnd;
|
||||
onEnd -= _OnEnd;
|
||||
_Others[^1].Events.OnEnd += onEnd;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ClipState CreateState()
|
||||
{
|
||||
if (_OnEnd == null)
|
||||
InitializeEndEventChain();
|
||||
else if (_Others.Length > 0)
|
||||
InitializeFirstEndEvent();
|
||||
|
||||
return base.CreateState();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is everything in this sequence valid?</summary>
|
||||
public override bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!base.IsValid)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < _Others.Length; i++)
|
||||
if (!_Others[i].IsValid)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is the last animation in this sequence looping?</summary>
|
||||
public override bool IsLooping
|
||||
=> _Others.Length > 0
|
||||
? LastTransition.IsLooping
|
||||
: base.IsLooping;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float Length
|
||||
{
|
||||
get
|
||||
{
|
||||
var length = base.Length;
|
||||
for (int i = 0; i < _Others.Length; i++)
|
||||
length += _Others[i].Length;
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float MaximumLength
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = base.MaximumLength;
|
||||
for (int i = 0; i < _Others.Length; i++)
|
||||
value += _Others[i].MaximumLength;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float AverageAngularSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
var speed = base.AverageAngularSpeed;
|
||||
if (_Others.Length == 0)
|
||||
return speed;
|
||||
|
||||
var duration = base.MaximumLength;
|
||||
speed *= duration;
|
||||
|
||||
for (int i = 0; i < _Others.Length; i++)
|
||||
{
|
||||
var other = _Others[i];
|
||||
var otherSpeed = other.AverageAngularSpeed;
|
||||
var otherDuration = other.MaximumLength;
|
||||
speed += otherSpeed * otherDuration;
|
||||
duration += otherDuration;
|
||||
}
|
||||
|
||||
return speed / duration;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Vector3 AverageVelocity
|
||||
{
|
||||
get
|
||||
{
|
||||
var velocity = base.AverageVelocity;
|
||||
|
||||
if (_Others.Length == 0)
|
||||
return velocity;
|
||||
|
||||
var duration = base.MaximumLength;
|
||||
velocity *= duration;
|
||||
|
||||
for (int i = 0; i < _Others.Length; i++)
|
||||
{
|
||||
var other = _Others[i];
|
||||
var otherVelocity = other.AverageVelocity;
|
||||
var otherDuration = other.MaximumLength;
|
||||
velocity += otherVelocity * otherDuration;
|
||||
duration += otherDuration;
|
||||
}
|
||||
|
||||
return velocity / duration;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds the <see cref="ClipTransition.Clip"/> of everything in this sequence to the collection.</summary>
|
||||
public override void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
{
|
||||
base.GatherAnimationClips(clips);
|
||||
for (int i = 0; i < _Others.Length; i++)
|
||||
_Others[i].GatherAnimationClips(clips);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Transition<ClipState> Clone(CloneContext context)
|
||||
{
|
||||
var clone = new ClipTransitionSequence();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(ClipTransition copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(ClipTransitionSequence copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
context.CloneArray(copyFrom._Others, ref _Others);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Tries to calculate the <see cref="AnimancerState.Time"/> relative to the start of this sequence.</summary>
|
||||
/// <returns>
|
||||
/// True if this sequence contains a transition matching the `state`.
|
||||
/// Otherwise false, indicating that it isn't associated with this sequence.
|
||||
/// </returns>
|
||||
public bool TryGetCumulativeTime(AnimancerState state, out float time)
|
||||
{
|
||||
time = state.Time;
|
||||
|
||||
var clip = state.Clip;
|
||||
var onEnd = state.SharedEvents?.OnEnd;
|
||||
if (Clip == clip && _OnEnd == onEnd)
|
||||
return true;
|
||||
|
||||
time += Clip.length;
|
||||
|
||||
for (int i = 0; i < _Others.Length; i++)
|
||||
{
|
||||
var other = _Others[i];
|
||||
if (other.Clip == clip && other.Events.OnEnd == onEnd)
|
||||
return true;
|
||||
|
||||
time += other.Length;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#region Events
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="AnimancerEvent.Sequence.EndEvent"/> of the last transition in this sequence.</summary>
|
||||
public AnimancerEvent EndEvent
|
||||
{
|
||||
get => LastTransition.Events.EndEvent;
|
||||
set => LastTransition.Events.EndEvent = value;
|
||||
}
|
||||
|
||||
/// <summary>The <see cref="AnimancerEvent.Sequence.OnEnd"/> of the last transition in this sequence.</summary>
|
||||
public Action OnEnd
|
||||
{
|
||||
get => LastTransition.Events.OnEnd;
|
||||
set => LastTransition.Events.OnEnd = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds an event at the specified time relative to the entire sequence.</summary>
|
||||
public void AddEvent(float time, bool normalized, Action callback)
|
||||
{
|
||||
// Convert time to Seconds.
|
||||
if (normalized)
|
||||
time *= Length;
|
||||
|
||||
if (TryAddEvent(this, base.Length, ref time, callback))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _Others.Length - 1; i++)
|
||||
{
|
||||
var other = _Others[i];
|
||||
if (TryAddEvent(other, other.Length, ref time, callback))
|
||||
return;
|
||||
}
|
||||
|
||||
AddEvent(LastTransition, time, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add the `callback` as an event to the `transition` if the `time` is within the `length` and
|
||||
/// returns true if successful. Otherwise subtracts the `length` from the `time` and returns false so it can be
|
||||
/// tried in the next transition in the sequence.
|
||||
/// </summary>
|
||||
private static bool TryAddEvent(ClipTransition transition, float length, ref float time, Action callback)
|
||||
{
|
||||
if (time > length)
|
||||
{
|
||||
time -= length;
|
||||
return false;
|
||||
}
|
||||
|
||||
AddEvent(transition, time, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the `callback` as an event to the `transition` at the specified `time` (in seconds, starting from the
|
||||
/// <see cref="ClipTransition.NormalizedStartTime"/>).
|
||||
/// </summary>
|
||||
private static void AddEvent(ClipTransition transition, float time, Action callback)
|
||||
{
|
||||
var start = transition.NormalizedStartTime;
|
||||
if (float.IsNaN(start))
|
||||
start = AnimancerEvent.Sequence.GetDefaultNormalizedStartTime(start);
|
||||
|
||||
time /= transition.Clip.length * (1 - start);
|
||||
time += start;
|
||||
|
||||
transition.Events.Add(time, callback);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endregion
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ccceda7244b0394c9b25f4100ecd458
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,253 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransition_1
|
||||
[Serializable]
|
||||
public abstract class ControllerTransition<TState> : Transition<TState>,
|
||||
IAnimationClipCollection,
|
||||
ICopyable<ControllerTransition<TState>>
|
||||
where TState : ControllerState
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private RuntimeAnimatorController _Controller;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// The <see cref="ControllerState.Controller"/> that will be used for the created state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you set this property and this transition has been played on multiple characters,
|
||||
/// you will need to call <see cref="Transition{T}.ReconcileMainObject(AnimancerGraph)"/>
|
||||
/// for each of them to create new states for the newly assigned object.
|
||||
/// </remarks>
|
||||
public RuntimeAnimatorController Controller
|
||||
{
|
||||
get => _Controller;
|
||||
set
|
||||
{
|
||||
_Controller = value;
|
||||
|
||||
if (BaseState != null)
|
||||
ReconcileMainObject(BaseState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Object MainObject => _Controller;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] The name of the serialized backing field of <see cref="Controller"/>.</summary>
|
||||
public const string ControllerFieldName = nameof(_Controller);
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Configure parameters in the Animator Controller to follow the values of Animancer parameters")]
|
||||
private ControllerState.SerializableParameterBindings _ParameterBindings;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// Parameter names which make parameters in the <see cref="RuntimeAnimatorController"/>
|
||||
/// follow the values of parameters in the <see cref="AnimancerGraph.Parameters"/>.
|
||||
/// </summary>
|
||||
public ref ControllerState.SerializableParameterBindings ParameterBindings
|
||||
=> ref _ParameterBindings;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Determines what each layer does when " +
|
||||
nameof(ControllerState) + "." + nameof(ControllerState.Stop) + " is called." +
|
||||
"\n• If empty, all layers will reset to their default state." +
|
||||
"\n• If this array is smaller than the layer count," +
|
||||
" any additional layers will use the last value in this array.")]
|
||||
private ControllerState.ActionOnStop[] _ActionsOnStop;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// Determines what each layer does when <see cref="ControllerState.Stop"/> is called.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If empty, all layers will reset to their <see cref="ControllerState.ActionOnStop.DefaultState"/>.
|
||||
/// <para></para>
|
||||
/// If this array is smaller than the
|
||||
/// <see cref="UnityEngine.Animations.AnimatorControllerPlayable.GetLayerCount"/>,
|
||||
/// any additional layers will use the last value in this array.
|
||||
/// </remarks>
|
||||
public ref ControllerState.ActionOnStop[] ActionsOnStop
|
||||
=> ref _ActionsOnStop;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float MaximumLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Controller == null)
|
||||
return 0;
|
||||
|
||||
var duration = 0f;
|
||||
|
||||
var clips = _Controller.animationClips;
|
||||
for (int i = 0; i < clips.Length; i++)
|
||||
{
|
||||
var length = clips[i].length;
|
||||
if (duration < length)
|
||||
duration = length;
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsValid
|
||||
=> _Controller != null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the <see cref="Controller"/>.</summary>
|
||||
public static implicit operator RuntimeAnimatorController(ControllerTransition<TState> transition)
|
||||
=> transition?._Controller;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(AnimancerState state)
|
||||
{
|
||||
if (state is ControllerState controllerState)
|
||||
controllerState.ActionsOnStop = _ActionsOnStop;
|
||||
|
||||
base.Apply(state);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds all clips in the <see cref="Controller"/> to the collection.</summary>
|
||||
void IAnimationClipCollection.GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
{
|
||||
if (_Controller != null)
|
||||
clips.Gather(_Controller.animationClips);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(Transition<TState> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(ControllerTransition<TState> copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
_Controller = context.GetCloneOrOriginal(copyFrom._Controller);
|
||||
_ActionsOnStop = copyFrom._ActionsOnStop;
|
||||
_ParameterBindings = context.GetOrCreateClone(copyFrom._ParameterBindings);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransition
|
||||
[Serializable]
|
||||
public class ControllerTransition : ControllerTransition<ControllerState>,
|
||||
ICopyable<ControllerTransition>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ControllerState CreateState()
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (Controller == null)
|
||||
throw new ArgumentException(
|
||||
$"Unable to create {nameof(ControllerState)} because the" +
|
||||
$" {nameof(ControllerTransition)}.{nameof(Controller)} is null.");
|
||||
#endif
|
||||
|
||||
return State = new(Controller, ActionsOnStop)
|
||||
{
|
||||
SerializedParameterBindings = ParameterBindings
|
||||
};
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="ControllerTransition"/>.</summary>
|
||||
public ControllerTransition() { }
|
||||
|
||||
/// <summary>Creates a new <see cref="ControllerTransition"/> with the specified Animator Controller.</summary>
|
||||
public ControllerTransition(RuntimeAnimatorController controller)
|
||||
=> Controller = controller;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="ControllerTransition"/> with the specified Animator Controller.</summary>
|
||||
public static implicit operator ControllerTransition(RuntimeAnimatorController controller)
|
||||
=> new(controller);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Transition<ControllerState> Clone(CloneContext context)
|
||||
{
|
||||
var clone = new ControllerTransition();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(ControllerTransition<ControllerState> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(ControllerTransition copyFrom, CloneContext context)
|
||||
=> base.CopyFrom(copyFrom, context);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="ControllerTransition"/>
|
||||
/// if the `target` is an <see cref="RuntimeAnimatorController"/>.
|
||||
/// </summary>
|
||||
[TryCreateTransition(typeof(RuntimeAnimatorController))]
|
||||
public static ITransition TryCreateTransition(Object target)
|
||||
=> target is not RuntimeAnimatorController controller
|
||||
? null
|
||||
: new ControllerTransition()
|
||||
{
|
||||
Controller = controller,
|
||||
};
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] Validates that the `command` is targeting an asset.</summary>
|
||||
[UnityEditor.MenuItem("CONTEXT/" + nameof(RuntimeAnimatorController) + "/Create Transition Asset", validate = true)]
|
||||
private static bool ValidateCreateTransitionAsset(UnityEditor.MenuCommand command)
|
||||
=> TryCreateTransitionAttribute.CanCreateAndSave(command.context);
|
||||
|
||||
/// <summary>[Editor-Only] Tries to create an asset containing an appropriate transition for the `command`.</summary>
|
||||
[UnityEditor.MenuItem("CONTEXT/" + nameof(RuntimeAnimatorController) + "/Create Transition Asset")]
|
||||
private static void CreateTransitionAsset(UnityEditor.MenuCommand command)
|
||||
=> TryCreateTransitionAttribute.TryCreateTransitionAsset(command.context, true);
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10f800de5c7f96247930743ca8f1c775
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,169 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/LinearMixerTransition
|
||||
[Serializable]
|
||||
public class LinearMixerTransition : MixerTransition<LinearMixerState, float>,
|
||||
ICopyable<LinearMixerTransition>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Should setting the Parameter above the highest threshold" +
|
||||
" increase the Speed of the mixer proportionally?")]
|
||||
private bool _ExtrapolateSpeed = true;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// Should setting the <see cref="MixerState{TParameter}.Parameter"/> above the highest threshold
|
||||
/// increase the <see cref="AnimancerNode.Speed"/> of the mixer proportionally?
|
||||
/// </summary>
|
||||
public ref bool ExtrapolateSpeed => ref _ExtrapolateSpeed;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(Strings.Tooltips.MixerParameterBinding)]
|
||||
private StringAsset _ParameterName;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The <see cref="LinearMixerState.ParameterName"/>.</summary>
|
||||
public ref StringAsset ParameterName => ref _ParameterName;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Are all <see cref="ManualMixerTransition{TMixer}.Animations"/> assigned and
|
||||
/// <see cref="MixerTransition{TMixer, TParameter}.Thresholds"/> unique and sorted in ascending order?
|
||||
/// </summary>
|
||||
public override bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!base.IsValid)
|
||||
return false;
|
||||
|
||||
var previous = float.NegativeInfinity;
|
||||
|
||||
var thresholds = Thresholds;
|
||||
for (int i = 0; i < thresholds.Length; i++)
|
||||
{
|
||||
var threshold = thresholds[i];
|
||||
if (threshold < previous)
|
||||
return false;
|
||||
else
|
||||
previous = threshold;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override LinearMixerState CreateState()
|
||||
{
|
||||
State = new()
|
||||
{
|
||||
ParameterName = ParameterName,
|
||||
};
|
||||
InitializeState();
|
||||
return State;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(AnimancerState state)
|
||||
{
|
||||
base.Apply(state);
|
||||
State.ExtrapolateSpeed = _ExtrapolateSpeed;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sorts all states so that their thresholds go from lowest to highest.</summary>
|
||||
/// <remarks>This method uses Bubble Sort which is inefficient for large numbers of states.</remarks>
|
||||
public void SortByThresholds()
|
||||
{
|
||||
var thresholdCount = Thresholds.Length;
|
||||
if (thresholdCount <= 1)
|
||||
return;
|
||||
|
||||
var speedCount = Speeds.Length;
|
||||
var syncCount = SynchronizeChildren.Length;
|
||||
|
||||
var previousThreshold = Thresholds[0];
|
||||
|
||||
for (int i = 1; i < thresholdCount; i++)
|
||||
{
|
||||
var threshold = Thresholds[i];
|
||||
if (threshold >= previousThreshold)
|
||||
{
|
||||
previousThreshold = threshold;
|
||||
continue;
|
||||
}
|
||||
|
||||
Thresholds.Swap(i, i - 1);
|
||||
Animations.Swap(i, i - 1);
|
||||
|
||||
if (i < speedCount)
|
||||
Speeds.Swap(i, i - 1);
|
||||
|
||||
if (i == syncCount && !SynchronizeChildren[i - 1])
|
||||
{
|
||||
var sync = SynchronizeChildren;
|
||||
Array.Resize(ref sync, ++syncCount);
|
||||
sync[i - 1] = true;
|
||||
sync[i] = false;
|
||||
SynchronizeChildren = sync;
|
||||
}
|
||||
else if (i < syncCount)
|
||||
{
|
||||
SynchronizeChildren.Swap(i, i - 1);
|
||||
}
|
||||
|
||||
if (i == 1)
|
||||
{
|
||||
i = 0;
|
||||
previousThreshold = float.NegativeInfinity;
|
||||
}
|
||||
else
|
||||
{
|
||||
i -= 2;
|
||||
previousThreshold = Thresholds[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Transition<LinearMixerState> Clone(CloneContext context)
|
||||
{
|
||||
var clone = new LinearMixerTransition();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(MixerTransition<LinearMixerState, float> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(LinearMixerTransition copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
_ExtrapolateSpeed = copyFrom._ExtrapolateSpeed;
|
||||
_ParameterName = copyFrom._ParameterName;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e050065887cfe9d479a79bf34f99a009
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ManualMixerTransition
|
||||
[Serializable]
|
||||
public class ManualMixerTransition : ManualMixerTransition<ManualMixerState>,
|
||||
ICopyable<ManualMixerTransition>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ManualMixerState CreateState()
|
||||
{
|
||||
State = new();
|
||||
InitializeState();
|
||||
return State;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Transition<ManualMixerState> Clone(CloneContext context)
|
||||
{
|
||||
var clone = new ManualMixerTransition();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(ManualMixerTransition<ManualMixerState> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(ManualMixerTransition copyFrom, CloneContext context)
|
||||
=> base.CopyFrom(copyFrom, context);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b09fd97d6dbbd3540ab614b18c96bd32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,264 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using Animancer.Units;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/ManualMixerTransition_1
|
||||
[Serializable]
|
||||
public abstract class ManualMixerTransition<TMixer> : Transition<TMixer>,
|
||||
IMotion,
|
||||
IAnimationClipCollection,
|
||||
ICopyable<ManualMixerTransition<TMixer>>
|
||||
where TMixer : ManualMixerState
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_Clips")]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_States")]
|
||||
private Object[] _Animations;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] Objects that define how to create each state in the mixer.</summary>
|
||||
/// <remarks>See <see cref="ManualMixerState.Initialize(Object[])"/> for more information.</remarks>
|
||||
public ref Object[] Animations => ref _Animations;
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Animations"/>.</summary>
|
||||
public const string AnimationsField = nameof(_Animations);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[AnimationSpeed(IsOptional = false)]
|
||||
[DefaultValue(1f, -1f)]
|
||||
private float[] _Speeds;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// The <see cref="AnimancerNode.Speed"/> to use for each state in the mixer.
|
||||
/// </summary>
|
||||
/// <remarks>If the size of this array doesn't match the <see cref="Animations"/>, it will be ignored.</remarks>
|
||||
public ref float[] Speeds => ref _Speeds;
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Speeds"/>.</summary>
|
||||
public const string SpeedsField = nameof(_Speeds);
|
||||
|
||||
/// <summary>Are there at least enough <see cref="Speeds"/> for each of the<see cref="Animations"/>?</summary>
|
||||
public bool HasSpeeds => _Speeds != null && _Speeds.Length >= _Animations.Length;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private bool[] _SynchronizeChildren;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// The flags to be used in <see cref="ManualMixerState.InitializeSynchronizedChildren"/>.
|
||||
/// </summary>
|
||||
/// <remarks>The array can be null or empty. Any elements not in the array will be treated as true.</remarks>
|
||||
public ref bool[] SynchronizeChildren => ref _SynchronizeChildren;
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="SynchronizeChildren"/>.</summary>
|
||||
public const string SynchronizeChildrenField = nameof(_SynchronizeChildren);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[<see cref="ITransition"/>] Are any of the <see cref="Animations"/> looping?</summary>
|
||||
public override bool IsLooping
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Animations == null)
|
||||
return false;
|
||||
|
||||
for (int i = _Animations.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (AnimancerUtilities.TryGetIsLooping(_Animations[i], out var isLooping) &&
|
||||
isLooping)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float MaximumLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Animations == null)
|
||||
return 0;
|
||||
|
||||
var duration = 0f;
|
||||
var hasSpeeds = HasSpeeds;
|
||||
|
||||
for (int i = _Animations.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (!AnimancerUtilities.TryGetLength(_Animations[i], out var length))
|
||||
continue;
|
||||
|
||||
if (hasSpeeds)
|
||||
length *= _Speeds[i];
|
||||
|
||||
if (duration < length)
|
||||
duration = length;
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual float AverageAngularSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Animations == null)
|
||||
return default;
|
||||
|
||||
var average = 0f;
|
||||
var hasSpeeds = HasSpeeds;
|
||||
|
||||
var count = 0;
|
||||
for (int i = _Animations.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (AnimancerUtilities.TryGetAverageAngularSpeed(_Animations[i], out var speed))
|
||||
{
|
||||
if (hasSpeeds)
|
||||
speed *= _Speeds[i];
|
||||
|
||||
average += speed;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return average / count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual Vector3 AverageVelocity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Animations == null)
|
||||
return default;
|
||||
|
||||
var average = new Vector3();
|
||||
var hasSpeeds = HasSpeeds;
|
||||
|
||||
var count = 0;
|
||||
for (int i = _Animations.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (AnimancerUtilities.TryGetAverageVelocity(_Animations[i], out var velocity))
|
||||
{
|
||||
if (hasSpeeds)
|
||||
velocity *= _Speeds[i];
|
||||
|
||||
average += velocity;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return average / count;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Are all <see cref="Animations"/> assigned?</summary>
|
||||
public override bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_Animations == null ||
|
||||
_Animations.Length == 0)
|
||||
return false;
|
||||
|
||||
for (int i = _Animations.Length - 1; i >= 0; i--)
|
||||
if (_Animations[i] == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Initializes the <see cref="Transition{TState}.State"/> immediately after it is created.</summary>
|
||||
public virtual void InitializeState()
|
||||
{
|
||||
var mixer = State;
|
||||
var childCount = mixer.ChildCount;
|
||||
|
||||
var auto = ManualMixerState.SynchronizeNewChildren;
|
||||
try
|
||||
{
|
||||
ManualMixerState.SynchronizeNewChildren = false;
|
||||
mixer.AddRange(_Animations);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ManualMixerState.SynchronizeNewChildren = auto;
|
||||
}
|
||||
|
||||
mixer.InitializeSynchronizedChildren(_SynchronizeChildren);
|
||||
|
||||
if (_Speeds != null)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (_Speeds.Length != 0 && _Speeds.Length != _Animations.Length)
|
||||
Debug.LogError(
|
||||
$"The number of serialized {nameof(Speeds)} ({_Speeds.Length})" +
|
||||
$" does not match the number of {nameof(Animations)} ({_Animations.Length}).",
|
||||
mixer.Graph?.Component as Object);
|
||||
#endif
|
||||
|
||||
var count = Math.Min(_Animations.Length, _Speeds.Length);
|
||||
for (int i = count - 1; i >= 0; i--)
|
||||
mixer.GetChild(childCount + i).Speed = _Speeds[i];
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(AnimancerState state)
|
||||
{
|
||||
base.Apply(state);
|
||||
|
||||
for (int i = 0; i < _Animations.Length; i++)
|
||||
if (_Animations[i] is ITransition transition)
|
||||
transition.Apply(state.GetChild(i));
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds the <see cref="Animations"/> to the collection.</summary>
|
||||
void IAnimationClipCollection.GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
=> clips.GatherFromSource(_Animations);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(Transition<TMixer> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(ManualMixerTransition<TMixer> copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Animations, ref _Animations);
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Speeds, ref _Speeds);
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._SynchronizeChildren, ref _SynchronizeChildren);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17acaf4d9cac1134db50d728bc63798e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/MixerTransition_2
|
||||
[Serializable]
|
||||
public abstract class MixerTransition<TMixer, TParameter> : ManualMixerTransition<TMixer>,
|
||||
ICopyable<MixerTransition<TMixer, TParameter>>
|
||||
where TMixer : MixerState<TParameter>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The parameter values at which each of the states are used and blended")]
|
||||
private TParameter[] _Thresholds;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// The parameter values at which each of the states are used and blended.
|
||||
/// </summary>
|
||||
public ref TParameter[] Thresholds => ref _Thresholds;
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Thresholds"/>.</summary>
|
||||
public const string ThresholdsField = nameof(_Thresholds);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The initial parameter value to give the mixer when first created")]
|
||||
private TParameter _DefaultParameter;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// The initial parameter value to give the mixer when first created.
|
||||
/// </summary>
|
||||
public ref TParameter DefaultParameter => ref _DefaultParameter;
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="DefaultParameter"/>.</summary>
|
||||
public const string DefaultParameterField = nameof(_DefaultParameter);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void InitializeState()
|
||||
{
|
||||
base.InitializeState();
|
||||
|
||||
State.SetThresholds(_Thresholds);
|
||||
State.Parameter = _DefaultParameter;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(ManualMixerTransition<TMixer> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(MixerTransition<TMixer, TParameter> copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
_DefaultParameter = copyFrom._DefaultParameter;
|
||||
AnimancerUtilities.CopyExactArray(copyFrom._Thresholds, ref _Thresholds);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e604dbbb3da8fd4cbef6261cdf5bd08
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/MixerTransition2D
|
||||
[Serializable]
|
||||
public class MixerTransition2D : MixerTransition<Vector2MixerState, Vector2>,
|
||||
ICopyable<MixerTransition2D>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// A type of <see cref="ManualMixerState"/> which can be
|
||||
/// created by a <see cref="MixerTransition2D"/>.
|
||||
/// </summary>
|
||||
public enum MixerType
|
||||
{
|
||||
/// <summary><see cref="CartesianMixerState"/></summary>
|
||||
Cartesian,
|
||||
|
||||
/// <summary><see cref="DirectionalMixerState"/></summary>
|
||||
Directional,
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private MixerType _Type;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// The type of <see cref="ManualMixerState"/> that this transition will create.
|
||||
/// </summary>
|
||||
public ref MixerType Type => ref _Type;
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Type"/>.</summary>
|
||||
public const string TypeField = nameof(_Type);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(Strings.Tooltips.MixerParameterBinding)]
|
||||
private StringAsset _ParameterNameX;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The <see cref="Vector2MixerState.ParameterNameX"/>.</summary>
|
||||
public ref StringAsset ParameterNameX => ref _ParameterNameX;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(Strings.Tooltips.MixerParameterBinding)]
|
||||
private StringAsset _ParameterNameY;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The <see cref="Vector2MixerState.ParameterNameY"/>.</summary>
|
||||
public ref StringAsset ParameterNameY => ref _ParameterNameY;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates and returns a new state depending on the <see cref="Type"/>.</summary>
|
||||
/// <remarks>
|
||||
/// Note that using methods like <see cref="AnimancerGraph.Play(ITransition)"/> will also call
|
||||
/// <see cref="ITransition.Apply"/>, so if you call this method manually you may want to call that method
|
||||
/// as well. Or you can just use <see cref="AnimancerUtilities.CreateStateAndApply"/>.
|
||||
/// <para></para>
|
||||
/// This method also assigns it as the <see cref="Transition{TState}.State"/>.
|
||||
/// </remarks>
|
||||
public override Vector2MixerState CreateState()
|
||||
{
|
||||
State = _Type switch
|
||||
{
|
||||
MixerType.Cartesian => new CartesianMixerState(),
|
||||
MixerType.Directional => new DirectionalMixerState(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(_Type)),
|
||||
};
|
||||
|
||||
State.ParameterNameX = ParameterNameX;
|
||||
State.ParameterNameY = ParameterNameY;
|
||||
|
||||
InitializeState();
|
||||
return State;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Transition<Vector2MixerState> Clone(CloneContext context)
|
||||
{
|
||||
var clone = new MixerTransition2D();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(MixerTransition<Vector2MixerState, Vector2> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(MixerTransition2D copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
_Type = copyFrom._Type;
|
||||
_ParameterNameX = copyFrom._ParameterNameX;
|
||||
_ParameterNameY = copyFrom._ParameterNameY;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b74f518cdad4d014db9c827829768b60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,171 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using Animancer.Units;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/PlayableAssetTransition
|
||||
[Serializable]
|
||||
public class PlayableAssetTransition : Transition<PlayableAssetState>,
|
||||
IAnimationClipCollection,
|
||||
ICopyable<PlayableAssetTransition>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField, Tooltip("The asset to play")]
|
||||
private PlayableAsset _Asset;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The asset to play.</summary>
|
||||
/// <remarks>
|
||||
/// If you set this property and this transition has been played on multiple characters,
|
||||
/// you will need to call <see cref="Transition{T}.ReconcileMainObject(AnimancerGraph)"/>
|
||||
/// for each of them to create new states for the newly assigned object.
|
||||
/// </remarks>
|
||||
public PlayableAsset Asset
|
||||
{
|
||||
get => _Asset;
|
||||
set
|
||||
{
|
||||
_Asset = value;
|
||||
|
||||
if (BaseState != null)
|
||||
ReconcileMainObject(BaseState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Asset"/>.</summary>
|
||||
public const string AssetField = nameof(_Asset);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Object MainObject => _Asset;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(Strings.Tooltips.NormalizedStartTime)]
|
||||
[DefaultValue(float.NaN, 0f)]
|
||||
[AnimationTime(
|
||||
AnimationTimeAttribute.Units.Normalized,
|
||||
DisabledText = Strings.Tooltips.StartTimeDisabled)]
|
||||
private float _NormalizedStartTime = float.NaN;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float NormalizedStartTime
|
||||
{
|
||||
get => _NormalizedStartTime;
|
||||
set => _NormalizedStartTime = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The objects controlled by each of the tracks in the Asset")]
|
||||
[NonReorderable]
|
||||
private Object[] _Bindings;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The objects controlled by each of the tracks in the Asset.</summary>
|
||||
public ref Object[] Bindings => ref _Bindings;
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="Bindings"/>.</summary>
|
||||
public const string BindingsField = nameof(_Bindings);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float MaximumLength
|
||||
=> _Asset != null
|
||||
? (float)_Asset.duration
|
||||
: 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsValid
|
||||
=> _Asset != null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PlayableAssetState CreateState()
|
||||
{
|
||||
State = new(_Asset);
|
||||
State.SetBindings(_Bindings);
|
||||
return State;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(AnimancerState state)
|
||||
{
|
||||
base.Apply(state);
|
||||
ApplyNormalizedStartTime(state, _NormalizedStartTime);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Gathers all the animations associated with this object.</summary>
|
||||
void IAnimationClipCollection.GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
=> clips.GatherFromAsset(_Asset);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Transition<PlayableAssetState> Clone(CloneContext context)
|
||||
{
|
||||
var clone = new PlayableAssetTransition();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(Transition<PlayableAssetState> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(PlayableAssetTransition copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
_Asset = context.GetCloneOrOriginal(copyFrom._Asset);
|
||||
_NormalizedStartTime = copyFrom._NormalizedStartTime;
|
||||
context.CloneArray(copyFrom._Bindings, ref _Bindings);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="PlayableAssetTransition"/>
|
||||
/// if the `target` is an <see cref="PlayableAsset"/>.
|
||||
/// </summary>
|
||||
[TryCreateTransition(typeof(PlayableAsset))]
|
||||
public static ITransition TryCreateTransition(Object target)
|
||||
=> target is not PlayableAsset asset
|
||||
? null
|
||||
: new PlayableAssetTransition()
|
||||
{
|
||||
Asset = asset,
|
||||
};
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only] Validates that the `command` is targeting an asset.</summary>
|
||||
[UnityEditor.MenuItem("CONTEXT/" + nameof(PlayableAsset) + "/Create Transition Asset", validate = true)]
|
||||
private static bool ValidateCreateTransitionAsset(UnityEditor.MenuCommand command)
|
||||
=> TryCreateTransitionAttribute.CanCreateAndSave(command.context);
|
||||
|
||||
/// <summary>[Editor-Only] Tries to create an asset containing an appropriate transition for the `command`.</summary>
|
||||
[UnityEditor.MenuItem("CONTEXT/" + nameof(PlayableAsset) + "/Create Transition Asset")]
|
||||
private static void CreateTransitionAsset(UnityEditor.MenuCommand command)
|
||||
=> TryCreateTransitionAttribute.TryCreateTransitionAsset(command.context, true);
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 006f1e7af7e528a47aab8fbce25b43ca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,170 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using Animancer.Units;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// <summary>A group of transitions which play one after the other.</summary>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/TransitionSequence
|
||||
///
|
||||
[Serializable]
|
||||
public class TransitionSequence : Transition<SequenceState>,
|
||||
IAnimationClipCollection,
|
||||
ICopyable<TransitionSequence>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(Strings.Tooltips.NormalizedStartTime)]
|
||||
[DefaultValue(float.NaN, 0f)]
|
||||
[AnimationTime(
|
||||
AnimationTimeAttribute.Units.Normalized,
|
||||
DisabledText = Strings.Tooltips.StartTimeDisabled)]
|
||||
private float _NormalizedStartTime = float.NaN;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float NormalizedStartTime
|
||||
{
|
||||
get => _NormalizedStartTime;
|
||||
set => _NormalizedStartTime = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this transition will set the <see cref="AnimancerState.Time"/>,
|
||||
/// then it needs to use <see cref="FadeMode.FromStart"/>.
|
||||
/// </summary>
|
||||
public override FadeMode FadeMode
|
||||
=> float.IsNaN(_NormalizedStartTime)
|
||||
? default
|
||||
: FadeMode.FromStart;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[DrawAfterEvents]
|
||||
[SerializeReference]
|
||||
[Tooltip("The transitions to play in this sequence.")]
|
||||
private ITransition[] _Transitions = Array.Empty<ITransition>();
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The transitions to play in this sequence.</summary>
|
||||
public ref ITransition[] Transitions
|
||||
=> ref _Transitions;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is everything in this sequence valid?</summary>
|
||||
public override bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < _Transitions.Length; i++)
|
||||
if (!_Transitions[i].IsValid())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Sequences don't loop.</summary>
|
||||
/// <remarks>
|
||||
/// If the last state in the sequence is set to loop it will do so,
|
||||
/// but the rest of the sequence won't replay automatically.
|
||||
/// </remarks>
|
||||
public override bool IsLooping
|
||||
=> false;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float MaximumLength
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = 0f;
|
||||
|
||||
for (int i = 0; i < _Transitions.Length; i++)
|
||||
{
|
||||
var transition = _Transitions[i];
|
||||
if (!transition.IsValid())
|
||||
continue;
|
||||
|
||||
var speed = transition.Speed;
|
||||
|
||||
var start = AnimancerEvent.Sequence.GetNormalizedStartTime(
|
||||
transition.NormalizedStartTime,
|
||||
speed);
|
||||
|
||||
var end = transition.SerializedEvents.GetNormalizedEndTime(
|
||||
speed);
|
||||
|
||||
var normalizedLength = (end - start) / speed;
|
||||
if (normalizedLength < 0)
|
||||
continue;
|
||||
|
||||
value += transition.MaximumLength * normalizedLength;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override SequenceState CreateState()
|
||||
{
|
||||
var state = new SequenceState();
|
||||
state.Set(_Transitions);
|
||||
return state;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(AnimancerState state)
|
||||
{
|
||||
base.Apply(state);
|
||||
ApplyNormalizedStartTime(state, _NormalizedStartTime);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Adds the <see cref="ClipTransition.Clip"/> of everything in this sequence to the collection.</summary>
|
||||
public virtual void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||||
{
|
||||
for (int i = 0; i < _Transitions.Length; i++)
|
||||
clips.GatherFromSource(_Transitions[i]);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Transition<SequenceState> Clone(CloneContext context)
|
||||
{
|
||||
var clone = new TransitionSequence();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override void CopyFrom(Transition<SequenceState> copyFrom, CloneContext context)
|
||||
=> this.CopyFromBase(copyFrom, context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(TransitionSequence copyFrom, CloneContext context)
|
||||
{
|
||||
base.CopyFrom(copyFrom, context);
|
||||
|
||||
_NormalizedStartTime = copyFrom._NormalizedStartTime;
|
||||
context.CloneArray(copyFrom._Transitions, ref _Transitions);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d0d81bfa0886584d8734dbaa0f3c441
|
||||
timeCreated: 1515060256
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,352 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using Animancer.Units;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>
|
||||
/// A serializable <see cref="ITransition"/> which can create a particular type of
|
||||
/// <see cref="AnimancerState"/> when passed into <see cref="AnimancerLayer.Play(ITransition)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions">
|
||||
/// Transitions</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/Transition_1
|
||||
[Serializable]
|
||||
public abstract class Transition<TState> :
|
||||
IPolymorphic,
|
||||
ITransition<TState>,
|
||||
ITransition,
|
||||
ICloneable<Transition<TState>>,
|
||||
ICopyable<Transition<TState>>
|
||||
where TState : AnimancerState
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(Strings.Tooltips.FadeDuration)]
|
||||
[AnimationTime(AnimationTimeAttribute.Units.Seconds, Rule = Validate.Value.IsNotNegative)]
|
||||
[DefaultFadeValue]
|
||||
private float _FadeDuration = AnimancerGraph.DefaultFadeDuration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>[<see cref="SerializeField"/>]</remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when setting the value to a negative number.</exception>
|
||||
public float FadeDuration
|
||||
{
|
||||
get => _FadeDuration;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(value),
|
||||
$"{nameof(FadeDuration)} must not be negative");
|
||||
|
||||
_FadeDuration = value;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip(Strings.Tooltips.OptionalSpeed)]
|
||||
[AnimationSpeed(DisabledText = Strings.Tooltips.SpeedDisabled)]
|
||||
[DefaultValue(1f, -1f)]
|
||||
private float _Speed = 1;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>]
|
||||
/// Determines how fast the animation plays (1x = normal speed, 2x = double speed).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This sets the <see cref="AnimancerNodeBase.Speed"/> when this transition is played.
|
||||
/// </remarks>
|
||||
public float Speed
|
||||
{
|
||||
get => _Speed;
|
||||
set => _Speed = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Returns <c>false</c> unless overridden.</remarks>
|
||||
public virtual bool IsLooping => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual float NormalizedStartTime
|
||||
{
|
||||
get => float.NaN;
|
||||
set { }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract float MaximumLength { get; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField, Tooltip(Strings.ProOnlyTag + "Events which will be triggered as the animation plays")]
|
||||
private AnimancerEvent.Sequence.Serializable _Events;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>This property returns the <see cref="AnimancerEvent.Sequence.Serializable.Events"/>.</remarks>
|
||||
public virtual AnimancerEvent.Sequence Events
|
||||
{
|
||||
get => (_Events ??= new()).Events;
|
||||
set => (_Events ??= new()).Events = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AnimancerEvent.Sequence.Serializable SerializedEvents
|
||||
{
|
||||
get => _Events;
|
||||
set => _Events = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// The state that was created by this object. Specifically, this is the state that was most recently
|
||||
/// passed into <see cref="Apply"/> (usually by <see cref="AnimancerGraph.Play(ITransition)"/>).
|
||||
/// <para></para>
|
||||
/// You can use <see cref="AnimancerStateDictionary.GetOrCreate(ITransition)"/> or
|
||||
/// <see cref="AnimancerLayer.GetOrCreateState(ITransition)"/> to get or create the state for a
|
||||
/// specific object.
|
||||
/// <para></para>
|
||||
/// <see cref="State"/> is simply a shorthand for casting this to <typeparamref name="TState"/>.
|
||||
/// </summary>
|
||||
public AnimancerState BaseState { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private TState _State;
|
||||
|
||||
/// <summary>
|
||||
/// The state that was created by this object. Specifically, this is the state that was most recently
|
||||
/// passed into <see cref="Apply"/> (usually by <see cref="AnimancerGraph.Play(ITransition)"/>).
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// You can use <see cref="AnimancerStateDictionary.GetOrCreate(ITransition)"/> or
|
||||
/// <see cref="AnimancerLayer.GetOrCreateState(ITransition)"/>
|
||||
/// to get or create the state for a specific object.
|
||||
/// <para></para>
|
||||
/// This property is shorthand for casting the <see cref="BaseState"/> to <typeparamref name="TState"/>.
|
||||
/// </remarks>
|
||||
///
|
||||
/// <exception cref="InvalidCastException">
|
||||
/// The <see cref="BaseState"/> is not actually a <typeparamref name="TState"/>.
|
||||
/// This should only happen if a different type of state was created by something else
|
||||
/// and registered using the <see cref="Key"/>,
|
||||
/// causing this <see cref="AnimancerGraph.Play(ITransition)"/> to pass that state into
|
||||
/// <see cref="Apply"/> instead of calling <see cref="CreateState"/>
|
||||
/// to make the correct type of state.
|
||||
/// </exception>
|
||||
public TState State
|
||||
{
|
||||
get => _State ??= (TState)BaseState;
|
||||
protected set => BaseState = _State = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Returns <c>true</c> unless overridden.</remarks>
|
||||
public virtual bool IsValid
|
||||
=> true;
|
||||
|
||||
/// <summary>The <see cref="AnimancerState.Key"/> which the created state will be registered with.</summary>
|
||||
/// <remarks>Returns <c>this</c> unless overridden.</remarks>
|
||||
public virtual object Key
|
||||
=> this;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Returns <see cref="FadeMode.FixedSpeed"/> unless overridden.</remarks>
|
||||
public virtual FadeMode FadeMode
|
||||
=> default;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract TState CreateState();
|
||||
|
||||
/// <inheritdoc/>
|
||||
AnimancerState ITransition.CreateState()
|
||||
=> CreateAndInitializeState();
|
||||
|
||||
/// <summary>Calls <see cref="CreateState"/> and assigns the <see cref="Events"/> to the state.</summary>
|
||||
public TState CreateAndInitializeState()
|
||||
{
|
||||
var state = CreateState();
|
||||
|
||||
AnimancerState.SetExpectFade(state, _FadeDuration);
|
||||
|
||||
State = state;
|
||||
|
||||
state.SharedEvents = _Events;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Apply(AnimancerState state)
|
||||
{
|
||||
#if UNITY_ASSERTIONS
|
||||
if (state.MainObject != MainObject)
|
||||
{
|
||||
OptionalWarning.MainObjectMismatch.Log(
|
||||
$"A state.{nameof(MainObject)} doesn't match the" +
|
||||
$" transition.{nameof(MainObject)} being applied to it." +
|
||||
$" transition.{nameof(ReconcileMainObject)} must be called" +
|
||||
$" for every state created by the transition" +
|
||||
$" after its {nameof(MainObject)} is changed." +
|
||||
$" This includes {nameof(ClipTransition)}.{nameof(ClipTransition.Clip)}," +
|
||||
$" {nameof(ControllerTransition)}.{nameof(ControllerTransition.Controller)}, and" +
|
||||
$" {nameof(PlayableAssetTransition)}.{nameof(PlayableAssetTransition.Asset)}" +
|
||||
$"\n• State: {state}" +
|
||||
$"\n• State.{nameof(MainObject)}: {state.MainObject}" +
|
||||
$"\n• Transition.{nameof(MainObject)}: {MainObject}" +
|
||||
$"\n• Component: {state.Graph?.Component}",
|
||||
state.Graph?.Component);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_State != state)
|
||||
{
|
||||
_State = null;
|
||||
BaseState = state;
|
||||
}
|
||||
|
||||
if (!float.IsNaN(_Speed))
|
||||
state.Speed = _Speed;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Applies the `normalizedStartTime` to the `state`.</summary>
|
||||
public static void ApplyNormalizedStartTime(AnimancerState state, float normalizedStartTime)
|
||||
{
|
||||
if (!float.IsNaN(normalizedStartTime))
|
||||
state.NormalizedTime = normalizedStartTime;
|
||||
else if (!state.IsActive)
|
||||
state.NormalizedTime = AnimancerEvent.Sequence.GetDefaultNormalizedStartTime(state.Speed);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The <see cref="AnimancerState.MainObject"/> that the created state will have.</summary>
|
||||
public virtual Object MainObject { get; }
|
||||
|
||||
/// <summary>The display name of this transition.</summary>
|
||||
public virtual string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
var mainObject = MainObject;
|
||||
return mainObject != null ? mainObject.name : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns the <see cref="Name"/> and type of this transition.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var type = GetType().FullName;
|
||||
|
||||
var name = Name;
|
||||
return name is null
|
||||
? type :
|
||||
$"{name} ({type})";
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If a state exists with its <see cref="AnimancerState.MainObject"/> not matching the
|
||||
/// <see cref="MainObject"/>, this method returns a new state for the correct object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method only applies to the state registered with the <see cref="Key"/> so
|
||||
/// if this transition is played on multiple different characters or used to create
|
||||
/// multiple states for the same character, this method must be called for each state.
|
||||
/// </remarks>
|
||||
public AnimancerState ReconcileMainObject(AnimancerGraph animancer)
|
||||
=> animancer.States.TryGet(this, out var state)
|
||||
? ReconcileMainObject(state)
|
||||
: null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="AnimancerState.MainObject"/> doesn't match the <see cref="MainObject"/>,
|
||||
/// this method returns a new state for the correct object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this transition is played on multiple different characters or used to create
|
||||
/// multiple states for the same character, this method must be called for each state.
|
||||
/// </remarks>
|
||||
public AnimancerState ReconcileMainObject(AnimancerState state)
|
||||
{
|
||||
var newMainObject = MainObject;
|
||||
if (newMainObject == null)
|
||||
return state;
|
||||
|
||||
var oldMainObject = state.MainObject;
|
||||
if (oldMainObject == newMainObject)
|
||||
return state;
|
||||
|
||||
#if UNITY_ASSERTIONS
|
||||
if (oldMainObject == null)
|
||||
Debug.LogError(
|
||||
$"{state} had no {nameof(state.MainObject)} to change from.",
|
||||
state.Graph?.Component as Object);
|
||||
if (newMainObject == null)
|
||||
Debug.LogError(
|
||||
$"{this} has no {nameof(MainObject)} to change to.",
|
||||
state.Graph?.Component as Object);
|
||||
#endif
|
||||
|
||||
// Change the old state's key to its object so we can get it back later.
|
||||
state.Key = oldMainObject;
|
||||
|
||||
// If there was already a state for the new object, give it the correct key.
|
||||
if (state.Graph.States.TryGet(newMainObject, out var existingState))
|
||||
{
|
||||
existingState.Key = Key;
|
||||
state = existingState;
|
||||
}
|
||||
else// Otherwise, create a state for the new object.
|
||||
{
|
||||
var layer = state.Layer;
|
||||
state = CreateState();
|
||||
state._Key = Key;
|
||||
state.SetParent(layer);
|
||||
}
|
||||
|
||||
_State = null;
|
||||
BaseState = state;
|
||||
return state;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract Transition<TState> Clone(CloneContext context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyFrom(Transition<TState> copyFrom, CloneContext context)
|
||||
{
|
||||
_FadeDuration = copyFrom._FadeDuration;
|
||||
_Speed = copyFrom._Speed;
|
||||
_Events = copyFrom._Events.Clone();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5a3052770fc6fc44be3d5f2b86f993b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/TransitionAsset
|
||||
[CreateAssetMenu(
|
||||
menuName = Strings.MenuPrefix + "Transition Asset",
|
||||
order = Strings.AssetMenuOrder + 1)]
|
||||
[AnimancerHelpUrl(typeof(TransitionAsset))]
|
||||
public class TransitionAsset : TransitionAsset<ITransition>
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] Sets the <see cref="TransitionAssetBase.CreateInstance"/>.</summary>
|
||||
[InitializeOnLoadMethod]
|
||||
private static void SetMainImplementation()
|
||||
=> CreateInstance = transition =>
|
||||
{
|
||||
var asset = CreateInstance<TransitionAsset>();
|
||||
asset.Transition = transition;
|
||||
return asset;
|
||||
};
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Reset()
|
||||
{
|
||||
Transition = new ClipTransition();
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] Validates that the `mainAsset` is actually an asset.</summary>
|
||||
public static bool ValidateCreate(Object mainAsset)
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(mainAsset);
|
||||
return !string.IsNullOrEmpty(path);
|
||||
}
|
||||
|
||||
/// <summary>[Editor-Only] Creates a <see cref="TransitionAsset"/> next to the `mainAsset`.</summary>
|
||||
public static TransitionAsset Create(Object mainAsset)
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(mainAsset);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Can't create {nameof(TransitionAsset)} for something that isn't an asset.",
|
||||
mainAsset);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
path = System.IO.Path.GetDirectoryName(path);
|
||||
path = System.IO.Path.Combine(path, $"{mainAsset.name}.asset");
|
||||
path = AssetDatabase.GenerateUniqueAssetPath(path);
|
||||
|
||||
var asset = CreateInstance<TransitionAsset>();
|
||||
AssetDatabase.CreateAsset(asset, path);
|
||||
Selection.activeObject = asset;
|
||||
return asset;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5a8877f26e7a6a43aaf06fade1a064a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>A <see cref="ScriptableObject"/> based <see cref="ITransition"/>.</summary>
|
||||
/// <remarks>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/assets">
|
||||
/// Transition Assets</see>
|
||||
/// <para></para>
|
||||
/// When adding a <see cref="CreateAssetMenuAttribute"/> to any derived classes, you can use
|
||||
/// <see cref="Strings.MenuPrefix"/> and <see cref="Strings.AssetMenuOrder"/>.
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/TransitionAssetBase
|
||||
[AnimancerHelpUrl(typeof(TransitionAssetBase))]
|
||||
public abstract partial class TransitionAssetBase : ScriptableObject,
|
||||
IAnimationClipSource,
|
||||
ITransition,
|
||||
IWrapper
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The name of the serialized backing field of <see cref="GetTransition"/>.</summary>
|
||||
public const string TransitionField = "_Transition";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Returns the <see cref="ITransition"/> wrapped by this <see cref="ScriptableObject"/>.</summary>
|
||||
public abstract ITransition GetTransition();
|
||||
|
||||
/// <inheritdoc/>
|
||||
object IWrapper.WrappedObject
|
||||
=> this != null
|
||||
? GetTransition()
|
||||
: null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual float FadeDuration
|
||||
=> GetTransition().FadeDuration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual object Key
|
||||
=> GetTransition().Key;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual FadeMode FadeMode
|
||||
=> GetTransition().FadeMode;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual AnimancerState CreateState()
|
||||
=> GetTransition().CreateState();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Apply(AnimancerState state)
|
||||
{
|
||||
GetTransition().Apply(state);
|
||||
state.SetDebugName(this);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Can this transition create a valid <see cref="AnimancerState"/>?</summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="AnimancerUtilities.IsValid(ITransition)"/>
|
||||
/// to also null check this reference, i.e: <c>transition.IsValid()</c>.
|
||||
/// </remarks>
|
||||
public virtual bool IsValid
|
||||
=> this != null
|
||||
&& GetTransition().IsValid();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLooping
|
||||
=> GetTransition().IsLooping;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float NormalizedStartTime
|
||||
{
|
||||
get => GetTransition().NormalizedStartTime;
|
||||
set => GetTransition().NormalizedStartTime = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float MaximumLength
|
||||
=> GetTransition().MaximumLength;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Speed
|
||||
{
|
||||
get => GetTransition().Speed;
|
||||
set => GetTransition().Speed = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Explains why Transition Assets warn about accessing their events.</summary>
|
||||
public const string ObsoleteEventsMessage =
|
||||
"Directly accessing the Events of a Transition Asset is generally not recommended" +
|
||||
" because any modifications will affect all characters who share the same asset" +
|
||||
" and will persist until the asset is destroyed (usually when the application exits)." +
|
||||
"\n\n" +
|
||||
"In most cases, the recommended approach is to initialize events on the " + nameof(AnimancerState) +
|
||||
" returned when you Play the Transition rather than modifying the Asset itself." +
|
||||
"\n\n" +
|
||||
"If you really need to access these events, you can use the asset.Transition.Events.";
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete(ObsoleteEventsMessage)]
|
||||
public AnimancerEvent.Sequence Events
|
||||
=> GetTransition().Events;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete(ObsoleteEventsMessage)]
|
||||
public AnimancerEvent.Sequence.Serializable SerializedEvents
|
||||
{
|
||||
get => GetTransition().SerializedEvents;
|
||||
set => GetTransition().SerializedEvents = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[<see cref="IAnimationClipSource"/>]
|
||||
/// Calls <see cref="AnimancerUtilities.GatherFromSource(ICollection{AnimationClip}, object)"/>.
|
||||
/// </summary>
|
||||
public virtual void GetAnimationClips(List<AnimationClip> clips)
|
||||
=> clips.GatherFromSource(GetTransition());
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] Creates an instance of the main non-abstract inheritor of this class.</summary>
|
||||
/// <remarks><c>TransitionAsset</c> sets this to use itself by default.</remarks>
|
||||
public static new Func<ITransition, TransitionAssetBase> CreateInstance { get; set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private const string CreateFromSelectionMenu = Strings.CreateMenuPrefix + "Transition Assets From Selection";
|
||||
|
||||
[UnityEditor.MenuItem(
|
||||
itemName: CreateFromSelectionMenu,
|
||||
validate = true)]
|
||||
private static bool ValidateCreateFromSelection()
|
||||
{
|
||||
var selection = UnityEditor.Selection.objects;
|
||||
foreach (var item in selection)
|
||||
if (TryCreateTransitionAttribute.CanCreateAndSave(item))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[UnityEditor.MenuItem(
|
||||
itemName: CreateFromSelectionMenu,
|
||||
priority = Strings.AssetMenuOrder + 2)]
|
||||
private static void CreateFromSelection()
|
||||
{
|
||||
var selection = UnityEditor.Selection.objects;
|
||||
foreach (var item in selection)
|
||||
TryCreateTransitionAttribute.TryCreateTransitionAsset(item, true);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25092f15b9160c1488a484ac9332fe36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,167 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>A [<see cref="SerializableAttribute"/>] wrapper around an <see cref="TransitionAssetBase"/>.</summary>
|
||||
/// <remarks>
|
||||
/// This allows Transition Assets to be referenced inside [<see cref="SerializeReference"/>]
|
||||
/// fields which can't directly reference <see cref="UnityEngine.Object"/>s.
|
||||
/// <para></para>
|
||||
/// <strong>Documentation:</strong>
|
||||
/// <see href="https://kybernetik.com.au/animancer/docs/manual/transitions/assets">
|
||||
/// Transition Assets</see>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/TransitionAssetReference
|
||||
[Serializable]
|
||||
public class TransitionAssetReference :
|
||||
IAnimationClipSource,
|
||||
ICloneable<TransitionAssetReference>,
|
||||
ICopyable<TransitionAssetReference>,
|
||||
IPolymorphic,
|
||||
ITransition,
|
||||
IWrapper
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeField]
|
||||
private TransitionAssetBase _Asset;
|
||||
|
||||
/// <summary>[<see cref="SerializeField"/>] The wrapped Transition Asset.</summary>
|
||||
public ref TransitionAssetBase Asset
|
||||
=> ref _Asset;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionAssetReference"/>.</summary>
|
||||
public TransitionAssetReference() { }
|
||||
|
||||
/// <summary>Creates a new <see cref="TransitionAssetReference"/>.</summary>
|
||||
public TransitionAssetReference(TransitionAssetBase asset)
|
||||
{
|
||||
_Asset = asset;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
object IWrapper.WrappedObject
|
||||
=> _Asset;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Can this transition create a valid <see cref="AnimancerState"/>?</summary>
|
||||
public virtual bool IsValid
|
||||
=> _Asset.IsValid();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual float FadeDuration
|
||||
=> _Asset != null
|
||||
? _Asset.FadeDuration
|
||||
: 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual object Key
|
||||
=> _Asset != null
|
||||
? _Asset.Key
|
||||
: null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual FadeMode FadeMode
|
||||
=> _Asset != null
|
||||
? _Asset.FadeMode
|
||||
: default;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLooping
|
||||
=> _Asset != null
|
||||
&& _Asset.IsLooping;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float NormalizedStartTime
|
||||
{
|
||||
get => _Asset != null
|
||||
? _Asset.NormalizedStartTime
|
||||
: float.NaN;
|
||||
set => _Asset.NormalizedStartTime = value;// No null check. Don't silently ignore commands.
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float MaximumLength
|
||||
=> _Asset != null
|
||||
? _Asset.MaximumLength
|
||||
: 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float Speed
|
||||
{
|
||||
get => _Asset != null
|
||||
? _Asset.Speed
|
||||
: 1;
|
||||
set => _Asset.Speed = value;// No null check. Don't silently ignore commands.
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete(TransitionAssetBase.ObsoleteEventsMessage)]
|
||||
public AnimancerEvent.Sequence Events
|
||||
=> _Asset != null
|
||||
? _Asset.Events
|
||||
: null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete(TransitionAssetBase.ObsoleteEventsMessage)]
|
||||
public AnimancerEvent.Sequence.Serializable SerializedEvents
|
||||
{
|
||||
get => _Asset.SerializedEvents;
|
||||
set => _Asset.SerializedEvents = value;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual AnimancerState CreateState()
|
||||
=> _Asset.CreateState();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Apply(AnimancerState state)
|
||||
=> _Asset.Apply(state);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[<see cref="IAnimationClipSource"/>]
|
||||
/// Calls <see cref="AnimancerUtilities.GatherFromSource(ICollection{AnimationClip}, object)"/>.
|
||||
/// </summary>
|
||||
public virtual void GetAnimationClips(List<AnimationClip> clips)
|
||||
=> clips.GatherFromSource(_Asset);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual TransitionAssetReference Clone(CloneContext context)
|
||||
{
|
||||
var clone = new TransitionAssetReference();
|
||||
clone.CopyFrom(this, context);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyFrom(TransitionAssetReference copyFrom, CloneContext context)
|
||||
{
|
||||
_Asset = context.GetCloneOrOriginal(copyFrom._Asset);
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Describes the <see cref="Asset"/>.</summary>
|
||||
public override string ToString()
|
||||
=> $"{nameof(TransitionAssetReference)}({_Asset})";
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0eaf7056755b5842870ae9a9df07b58
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,110 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/TransitionAsset_1
|
||||
[AnimancerHelpUrl(typeof(TransitionAsset<ITransition>))]
|
||||
public class TransitionAsset<TTransition> : TransitionAssetBase
|
||||
where TTransition : ITransition
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
[SerializeReference]
|
||||
private TTransition _Transition;
|
||||
|
||||
/// <summary>[<see cref="SerializeReference"/>]
|
||||
/// The <see cref="ITransition"/> wrapped by this <see cref="ScriptableObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// WARNING: the <see cref="Transition{TState}.State"/> holds the most recently played state, so
|
||||
/// if you're sharing this transition between multiple objects it will only remember one of them.
|
||||
/// <para></para>
|
||||
/// You can use <see cref="AnimancerStateDictionary.GetOrCreate(ITransition)"/>
|
||||
/// or <see cref="AnimancerLayer.GetOrCreateState(ITransition)"/>
|
||||
/// to get or create the state for a specific object.
|
||||
/// </remarks>
|
||||
public TTransition Transition
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertTransition();
|
||||
return _Transition;
|
||||
}
|
||||
set => _Transition = value;
|
||||
}
|
||||
|
||||
/// <summary>Returns the <see cref="ITransition"/> wrapped by this <see cref="ScriptableObject"/>.</summary>
|
||||
public override ITransition GetTransition()
|
||||
{
|
||||
AssertTransition();
|
||||
return _Transition;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Assert-Conditional]
|
||||
/// Throws a <see cref="NullReferenceException"/> if the <see cref="Transition"/> is null.
|
||||
/// </summary>
|
||||
[System.Diagnostics.Conditional(Strings.Assertions)]
|
||||
private void AssertTransition()
|
||||
{
|
||||
if (_Transition == null)
|
||||
throw new NullReferenceException(
|
||||
$"'{name}' {nameof(Transition)} is null." +
|
||||
$" {nameof(HasTransition)} can be used to check without triggering this error.");
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsValid => _Transition.IsValid();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override float FadeDuration => _Transition.FadeDuration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object Key => _Transition.Key;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override FadeMode FadeMode => _Transition.FadeMode;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AnimancerState CreateState() => _Transition.CreateState();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(AnimancerState state)
|
||||
{
|
||||
_Transition.Apply(state);
|
||||
state.SetDebugName(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GetAnimationClips(List<AnimationClip> clips)
|
||||
=> clips.GatherFromSource(_Transition);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Is the <see cref="Transition"/> assigned (i.e. not <c>null</c>)?</summary>
|
||||
public bool HasTransition => _Transition != null;
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>[Editor-Only]
|
||||
/// Assigns a default <typeparamref name="TTransition"/> to the <see cref="Transition"/> field.
|
||||
/// </summary>
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_Transition = AnimancerReflection.CreateDefaultInstance<TTransition>();
|
||||
}
|
||||
#endif
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6855b77e046ea2045ac187ab02afcdad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,156 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Animancer
|
||||
{
|
||||
/// <summary>Attribute for static methods which try to create a transition from an object.</summary>
|
||||
/// <remarks>
|
||||
/// The method signature must be:
|
||||
/// <c>static ITransition TryCreateTransition(Object target)</c>
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/TryCreateTransitionAttribute
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class TryCreateTransitionAttribute : Attribute
|
||||
{
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>The base type of object which the attributed method can handle.</summary>
|
||||
public Type ObjectType { get; private set; }
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>Creates a new <see cref="TryCreateTransitionAttribute"/>.</summary>
|
||||
public TryCreateTransitionAttribute(Type objectType)
|
||||
{
|
||||
ObjectType = objectType;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#if UNITY_EDITOR
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private static List<Func<Object, ITransition>> _Methods;
|
||||
private static List<Type> _TargetTypes;
|
||||
|
||||
/// <summary>[Editor-Only] Ensures that all methods with this attribute have been gathered.</summary>
|
||||
private static void InitializeMethods()
|
||||
{
|
||||
if (_Methods != null)
|
||||
return;
|
||||
|
||||
_Methods = new();
|
||||
_TargetTypes = new();
|
||||
|
||||
foreach (var method in TypeCache.GetMethodsWithAttribute<TryCreateTransitionAttribute>())
|
||||
{
|
||||
try
|
||||
{
|
||||
var attributes = method.GetCustomAttributes(typeof(TryCreateTransitionAttribute), true);
|
||||
if (!attributes.IsNullOrEmpty())
|
||||
{
|
||||
var attribute = attributes[0] as TryCreateTransitionAttribute;
|
||||
if (attribute?.ObjectType != null)
|
||||
_TargetTypes.Add(attribute.ObjectType);
|
||||
}
|
||||
|
||||
var func = Delegate.CreateDelegate(typeof(Func<Object, ITransition>), method);
|
||||
_Methods.Add((Func<Object, ITransition>)func);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Failed to create delegate for" +
|
||||
$" {method.DeclaringType.GetNameCS()}.{method.Name}," +
|
||||
$" it must take one {typeof(Object).FullName} parameter" +
|
||||
$" and return {typeof(ITransition).FullName}" +
|
||||
$": {exception}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] Validates that the `mainAsset` is actually an asset.</summary>
|
||||
public static bool CanCreateAndSave(Object mainAsset)
|
||||
{
|
||||
if (TransitionAssetBase.CreateInstance == null)
|
||||
return false;
|
||||
|
||||
if (!CanCreate(mainAsset.GetType()))
|
||||
return false;
|
||||
|
||||
var path = AssetDatabase.GetAssetPath(mainAsset);
|
||||
return !string.IsNullOrEmpty(path);
|
||||
}
|
||||
|
||||
/// <summary>[Editor-Only] Validates that the `mainAsset` is actually an asset.</summary>
|
||||
public static bool CanCreate(Type targetType)
|
||||
{
|
||||
InitializeMethods();
|
||||
for (int i = 0; i < _TargetTypes.Count; i++)
|
||||
if (_TargetTypes[i].IsAssignableFrom(targetType))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <summary>[Editor-Only] Tries to create an asset containing an appropriate transition for the `target`.</summary>
|
||||
public static TransitionAssetBase TryCreateTransitionAsset(Object target, bool saveNextToTarget = false)
|
||||
{
|
||||
if (target is TransitionAssetBase asset)
|
||||
return asset;
|
||||
|
||||
var assetType = TransitionAssetBase.CreateInstance;
|
||||
if (assetType == null)
|
||||
return null;
|
||||
|
||||
InitializeMethods();
|
||||
|
||||
for (int i = 0; i < _Methods.Count; i++)
|
||||
{
|
||||
var transition = _Methods[i](target);
|
||||
if (transition is not null)
|
||||
{
|
||||
var created = TransitionAssetBase.CreateInstance(transition);
|
||||
created.name = target.name;
|
||||
|
||||
if (saveNextToTarget)
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(target);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Can't create TransitionAsset for '{target}' because it isn't an asset.",
|
||||
target);
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
path = System.IO.Path.GetDirectoryName(path);
|
||||
path = System.IO.Path.Combine(path, $"{target.name}.asset");
|
||||
path = AssetDatabase.GenerateUniqueAssetPath(path);
|
||||
|
||||
AssetDatabase.CreateAsset(created, path);
|
||||
Selection.activeObject = created;
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
#endif
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e97d40597e12a74c988bd34036e52ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user