You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1005 lines
42 KiB
C#
1005 lines
42 KiB
C#
// Animancer // Copyright 2020 Kybernetik //
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
using UnityEngine.Animations;
|
|
using UnityEngine.Playables;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace Animancer
|
|
{
|
|
/// <summary>
|
|
/// A layer on which animations can play with their states managed independantly of other layers while blending the
|
|
/// output with those layers.
|
|
/// <para></para>
|
|
/// This class can be used as a custom yield instruction to wait until all animations finish playing.
|
|
/// </summary>
|
|
public sealed class AnimancerLayer : AnimancerNode, IAnimationClipCollection
|
|
{
|
|
/************************************************************************************************************************/
|
|
#region Fields and Properties
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Internal] Constructs a new <see cref="AnimancerLayer"/>.</summary>
|
|
internal AnimancerLayer(AnimancerPlayable root, int index)
|
|
: base(root)
|
|
{
|
|
|
|
Index = index;
|
|
_Playable = AnimationMixerPlayable.Create(Root._Graph);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>A layer is its own root.</summary>
|
|
public override AnimancerLayer Layer { get { return this; } }
|
|
|
|
/// <summary>The <see cref="AnimancerNode.Root"/> receives the output of the <see cref="Playable"/>.</summary>
|
|
public override IPlayableWrapper Parent { get { return Root; } }
|
|
|
|
/// <summary>Indicates whether child playables should stay connected to this mixer at all times.</summary>
|
|
public override bool KeepChildrenConnected { get { return Root.KeepChildrenConnected; } }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>All of the animation states connected to this layer.</summary>
|
|
private readonly List<AnimancerState> States = new List<AnimancerState>();
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private AnimancerState _CurrentState;
|
|
|
|
/// <summary>
|
|
/// The state of the animation currently being played.
|
|
/// <para></para>
|
|
/// Specifically, this is the state that was most recently started using any of the Play or CrossFade methods
|
|
/// on this layer. States controlled individually via methods in the <see cref="AnimancerState"/> itself will
|
|
/// not register in this property.
|
|
/// <para></para>
|
|
/// Each time this property changes, the <see cref="CommandCount"/> is incremented.
|
|
/// </summary>
|
|
public AnimancerState CurrentState
|
|
{
|
|
get { return _CurrentState; }
|
|
private set
|
|
{
|
|
_CurrentState = value;
|
|
CommandCount++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number of times the <see cref="CurrentState"/> has changed. By storing this value and later comparing
|
|
/// the stored value to the current value, you can determine whether the state has been changed since then,
|
|
/// even it has changed back to the same state.
|
|
/// </summary>
|
|
public int CommandCount { get; private set; }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Pro-Only]
|
|
/// Determines whether this layer is set to additive blending. Otherwise it will override any earlier layers.
|
|
/// </summary>
|
|
public bool IsAdditive
|
|
{
|
|
get { return Root.Layers.IsAdditive(Index); }
|
|
set { Root.Layers.SetAdditive(Index, value); }
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Pro-Only]
|
|
/// Sets an <see cref="AvatarMask"/> to determine which bones this layer will affect.
|
|
/// </summary>
|
|
public void SetMask(AvatarMask mask)
|
|
{
|
|
Root.Layers.SetMask(Index, mask);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>[Editor-Only]
|
|
/// The <see cref="AvatarMask"/> that determines which bones this layer will affect.
|
|
/// </summary>
|
|
internal AvatarMask _Mask;
|
|
#endif
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// The average velocity of the root motion of all currently playing animations, taking their current
|
|
/// <see cref="AnimancerNode.Weight"/> into account.
|
|
/// </summary>
|
|
public Vector3 AverageVelocity
|
|
{
|
|
get
|
|
{
|
|
var velocity = default(Vector3);
|
|
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var state = States[i];
|
|
velocity += state.AverageVelocity * state.Weight;
|
|
}
|
|
|
|
return velocity;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Child States
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The number of states using this layer as their <see cref="AnimancerState.Parent"/>.</summary>
|
|
public override int ChildCount { get { return States.Count; } }
|
|
|
|
/// <summary>Returns the state connected to the specified `index` as a child of this layer.</summary>
|
|
/// <remarks>This method is identical to <see cref="this[int]"/>.</remarks>
|
|
public override AnimancerState GetChild(int index)
|
|
{
|
|
return States[index];
|
|
}
|
|
|
|
/// <summary>Returns the state connected to the specified `index` as a child of this layer.</summary>
|
|
/// <remarks>This indexer is identical to <see cref="GetChild(int)"/>.</remarks>
|
|
public AnimancerState this[int index]
|
|
{
|
|
get { return States[index]; }
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Adds a new port and uses <see cref="AnimancerState.SetParent"/> to connect the `state` to it.
|
|
/// </summary>
|
|
public void AddChild(AnimancerState state)
|
|
{
|
|
if (state.Parent == this)
|
|
return;
|
|
|
|
var index = States.Count;
|
|
States.Add(null);
|
|
_Playable.SetInputCount(index + 1);
|
|
state.SetParent(this, index);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Connects the `state` to this layer at its <see cref="AnimancerNode.Index"/>.</summary>
|
|
protected internal override void OnAddChild(AnimancerState state)
|
|
{
|
|
OnAddChild(States, state);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Disconnects the `state` from this layer at its <see cref="AnimancerNode.Index"/>.</summary>
|
|
protected internal override void OnRemoveChild(AnimancerState state)
|
|
{
|
|
var index = state.Index;
|
|
Validate.RemoveChild(state, States);
|
|
|
|
if (_Playable.GetInput(index).IsValid())
|
|
Root._Graph.Disconnect(_Playable, index);
|
|
|
|
// Swap the last state into the place of the one that was just removed.
|
|
var lastPort = States.Count - 1;
|
|
if (index < lastPort)
|
|
{
|
|
state = States[lastPort];
|
|
state.DisconnectFromGraph();
|
|
|
|
States[index] = state;
|
|
state.Index = index;
|
|
|
|
if (KeepChildrenConnected || state.Weight != 0)
|
|
state.ConnectToGraph();
|
|
}
|
|
|
|
States.RemoveAt(lastPort);
|
|
_Playable.SetInputCount(lastPort);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Returns an enumerator that will iterate through all states connected directly to this layer (not inside
|
|
/// <see cref="MixerState"/>s).
|
|
/// </summary>
|
|
public override IEnumerator<AnimancerState> GetEnumerator() { return States.GetEnumerator(); }
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Create State
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Creates and returns a new <see cref="ClipState"/> to play the `clip`.
|
|
/// <para></para>
|
|
/// This method uses <see cref="AnimancerPlayable.GetKey"/> to determine the <see cref="AnimancerState.Key"/>.
|
|
/// </summary>
|
|
public ClipState CreateState(AnimationClip clip)
|
|
{
|
|
return CreateState(Root.GetKey(clip), clip);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates and returns a new <see cref="ClipState"/> to play the `clip` and registers it with the `key`.
|
|
/// </summary>
|
|
public ClipState CreateState(object key, AnimationClip clip)
|
|
{
|
|
var state = new ClipState(this, clip);
|
|
Root.States.Register(key, state);
|
|
return state;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Calls <see cref="GetOrCreateState(AnimationClip, bool)"/> for each of the specified clips.
|
|
/// <para></para>
|
|
/// If you only want to create a single state, use <see cref="CreateState(AnimationClip)"/>.
|
|
/// </summary>
|
|
public void CreateIfNew(AnimationClip clip0, AnimationClip clip1)
|
|
{
|
|
GetOrCreateState(clip0);
|
|
GetOrCreateState(clip1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls <see cref="GetOrCreateState(AnimationClip, bool)"/> for each of the specified clips.
|
|
/// <para></para>
|
|
/// If you only want to create a single state, use <see cref="CreateState(AnimationClip)"/>.
|
|
/// </summary>
|
|
public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2)
|
|
{
|
|
GetOrCreateState(clip0);
|
|
GetOrCreateState(clip1);
|
|
GetOrCreateState(clip2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls <see cref="GetOrCreateState(AnimationClip, bool)"/> for each of the specified clips.
|
|
/// <para></para>
|
|
/// If you only want to create a single state, use <see cref="CreateState(AnimationClip)"/>.
|
|
/// </summary>
|
|
public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2, AnimationClip clip3)
|
|
{
|
|
GetOrCreateState(clip0);
|
|
GetOrCreateState(clip1);
|
|
GetOrCreateState(clip2);
|
|
GetOrCreateState(clip3);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls <see cref="GetOrCreateState(AnimationClip, bool)"/> for each of the specified clips.
|
|
/// <para></para>
|
|
/// If you only want to create a single state, use <see cref="CreateState(AnimationClip)"/>.
|
|
/// </summary>
|
|
public void CreateIfNew(params AnimationClip[] clips)
|
|
{
|
|
if (clips == null)
|
|
return;
|
|
|
|
var count = clips.Length;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var clip = clips[i];
|
|
if (clip != null)
|
|
GetOrCreateState(clip);
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Calls <see cref="AnimancerPlayable.GetKey"/> and returns the state which registered with that key or
|
|
/// creates one if it doesn't exist.
|
|
/// <para></para>
|
|
/// If the state already exists but has the wrong <see cref="AnimancerState.Clip"/>, the `allowSetClip`
|
|
/// parameter determines what will happen. False causes it to throw an <see cref="ArgumentException"/> while
|
|
/// true allows it to change the <see cref="AnimancerState.Clip"/>. Note that the change is somewhat costly to
|
|
/// performance to use with caution.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentException"/>
|
|
public AnimancerState GetOrCreateState(AnimationClip clip, bool allowSetClip = false)
|
|
{
|
|
return GetOrCreateState(Root.GetKey(clip), clip, allowSetClip);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the state registered with the <see cref="IHasKey.Key"/> if there is one. Otherwise
|
|
/// this method uses <see cref="ITransition.CreateState"/> to create a new one and registers it with
|
|
/// that key before returning it.
|
|
/// </summary>
|
|
public AnimancerState GetOrCreateState(ITransition transition)
|
|
{
|
|
var key = transition.Key;
|
|
|
|
AnimancerState state;
|
|
if (!Root.States.TryGet(key, out state))
|
|
{
|
|
state = transition.CreateState(this);
|
|
Root.States.Register(key, state);
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the state which registered with the `key` or creates one if it doesn't exist.
|
|
/// <para></para>
|
|
/// If the state already exists but has the wrong <see cref="AnimancerState.Clip"/>, the `allowSetClip`
|
|
/// parameter determines what will happen. False causes it to throw an <see cref="ArgumentException"/> while
|
|
/// true allows it to change the <see cref="AnimancerState.Clip"/>. Note that the change is somewhat costly to
|
|
/// performance to use with caution.
|
|
/// <seealso cref="AnimancerState"/>
|
|
/// </summary>
|
|
/// <exception cref="ArgumentException"/>
|
|
/// <exception cref="ArgumentNullException">Thrown if the `key` is null.</exception>
|
|
/// <remarks>
|
|
/// See also: <see cref="AnimancerPlayable.StateDictionary.GetOrCreate(object, AnimationClip, bool)"/>.
|
|
/// </remarks>
|
|
public AnimancerState GetOrCreateState(object key, AnimationClip clip, bool allowSetClip = false)
|
|
{
|
|
if (key == null)
|
|
throw new ArgumentNullException("key");
|
|
|
|
AnimancerState state;
|
|
if (Root.States.TryGet(key, out state))
|
|
{
|
|
// If a state exists with the 'key' but has the wrong clip, either change it or complain.
|
|
if (!ReferenceEquals(state.Clip, clip))
|
|
{
|
|
if (allowSetClip)
|
|
{
|
|
state.Clip = clip;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException(string.Concat(
|
|
"A state already exists using the specified 'key', but has a different AnimationClip:",
|
|
"\n - Key: ", key.ToString(),
|
|
"\n - Existing Clip: ", state.Clip.ToString(),
|
|
"\n - New Clip: ", clip.ToString()));
|
|
}
|
|
}
|
|
// Otherwise make sure it is on the correct layer.
|
|
else
|
|
{
|
|
AddChild(state);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
state = CreateState(key, clip);
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Destroys all states connected to this layer. This operation cannot be undone.
|
|
/// </summary>
|
|
public void DestroyStates()
|
|
{
|
|
var count = States.Count;
|
|
while (--count >= 0)
|
|
{
|
|
States[count].Destroy();
|
|
}
|
|
|
|
States.Clear();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Play Management
|
|
/************************************************************************************************************************/
|
|
// Starting
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Called by <see cref="AnimancerNode.StartFade"/> (when this layer starts fading, not when one of its states
|
|
/// starts fading). Clears the <see cref="AnimancerState.Events"/> of all states.
|
|
/// </summary>
|
|
protected internal override void OnStartFade()
|
|
{
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
States[i].OnStartFade();
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Stops all other animations, plays the `clip`, and returns its state.
|
|
/// <para></para>
|
|
/// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
|
|
/// To restart it from the beginning you can use <c>...Play(clip).Time = 0;</c>.
|
|
/// </summary>
|
|
public AnimancerState Play(AnimationClip clip)
|
|
{
|
|
return Play(GetOrCreateState(clip));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops all other animations, plays the `state`, and returns it.
|
|
/// <para></para>
|
|
/// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
|
|
/// If you wish to force it back to the start, you can simply set the `state`s time to 0.
|
|
/// </summary>
|
|
public AnimancerState Play(AnimancerState state)
|
|
{
|
|
Validate.Root(state, Root);
|
|
|
|
if (TargetWeight != 1)
|
|
Weight = 1;
|
|
|
|
AddChild(state);
|
|
|
|
CurrentState = state;
|
|
|
|
state.Play();
|
|
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var otherState = States[i];
|
|
if (otherState != state)
|
|
otherState.Stop();
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a state for the `transition` if it didn't already exist, then calls
|
|
/// <see cref="Play(AnimancerState)"/> or <see cref="Play(AnimancerState, float, FadeMode)"/>
|
|
/// depending on the <see cref="ITransition.FadeDuration"/>.
|
|
/// </summary>
|
|
public AnimancerState Play(ITransition transition)
|
|
{
|
|
return Play(transition, transition.FadeDuration, transition.FadeMode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops all other animations, plays the animation registered with the `key`, and returns that
|
|
/// state. If no state is registered with the `key`, this method does nothing and returns null.
|
|
/// <para></para>
|
|
/// The animation will continue playing from its current <see cref="AnimancerState.Time"/>.
|
|
/// If you wish to force it back to the start, you can simply set the returned state's time to 0.
|
|
/// on the returned state.
|
|
/// </summary>
|
|
public AnimancerState Play(object key)
|
|
{
|
|
AnimancerState state;
|
|
if (Root.States.TryGet(key, out state))
|
|
return Play(state);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Starts fading in the `clip` over the course of the `fadeDuration` while fading out all others in the same
|
|
/// layer. Returns its state.
|
|
/// <para></para>
|
|
/// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
|
|
/// method will allow it to complete the existing fade rather than starting a slower one.
|
|
/// <para></para>
|
|
/// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
|
|
/// and simply <see cref="AnimancerState.Play"/> the `state`.
|
|
/// <para></para>
|
|
/// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build.
|
|
/// </summary>
|
|
public AnimancerState Play(AnimationClip clip, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed)
|
|
{
|
|
return Play(Root.States.GetOrCreate(clip), fadeDuration, mode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts fading in the `state` over the course of the `fadeDuration` while fading out all others in this
|
|
/// layer. Returns the `state`.
|
|
/// <para></para>
|
|
/// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
|
|
/// method will allow it to complete the existing fade rather than starting a slower one.
|
|
/// <para></para>
|
|
/// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
|
|
/// and simply <see cref="AnimancerState.Play"/> the `state`.
|
|
/// <para></para>
|
|
/// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build.
|
|
/// </summary>
|
|
public AnimancerState Play(AnimancerState state, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed)
|
|
{
|
|
Validate.Root(state, Root);
|
|
|
|
if (fadeDuration <= 0 ||// With no fade duration, Play immediately.
|
|
(Index == 0 && Weight == 0))// First animation on Layer 0 snap Weight to 1.
|
|
return Play(state);
|
|
|
|
bool isFixedDuration;
|
|
EvaluateFadeMode(mode, ref state, ref fadeDuration, out isFixedDuration);
|
|
|
|
StartFade(1, fadeDuration);
|
|
if (Weight == 0)
|
|
return Play(state);
|
|
|
|
AddChild(state);
|
|
|
|
CurrentState = state;
|
|
|
|
// If the state is already playing or will finish fading in faster than this new fade,
|
|
// continue the existing fade but still pretend it was restarted.
|
|
if (state.IsPlaying && state.TargetWeight == 1 &&
|
|
(state.Weight == 1 || state.FadeSpeed * fadeDuration > Math.Abs(1 - state.Weight)))
|
|
{
|
|
OnStartFade();
|
|
}
|
|
// Otherwise fade in the target state and fade out all others.
|
|
else
|
|
{
|
|
state.IsPlaying = true;
|
|
state.StartFade(1, fadeDuration);
|
|
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var otherState = States[i];
|
|
if (otherState != state)
|
|
otherState.StartFade(0, fadeDuration);
|
|
}
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a state for the `transition` if it didn't already exist, then calls
|
|
/// <see cref="Play(AnimancerState)"/> or <see cref="Play(AnimancerState, float, FadeMode)"/>
|
|
/// depending on the <see cref="ITransition.FadeDuration"/>.
|
|
/// </summary>
|
|
public AnimancerState Play(ITransition transition, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed)
|
|
{
|
|
var state = Root.States.GetOrCreate(transition);
|
|
state = Play(state, fadeDuration, mode);
|
|
transition.Apply(state);
|
|
return state;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts fading in the animation registered with the `key` over the course of the `fadeDuration` while fading
|
|
/// out all others in the same layer. Returns the animation's state (or null if none was registered).
|
|
/// <para></para>
|
|
/// If the `state` was already playing and fading in with less time remaining than the `fadeDuration`, this
|
|
/// method will allow it to complete the existing fade rather than starting a slower one.
|
|
/// <para></para>
|
|
/// If the layer currently has 0 <see cref="AnimancerNode.Weight"/>, this method will fade in the layer itself
|
|
/// and simply <see cref="AnimancerState.Play"/> the `state`.
|
|
/// <para></para>
|
|
/// Animancer Lite only allows the default `fadeDuration` (0.25 seconds) in a runtime build.
|
|
/// </summary>
|
|
public AnimancerState Play(object key, float fadeDuration, FadeMode mode = FadeMode.FixedSpeed)
|
|
{
|
|
AnimancerState state;
|
|
if (Root.States.TryGet(key, out state))
|
|
return Play(state, fadeDuration, mode);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Manipulates the other parameters according to the `mode`.
|
|
/// </summary>
|
|
private void EvaluateFadeMode(FadeMode mode, ref AnimancerState state, ref float fadeDuration, out bool isFixedDuration)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case FadeMode.FixedSpeed:
|
|
fadeDuration *= Mathf.Abs(1 - state.Weight);
|
|
isFixedDuration = false;
|
|
break;
|
|
|
|
case FadeMode.FixedDuration:
|
|
isFixedDuration = true;
|
|
break;
|
|
|
|
case FadeMode.FromStart:
|
|
{
|
|
var previousState = state;
|
|
state = GetOrCreateWeightlessState(state);
|
|
if (previousState != state)
|
|
{
|
|
var previousLayer = previousState.Layer;
|
|
if (previousLayer != this && previousLayer.CurrentState == previousState)
|
|
previousLayer.StartFade(0, fadeDuration);
|
|
}
|
|
isFixedDuration = false;
|
|
break;
|
|
}
|
|
|
|
case FadeMode.NormalizedSpeed:
|
|
fadeDuration *= Mathf.Abs(1 - state.Weight) * state.Length;
|
|
isFixedDuration = false;
|
|
break;
|
|
|
|
case FadeMode.NormalizedDuration:
|
|
fadeDuration *= state.Length;
|
|
isFixedDuration = true;
|
|
break;
|
|
|
|
case FadeMode.NormalizedFromStart:
|
|
{
|
|
var previousState = state;
|
|
state = GetOrCreateWeightlessState(state);
|
|
fadeDuration *= state.Length;
|
|
if (previousState != state)
|
|
{
|
|
var previousLayer = previousState.Layer;
|
|
if (previousLayer != this && previousLayer.CurrentState == previousState)
|
|
previousLayer.StartFade(0, fadeDuration);
|
|
}
|
|
isFixedDuration = false;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new ArgumentException("Invalid FadeMode: " + mode, "mode");
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>[Editor-Only]
|
|
/// The maximum number of duplicate states that can be created by <see cref="GetOrCreateWeightlessState"/> for
|
|
/// a single clip before it will start giving usage warnings. Default = 5.
|
|
/// </summary>
|
|
public static int maxStateDepth = 5;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// If the `state` is not currently at 0 <see cref="AnimancerNode.Weight"/>, this method finds a copy of it
|
|
/// which is at 0 or creates a new one.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">Thrown if the <see cref="AnimancerState.Clip"/> is null.</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException">
|
|
/// Thrown if more states have been created for this <see cref="AnimancerState.Clip"/> than the
|
|
/// <see cref="maxStateDepth"/> allows.
|
|
/// </exception>
|
|
public AnimancerState GetOrCreateWeightlessState(AnimancerState state)
|
|
{
|
|
if (state.Weight != 0)
|
|
{
|
|
var clip = state.Clip;
|
|
if (clip == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"GetOrCreateWeightlessState was called on a state which has no clip: " + state);
|
|
|
|
// We could probably support any state type by giving them a Clone method, but that would take a
|
|
// lot of work for something that might never get used.
|
|
}
|
|
else
|
|
{
|
|
// Get the default state registered with the clip.
|
|
if (state.Key as Object != clip)
|
|
state = Root.States.GetOrCreate(clip, clip);
|
|
|
|
#if UNITY_EDITOR
|
|
int depth = 0;
|
|
#endif
|
|
|
|
// If that state is not at 0 weight, get or create another state registered using the previous state as a key.
|
|
// Keep going through states in this manner until you find one at 0 weight.
|
|
while (state.Weight != 0)
|
|
{
|
|
// Explicitly cast the state to an object to avoid the overload that warns about using a state as a key.
|
|
state = Root.States.GetOrCreate((object)state, clip);
|
|
|
|
#if UNITY_EDITOR
|
|
if (++depth == maxStateDepth)
|
|
{
|
|
throw new ArgumentOutOfRangeException("depth", "GetOrCreateWeightlessState has created " +
|
|
maxStateDepth + " or more states for a single clip." +
|
|
" This is most likely a result of calling the method repeatedly on consecutive frames." +
|
|
" You probably just want to use FadeMode.FixedSpeed instead, but you can increase" +
|
|
" AnimancerLayer.maxStateDepth if necessary.");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure it is on this layer and at time 0.
|
|
AddChild(state);
|
|
state.Time = 0;
|
|
|
|
return state;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
// Stopping
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Sets <see cref="AnimancerNode.Weight"/> = 0 and calls <see cref="AnimancerState.Stop"/> on all animations
|
|
/// to stop them from playing and rewind them to the start.
|
|
/// </summary>
|
|
public override void Stop()
|
|
{
|
|
base.Stop();
|
|
|
|
CurrentState = null;
|
|
|
|
var count = States.Count;
|
|
while (--count >= 0)
|
|
{
|
|
States[count].Stop();
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
// Checking
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Returns true if the `clip` is currently being played by at least one state.
|
|
/// </summary>
|
|
public bool IsPlayingClip(AnimationClip clip)
|
|
{
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var state = States[i];
|
|
if (state.Clip == clip && state.IsPlaying)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if at least one animation is being played.
|
|
/// </summary>
|
|
public bool IsAnyStatePlaying()
|
|
{
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (States[i].IsPlaying)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the <see cref="CurrentState"/> is playing and hasn't yet reached its end.
|
|
/// <para></para>
|
|
/// This method is called by <see cref="IEnumerator.MoveNext"/> so this object can be used as a custom yield
|
|
/// instruction to wait until it finishes.
|
|
/// </summary>
|
|
protected internal override bool IsPlayingAndNotEnding()
|
|
{
|
|
return _CurrentState != null && _CurrentState.IsPlayingAndNotEnding();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Calculates the total <see cref="AnimancerNode.Weight"/> of all states in this layer.
|
|
/// </summary>
|
|
public float GetTotalWeight()
|
|
{
|
|
float weight = 0;
|
|
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
weight += States[i].Weight;
|
|
}
|
|
|
|
return weight;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Invokes the <see cref="AnimancerEvent.Sequence.OnEnd"/> callback of the state that is playing the animation
|
|
/// which triggered the event. Returns true if such a state exists (even if it doesn't have a callback).
|
|
/// </summary>
|
|
internal bool TryInvokeOnEndEvent(AnimationEvent animationEvent)
|
|
{
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (AnimancerPlayable.TryInvokeOnEndEvent(animationEvent, States[i]))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Inverse Kinematics
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Determines the default value of <see cref="AnimancerState.ApplyAnimatorIK"/> for all new states created in
|
|
/// this layer. Default false.
|
|
/// <para></para>
|
|
/// It requires Unity 2018.1 or newer, however 2018.3 or newer is recommended because a bug in earlier versions
|
|
/// of the Playables API caused this value to only take effect while at least one state was at
|
|
/// <see cref="AnimancerNode.Weight"/> = 1 which meant that IK would not work while fading between animations.
|
|
/// </summary>
|
|
public bool DefaultApplyAnimatorIK { get; set; }
|
|
|
|
/// <summary>
|
|
/// Determines whether <c>OnAnimatorIK(int layerIndex)</c> will be called on the animated object for any
|
|
/// <see cref="States"/>. The initial value is determined by <see cref="DefaultApplyAnimatorIK"/> when a new
|
|
/// state is created and setting this value will also set the default.
|
|
/// <para></para>
|
|
/// This is equivalent to the "IK Pass" toggle in Animator Controller layers, except that due to limitations in
|
|
/// the Playables API the <c>layerIndex</c> will always be zero.
|
|
/// <para></para>
|
|
/// It requires Unity 2018.1 or newer, however 2018.3 or newer is recommended because a bug in earlier versions
|
|
/// of the Playables API caused this value to only take effect while at least one state was at
|
|
/// <see cref="AnimancerNode.Weight"/> = 1 which meant that IK would not work while fading between animations.
|
|
/// </summary>
|
|
public bool ApplyAnimatorIK
|
|
{
|
|
get
|
|
{
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (States[i].ApplyAnimatorIK)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
set
|
|
{
|
|
DefaultApplyAnimatorIK = value;
|
|
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
States[i].ApplyAnimatorIK = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Determines the default value of <see cref="AnimancerState.ApplyFootIK"/> for all new states created in this
|
|
/// layer. Default false.
|
|
/// </summary>
|
|
public bool DefaultApplyFootIK { get; set; }
|
|
|
|
/// <summary>
|
|
/// Determines whether any of the <see cref="States"/> in this layer are applying IK to the character's feet.
|
|
/// The initial value is determined by <see cref="DefaultApplyFootIK"/> when a new state is created.
|
|
/// <para></para>
|
|
/// This is equivalent to the "Foot IK" toggle in Animator Controller states (applied to the whole layer).
|
|
/// </summary>
|
|
public bool ApplyFootIK
|
|
{
|
|
get
|
|
{
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (States[i].ApplyFootIK)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
set
|
|
{
|
|
DefaultApplyFootIK = value;
|
|
|
|
var count = States.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
States[i].ApplyFootIK = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Inspector
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[<see cref="IAnimationClipCollection"/>]
|
|
/// Gathers all the animations in this layer.
|
|
/// </summary>
|
|
public void GatherAnimationClips(ICollection<AnimationClip> clips)
|
|
{
|
|
clips.GatherFromSources(States);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>[Editor-Only] The Inspector display name of this layer.</summary>
|
|
private string _Name;
|
|
#endif
|
|
|
|
/// <summary>The Inspector display name of this layer.</summary>
|
|
public override string ToString()
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (_Name == null)
|
|
{
|
|
if (_Mask != null)
|
|
return _Mask.name;
|
|
|
|
_Name = Index == 0 ? "Base Layer" : "Layer " + Index;
|
|
}
|
|
|
|
return _Name;
|
|
#else
|
|
return "Layer " + Index;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>[Editor-Conditional]
|
|
/// Sets the Inspector display name of this layer. Note that layer names are Editor-Only so any calls to this
|
|
/// method will automatically be compiled out of a runtime build.
|
|
/// </summary>
|
|
[System.Diagnostics.Conditional(Strings.EditorOnly)]
|
|
public void SetName(string name)
|
|
{
|
|
#if UNITY_EDITOR
|
|
_Name = name;
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Called by <see cref="AnimancerNode.AppendDescription"/> to append the details of this node.
|
|
/// </summary>
|
|
protected override void AppendDetails(StringBuilder text, string delimiter)
|
|
{
|
|
base.AppendDetails(text, delimiter);
|
|
|
|
text.Append(delimiter).Append("CurrentState: ").Append(CurrentState);
|
|
text.Append(delimiter).Append("CommandCount: ").Append(CommandCount);
|
|
text.Append(delimiter).Append("IsAdditive: ").Append(IsAdditive);
|
|
|
|
#if UNITY_EDITOR
|
|
text.Append(delimiter).Append("AvatarMask: ").Append(_Mask);
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|