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