namespace Fusion.Addons.AnimationController
{
using System.Collections.Generic;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
///
/// Animation layer component.
///
public unsafe partial class AnimationLayer : MonoBehaviour, IAnimationStateOwner, IAnimationWeightProvider, IAnimationFadingProvider
{
// PUBLIC MEMBERS
///
/// Reference to animation controller.
///
public AnimationController Controller => _controller;
///
/// List of animation states (direct children).
///
public IList States => _states;
///
/// Playable mixer which represents this layer and mixes outputs from states.
///
public AnimationMixerPlayable Mixer => _mixer;
///
/// Input port used to identify connection to owner (controller) mixer.
///
public int Port => _port;
///
/// Weight of the layer.
///
public float Weight { get { return _weight; } protected set { _weight = value; } }
///
/// Fading speed of the layer. Controls calculation of Weight over time.
///
public float FadingSpeed { get { return _fadingSpeed; } protected set { _fadingSpeed = value; } }
///
/// Interpolated weight used in render update.
///
public float InterpolatedWeight { get { return _interpolatedWeight; } protected set { _interpolatedWeight = value; } }
///
/// Interpolated fading speed used in render update.
///
public float InterpolatedFadingSpeed { get { return _interpolatedFadingSpeed; } protected set { _interpolatedFadingSpeed = value; } }
// PRIVATE MEMBERS
[SerializeField]
private AvatarMask _avatarMask;
[SerializeField]
private bool _isAdditive;
[SerializeField]
public float _initialWeight = 1.0f;
private AnimationController _controller;
private AnimationState[] _states;
private AnimationMixerPlayable _mixer;
private int _port;
private float _weight;
private float _fadingSpeed;
private float _cachedWeight;
private float _interpolatedWeight;
private float _interpolatedFadingSpeed;
private ProfilerMarker _profilerMarker;
// PUBLIC METHODS
///
/// Returns true if the layer is fading in or if the Weight is greater than zero and the layer is not fading out.
///
public bool IsActive()
{
return (_fadingSpeed == 0.0f && _weight > 0.0f) || _fadingSpeed > 0.0f;
}
///
/// Returns true if the layer is fading in or if the Weight is greater than zero.
///
public bool IsPlaying()
{
return _fadingSpeed > 0.0f || _weight > 0.0f;
}
///
/// Returns true if the layer is fading in (fading speed greater than zero).
///
public bool IsFadingIn()
{
return _fadingSpeed > 0.0f;
}
///
/// Returns true if the layer is fading out (fading speed lower than zero).
///
public bool IsFadingOut()
{
return _fadingSpeed < 0.0f;
}
///
/// Activate the layer.
/// How long it takes to reach full Weight from zero.
///
public void Activate(float fadeDuration)
{
if ((_fadingSpeed == 0.0f && _weight >= 1.0f) || _fadingSpeed > 0.0f)
return;
if (fadeDuration <= 0.0f)
{
_weight = 1.0f;
_fadingSpeed = 0.0f;
}
else
{
_fadingSpeed = 1.0f / fadeDuration;
}
AnimationProfiler.Log(Controller, $"{nameof(AnimationLayer)}.{nameof(Activate)} ({name}), Fade Duration: {fadeDuration:F3}", gameObject);
OnActivate();
}
///
/// Deactivate the layer.
/// How long it takes to reach zero Weight from full.
///
public void Deactivate(float fadeDuration)
{
if ((_fadingSpeed == 0.0f && _weight <= 0.0f) || _fadingSpeed < 0.0f)
return;
if (fadeDuration <= 0.0f)
{
_weight = 0.0f;
_fadingSpeed = 0.0f;
}
else
{
_fadingSpeed = 1.0f / -fadeDuration;
}
AnimationProfiler.Log(Controller, $"{nameof(AnimationLayer)}.{nameof(Deactivate)} ({name}), Fade Duration: {fadeDuration:F3}", gameObject);
OnDeactivate();
}
///
/// Returns true if the layer has an active state. This call is non-recursive.
///
public bool HasActiveState()
{
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState state = states[i];
if (state.IsActive(true) == true)
return true;
}
return false;
}
///
/// Returns true if the layer has an active state of type T. This call is non-recursive.
///
public bool HasActiveState() where T : class
{
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState state = states[i];
if (state.IsActive(true) == true && state is T stateAsT)
return true;
}
return false;
}
///
/// Returns active state. This call is non-recursive.
///
public AnimationState GetActiveState()
{
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState state = states[i];
if (state.IsActive(true) == true)
return state;
}
return null;
}
///
/// Returns active state of type T. This call is non-recursive.
///
public bool GetActiveState(out T activeState) where T : class
{
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState state = states[i];
if (state.IsActive(true) == true && state is T stateAsT)
{
activeState = stateAsT;
return true;
}
}
activeState = default;
return false;
}
///
/// Deactivate all states within this layer.
/// How long it takes to reach zero Weight from full.
/// If true all sub-states will be deactivated as well.
///
public void DeactivateAllStates(float fadeDuration, bool recursive)
{
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState state = states[i];
if (recursive == true)
{
state.DeactivateAllStates(fadeDuration, true);
}
state.Deactivate(fadeDuration, true);
}
}
///
/// Called when Animation Controller is initialized.
///
public void Initialize(AnimationController controller)
{
_controller = controller;
#if ANIMATIONS_PROFILE
_profilerMarker = new ProfilerMarker(GetType().Name);
#endif
InitializeStates();
OnInitialize();
}
///
/// Called when Animation Controller is deinitialized.
///
public void Deinitialize()
{
OnDeinitialize();
DeinitializeStates();
_controller = default;
}
///
/// Called when Animation Controller is spawned.
///
public void Spawned()
{
_mixer = AnimationMixerPlayable.Create(_controller.Graph);
_port = _controller.Mixer.AddInput(_mixer, 0, _initialWeight);
_weight = _initialWeight;
_fadingSpeed = 0.0f;
_cachedWeight = _initialWeight;
_interpolatedWeight = _initialWeight;
_interpolatedFadingSpeed = _fadingSpeed;
_controller.Mixer.SetLayerAdditive((uint)_port, _isAdditive);
if (_avatarMask != null)
{
_controller.Mixer.SetLayerMaskFromAvatarMask((uint)_port, _avatarMask);
}
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState state = states[i];
state.Spawned();
}
OnSpawned();
}
///
/// Called when Animation Controller is despawned.
///
public void Despawned()
{
OnDespawned();
AnimationState[] states = _states;
for (int i = 0, count = states != null ? states.Length : 0; i < count; ++i)
{
AnimationState state = states[i];
if (state != null)
{
state.Despawned();
}
}
if (_mixer.IsValid() == true)
{
_mixer.Destroy();
}
}
///
/// Manual fixed update execution, called internally by Animation Controller.
///
public void ManualFixedUpdate()
{
if (_fadingSpeed <= 0.0f && _weight <= 0.0f)
return;
#if ANIMATIONS_PROFILE
_profilerMarker.Begin();
#endif
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState state = states[i];
state.ManualFixedUpdate();
}
if (_fadingSpeed != 0.0f)
{
_weight += _fadingSpeed * _controller.DeltaTime;
if (_weight <= 0.0f)
{
_weight = 0.0f;
_fadingSpeed = 0.0f;
}
else if (_weight >= 1.0f)
{
_weight = 1.0f;
_fadingSpeed = 0.0f;
}
}
OnFixedUpdate();
#if ANIMATIONS_PROFILE
_profilerMarker.End();
#endif
}
///
/// Manual interpolation, called internally by Animation Controller.
///
public void Interpolate()
{
if (_interpolatedWeight <= 0.0f)
return;
#if ANIMATIONS_PROFILE
_profilerMarker.Begin();
#endif
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState state = states[i];
state.Interpolate();
}
OnInterpolate();
#if ANIMATIONS_PROFILE
_profilerMarker.End();
#endif
}
///
/// Returns input weight used in playable graph. Used internally by Animation Controller.
///
public float GetPlayableInputWeight()
{
return _controller.Mixer.GetInputWeight(_port);
}
///
/// Calculates and applies weights in playable graph. Requires normalization, final weights can be different than weights stores in layers/states.
/// If true interpolated properties will be used.
///
public void SetPlayableInputWeights(bool interpolated)
{
float layerWeight = interpolated == true ? _interpolatedWeight : _weight;
if (layerWeight <= 0.0f)
{
SetPlayableInputWeight(0.0f);
return;
}
AnimationState[] states = _states;
int stateCount = states.Length;
if (stateCount == 0)
{
SetPlayableInputWeight(layerWeight);
return;
}
if (stateCount == 1)
{
AnimationState state = states[0];
float playableWeight = state.CalculatePlayableWeights(interpolated, out float maxChildWeight);
state.SetPlayableInputWeight(playableWeight > 0.0f ? 1.0f : 0.0f);
SetPlayableInputWeight(maxChildWeight * layerWeight);
return;
}
float maxWeight = 0.0f;
float childrenWeight = 0.0f;
for (int i = 0; i < stateCount; ++i)
{
childrenWeight += states[i].CalculatePlayableWeights(interpolated, out float maxChildWeight);
if (maxChildWeight > maxWeight)
{
maxWeight = maxChildWeight;
}
}
if (childrenWeight < 0.001f)
{
childrenWeight = 0.0f;
for (int i = 0; i < stateCount; ++i)
{
states[i].SetPlayableInputWeight(0.0f);
}
}
else
{
float weightMultiplier = 1.0f / childrenWeight;
float multipliedWeightSum = 0.0f;
for (int i = 0; i < stateCount; ++i)
{
multipliedWeightSum += states[i].RecalculatePlayableWeight(weightMultiplier);
}
if (multipliedWeightSum < 0.001f)
{
weightMultiplier = 0.0f;
multipliedWeightSum = 0.0f;
}
else
{
weightMultiplier = 1.0f / multipliedWeightSum;
}
for (int i = 0; i < stateCount; ++i)
{
AnimationState state = states[i];
state.RecalculatePlayableWeight(weightMultiplier);
state.SetPlayableInputWeight();
}
if (childrenWeight > 1.0f)
{
childrenWeight = 1.0f;
}
}
if (childrenWeight > maxWeight)
{
maxWeight = childrenWeight;
}
SetPlayableInputWeight(maxWeight * layerWeight);
}
///
/// Returns first animation state with the given name.
///
/// Name of the state.
/// Search through children states, using Breadth-First Search.
public AnimationState FindState(string stateName, bool recursive = true)
{
return FindState(stateName, out AnimationState state, recursive) == true ? state : default;
}
///
/// Returns first animation state of type T.
///
/// Search through children states, using Breadth-First Search.
public T FindState(bool recursive = true) where T : class
{
return FindState(default, out T state, recursive) == true ? state : default;
}
///
/// Returns first animation state of type T with the given name.
///
/// Name of the state.
/// Search through children states, using Breadth-First Search.
public T FindState(string stateName, bool recursive = true) where T : class
{
return FindState(stateName, out T state, recursive) == true ? state : default;
}
///
/// Returns first animation state of type T.
///
/// Reference to the state.
/// Search through children states, using Breadth-First Search.
public bool FindState(out T state, bool recursive = true) where T : class
{
return FindState(default, out state, recursive);
}
///
/// Returns first animation state of type T with the given name.
///
/// Name of the state.
/// Reference to the state.
/// Search through children states, using Breadth-First Search.
public bool FindState(string stateName, out T state, bool recursive = true) where T : class
{
AnimationState[] states = _states;
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState layerState = states[i];
if (layerState is T layerStateAsT && (stateName == default || string.CompareOrdinal(layerState.name, stateName) == 0))
{
state = layerStateAsT;
return true;
}
}
if (recursive == true)
{
for (int i = 0, count = states.Length; i < count; ++i)
{
AnimationState layerState = states[i];
if (layerState.FindState(stateName, out T innerState, true) == true)
{
state = innerState;
return true;
}
}
}
state = default;
return false;
}
public virtual void OnInspectorGUI()
{
#if UNITY_EDITOR
//UnityEditor.EditorGUILayout.LabelField("", .ToString("0.00"));
#endif
}
// AnimationLayer INTERFACE
protected virtual void OnInitialize() {}
protected virtual void OnDeinitialize() {}
protected virtual void OnSpawned() {}
protected virtual void OnDespawned() {}
protected virtual void OnFixedUpdate() {}
protected virtual void OnInterpolate() {}
protected virtual void OnActivate() {}
protected virtual void OnDeactivate() {}
// IAnimationStateOwner INTERFACE
AnimationMixerPlayable IAnimationStateOwner.Mixer => _mixer;
bool IAnimationStateOwner.IsActive(bool self)
{
return (_fadingSpeed == 0.0f && _weight > 0.0f) || _fadingSpeed > 0.0f;
}
bool IAnimationStateOwner.IsPlaying(bool self)
{
return _fadingSpeed > 0.0f || _weight > 0.0f;
}
bool IAnimationStateOwner.IsFadingIn(bool self)
{
return _fadingSpeed > 0.0f;
}
bool IAnimationStateOwner.IsFadingOut(bool self)
{
return _fadingSpeed < 0.0f;
}
void IAnimationStateOwner.Activate(AnimationState source, float fadeDuration)
{
IList states = States;
for (int i = 0, count = states.Count; i < count; ++i)
{
AnimationState state = states[i];
if (state.Port != source.Port)
{
state.Deactivate(fadeDuration, true);
}
}
}
void IAnimationStateOwner.Deactivate(AnimationState source, float fadeDuration)
{
}
// IAnimationWeightProvider INTERFACE
float IAnimationWeightProvider.Weight { get { return _weight; } set { _weight = value; } }
float IAnimationWeightProvider.InterpolatedWeight { get { return _interpolatedWeight; } set { _interpolatedWeight = value; } }
// IAnimationFadingProvider INTERFACE
float IAnimationFadingProvider.FadingSpeed { get { return _fadingSpeed; } set { _fadingSpeed = value; } }
float IAnimationFadingProvider.InterpolatedFadingSpeed { get { return _interpolatedFadingSpeed; } set { _interpolatedFadingSpeed = value; } }
// PRIVATE METHODS
private void SetPlayableInputWeight(float weight)
{
if (weight == _cachedWeight)
return;
_cachedWeight = weight;
_controller.Mixer.SetInputWeight(_port, weight);
}
private void InitializeStates()
{
if (_states != null)
return;
List activeStates = new List(8);
Transform root = transform;
for (int i = 0, count = root.childCount; i < count; ++i)
{
Transform child = root.GetChild(i);
AnimationState state = child.GetComponentNoAlloc();
if (state != null && state.enabled == true && state.gameObject.activeSelf == true)
{
activeStates.Add(state);
}
}
_states = activeStates.ToArray();
AnimationState[] states = _states;
for (int i = 0, count = states != null ? states.Length : 0; i < count; ++i)
{
AnimationState state = states[i];
state.Initialize(_controller, this);
}
}
private void DeinitializeStates()
{
AnimationState[] states = _states;
for (int i = 0, count = states != null ? states.Length : 0; i < count; ++i)
{
AnimationState state = states[i];
if (state != null)
{
state.Deinitialize();
}
}
_states = null;
}
}
}