// Animancer // Copyright 2020 Kybernetik //
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
using System.Collections;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditorInternal;
#endif
namespace Animancer
{
/// [Pro-Only]
/// Base class for s which blend other states together.
///
public abstract partial class MixerState : AnimancerState
{
/************************************************************************************************************************/
#region Properties
/************************************************************************************************************************/
/// The number of input ports in the .
public abstract int PortCount { get; }
/************************************************************************************************************************/
/// Mixers should keep child playables connected to the graph at all times.
public override bool KeepChildrenConnected { get { return true; } }
/// An has no .
public override AnimationClip Clip { get { return null; } }
/************************************************************************************************************************/
///
/// Returns the collection of states connected to this mixer. Note that some elements may be null.
///
/// Getting an enumerator that automatically skips over null states is slower and creates garbage, so
/// internally we use this property and perform null checks manually even though it increases the code
/// complexity a bit.
///
public abstract IList ChildStates { get; }
/// Returns an enumerator which will iterate through each state connected to this mixer.
public override IEnumerator GetEnumerator()
{
var childStates = ChildStates;
if (childStates == null)
yield break;
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
yield return state;
}
}
/************************************************************************************************************************/
/// Indicates whether the weights of all child states should be recalculated.
public bool WeightsAreDirty { get; set; }
/************************************************************************************************************************/
///
/// Determines whether the states in this mixer are playing.
///
public override bool IsPlaying
{
get { return base.IsPlaying; }
set
{
base.IsPlaying = value;
var childStates = ChildStates;
if (childStates == null)
return;
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
state.IsPlaying = value;
}
}
}
/************************************************************************************************************************/
/// Returns true if any child state is looping.
public override bool IsLooping
{
get
{
var childStates = ChildStates;
if (childStates == null)
return false;
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
if (state.IsLooping)
return true;
}
return false;
}
}
/************************************************************************************************************************/
private bool[] _SynchroniseChildren;
///
/// Indicates which children should have their modified in order to keep
/// their at approximately the same value.
///
/// The array can be null or empty. Any elements not in the array will be treated as true.
///
///
/// The is modified to allow each state to trigger its events properly
/// where setting the directly would prevent any events.
///
public bool[] SynchroniseChildren
{
get { return _SynchroniseChildren; }
set
{
if (value == null || value.Length == 0)
{
_SynchroniseChildren = null;
}
else
{
// Reset the speed of any children that are no longer synced to 1.
var childStates = ChildStates;
if (childStates != null)
{
var count = Mathf.Min(childStates.Count, value.Length);
for (int i = 0; i < count; i++)
{
if (value[i])
continue;
var state = childStates[i];
if (state == null)
continue;
state.Speed = 1;
}
}
_SynchroniseChildren = value;
}
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Initialisation
/************************************************************************************************************************/
///
/// Constructs a new without connecting it to the .
///
public MixerState(AnimancerPlayable root)
: base(root)
{
_Playable = AnimationMixerPlayable.Create(root._Graph, 0, true);
}
///
/// Constructs a new and connects it to the `layer`.
///
public MixerState(AnimancerLayer layer)
: this(layer.Root)
{
layer.AddChild(this);
}
///
/// Constructs a new and connects it to the `parent` at the specified
/// `index`.
///
public MixerState(AnimancerNode parent, int index)
: this(parent.Root)
{
SetParent(parent, index);
}
/************************************************************************************************************************/
///
/// Creates and returns a new to play the `clip` with this
/// as its parent.
///
public virtual ClipState CreateState(int index, AnimationClip clip)
{
return new ClipState(this, index, clip)
{
IsPlaying = IsPlaying,
Time = Time
};
}
/************************************************************************************************************************/
/// The number of states using this mixer as their .
public override int ChildCount { get { return ChildStates.Count; } }
///
/// Returns the state connected to the specified `index` as a child of this mixer.
///
public override AnimancerState GetChild(int index)
{
return ChildStates[index];
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Updates
/************************************************************************************************************************/
///
/// Updates the time of this mixer and all of its child states.
///
protected internal override void Update(out bool needsMoreUpdates)
{
base.Update(out needsMoreUpdates);
var childStates = ChildStates;
if (childStates == null)
return;
if (WeightsAreDirty && Weight != 0)
{
RecalculateWeights();
Debug.Assert(!WeightsAreDirty,
"MixerState.AreWeightsDirty has not been set to false by RecalculateWeights().");
// We need to apply the child weights immediately to ensure they are all in sync. Otherwise some of the
// child states might have already been updated before the mixer and would not apply it until next frame.
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
state.ApplyWeight();
}
}
ApplySynchroniseChildren(ref needsMoreUpdates);
}
/************************************************************************************************************************/
/// Applies the effects of .
protected void ApplySynchroniseChildren(ref bool needsMoreUpdates)
{
if (AnimancerPlayable.DeltaTime == 0)
return;
var childStates = ChildStates;
var childCount = childStates.Count;
if (childStates == null)
return;
// Get the number of elements in the array.
int flagCount;
if (_SynchroniseChildren == null)
{
flagCount = 0;
}
else
{
flagCount = _SynchroniseChildren.Length;
if (flagCount >= childCount)
{
for (int i = 0; i < flagCount; i++)
{
if (_SynchroniseChildren[i])
goto Continue;
}
// If none of the flags are true, do nothing.
return;
}
}
Continue:
needsMoreUpdates = true;
// Calculate the weighted average normalized time of all children.
var totalWeight = 0f;
var weightedNormalizedTime = 0f;
var weightedLength = 0f;
for (int i = 0; i < childCount; i++)
{
if (i < flagCount && !_SynchroniseChildren[i])
continue;
var state = childStates[i];
if (state == null)
continue;
var weight = state.Weight;
if (weight == 0)
continue;
totalWeight += weight;
weightedNormalizedTime += state.NormalizedTime * weight;
weightedLength += state.Length * weight;
}
if (totalWeight <= 0 || weightedLength <= 0)
return;
// Increment that time value according to delta time.
weightedNormalizedTime /= totalWeight;
weightedNormalizedTime += AnimancerPlayable.DeltaTime * totalWeight / weightedLength;
// Modify the speed of all children to go from their current normalized time to the average in one frame.
var inverseDeltaTime = 1f / AnimancerPlayable.DeltaTime;
for (int i = 0; i < childCount; i++)
{
if (i < flagCount && !_SynchroniseChildren[i])
continue;
var state = childStates[i];
if (state == null)
continue;
state.Speed = (weightedNormalizedTime - state.NormalizedTime) * state.Length * inverseDeltaTime;
}
// After this, all the playables will update and advance according to their new speeds this frame.
}
/************************************************************************************************************************/
///
/// Recalculates the weights of all based on the current value of the
/// and the thresholds.
///
/// Overrides of this method must set = false.
///
public abstract void RecalculateWeights();
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Inverse Kinematics
/************************************************************************************************************************/
///
/// Determines whether OnAnimatorIK(int layerIndex) will be called on the animated object for any
/// .
/// The initial value is determined by .
///
/// This is equivalent to the "IK Pass" toggle in Animator Controller layers.
///
/// It requires Unity 2018.1 or newer, however 2018.3 or newer is recommended because a bug in earlier versions
/// of the Playables API caused this value to only take effect while a state was at
/// == 1 which meant that IK would not work while fading between animations.
///
public override bool ApplyAnimatorIK
{
get
{
var childStates = ChildStates;
if (childStates == null)
return false;
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
if (state.ApplyAnimatorIK)
return true;
}
return false;
}
set
{
var childStates = ChildStates;
if (childStates == null)
return;
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
state.ApplyAnimatorIK = value;
}
}
}
/************************************************************************************************************************/
///
/// Indicates whether any of the in this mixer are applying IK to the character's feet.
/// The initial value is determined by .
///
/// This is equivalent to the "Foot IK" toggle in Animator Controller states.
///
public override bool ApplyFootIK
{
get
{
var childStates = ChildStates;
if (childStates == null)
return false;
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
if (state.ApplyFootIK)
return true;
}
return false;
}
set
{
var childStates = ChildStates;
if (childStates == null)
return;
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
state.ApplyFootIK = value;
}
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Other Methods
/************************************************************************************************************************/
///
/// Sets for all .
///
public void SetChildrenTime(float value, bool normalized = false)
{
var childStates = ChildStates;
if (childStates == null)
return;
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
if (normalized)
state.NormalizedTime = value;
else
state.Time = value;
}
}
/************************************************************************************************************************/
/// The average velocity of the root motion caused by this state.
public override Vector3 AverageVelocity
{
get
{
var velocity = default(Vector3);
var childStates = ChildStates;
if (childStates != null)
{
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
velocity += state.AverageVelocity * state.Weight;
}
}
return velocity;
}
}
/************************************************************************************************************************/
///
/// Recalculates the of all child states so that they add up to 1.
///
/// Thrown if there are any states with no .
public void NormalizeDurations()
{
var childStates = ChildStates;
if (childStates == null)
return;
int divideBy = 0;
float totalDuration = 0f;
// Count the number of states that exist and their total duration.
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
divideBy++;
totalDuration += state.Duration;
}
// Calculate the average duration.
totalDuration /= divideBy;
// Set all states to that duration.
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
state.Duration = totalDuration;
}
}
/************************************************************************************************************************/
/// Gets a user-friendly key to identify the `state` in the Inspector.
public abstract string GetDisplayKey(AnimancerState state);
/************************************************************************************************************************/
///
/// Returns a string describing the type of this mixer and the name of s connected to it.
///
public override string ToString()
{
var name = new StringBuilder();
name.Append(GetType().Name);
name.Append(" (");
bool first = true;
var childStates = ChildStates;
if (childStates == null)
{
name.Append("null");
}
else
{
var count = childStates.Count;
for (int i = 0; i < count; i++)
{
var state = childStates[i];
if (state == null)
continue;
if (first)
first = false;
else
name.Append(", ");
if (state.Clip != null)
name.Append(state.Clip.name);
else
name.Append(state);
}
}
name.Append(")");
return name.ToString();
}
/************************************************************************************************************************/
/// []
/// Gathers all the animations in this state.
///
public override void GatherAnimationClips(ICollection clips)
{
clips.GatherFromSources((IList)ChildStates);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Inspector
/************************************************************************************************************************/
/// The number of parameters being managed by this state.
protected virtual int ParameterCount { get { return 0; } }
/// Returns the name of a parameter being managed by this state.
/// Thrown if this state doesn't manage any parameters.
protected virtual string GetParameterName(int index) { throw new NotSupportedException(); }
/// Returns the type of a parameter being managed by this state.
/// Thrown if this state doesn't manage any parameters.
protected virtual UnityEngine.AnimatorControllerParameterType GetParameterType(int index) { throw new NotSupportedException(); }
/// Returns the value of a parameter being managed by this state.
/// Thrown if this state doesn't manage any parameters.
protected virtual object GetParameterValue(int index) { throw new NotSupportedException(); }
/// Sets the value of a parameter being managed by this state.
/// Thrown if this state doesn't manage any parameters.
protected virtual void SetParameterValue(int index, object value) { throw new NotSupportedException(); }
/************************************************************************************************************************/
#if UNITY_EDITOR
/************************************************************************************************************************/
/// [Editor-Only] Returns a for this state.
protected internal override Editor.IAnimancerNodeDrawer GetDrawer()
{
return new Drawer(this);
}
/************************************************************************************************************************/
/// [Editor-Only] Draws the Inspector GUI for an .
public sealed class Drawer : Editor.ParametizedAnimancerStateDrawer
{
/************************************************************************************************************************/
///
/// Constructs a new to manage the Inspector GUI for the `state`.
///
public Drawer(MixerState state) : base(state) { }
/************************************************************************************************************************/
/// The number of parameters being managed by the target state.
public override int ParameterCount { get { return Target.ParameterCount; } }
/// Returns the name of a parameter being managed by the target state.
/// Thrown if the target state doesn't manage any parameters.
public override string GetParameterName(int index) { return Target.GetParameterName(index); }
/// Returns the type of a parameter being managed by the target state.
/// Thrown if the target state doesn't manage any parameters.
public override UnityEngine.AnimatorControllerParameterType GetParameterType(int index) { return Target.GetParameterType(index); }
/// Returns the value of a parameter being managed by the target state.
/// Thrown if the target state doesn't manage any parameters.
public override object GetParameterValue(int index) { return Target.GetParameterValue(index); }
/// Sets the value of a parameter being managed by the target state.
/// Thrown if the target state doesn't manage any parameters.
public override void SetParameterValue(int index, object value) { Target.SetParameterValue(index, value); }
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Transition
/************************************************************************************************************************/
///
/// Base class for serializable s which can create a particular type of
/// when passed into
/// .
///
///
/// Unfortunately the tool used to generate this documentation does not currently support nested types with
/// identical names, so only one Transition class will actually have a documentation page.
///
/// Even though it has the , this class won't actually get serialized
/// by Unity because it's generic and abstract. Each child class still needs to include the attribute.
///
[Serializable]
public abstract class Transition : ManualMixerState.Transition
where TMixer : MixerState
{
/************************************************************************************************************************/
[SerializeField, HideInInspector]
private TParameter[] _Thresholds;
/// []
/// The parameter values at which each of the states are used and blended.
///
public TParameter[] Thresholds
{
get { return _Thresholds; }
set { _Thresholds = value; }
}
/************************************************************************************************************************/
[SerializeField]
private TParameter _DefaultParameter;
/// []
/// The initial parameter value to give the mixer when it is first created.
///
public TParameter DefaultParameter
{
get { return _DefaultParameter; }
set { _DefaultParameter = value; }
}
/************************************************************************************************************************/
///
/// Initialises the immediately after it is created.
///
public override void InitialiseState()
{
base.InitialiseState();
State.SetThresholds(_Thresholds);
State.Parameter = _DefaultParameter;
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Transition 2D
/************************************************************************************************************************/
///
/// A serializable which can create a or
/// when passed into
/// .
///
[Serializable]
public class Transition2D : Transition, Vector2>
{
/************************************************************************************************************************/
///
/// A type of that can be created by a .
///
public enum MixerType
{
///
Cartesian,
///
Directional,
}
[SerializeField]
private MixerType _Type;
/// []
/// The type of that this transition will create.
///
public MixerType Type
{
get { return _Type; }
set { _Type = value; }
}
/************************************************************************************************************************/
///
/// Creates and returns a new or
/// depending on the connected to the `layer`.
///
/// This method also assigns it as the .
///
public override MixerState CreateState(AnimancerLayer layer)
{
switch (_Type)
{
case MixerType.Cartesian: State = new CartesianMixerState(layer); break;
case MixerType.Directional: State = new DirectionalMixerState(layer); break;
default: throw new ArgumentOutOfRangeException("_Type");
}
InitialiseState();
return State;
}
/************************************************************************************************************************/
#region Drawer
#if UNITY_EDITOR
/************************************************************************************************************************/
/// [Editor-Only] Adds context menu functions for this transition.
protected override void AddItemsToContextMenu(GenericMenu menu, SerializedProperty property,
Editor.Serialization.PropertyAccessor accessor)
{
base.AddItemsToContextMenu(menu, property, accessor);
Drawer.AddItemsToContextMenu(menu);
}
/************************************************************************************************************************/
/// [Editor-Only]
/// Draws the Inspector GUI for a .
///
[CustomPropertyDrawer(typeof(Transition2D), true)]
public class Drawer : TransitionDrawer
{
/************************************************************************************************************************/
///
/// Constructs a new using the a wider `thresholdWidth` than usual to accomodate
/// both the X and Y values.
///
public Drawer() : base(ThresholdLabelWidth * 2 + 20) { }
/************************************************************************************************************************/
///
/// Fills the `menu` with functions relevant to the `rootProperty`.
///
public static void AddItemsToContextMenu(GenericMenu menu)
{
AddPropertyModifierFunction(menu, "Initialise Standard 4 Directions", InitialiseStandard4Directions);
AddThresholdItemsToContextMenu(menu);
}
///
/// Fills the `menu` with functions relevant to the `rootProperty`.
///
public static void AddThresholdItemsToContextMenu(GenericMenu menu, string prefix = "Calculate Thresholds")
{
var fromVelocity = "From Velocity";
if (prefix != null)
fromVelocity = prefix + "/" + fromVelocity;
AddCalculateThresholdsFunction(menu, fromVelocity + "/XY", (clip, threshold) =>
{
var velocity = clip.averageSpeed;
return new Vector2(velocity.x, velocity.y);
});
AddCalculateThresholdsFunction(menu, fromVelocity + "/XZ", (clip, threshold) =>
{
var velocity = clip.averageSpeed;
return new Vector2(velocity.x, velocity.z);
});
AddCalculateThresholdsFunctionPerAxis(menu, prefix, "From Speed",
(clip, threshold) => clip.apparentSpeed);
AddCalculateThresholdsFunctionPerAxis(menu, prefix, "From Velocity X",
(clip, threshold) => clip.averageSpeed.x);
AddCalculateThresholdsFunctionPerAxis(menu, prefix, "From Velocity Y",
(clip, threshold) => clip.averageSpeed.z);
AddCalculateThresholdsFunctionPerAxis(menu, prefix, "From Velocity Z",
(clip, threshold) => clip.averageSpeed.z);
AddCalculateThresholdsFunctionPerAxis(menu, prefix, "From Angular Speed (Rad)",
(clip, threshold) => clip.averageAngularSpeed * Mathf.Deg2Rad);
AddCalculateThresholdsFunctionPerAxis(menu, prefix, "From Angular Speed (Deg)",
(clip, threshold) => clip.averageAngularSpeed);
}
/************************************************************************************************************************/
private static void InitialiseStandard4Directions(SerializedProperty property)
{
var oldSpeedCount = CurrentSpeeds.arraySize;
CurrentClips.arraySize = CurrentThresholds.arraySize = CurrentSpeeds.arraySize = 5;
CurrentThresholds.GetArrayElementAtIndex(0).vector2Value = Vector2.zero;
CurrentThresholds.GetArrayElementAtIndex(1).vector2Value = Vector2.up;
CurrentThresholds.GetArrayElementAtIndex(2).vector2Value = Vector2.right;
CurrentThresholds.GetArrayElementAtIndex(3).vector2Value = Vector2.down;
CurrentThresholds.GetArrayElementAtIndex(4).vector2Value = Vector2.left;
InitialiseSpeeds(oldSpeedCount);
var type = property.FindPropertyRelative("_Type");
type.enumValueIndex = (int)MixerType.Directional;
}
/************************************************************************************************************************/
private static void AddCalculateThresholdsFunction(GenericMenu menu, string label,
Func calculateThreshold)
{
AddPropertyModifierFunction(menu, label, (property) =>
{
var count = CurrentClips.arraySize;
for (int i = 0; i < count; i++)
{
var clip = CurrentClips.GetArrayElementAtIndex(i).objectReferenceValue as AnimationClip;
if (clip == null)
continue;
var threshold = CurrentThresholds.GetArrayElementAtIndex(i);
var value = calculateThreshold(clip, threshold.vector2Value);
if (!float.IsNaN(value.x) && !float.IsNaN(value.y))
threshold.vector2Value = value;
}
});
}
/************************************************************************************************************************/
private static void AddCalculateThresholdsFunctionPerAxis(GenericMenu menu, string prefix, string label,
Func calculateThreshold)
{
if (prefix != null)
prefix += " ";
AddCalculateThresholdsFunction(menu, prefix + "X/" + label, 0, calculateThreshold);
AddCalculateThresholdsFunction(menu, prefix + "Y/" + label, 1, calculateThreshold);
}
private static void AddCalculateThresholdsFunction(GenericMenu menu, string label, int axis,
Func calculateThreshold)
{
AddPropertyModifierFunction(menu, label, (property) =>
{
var count = CurrentClips.arraySize;
for (int i = 0; i < count; i++)
{
var clip = CurrentClips.GetArrayElementAtIndex(i).objectReferenceValue as AnimationClip;
if (clip == null)
continue;
var threshold = CurrentThresholds.GetArrayElementAtIndex(i);
var value = threshold.vector2Value;
var newValue = calculateThreshold(clip, value[axis]);
if (!float.IsNaN(newValue))
value[axis] = newValue;
threshold.vector2Value = value;
}
});
}
/************************************************************************************************************************/
/// will add some functions to the menu.
protected override bool HasThresholdContextMenu { get { return true; } }
/// Adds functions to the `menu` relating to the thresholds.
protected override void AddThresholdItemsToMenu(GenericMenu menu)
{
AddThresholdItemsToContextMenu(menu, null);
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endif
#endregion
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Transition Drawer
#if UNITY_EDITOR
/************************************************************************************************************************/
/// [Editor-Only] Draws the Inspector GUI for a .
///
/// This class would be nested inside , but the generic parameters
/// cause problems in Unity 2019.3.
///
[CustomPropertyDrawer(typeof(Transition<,>), true)]
public class TransitionDrawer : ManualMixerState.Transition.Drawer
{
/************************************************************************************************************************/
///
/// The number of horizontal pixels the "Threshold" label occupies.
///
private readonly float ThresholdWidth;
private static float _ThresholdLabelWidth;
///
/// The number of horizontal pixels the word "Threshold" label occupies when drawn with the standard
/// style.
///
protected static float ThresholdLabelWidth
{
get
{
if (_ThresholdLabelWidth == 0)
_ThresholdLabelWidth = Editor.AnimancerGUI.CalculateWidth(EditorStyles.popup, "Threshold");
return _ThresholdLabelWidth;
}
}
/************************************************************************************************************************/
///
/// Constructs a new using the default .
///
public TransitionDrawer() : this(ThresholdLabelWidth) { }
///
/// Constructs a new using a custom width for its threshold labels.
///
protected TransitionDrawer(float thresholdWidth)
{
ThresholdWidth = thresholdWidth;
}
/************************************************************************************************************************/
///
/// The serialized of the
/// .
///
protected static SerializedProperty CurrentThresholds { get; private set; }
/************************************************************************************************************************/
///
/// Called every time a `property` is drawn to find the relevant child properties and store them to be
/// used in and
/// .
///
protected override void GatherSubProperties(SerializedProperty property)
{
base.GatherSubProperties(property);
CurrentThresholds = property.FindPropertyRelative("_Thresholds");
var count = Math.Max(CurrentClips.arraySize, CurrentThresholds.arraySize);
CurrentClips.arraySize = count;
CurrentThresholds.arraySize = count;
if (CurrentSpeeds.arraySize != 0)
CurrentSpeeds.arraySize = count;
}
/************************************************************************************************************************/
/// Splits the specified `area` into separate sections.
protected void SplitListRect(Rect area, out Rect animation, out Rect threshold, out Rect speed, out Rect sync)
{
SplitListRect(area, out animation, out speed, out sync);
threshold = animation;
var xMin = threshold.xMin = Math.Max(
EditorGUIUtility.labelWidth + Editor.AnimancerGUI.IndentSize,
threshold.xMax - ThresholdWidth);
animation.xMax = xMin - Editor.AnimancerGUI.StandardSpacing;
}
/************************************************************************************************************************/
/// Draws the headdings of the state list.
protected override void DoStateListHeaderGUI(Rect area)
{
Rect animationArea, thresholdArea, speedArea, syncArea;
SplitListRect(area, out animationArea, out thresholdArea, out speedArea, out syncArea);
DoAnimationLabelGUI(animationArea);
EditorGUI.BeginProperty(thresholdArea, GUIContent.none, CurrentThresholds);
var content = Editor.AnimancerGUI.TempContent("Threshold",
"The parameter values at which each child state will be fully active");
if (HasThresholdContextMenu)
{
if (EditorGUI.DropdownButton(thresholdArea, content, FocusType.Passive))
{
var menu = new GenericMenu();
AddThresholdItemsToMenu(menu);
menu.ShowAsContext();
}
}
else
{
GUI.Label(thresholdArea, content);
}
EditorGUI.EndProperty();
DoSpeedLabelGUI(speedArea);
DoSyncLabelGUI(syncArea);
}
/************************************************************************************************************************/
/// Draws the GUI of the state at the specified `index`.
protected override void DoElementGUI(Rect area, int index,
SerializedProperty clip, SerializedProperty speed)
{
var threshold = CurrentThresholds.GetArrayElementAtIndex(index);
Rect animationArea, thresholdArea, speedArea, syncArea;
SplitListRect(area, out animationArea, out thresholdArea, out speedArea, out syncArea);
DoElementGUI(animationArea, speedArea, syncArea, index, clip, speed);
EditorGUI.PropertyField(thresholdArea, threshold, GUIContent.none);
}
/************************************************************************************************************************/
///
/// Called when adding a new state to the list to ensure that any other relevant arrays have new
/// elements added as well.
///
protected override void OnAddElement(ReorderableList list)
{
var index = CurrentClips.arraySize;
base.OnAddElement(list);
CurrentThresholds.InsertArrayElementAtIndex(index);
}
/************************************************************************************************************************/
///
/// Called when removing a state from the list to ensure that any other relevant arrays have elements
/// removed as well.
///
protected override void OnRemoveElement(ReorderableList list)
{
base.OnRemoveElement(list);
RemoveArrayElement(CurrentThresholds, list.index);
}
/************************************************************************************************************************/
///
/// Called when reordering states in the list to ensure that any other relevant arrays have their
/// corresponding elements reordered as well.
///
protected override void OnReorderList(ReorderableList list, int oldIndex, int newIndex)
{
base.OnReorderList(list, oldIndex, newIndex);
CurrentThresholds.MoveArrayElement(oldIndex, newIndex);
}
/************************************************************************************************************************/
/// Indicates whether will add anything to the menu.
protected virtual bool HasThresholdContextMenu { get { return false; } }
/// Adds functions to the `menu` relating to the thresholds.
protected virtual void AddThresholdItemsToMenu(GenericMenu menu) { }
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endif
#endregion
/************************************************************************************************************************/
}
}