2025-05-09 15:40:34 +08:00

226 lines
8.9 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System;
using Unity.Collections;
using UnityEngine;
namespace Animancer
{
/// <summary>
/// Replaces the default <see cref="AnimancerLayerMixerList"/>
/// with a <see cref="WeightedMaskLayerList"/>.
/// </summary>
/// 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;
/// <summary>[<see cref="SerializeField"/>] The component to apply the layers to.</summary>
public AnimancerComponent Animancer
=> _Animancer;
/************************************************************************************************************************/
[SerializeField] private WeightedMaskLayersDefinition _Definition;
/// <summary>[<see cref="SerializeField"/>]
/// The definition of transforms to control and weights to apply to them.
/// </summary>
public ref WeightedMaskLayersDefinition Definition
=> ref _Definition;
/************************************************************************************************************************/
/// <summary>The layer list created at runtime and assigned to <see cref="AnimancerGraph.Layers"/>.</summary>
public WeightedMaskLayerList Layers { get; protected set; }
/************************************************************************************************************************/
/// <summary>The index of each of the <see cref="WeightedMaskLayersDefinition.Transforms"/>.</summary>
public int[] Indices { get; protected set; }
/************************************************************************************************************************/
/// <summary>Finds the <see cref="Animancer"/> reference if it was missing.</summary>
protected virtual void OnValidate()
{
gameObject.GetComponentInParentOrChildren(ref _Animancer);
}
/************************************************************************************************************************/
/// <summary>Initializes the <see cref="Layers"/> and applies the default group weights.</summary>
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);
}
/************************************************************************************************************************/
/// <summary>Applies the weights of the specified group.</summary>
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;
/// <summary>Fades the weights towards the specified group.</summary>
public void FadeWeights(
int groupIndex,
float fadeDuration,
Func<float, float> easing = null)
{
if (fadeDuration > 0)
{
_Fade ??= new();
_Fade.Start(this, groupIndex, fadeDuration, easing);
}
else
{
SetWeights(groupIndex);
}
}
/************************************************************************************************************************/
/// <summary>An <see cref="IUpdatable"/> which fades <see cref="WeightedMaskLayers"/> over time.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer/Fade
public class Fade : Updatable
{
/************************************************************************************************************************/
private NativeArray<float> _CurrentWeights;
private float[] _OriginalWeights;
private WeightedMaskLayers _Layers;
private int _TargetWeightIndex;
private Func<float, float> _Easing;
/// <summary>The amount of time that has passed since the start of this fade (in seconds).</summary>
public float ElapsedTime { get; set; }
/// <summary>The total amount of time this fade will take (in seconds).</summary>
public float Duration { get; set; }
/************************************************************************************************************************/
/// <summary>Initializes this fade and registers it to receive updates.</summary>
public void Start(
WeightedMaskLayers layers,
int groupIndex,
float duration,
Func<float, float> 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);
}
/************************************************************************************************************************/
/// <inheritdoc/>
public override void Update()
{
ElapsedTime += AnimancerGraph.DeltaTime;
if (ElapsedTime < Duration)
{
ApplyFade(ElapsedTime / Duration);
}
else
{
ApplyTargetWeights();
AnimancerGraph.Current.CancelPreUpdate(this);
}
}
/************************************************************************************************************************/
/// <summary>Recalculates the weights by interpolating based on `t`.</summary>
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);
}
}
/// <summary>Recalculates the target weights.</summary>
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;
}
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
}
}