You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
853 lines
38 KiB
853 lines
38 KiB
// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 Kybernetik // |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using UnityEngine; |
|
using Object = UnityEngine.Object; |
|
|
|
#if UNITY_EDITOR |
|
using Animancer.Editor; |
|
using UnityEditor; |
|
using UnityEditorInternal; |
|
#endif |
|
|
|
namespace Animancer |
|
{ |
|
/// <inheritdoc/> |
|
/// https://kybernetik.com.au/animancer/api/Animancer/ManualMixerTransition |
|
[Serializable] |
|
public class ManualMixerTransition : ManualMixerTransition<ManualMixerState>, ManualMixerState.ITransition |
|
{ |
|
/************************************************************************************************************************/ |
|
|
|
/// <inheritdoc/> |
|
public override ManualMixerState CreateState() |
|
{ |
|
State = new ManualMixerState(); |
|
InitializeState(); |
|
return State; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
#if UNITY_EDITOR |
|
/************************************************************************************************************************/ |
|
|
|
/// <inheritdoc/> |
|
[CustomPropertyDrawer(typeof(ManualMixerTransition), true)] |
|
public class Drawer : TransitionDrawer |
|
{ |
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>The property this drawer is currently drawing.</summary> |
|
/// <remarks>Normally each property has its own drawer, but arrays share a single drawer for all elements.</remarks> |
|
public static SerializedProperty CurrentProperty { get; private set; } |
|
|
|
/// <summary>The <see cref="ManualMixerTransition{TState}.Animations"/> field.</summary> |
|
public static SerializedProperty CurrentAnimations { get; private set; } |
|
|
|
/// <summary>The <see cref="ManualMixerTransition{TState}.Speeds"/> field.</summary> |
|
public static SerializedProperty CurrentSpeeds { get; private set; } |
|
|
|
/// <summary>The <see cref="ManualMixerTransition{TState}.SynchronizeChildren"/> field.</summary> |
|
public static SerializedProperty CurrentSynchronizeChildren { get; private set; } |
|
|
|
private readonly Dictionary<string, ReorderableList> |
|
PropertyPathToStates = new Dictionary<string, ReorderableList>(); |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Gather the details of the `property`.</summary> |
|
/// <remarks> |
|
/// This method gets called by every <see cref="GetPropertyHeight"/> and <see cref="OnGUI"/> call since |
|
/// Unity uses the same <see cref="PropertyDrawer"/> instance for each element in a collection, so it |
|
/// needs to gather the details associated with the current property. |
|
/// </remarks> |
|
protected virtual ReorderableList GatherDetails(SerializedProperty property) |
|
{ |
|
InitializeMode(property); |
|
GatherSubProperties(property); |
|
|
|
if (CurrentAnimations == null) |
|
return null; |
|
|
|
var path = property.propertyPath; |
|
|
|
if (!PropertyPathToStates.TryGetValue(path, out var states)) |
|
{ |
|
states = new ReorderableList(CurrentAnimations.serializedObject, CurrentAnimations) |
|
{ |
|
drawHeaderCallback = DoChildListHeaderGUI, |
|
elementHeightCallback = GetElementHeight, |
|
drawElementCallback = DoElementGUI, |
|
onAddCallback = OnAddElement, |
|
onRemoveCallback = OnRemoveElement, |
|
onReorderCallbackWithDetails = OnReorderList, |
|
drawFooterCallback = DoChildListFooterGUI, |
|
}; |
|
|
|
PropertyPathToStates.Add(path, states); |
|
} |
|
|
|
states.serializedProperty = CurrentAnimations; |
|
|
|
return states; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Called every time a `property` is drawn to find the relevant child properties and store them to be |
|
/// used in <see cref="GetPropertyHeight"/> and <see cref="OnGUI"/>. |
|
/// </summary> |
|
protected virtual void GatherSubProperties(SerializedProperty property) |
|
{ |
|
CurrentProperty = property; |
|
CurrentAnimations = property.FindPropertyRelative(AnimationsField); |
|
CurrentSpeeds = property.FindPropertyRelative(SpeedsField); |
|
CurrentSynchronizeChildren = property.FindPropertyRelative(SynchronizeChildrenField); |
|
|
|
if (CurrentAnimations != null && |
|
CurrentSpeeds != null && |
|
CurrentSpeeds.arraySize != 0) |
|
CurrentSpeeds.arraySize = CurrentAnimations.arraySize; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Adds a menu item that will call <see cref="GatherSubProperties"/> then run the specified |
|
/// `function`. |
|
/// </summary> |
|
protected void AddPropertyModifierFunction(GenericMenu menu, string label, |
|
MenuFunctionState state, Action<SerializedProperty> function) |
|
{ |
|
Serialization.AddPropertyModifierFunction(menu, CurrentProperty, label, state, (property) => |
|
{ |
|
GatherSubProperties(property); |
|
function(property); |
|
}); |
|
} |
|
|
|
/// <summary> |
|
/// Adds a menu item that will call <see cref="GatherSubProperties"/> then run the specified |
|
/// `function`. |
|
/// </summary> |
|
protected void AddPropertyModifierFunction(GenericMenu menu, string label, |
|
Action<SerializedProperty> function) |
|
{ |
|
Serialization.AddPropertyModifierFunction(menu, CurrentProperty, label, (property) => |
|
{ |
|
GatherSubProperties(property); |
|
function(property); |
|
}); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <inheritdoc/> |
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) |
|
{ |
|
var height = EditorGUI.GetPropertyHeight(property, label); |
|
|
|
if (property.isExpanded) |
|
{ |
|
var states = GatherDetails(property); |
|
if (states != null) |
|
{ |
|
height -= AnimancerGUI.StandardSpacing + EditorGUI.GetPropertyHeight(CurrentAnimations, label); |
|
height -= AnimancerGUI.StandardSpacing + EditorGUI.GetPropertyHeight(CurrentSpeeds, label); |
|
height -= AnimancerGUI.StandardSpacing + EditorGUI.GetPropertyHeight(CurrentSynchronizeChildren, label); |
|
height += AnimancerGUI.StandardSpacing + states.GetHeight(); |
|
} |
|
} |
|
|
|
return height; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
private SerializedProperty _RootProperty; |
|
private ReorderableList _CurrentChildList; |
|
|
|
/// <inheritdoc/> |
|
public override void OnGUI(Rect area, SerializedProperty property, GUIContent label) |
|
{ |
|
_RootProperty = null; |
|
|
|
base.OnGUI(area, property, label); |
|
|
|
if (_RootProperty == null || |
|
!_RootProperty.isExpanded) |
|
return; |
|
|
|
using (DrawerContext.Get(_RootProperty)) |
|
{ |
|
if (Context.Transition == null) |
|
return; |
|
|
|
_CurrentChildList = GatherDetails(_RootProperty); |
|
|
|
var indentLevel = EditorGUI.indentLevel; |
|
|
|
area.yMin = area.yMax - _CurrentChildList.GetHeight(); |
|
|
|
EditorGUI.indentLevel++; |
|
area = EditorGUI.IndentedRect(area); |
|
|
|
EditorGUI.indentLevel = 0; |
|
_CurrentChildList.DoList(area); |
|
|
|
EditorGUI.indentLevel = indentLevel; |
|
|
|
TryCollapseArrays(); |
|
} |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <inheritdoc/> |
|
protected override void DoChildPropertyGUI(ref Rect area, |
|
SerializedProperty rootProperty, SerializedProperty property, GUIContent label) |
|
{ |
|
if (Context?.Transition != null) |
|
{ |
|
area.height = 0; |
|
|
|
// If we find the Animations property, hide it to draw it last. |
|
|
|
var path = property.propertyPath; |
|
if (path.EndsWith("." + AnimationsField)) |
|
{ |
|
_RootProperty = rootProperty; |
|
return; |
|
} |
|
else if (_RootProperty != null) |
|
{ |
|
// If we already found the Animations property, also hide Speeds and Synchronize Children. |
|
if (path.EndsWith("." + SpeedsField) || |
|
path.EndsWith("." + SynchronizeChildrenField)) |
|
return; |
|
} |
|
} |
|
|
|
base.DoChildPropertyGUI(ref area, rootProperty, property, label); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
private static float _SpeedLabelWidth; |
|
private static float _SyncLabelWidth; |
|
|
|
/// <summary>Splits the specified `area` into separate sections.</summary> |
|
protected static void SplitListRect(Rect area, bool isHeader, out Rect animation, out Rect speed, out Rect sync) |
|
{ |
|
if (_SpeedLabelWidth == 0) |
|
_SpeedLabelWidth = AnimancerGUI.CalculateWidth(EditorStyles.popup, "Speed"); |
|
|
|
if (_SyncLabelWidth == 0) |
|
_SyncLabelWidth = AnimancerGUI.CalculateWidth(EditorStyles.popup, "Sync"); |
|
|
|
var spacing = AnimancerGUI.StandardSpacing; |
|
|
|
var syncWidth = isHeader ? |
|
_SyncLabelWidth : |
|
AnimancerGUI.ToggleWidth - spacing; |
|
|
|
var speedWidth = _SpeedLabelWidth + _SyncLabelWidth - syncWidth; |
|
if (!isHeader) |
|
{ |
|
// Don't use Clamp because the max might be smaller than the min. |
|
var max = Math.Max(area.height, area.width * 0.25f - 30); |
|
speedWidth = Math.Min(speedWidth, max); |
|
} |
|
|
|
area.width += spacing; |
|
sync = AnimancerGUI.StealFromRight(ref area, syncWidth, spacing); |
|
speed = AnimancerGUI.StealFromRight(ref area, speedWidth, spacing); |
|
animation = area; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
#region Headers |
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Draws the headdings of the child list.</summary> |
|
protected virtual void DoChildListHeaderGUI(Rect area) |
|
{ |
|
SplitListRect(area, true, out var animationArea, out var speedArea, out var syncArea); |
|
|
|
DoAnimationHeaderGUI(animationArea); |
|
DoSpeedHeaderGUI(speedArea); |
|
DoSyncHeaderGUI(syncArea); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Draws an "Animation" header.</summary> |
|
protected static void DoAnimationHeaderGUI(Rect area) |
|
{ |
|
using (ObjectPool.Disposable.AcquireContent(out var label, "Animation", |
|
$"The animations that will be used for each child state" + |
|
$"\n\nCtrl + Click to allow picking Transition Assets (or anything that implements {nameof(ITransition)})")) |
|
{ |
|
DoHeaderDropdownGUI(area, CurrentAnimations, label, null); |
|
} |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
#region Speeds |
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Draws a "Speed" header.</summary> |
|
protected void DoSpeedHeaderGUI(Rect area) |
|
{ |
|
using (ObjectPool.Disposable.AcquireContent(out var label, "Speed", Strings.Tooltips.Speed)) |
|
{ |
|
DoHeaderDropdownGUI(area, CurrentSpeeds, label, (menu) => |
|
{ |
|
AddPropertyModifierFunction(menu, "Reset All to 1", |
|
CurrentSpeeds.arraySize == 0 ? MenuFunctionState.Selected : MenuFunctionState.Normal, |
|
(_) => CurrentSpeeds.arraySize = 0); |
|
|
|
AddPropertyModifierFunction(menu, "Normalize Durations", MenuFunctionState.Normal, NormalizeDurations); |
|
}); |
|
} |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Recalculates the <see cref="CurrentSpeeds"/> depending on the <see cref="AnimationClip.length"/> of |
|
/// their animations so that they all take the same amount of time to play fully. |
|
/// </summary> |
|
private static void NormalizeDurations(SerializedProperty property) |
|
{ |
|
var speedCount = CurrentSpeeds.arraySize; |
|
|
|
var lengths = new float[CurrentAnimations.arraySize]; |
|
if (lengths.Length <= 1) |
|
return; |
|
|
|
int nonZeroLengths = 0; |
|
float totalLength = 0; |
|
float totalSpeed = 0; |
|
for (int i = 0; i < lengths.Length; i++) |
|
{ |
|
var state = CurrentAnimations.GetArrayElementAtIndex(i).objectReferenceValue; |
|
if (AnimancerUtilities.TryGetLength(state, out var length) && |
|
length > 0) |
|
{ |
|
nonZeroLengths++; |
|
totalLength += length; |
|
lengths[i] = length; |
|
|
|
if (speedCount > 0) |
|
totalSpeed += CurrentSpeeds.GetArrayElementAtIndex(i).floatValue; |
|
} |
|
} |
|
|
|
if (nonZeroLengths == 0) |
|
return; |
|
|
|
var averageLength = totalLength / nonZeroLengths; |
|
var averageSpeed = speedCount > 0 ? totalSpeed / nonZeroLengths : 1; |
|
|
|
CurrentSpeeds.arraySize = lengths.Length; |
|
InitializeSpeeds(speedCount); |
|
|
|
for (int i = 0; i < lengths.Length; i++) |
|
{ |
|
if (lengths[i] == 0) |
|
continue; |
|
|
|
CurrentSpeeds.GetArrayElementAtIndex(i).floatValue = averageSpeed * lengths[i] / averageLength; |
|
} |
|
|
|
TryCollapseArrays(); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Initializes every element in the <see cref="CurrentSpeeds"/> array from the `start` to the end of |
|
/// the array to contain a value of 1. |
|
/// </summary> |
|
public static void InitializeSpeeds(int start) |
|
{ |
|
var count = CurrentSpeeds.arraySize; |
|
while (start < count) |
|
CurrentSpeeds.GetArrayElementAtIndex(start++).floatValue = 1; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
#endregion |
|
/************************************************************************************************************************/ |
|
#region Sync |
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Draws a "Sync" header.</summary> |
|
protected void DoSyncHeaderGUI(Rect area) |
|
{ |
|
using (ObjectPool.Disposable.AcquireContent(out var label, "Sync", |
|
"Determines which child states have their normalized times constantly synchronized")) |
|
{ |
|
DoHeaderDropdownGUI(area, CurrentSpeeds, label, (menu) => |
|
{ |
|
var syncCount = CurrentSynchronizeChildren.arraySize; |
|
|
|
var allState = syncCount == 0 ? MenuFunctionState.Selected : MenuFunctionState.Normal; |
|
AddPropertyModifierFunction(menu, "All", allState, |
|
(_) => CurrentSynchronizeChildren.arraySize = 0); |
|
|
|
var syncNone = syncCount == CurrentAnimations.arraySize; |
|
if (syncNone) |
|
{ |
|
for (int i = 0; i < syncCount; i++) |
|
{ |
|
if (CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue) |
|
{ |
|
syncNone = false; |
|
break; |
|
} |
|
} |
|
} |
|
var noneState = syncNone ? MenuFunctionState.Selected : MenuFunctionState.Normal; |
|
AddPropertyModifierFunction(menu, "None", noneState, (_) => |
|
{ |
|
var count = CurrentSynchronizeChildren.arraySize = CurrentAnimations.arraySize; |
|
for (int i = 0; i < count; i++) |
|
CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = false; |
|
}); |
|
|
|
AddPropertyModifierFunction(menu, "Invert", MenuFunctionState.Normal, (_) => |
|
{ |
|
var count = CurrentSynchronizeChildren.arraySize; |
|
for (int i = 0; i < count; i++) |
|
{ |
|
var property = CurrentSynchronizeChildren.GetArrayElementAtIndex(i); |
|
property.boolValue = !property.boolValue; |
|
} |
|
|
|
var newCount = CurrentSynchronizeChildren.arraySize = CurrentAnimations.arraySize; |
|
for (int i = count; i < newCount; i++) |
|
CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = false; |
|
}); |
|
|
|
AddPropertyModifierFunction(menu, "Non-Stationary", MenuFunctionState.Normal, (_) => |
|
{ |
|
var count = CurrentAnimations.arraySize; |
|
|
|
for (int i = 0; i < count; i++) |
|
{ |
|
var state = CurrentAnimations.GetArrayElementAtIndex(i).objectReferenceValue; |
|
if (state == null) |
|
continue; |
|
|
|
if (i >= syncCount) |
|
{ |
|
CurrentSynchronizeChildren.arraySize = i + 1; |
|
for (int j = syncCount; j < i; j++) |
|
CurrentSynchronizeChildren.GetArrayElementAtIndex(j).boolValue = true; |
|
syncCount = i + 1; |
|
} |
|
|
|
CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = |
|
AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) && |
|
velocity != default; |
|
} |
|
|
|
TryCollapseSync(); |
|
}); |
|
}); |
|
} |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
private static void SyncNone() |
|
{ |
|
var count = CurrentSynchronizeChildren.arraySize = CurrentAnimations.arraySize; |
|
for (int i = 0; i < count; i++) |
|
CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = false; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
#endregion |
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Draws the GUI for a header dropdown button.</summary> |
|
public static void DoHeaderDropdownGUI(Rect area, SerializedProperty property, GUIContent content, |
|
Action<GenericMenu> populateMenu) |
|
{ |
|
if (property != null) |
|
EditorGUI.BeginProperty(area, GUIContent.none, property); |
|
|
|
if (populateMenu != null) |
|
{ |
|
if (EditorGUI.DropdownButton(area, content, FocusType.Passive)) |
|
{ |
|
var menu = new GenericMenu(); |
|
populateMenu(menu); |
|
menu.ShowAsContext(); |
|
} |
|
} |
|
else |
|
{ |
|
GUI.Label(area, content); |
|
} |
|
|
|
if (property != null) |
|
EditorGUI.EndProperty(); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Draws the footer of the child list.</summary> |
|
protected virtual void DoChildListFooterGUI(Rect area) |
|
{ |
|
ReorderableList.defaultBehaviours.DrawFooter(area, _CurrentChildList); |
|
|
|
EditorGUI.BeginChangeCheck(); |
|
|
|
area.xMax = EditorGUIUtility.labelWidth + AnimancerGUI.IndentSize; |
|
|
|
area.y++; |
|
area.height = AnimancerGUI.LineHeight; |
|
|
|
using (ObjectPool.Disposable.AcquireContent(out var label, "Count")) |
|
{ |
|
var indentLevel = EditorGUI.indentLevel; |
|
EditorGUI.indentLevel = 0; |
|
|
|
var labelWidth = EditorGUIUtility.labelWidth; |
|
EditorGUIUtility.labelWidth = AnimancerGUI.CalculateLabelWidth(label.text); |
|
|
|
var count = EditorGUI.DelayedIntField(area, label, _CurrentChildList.count); |
|
|
|
if (EditorGUI.EndChangeCheck()) |
|
ResizeList(count); |
|
|
|
EditorGUIUtility.labelWidth = labelWidth; |
|
|
|
EditorGUI.indentLevel = indentLevel; |
|
} |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
#endregion |
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Calculates the height of the state at the specified `index`.</summary> |
|
protected virtual float GetElementHeight(int index) => AnimancerGUI.LineHeight; |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Draws the GUI of the state at the specified `index`.</summary> |
|
private void DoElementGUI(Rect area, int index, bool isActive, bool isFocused) |
|
{ |
|
if (index < 0 || index > CurrentAnimations.arraySize) |
|
return; |
|
|
|
area.height = AnimancerGUI.LineHeight; |
|
|
|
var state = CurrentAnimations.GetArrayElementAtIndex(index); |
|
var speed = CurrentSpeeds.arraySize > 0 ? CurrentSpeeds.GetArrayElementAtIndex(index) : null; |
|
DoElementGUI(area, index, state, speed); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Draws the GUI of the state at the specified `index`.</summary> |
|
protected virtual void DoElementGUI(Rect area, int index, |
|
SerializedProperty state, SerializedProperty speed) |
|
{ |
|
SplitListRect(area, false, out var animationArea, out var speedArea, out var syncArea); |
|
|
|
DoElementGUI(animationArea, speedArea, syncArea, index, state, speed); |
|
} |
|
|
|
/// <summary>Draws the GUI of the state at the specified `index`.</summary> |
|
protected void DoElementGUI(Rect animationArea, Rect speedArea, Rect syncArea, int index, |
|
SerializedProperty state, SerializedProperty speed) |
|
{ |
|
DoAnimationField(animationArea, state); |
|
|
|
if (speed != null) |
|
{ |
|
EditorGUI.PropertyField(speedArea, speed, GUIContent.none); |
|
} |
|
else// If this element doesn't have its own speed property, just show 1. |
|
{ |
|
EditorGUI.BeginProperty(speedArea, GUIContent.none, CurrentSpeeds); |
|
|
|
var value = Units.UnitsAttribute.DoSpecialFloatField( |
|
speedArea, null, 1, Units.AnimationSpeedAttribute.DisplayConverters[0]); |
|
|
|
// Middle Click toggles from 1 to -1. |
|
if (AnimancerGUI.TryUseClickEvent(speedArea, 2)) |
|
value = -1; |
|
|
|
if (value != 1) |
|
{ |
|
CurrentSpeeds.InsertArrayElementAtIndex(0); |
|
CurrentSpeeds.GetArrayElementAtIndex(0).floatValue = 1; |
|
CurrentSpeeds.arraySize = CurrentAnimations.arraySize; |
|
CurrentSpeeds.GetArrayElementAtIndex(index).floatValue = value; |
|
} |
|
|
|
EditorGUI.EndProperty(); |
|
} |
|
|
|
DoSyncToggleGUI(syncArea, index); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Draws an <see cref="EditorGUI.ObjectField(Rect, GUIContent, Object, Type, bool)"/> that accepts |
|
/// <see cref="AnimationClip"/>s and <see cref="ITransition"/>s |
|
/// </summary> |
|
public static void DoAnimationField(Rect area, SerializedProperty property) |
|
{ |
|
EditorGUI.BeginProperty(area, GUIContent.none, property); |
|
|
|
var targetObject = property.serializedObject.targetObject; |
|
var oldReference = property.objectReferenceValue; |
|
|
|
var currentEvent = Event.current; |
|
var isDrag = |
|
currentEvent.type == EventType.DragUpdated || |
|
currentEvent.type == EventType.DragPerform; |
|
var type = |
|
isDrag || |
|
currentEvent.control || |
|
currentEvent.commandName == "ObjectSelectorUpdated" ? |
|
typeof(Object) : typeof(AnimationClip); |
|
|
|
var allowSceneObjects = targetObject != null && !EditorUtility.IsPersistent(targetObject); |
|
|
|
EditorGUI.BeginChangeCheck(); |
|
var newReference = EditorGUI.ObjectField(area, GUIContent.none, oldReference, type, allowSceneObjects); |
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
if (newReference == null || (IsClipOrTransition(newReference) && newReference != targetObject)) |
|
property.objectReferenceValue = newReference; |
|
} |
|
|
|
if (isDrag && area.Contains(currentEvent.mousePosition)) |
|
{ |
|
var objects = DragAndDrop.objectReferences; |
|
if (objects.Length != 1 || |
|
!IsClipOrTransition(objects[0]) || |
|
objects[0] == targetObject) |
|
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected; |
|
} |
|
|
|
EditorGUI.EndProperty(); |
|
} |
|
|
|
/// <summary>Is the `clipOrTransition` an <see cref="AnimationClip"/> or <see cref="ITransition"/>?</summary> |
|
public static bool IsClipOrTransition(object clipOrTransition) |
|
=> clipOrTransition is AnimationClip || clipOrTransition is ITransition; |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Draws a toggle to enable or disable <see cref="MixerState.SynchronizedChildren"/> for the child at |
|
/// the specified `index`. |
|
/// </summary> |
|
protected void DoSyncToggleGUI(Rect area, int index) |
|
{ |
|
var syncProperty = CurrentSynchronizeChildren; |
|
var syncFlagCount = syncProperty.arraySize; |
|
|
|
var enabled = true; |
|
|
|
if (index < syncFlagCount) |
|
{ |
|
syncProperty = syncProperty.GetArrayElementAtIndex(index); |
|
enabled = syncProperty.boolValue; |
|
} |
|
|
|
EditorGUI.BeginChangeCheck(); |
|
EditorGUI.BeginProperty(area, GUIContent.none, syncProperty); |
|
|
|
enabled = GUI.Toggle(area, enabled, GUIContent.none); |
|
|
|
EditorGUI.EndProperty(); |
|
if (EditorGUI.EndChangeCheck()) |
|
{ |
|
if (index < syncFlagCount) |
|
{ |
|
syncProperty.boolValue = enabled; |
|
} |
|
else |
|
{ |
|
syncProperty.arraySize = index + 1; |
|
|
|
for (int i = syncFlagCount; i < index; i++) |
|
{ |
|
syncProperty.GetArrayElementAtIndex(i).boolValue = true; |
|
} |
|
|
|
syncProperty.GetArrayElementAtIndex(index).boolValue = enabled; |
|
} |
|
} |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Called when adding a new state to the list to ensure that any other relevant arrays have new |
|
/// elements added as well. |
|
/// </summary> |
|
private void OnAddElement(ReorderableList list) |
|
{ |
|
var index = list.index; |
|
if (index < 0 || Event.current.button == 1)// Right Click to add at the end. |
|
{ |
|
index = CurrentAnimations.arraySize - 1; |
|
if (index < 0) |
|
index = 0; |
|
} |
|
|
|
OnAddElement(index); |
|
} |
|
|
|
/// <summary> |
|
/// Called when adding a new state to the list to ensure that any other relevant arrays have new |
|
/// elements added as well. |
|
/// </summary> |
|
protected virtual void OnAddElement(int index) |
|
{ |
|
CurrentAnimations.InsertArrayElementAtIndex(index); |
|
|
|
if (CurrentSpeeds.arraySize > 0) |
|
CurrentSpeeds.InsertArrayElementAtIndex(index); |
|
|
|
if (CurrentSynchronizeChildren.arraySize > index) |
|
CurrentSynchronizeChildren.InsertArrayElementAtIndex(index); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Called when removing a state from the list to ensure that any other relevant arrays have elements |
|
/// removed as well. |
|
/// </summary> |
|
protected virtual void OnRemoveElement(ReorderableList list) |
|
{ |
|
var index = list.index; |
|
|
|
Serialization.RemoveArrayElement(CurrentAnimations, index); |
|
|
|
if (CurrentSpeeds.arraySize > index) |
|
Serialization.RemoveArrayElement(CurrentSpeeds, index); |
|
|
|
if (CurrentSynchronizeChildren.arraySize > index) |
|
Serialization.RemoveArrayElement(CurrentSynchronizeChildren, index); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Sets the number of items in the child list.</summary> |
|
protected virtual void ResizeList(int size) |
|
{ |
|
CurrentAnimations.arraySize = size; |
|
|
|
if (CurrentSpeeds.arraySize > size) |
|
CurrentSpeeds.arraySize = size; |
|
|
|
if (CurrentSynchronizeChildren.arraySize > size) |
|
CurrentSynchronizeChildren.arraySize = size; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Called when reordering states in the list to ensure that any other relevant arrays have their |
|
/// corresponding elements reordered as well. |
|
/// </summary> |
|
protected virtual void OnReorderList(ReorderableList list, int oldIndex, int newIndex) |
|
{ |
|
CurrentSpeeds.MoveArrayElement(oldIndex, newIndex); |
|
|
|
var syncCount = CurrentSynchronizeChildren.arraySize; |
|
if (Math.Max(oldIndex, newIndex) >= syncCount) |
|
{ |
|
CurrentSynchronizeChildren.arraySize++; |
|
CurrentSynchronizeChildren.GetArrayElementAtIndex(syncCount).boolValue = true; |
|
CurrentSynchronizeChildren.arraySize = newIndex + 1; |
|
} |
|
|
|
CurrentSynchronizeChildren.MoveArrayElement(oldIndex, newIndex); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Calls <see cref="TryCollapseSpeeds"/> and <see cref="TryCollapseSync"/>. |
|
/// </summary> |
|
public static void TryCollapseArrays() |
|
{ |
|
TryCollapseSpeeds(); |
|
TryCollapseSync(); |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// If every element in the <see cref="CurrentSpeeds"/> array is 1, this method sets the array size to 0. |
|
/// </summary> |
|
public static void TryCollapseSpeeds() |
|
{ |
|
var property = CurrentSpeeds; |
|
var speedCount = property.arraySize; |
|
if (speedCount <= 0) |
|
return; |
|
|
|
for (int i = 0; i < speedCount; i++) |
|
{ |
|
if (property.GetArrayElementAtIndex(i).floatValue != 1) |
|
return; |
|
} |
|
|
|
property.arraySize = 0; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Removes any true elements from the end of the <see cref="CurrentSynchronizeChildren"/> array. |
|
/// </summary> |
|
public static void TryCollapseSync() |
|
{ |
|
var property = CurrentSynchronizeChildren; |
|
var count = property.arraySize; |
|
var changed = false; |
|
|
|
for (int i = count - 1; i >= 0; i--) |
|
{ |
|
if (property.GetArrayElementAtIndex(i).boolValue) |
|
{ |
|
count = i; |
|
changed = true; |
|
} |
|
else |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
if (changed) |
|
property.arraySize = count; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
#endif |
|
/************************************************************************************************************************/ |
|
} |
|
}
|
|
|