// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System;
using Unity.Collections;
using UnityEngine;
namespace Animancer
{
///
/// Replaces the default
/// with a .
///
/// https://kybernetik.com.au/animancer/api/Animancer/WeightedMaskLayers
[AddComponentMenu(Strings.MenuPrefix + "Weighted Mask Layers")]
[AnimancerHelpUrl(typeof(WeightedMaskLayers))]
[DefaultExecutionOrder(-10000)]// Awake before anything else initializes Animancer.
public class WeightedMaskLayers : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField] private AnimancerComponent _Animancer;
/// [] The component to apply the layers to.
public AnimancerComponent Animancer
=> _Animancer;
/************************************************************************************************************************/
[SerializeField] private WeightedMaskLayersDefinition _Definition;
/// []
/// The definition of transforms to control and weights to apply to them.
///
public ref WeightedMaskLayersDefinition Definition
=> ref _Definition;
/************************************************************************************************************************/
/// The layer list created at runtime and assigned to .
public WeightedMaskLayerList Layers { get; protected set; }
/************************************************************************************************************************/
/// The index of each of the .
public int[] Indices { get; protected set; }
/************************************************************************************************************************/
/// Finds the reference if it was missing.
protected virtual void OnValidate()
{
gameObject.GetComponentInParentOrChildren(ref _Animancer);
}
/************************************************************************************************************************/
/// Initializes the and applies the default group weights.
protected virtual void Awake()
{
if (Definition == null ||
!Definition.IsValid)
return;
if (_Animancer == null)
TryGetComponent(out _Animancer);
Layers = WeightedMaskLayerList.Create(_Animancer.Animator);
_Animancer.InitializePlayable(Layers.Graph);
Indices = Definition.CalculateIndices(Layers);
SetWeights(0);
}
/************************************************************************************************************************/
/// Applies the weights of the specified group.
public void SetWeights(int groupIndex)
{
Definition.AssertGroupIndex(groupIndex);
var boneWeights = Layers.BoneWeights;
var definitionWeights = Definition.Weights;
var start = groupIndex * Indices.Length;
for (int i = 0; i < Indices.Length; i++)
{
var index = Indices[i];
var weight = definitionWeights[start + i];
boneWeights[index] = weight;
}
}
/************************************************************************************************************************/
private Fade _Fade;
/// Fades the weights towards the specified group.
public void FadeWeights(
int groupIndex,
float fadeDuration,
Func easing = null)
{
if (fadeDuration > 0)
{
_Fade ??= new();
_Fade.Start(this, groupIndex, fadeDuration, easing);
}
else
{
SetWeights(groupIndex);
}
}
/************************************************************************************************************************/
/// An which fades over time.
/// https://kybernetik.com.au/animancer/api/Animancer/Fade
public class Fade : Updatable
{
/************************************************************************************************************************/
private NativeArray _CurrentWeights;
private float[] _OriginalWeights;
private WeightedMaskLayers _Layers;
private int _TargetWeightIndex;
private Func _Easing;
/// The amount of time that has passed since the start of this fade (in seconds).
public float ElapsedTime { get; set; }
/// The total amount of time this fade will take (in seconds).
public float Duration { get; set; }
/************************************************************************************************************************/
/// Initializes this fade and registers it to receive updates.
public void Start(
WeightedMaskLayers layers,
int groupIndex,
float duration,
Func easing = null)
{
layers.Definition.AssertGroupIndex(groupIndex);
_CurrentWeights = layers.Layers.BoneWeights;
_Easing = easing;
_Layers = layers;
_TargetWeightIndex = layers.Definition.IndexOf(groupIndex, 0);
Duration = duration;
var indices = _Layers.Indices;
AnimancerUtilities.SetLength(ref _OriginalWeights, indices.Length);
for (int i = 0; i < _OriginalWeights.Length; i++)
{
var index = indices[i];
_OriginalWeights[i] = _CurrentWeights[index];
}
ElapsedTime = 0;
layers.Layers.Graph.RequirePreUpdate(this);
}
/************************************************************************************************************************/
///
public override void Update()
{
ElapsedTime += AnimancerGraph.DeltaTime;
if (ElapsedTime < Duration)
{
ApplyFade(ElapsedTime / Duration);
}
else
{
ApplyTargetWeights();
AnimancerGraph.Current.CancelPreUpdate(this);
}
}
/************************************************************************************************************************/
/// Recalculates the weights by interpolating based on `t`.
private void ApplyFade(float t)
{
if (_Easing != null)
t = _Easing(t);
var targetWeights = _Layers.Definition.Weights;
var indices = _Layers.Indices;
var boneWeights = _CurrentWeights;
for (int i = 0; i < indices.Length; i++)
{
var index = indices[i];
var from = _OriginalWeights[i];
var to = targetWeights[_TargetWeightIndex + i];
boneWeights[index] = Mathf.LerpUnclamped(from, to, t);
}
}
/// Recalculates the target weights.
private void ApplyTargetWeights()
{
var targetWeights = _Layers.Definition.Weights;
var indices = _Layers.Indices;
var boneWeights = _CurrentWeights;
for (int i = 0; i < indices.Length; i++)
{
var index = indices[i];
var to = targetWeights[_TargetWeightIndex + i];
boneWeights[index] = to;
}
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
}
}