// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; namespace Animancer { /// A list of s with methods to control their mixing and masking. /// /// The default implementation of this class is . /// /// Documentation: /// /// Layers /// /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerLayerList public abstract class AnimancerLayerList : IEnumerable, IAnimationClipCollection { /************************************************************************************************************************/ #region Fields /************************************************************************************************************************/ /// The containing this list. public readonly AnimancerGraph Graph; /// The layers which each manage their own set of animations. /// This field should never be null so it shouldn't need null-checking. private AnimancerLayer[] _Layers; /// The number of layers that have actually been created. private int _Count; /// The which blends the layers. public Playable Playable { get; protected set; } /************************************************************************************************************************/ /// Creates a new . /// The must be assigned by the end of the derived constructor. protected AnimancerLayerList(AnimancerGraph graph) { Graph = graph; _Layers = new AnimancerLayer[DefaultCapacity]; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region List Operations /************************************************************************************************************************/ /// [Pro-Only] The number of layers in this list. /// /// The value is set higher than the . This is simply a safety measure, /// so if you do actually need more layers you can just increase the limit. /// /// The value is set to a negative number. public int Count { get => _Count; set { var count = _Count; if (value == count) return; CheckAgain: if (value > count)// Increasing. { Add(); count++; goto CheckAgain; } else// Decreasing. { while (value < count--) { var layer = _Layers[count]; if (layer._Playable.IsValid()) Graph._PlayableGraph.DestroySubgraph(layer._Playable); layer.DestroyStates(); } Array.Clear(_Layers, value, _Count - value); _Count = value; Playable.SetInputCount(value); } } } /************************************************************************************************************************/ /// [Pro-Only] /// If the is below the specified `min`, this method increases it to that value. /// public void SetMinCount(int min) { if (Count < min) Count = min; } /************************************************************************************************************************/ /// [Pro-Only] /// The maximum number of layers that can be created before an will /// be thrown (default 4). /// /// Lowering this value will not affect layers that have already been created. /// /// /// Example: /// To set this value automatically when the application starts, place a method like this in any class: /// /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] /// private static void SetMaxLayerCount() /// { /// Animancer.AnimancerLayerList.DefaultCapacity = 8; /// } /// /// Otherwise you can set the of each individual list: /// /// AnimancerComponent animancer; /// animancer.Layers.Capacity = 8; /// public static int DefaultCapacity { get; set; } = 4; /// [Pro-Only] /// If the is below the specified `min`, this method increases it to that value. /// public static void SetMinDefaultCapacity(int min) { if (DefaultCapacity < min) DefaultCapacity = min; } /************************************************************************************************************************/ /// [Pro-Only] /// The maximum number of layers that can be created before an will /// be thrown. The initial capacity is determined by . /// /// /// /// Lowering this value will destroy any layers beyond the specified value. /// /// Changing this value will cause the allocation of a new array and garbage collection of the old one, /// so you should generally set the before initializing this list. /// /// /// The value is not greater than 0. public int Capacity { get => _Layers.Length; set { if (value <= 0) throw new ArgumentOutOfRangeException(nameof(Capacity), $"must be greater than 0 ({value} <= 0)"); if (_Count > value) Count = value; Array.Resize(ref _Layers, value); } } /************************************************************************************************************************/ /// [Pro-Only] Creates and returns a new at the end of this list. /// If the would be exceeded, it will be doubled. public AnimancerLayer Add() { var index = _Count; if (index >= _Layers.Length) Capacity *= 2; var layer = new AnimancerLayer(Graph, index); _Count = index + 1; Playable.SetInputCount(_Count); Graph._PlayableGraph.Connect(Playable, layer._Playable, index, 0); _Layers[index] = layer; return layer; } /************************************************************************************************************************/ /// Returns the layer at the specified index. If it didn't already exist, this method creates it. /// To only get an existing layer without creating new ones, use instead. public AnimancerLayer this[int index] { get { SetMinCount(index + 1); return _Layers[index]; } } /************************************************************************************************************************/ /// Returns the layer at the specified index. /// To create a new layer if the target doesn't exist, use instead. public AnimancerLayer GetLayer(int index) => _Layers[index]; /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Enumeration /************************************************************************************************************************/ /// Returns an enumerator that will iterate through all layers. public FastEnumerator GetEnumerator() => new(_Layers, _Count); /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /************************************************************************************************************************/ /// [] Gathers all the animations in all layers. public void GatherAnimationClips(ICollection clips) => clips.GatherFromSource(_Layers); /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Layer Details /************************************************************************************************************************/ /// [Pro-Only] /// Is the layer at the specified index is set to additive blending? /// Otherwise it will override lower layers. /// public virtual bool IsAdditive(int index) => false; /// [Pro-Only] /// Sets the layer at the specified index to blend additively with earlier layers (if true) /// or to override them (if false). Newly created layers will override by default. /// public virtual void SetAdditive(int index, bool value) { } /************************************************************************************************************************/ /// [Pro-Only] /// Sets an to determine which bones the layer at the specified index will affect. /// /// /// Don't assign the same mask repeatedly unless you have modified it. /// This property doesn't check if the mask is the same /// so repeatedly assigning the same thing will simply waste performance. /// public virtual void SetMask(int index, AvatarMask mask) { } /************************************************************************************************************************/ /// [Editor-Conditional] Sets the Inspector display name of the layer at the specified index. [System.Diagnostics.Conditional(Strings.UnityEditor)] public void SetDebugName(int index, string name) => this[index].SetDebugName(name); /************************************************************************************************************************/ /// /// The average velocity of the root motion of all currently playing animations, /// taking their current into account. /// public Vector3 AverageVelocity { get { var velocity = default(Vector3); for (int i = 0; i < _Count; i++) { var layer = _Layers[i]; velocity += layer.AverageVelocity * layer.Weight; } return velocity; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }