// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value. using System; using UnityEngine; #if UNITY_EDITOR using Animancer.Editor; using UnityEditor; using UnityEditorInternal; #endif namespace Animancer { /// /// https://kybernetik.com.au/animancer/api/Animancer/MixerTransition_2 [Serializable] #if ! UNITY_EDITOR [System.Obsolete(Validate.ProOnlyMessage)] #endif public abstract class MixerTransition : ManualMixerTransition, ICopyable> where TMixer : MixerState { /************************************************************************************************************************/ [SerializeField] private TParameter[] _Thresholds; /// [] /// The parameter values at which each of the states are used and blended. /// public ref TParameter[] Thresholds => ref _Thresholds; /// The name of the serialized backing field of . public const string ThresholdsField = nameof(_Thresholds); /************************************************************************************************************************/ [SerializeField] private TParameter _DefaultParameter; /// [] /// The initial parameter value to give the mixer when it is first created. /// public ref TParameter DefaultParameter => ref _DefaultParameter; /// The name of the serialized backing field of . public const string DefaultParameterField = nameof(_DefaultParameter); /************************************************************************************************************************/ /// public override void InitializeState() { base.InitializeState(); State.SetThresholds(_Thresholds); State.Parameter = _DefaultParameter; } /************************************************************************************************************************/ /// public virtual void CopyFrom(MixerTransition copyFrom) { CopyFrom((ManualMixerTransition)copyFrom); if (copyFrom == null) { _DefaultParameter = default; _Thresholds = default; return; } _DefaultParameter = copyFrom._DefaultParameter; AnimancerUtilities.CopyExactArray(copyFrom._Thresholds, ref _Thresholds); } /************************************************************************************************************************/ } /************************************************************************************************************************/ #if UNITY_EDITOR /// [Editor-Only] Draws the Inspector GUI for a . /// /// Documentation: Transitions /// and Mixers /// /// https://kybernetik.com.au/animancer/api/Animancer/MixerTransitionDrawer /// public class MixerTransitionDrawer : ManualMixerTransition.Drawer { /************************************************************************************************************************/ /// The number of horizontal pixels the "Threshold" label occupies. private readonly float ThresholdWidth; /************************************************************************************************************************/ private static float _StandardThresholdWidth; /// /// The number of horizontal pixels the word "Threshold" occupies when drawn with the /// style. /// protected static float StandardThresholdWidth { get { if (_StandardThresholdWidth == 0) _StandardThresholdWidth = AnimancerGUI.CalculateWidth(EditorStyles.popup, "Threshold"); return _StandardThresholdWidth; } } /************************************************************************************************************************/ /// /// Creates a new using the default . /// public MixerTransitionDrawer() : this(StandardThresholdWidth) { } /// /// Creates a new using a custom width for its threshold labels. /// protected MixerTransitionDrawer(float thresholdWidth) => ThresholdWidth = thresholdWidth; /************************************************************************************************************************/ /// /// The serialized of the /// . /// protected static SerializedProperty CurrentThresholds { get; private set; } /************************************************************************************************************************/ /// protected override void GatherSubProperties(SerializedProperty property) { base.GatherSubProperties(property); CurrentThresholds = property.FindPropertyRelative(MixerTransition2D.ThresholdsField); if (CurrentAnimations == null || CurrentThresholds == null || property.hasMultipleDifferentValues) return; var count = Math.Max(CurrentAnimations.arraySize, CurrentThresholds.arraySize); CurrentAnimations.arraySize = count; CurrentThresholds.arraySize = count; if (CurrentSpeeds != null && CurrentSpeeds.arraySize != 0) CurrentSpeeds.arraySize = count; } /************************************************************************************************************************/ /// public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { var height = base.GetPropertyHeight(property, label); if (property.isExpanded) { if (CurrentThresholds != null) { height -= AnimancerGUI.StandardSpacing + EditorGUI.GetPropertyHeight(CurrentThresholds, label); } } return height; } /************************************************************************************************************************/ /// protected override void DoChildPropertyGUI(ref Rect area, SerializedProperty rootProperty, SerializedProperty property, GUIContent label) { if (property.propertyPath.EndsWith($".{MixerTransition2D.ThresholdsField}")) return; base.DoChildPropertyGUI(ref area, rootProperty, property, label); } /************************************************************************************************************************/ /// Splits the specified `area` into separate sections. protected void SplitListRect(Rect area, bool isHeader, out Rect animation, out Rect threshold, out Rect speed, out Rect sync) { SplitListRect(area, isHeader, out animation, out speed, out sync); if (TwoLineMode && !isHeader) { threshold = AnimancerGUI.StealFromLeft(ref speed, ThresholdWidth, AnimancerGUI.StandardSpacing); } else { threshold = animation; var xMin = threshold.xMin = EditorGUIUtility.labelWidth + AnimancerGUI.IndentSize; animation.xMax = xMin - AnimancerGUI.StandardSpacing; } } /************************************************************************************************************************/ /// protected override void DoChildListHeaderGUI(Rect area) { SplitListRect(area, true, out var animationArea, out var thresholdArea, out var speedArea, out var syncArea); DoAnimationHeaderGUI(animationArea); var attribute = AttributeCache.FindAttribute(CurrentThresholds); var text = attribute != null ? attribute.Label : "Threshold"; using (ObjectPool.Disposable.AcquireContent(out var label, text, "The parameter values at which each child state will be fully active")) DoHeaderDropdownGUI(thresholdArea, CurrentThresholds, label, AddThresholdFunctionsToMenu); DoSpeedHeaderGUI(speedArea); DoSyncHeaderGUI(syncArea); } /************************************************************************************************************************/ /// protected override void DoElementGUI(Rect area, int index, SerializedProperty animation, SerializedProperty speed) { SplitListRect(area, false, out var animationArea, out var thresholdArea, out var speedArea, out var syncArea); DoAnimationField(animationArea, animation); DoThresholdGUI(thresholdArea, index); DoSpeedFieldGUI(speedArea, speed, index); DoSyncToggleGUI(syncArea, index); } /************************************************************************************************************************/ /// Draws the GUI of the threshold at the specified `index`. protected virtual void DoThresholdGUI(Rect area, int index) { var threshold = CurrentThresholds.GetArrayElementAtIndex(index); EditorGUI.PropertyField(area, threshold, GUIContent.none); } /************************************************************************************************************************/ /// protected override void OnAddElement(int index) { base.OnAddElement(index); if (CurrentThresholds.arraySize > 0) CurrentThresholds.InsertArrayElementAtIndex(index); } /************************************************************************************************************************/ /// protected override void OnRemoveElement(ReorderableList list) { base.OnRemoveElement(list); Serialization.RemoveArrayElement(CurrentThresholds, list.index); } /************************************************************************************************************************/ /// protected override void ResizeList(int size) { base.ResizeList(size); CurrentThresholds.arraySize = size; } /************************************************************************************************************************/ /// protected override void OnReorderList(ReorderableList list, int oldIndex, int newIndex) { base.OnReorderList(list, oldIndex, newIndex); CurrentThresholds.MoveArrayElement(oldIndex, newIndex); } /************************************************************************************************************************/ /// Adds functions to the `menu` relating to the thresholds. protected virtual void AddThresholdFunctionsToMenu(GenericMenu menu) { } /************************************************************************************************************************/ } #endif }