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