// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using Object = UnityEngine.Object; using Animancer.Units; #if UNITY_EDITOR using UnityEditor; using UnityEditor.Animations; #endif namespace Animancer { /// /// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransitionAsset #if !UNITY_EDITOR [System.Obsolete(Validate.ProOnlyMessage)] #endif [CreateAssetMenu(menuName = Strings.MenuPrefix + "Controller Transition/Base", order = Strings.AssetMenuOrder + 5)] [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(ControllerTransitionAsset))] public class ControllerTransitionAsset : AnimancerTransitionAsset { /// [Serializable] public new class UnShared : UnShared, ControllerState.ITransition { } } /************************************************************************************************************************/ /// /// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransition_1 [Serializable] #if ! UNITY_EDITOR [System.Obsolete(Validate.ProOnlyMessage)] #endif public abstract class ControllerTransition : AnimancerTransition, IAnimationClipCollection, ICopyable> where TState : ControllerState { /************************************************************************************************************************/ [SerializeField] private RuntimeAnimatorController _Controller; /// [] /// The that will be used for the created state. /// public ref RuntimeAnimatorController Controller => ref _Controller; /// public override Object MainObject => _Controller; #if UNITY_EDITOR /// [Editor-Only] The name of the serialized backing field of . public const string ControllerFieldName = nameof(_Controller); #endif /************************************************************************************************************************/ [SerializeField] [Tooltip("Determines what each layer does when " + nameof(ControllerState) + "." + nameof(ControllerState.Stop) + " is called." + "\n• If empty, all layers will reset to their default state." + "\n• If this array is smaller than the layer count, any additional layers will use the last value in this array.")] private ControllerState.ActionOnStop[] _ActionsOnStop; /// [] /// Determines what each layer does when is called. /// /// /// If empty, all layers will reset to their . /// /// If this array is smaller than the /// , any additional /// layers will use the last value in this array. /// public ref ControllerState.ActionOnStop[] ActionsOnStop => ref _ActionsOnStop; /************************************************************************************************************************/ /// public override float MaximumDuration { get { if (_Controller == null) return 0; var duration = 0f; var clips = _Controller.animationClips; for (int i = 0; i < clips.Length; i++) { var length = clips[i].length; if (duration < length) duration = length; } return duration; } } /************************************************************************************************************************/ /// public override bool IsValid #if UNITY_EDITOR => _Controller != null; #else => false; #endif /************************************************************************************************************************/ /// Returns the . public static implicit operator RuntimeAnimatorController(ControllerTransition transition) => transition?._Controller; /************************************************************************************************************************/ /// public override void Apply(AnimancerState state) { if (state is ControllerState controllerState) controllerState.ActionsOnStop = _ActionsOnStop; base.Apply(state); } /************************************************************************************************************************/ /// Adds all clips in the to the collection. void IAnimationClipCollection.GatherAnimationClips(ICollection clips) { if (_Controller != null) clips.Gather(_Controller.animationClips); } /************************************************************************************************************************/ /// public virtual void CopyFrom(ControllerTransition copyFrom) { CopyFrom((AnimancerTransition)copyFrom); if (copyFrom == null) { _Controller = default; _ActionsOnStop = Array.Empty(); return; } _Controller = copyFrom._Controller; _ActionsOnStop = copyFrom._ActionsOnStop; } /************************************************************************************************************************/ } /************************************************************************************************************************/ /// /// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransition [Serializable] #if ! UNITY_EDITOR [System.Obsolete(Validate.ProOnlyMessage)] #endif public class ControllerTransition : ControllerTransition, ControllerState.ITransition, ICopyable { /************************************************************************************************************************/ /// public override ControllerState CreateState() { #if UNITY_ASSERTIONS if (Controller == null) throw new ArgumentException( $"Unable to create {nameof(ControllerState)} because the" + $" {nameof(ControllerTransition)}.{nameof(Controller)} is null."); #endif return State = new ControllerState(Controller, ActionsOnStop); } /************************************************************************************************************************/ /// Creates a new . public ControllerTransition() { } /// Creates a new with the specified Animator Controller. public ControllerTransition(RuntimeAnimatorController controller) => Controller = controller; /************************************************************************************************************************/ /// Creates a new with the specified Animator Controller. public static implicit operator ControllerTransition(RuntimeAnimatorController controller) => new ControllerTransition(controller); /************************************************************************************************************************/ /// public virtual void CopyFrom(ControllerTransition copyFrom) { CopyFrom((ControllerTransition)copyFrom); } /************************************************************************************************************************/ #region Drawer #if UNITY_EDITOR /************************************************************************************************************************/ /// [CustomPropertyDrawer(typeof(ControllerTransition<>), true)] [CustomPropertyDrawer(typeof(ControllerTransition), true)] public class Drawer : Editor.TransitionDrawer { /************************************************************************************************************************/ private readonly string[] Parameters; private readonly string[] ParameterPropertySuffixes; /************************************************************************************************************************/ /// Creates a new without any parameters. public Drawer() : base(ControllerFieldName) { } /// Creates a new and sets the . public Drawer(params string[] parameters) : base(ControllerFieldName) { Parameters = parameters; if (parameters == null) return; ParameterPropertySuffixes = new string[parameters.Length]; for (int i = 0; i < ParameterPropertySuffixes.Length; i++) { ParameterPropertySuffixes[i] = "." + parameters[i]; } } /************************************************************************************************************************/ /// protected override void DoChildPropertyGUI( ref Rect area, SerializedProperty rootProperty, SerializedProperty property, GUIContent label) { var path = property.propertyPath; if (ParameterPropertySuffixes != null) { var controllerProperty = rootProperty.FindPropertyRelative(MainPropertyName); if (controllerProperty.objectReferenceValue is AnimatorController controller) { for (int i = 0; i < ParameterPropertySuffixes.Length; i++) { if (path.EndsWith(ParameterPropertySuffixes[i])) { area.height = Editor.AnimancerGUI.LineHeight; DoParameterGUI(area, controller, property); return; } } } } EditorGUI.BeginChangeCheck(); base.DoChildPropertyGUI(ref area, rootProperty, property, label); // When the controller changes, validate all parameters. if (EditorGUI.EndChangeCheck() && Parameters != null && path.EndsWith(MainPropertyPathSuffix)) { if (property.objectReferenceValue is AnimatorController controller) { for (int i = 0; i < Parameters.Length; i++) { property = rootProperty.FindPropertyRelative(Parameters[i]); var parameterName = property.stringValue; // If a parameter is missing, assign it to the first float parameter. if (!HasFloatParameter(controller, parameterName)) { parameterName = GetFirstFloatParameterName(controller); if (!string.IsNullOrEmpty(parameterName)) property.stringValue = parameterName; } } } } } /************************************************************************************************************************/ /// Draws a dropdown menu to select the name of a parameter in the `controller`. protected void DoParameterGUI(Rect area, AnimatorController controller, SerializedProperty property) { var parameterName = property.stringValue; var parameters = controller.parameters; using (ObjectPool.Disposable.AcquireContent(out var label, property)) { label = EditorGUI.BeginProperty(area, label, property); var xMax = area.xMax; area.width = EditorGUIUtility.labelWidth; EditorGUI.PrefixLabel(area, label); area.x += area.width; area.xMax = xMax; } var color = GUI.color; if (!HasFloatParameter(controller, parameterName)) GUI.color = Editor.AnimancerGUI.ErrorFieldColor; using (ObjectPool.Disposable.AcquireContent(out var label, parameterName)) { if (EditorGUI.DropdownButton(area, label, FocusType.Passive)) { property = property.Copy(); var menu = new GenericMenu(); for (int i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; Editor.Serialization.AddPropertyModifierFunction(menu, property, parameter.name, parameter.type == AnimatorControllerParameterType.Float, (targetProperty) => { targetProperty.stringValue = parameter.name; }); } if (menu.GetItemCount() == 0) menu.AddDisabledItem(new GUIContent("No Parameters")); menu.ShowAsContext(); } } GUI.color = color; EditorGUI.EndProperty(); } /************************************************************************************************************************/ private static bool HasFloatParameter(AnimatorController controller, string name) { if (string.IsNullOrEmpty(name)) return false; var parameters = controller.parameters; for (int i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; if (parameter.type == AnimatorControllerParameterType.Float && parameter.name == name) { return true; } } return false; } /************************************************************************************************************************/ private static string GetFirstFloatParameterName(AnimatorController controller) { var parameters = controller.parameters; for (int i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; if (parameter.type == AnimatorControllerParameterType.Float) { return parameter.name; } } return ""; } /************************************************************************************************************************/ } /************************************************************************************************************************/ #endif #endregion /************************************************************************************************************************/ } }