// 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; } } /************************************************************************************************************************/ } /************************************************************************************************************************/ } }