// Animancer // Copyright 2020 Kybernetik //
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
namespace Animancer
{
/// [Pro-Only]
/// Base class for s which blend an array of together
/// based on a .
///
public abstract class MixerState : ManualMixerState
{
/************************************************************************************************************************/
#region Properties
/************************************************************************************************************************/
///
/// The parameter values at which each of the are used and blended.
///
private TParameter[] _Thresholds;
/************************************************************************************************************************/
private TParameter _Parameter;
/// The value used to calculate the weights of the .
public TParameter Parameter
{
get { return _Parameter; }
set
{
_Parameter = value;
WeightsAreDirty = true;
Root.RequireUpdate(this);
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Thresholds
/************************************************************************************************************************/
///
/// Returns true if the thresholds array is not null.
///
public bool HasThresholds()
{
return _Thresholds != null;
}
/************************************************************************************************************************/
///
/// Returns the value of the threshold associated with the specified index.
///
public TParameter GetThreshold(int index)
{
return _Thresholds[index];
}
/************************************************************************************************************************/
///
/// Sets the value of the threshold associated with the specified index.
///
public void SetThreshold(int index, TParameter threshold)
{
_Thresholds[index] = threshold;
OnThresholdsChanged();
}
/************************************************************************************************************************/
///
/// Assigns the specified array as the thresholds to use for blending.
///
/// WARNING: if you keep a reference to the `thresholds` array you must call
/// whenever any changes are made to it, otherwise this mixer may not blend correctly.
///
public void SetThresholds(TParameter[] thresholds)
{
if (thresholds.Length != States.Length)
throw new ArgumentOutOfRangeException("thresholds", "Incorrect threshold count. There are " + States.Length +
" states, but the specified thresholds array contains " + thresholds.Length + " elements.");
_Thresholds = thresholds;
OnThresholdsChanged();
}
/************************************************************************************************************************/
///
/// If the don't have the same as the
/// , this method allocates and assigns a new array of that size.
///
public bool ValidateThresholdCount()
{
if (States == null)
return false;
if (_Thresholds == null || _Thresholds.Length != States.Length)
{
_Thresholds = new TParameter[States.Length];
return true;
}
return false;
}
/************************************************************************************************************************/
///
/// Called whenever the thresholds are changed. By default this method simply indicates that the blend weights
/// need recalculating but it can be overridden by child classes to perform validation checks or optimisations.
///
public virtual void OnThresholdsChanged()
{
WeightsAreDirty = true;
Root.RequireUpdate(this);
}
/************************************************************************************************************************/
///
/// Calls `calculate` for each of the and stores the returned value as
/// the threshold for that state.
///
public void CalculateThresholds(Func calculate)
{
ValidateThresholdCount();
var count = States.Length;
for (int i = 0; i < count; i++)
{
var state = States[i];
if (state == null)
continue;
_Thresholds[i] = calculate(state);
}
OnThresholdsChanged();
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Initialisation
/************************************************************************************************************************/
///
/// Constructs a new without connecting it to the .
///
protected MixerState(AnimancerPlayable root) : base(root) { }
///
/// Constructs a new and connects it to the `layer`.
///
public MixerState(AnimancerLayer layer) : base(layer) { }
///
/// Constructs a new and connects it to the `parent` at the specified
/// `index`.
///
public MixerState(AnimancerNode parent, int index) : base(parent, index) { }
/************************************************************************************************************************/
///
/// Initialises this mixer with the specified number of ports which can be filled individually by .
///
public override void Initialise(int portCount)
{
base.Initialise(portCount);
_Thresholds = new TParameter[portCount];
OnThresholdsChanged();
}
/************************************************************************************************************************/
///
/// Initialises the and with one
/// state per clip and assigns the `thresholds`.
///
/// WARNING: if you keep a reference to the `thresholds` array, you must call
/// whenever any changes are made to it, otherwise this mixer may not blend
/// correctly.
///
public void Initialise(AnimationClip[] clips, TParameter[] thresholds)
{
Initialise(clips);
_Thresholds = thresholds;
OnThresholdsChanged();
}
///
/// Initialises the and with one
/// state per clip and assigns the thresholds by calling `calculateThreshold` for each state.
///
public void Initialise(AnimationClip[] clips, Func calculateThreshold)
{
Initialise(clips);
CalculateThresholds(calculateThreshold);
}
/************************************************************************************************************************/
///
/// Creates and returns a new to play the `clip` with this
/// as its parent, connects it to the specified `index`, and assigns the
/// `threshold` for it.
///
public ClipState CreateState(int index, AnimationClip clip, TParameter threshold)
{
SetThreshold(index, threshold);
return CreateState(index, clip);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Descriptions
/************************************************************************************************************************/
/// Gets a user-friendly key to identify the `state` in the Inspector.
public override string GetDisplayKey(AnimancerState state)
{
return string.Concat("[", state.Index.ToString(), "] ", _Thresholds[state.Index].ToString());
}
/************************************************************************************************************************/
///
/// Called by to append the details of this node.
///
protected override void AppendDetails(StringBuilder text, string delimiter)
{
text.Append(delimiter);
text.Append("Parameter: ");
AppendParameter(text);
base.AppendDetails(text, delimiter);
}
/************************************************************************************************************************/
/// Appends the current parameter value of this mixer.
public virtual void AppendParameter(StringBuilder description)
{
description.Append(Parameter);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}