// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 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 [CreateAssetMenu(menuName = Strings.MenuPrefix + "Controller Transition/Base", order = Strings.AssetMenuOrder + 5)] [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(ControllerTransitionAsset))] public class ControllerTransitionAsset : AnimancerTransitionAsset { /// [Serializable] public class UnShared : AnimancerTransitionAsset.UnShared, ControllerState.ITransition { } } /************************************************************************************************************************/ /// /// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransition_1 [Serializable] public abstract class ControllerTransition : AnimancerTransition, IAnimationClipCollection 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(Strings.Tooltips.NormalizedStartTime)] [AnimationTime(AnimationTimeAttribute.Units.Normalized)] [DefaultValue(0, float.NaN)] private float _NormalizedStartTime; /// public override float NormalizedStartTime { get => _NormalizedStartTime; set => _NormalizedStartTime = value; } /************************************************************************************************************************/ [SerializeField, Tooltip("If false, stopping this state will reset all its layers to their default state")] private bool _KeepStateOnStop; /// [] /// If false, will reset all layers to their default state. /// /// If you set this value to false after the is created, you must assign the /// or call yourself. /// public ref bool KeepStateOnStop => ref _KeepStateOnStop; /************************************************************************************************************************/ /// 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 => _Controller != null; /************************************************************************************************************************/ /// Returns the . public static implicit operator RuntimeAnimatorController(ControllerTransition transition) => transition?._Controller; /************************************************************************************************************************/ /// public override void Apply(AnimancerState state) { var controllerState = State; if (controllerState != null) { controllerState.KeepStateOnStop = _KeepStateOnStop; if (!float.IsNaN(_NormalizedStartTime)) { if (!_KeepStateOnStop) { controllerState.Playable.Play(controllerState.DefaultStateHashes[0], 0, _NormalizedStartTime); } else { state.NormalizedTime = _NormalizedStartTime; } } } else { if (!float.IsNaN(_NormalizedStartTime)) state.NormalizedTime = _NormalizedStartTime; } base.Apply(state); } /************************************************************************************************************************/ /// Adds all clips in the to the collection. void IAnimationClipCollection.GatherAnimationClips(ICollection clips) { if (_Controller != null) clips.Gather(_Controller.animationClips); } /************************************************************************************************************************/ } /************************************************************************************************************************/ /// /// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransition [Serializable] public class ControllerTransition : ControllerTransition, ControllerState.ITransition { /************************************************************************************************************************/ /// public override ControllerState CreateState() => State = new ControllerState(Controller, KeepStateOnStop); /************************************************************************************************************************/ /// 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); /************************************************************************************************************************/ #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[] ParameterPrefixes; /************************************************************************************************************************/ /// Creates a new without any parameters. public Drawer() : this(null) { } /// Creates a new and sets the . public Drawer(params string[] parameters) : base(ControllerFieldName) { Parameters = parameters; if (parameters == null) return; ParameterPrefixes = new string[parameters.Length]; for (int i = 0; i < ParameterPrefixes.Length; i++) { ParameterPrefixes[i] = "." + parameters[i]; } } /************************************************************************************************************************/ /// protected override void DoChildPropertyGUI(ref Rect area, SerializedProperty rootProperty, SerializedProperty property, GUIContent label) { var path = property.propertyPath; if (ParameterPrefixes != null) { var controllerProperty = rootProperty.FindPropertyRelative(MainPropertyName); var controller = controllerProperty.objectReferenceValue as AnimatorController; if (controller != null) { for (int i = 0; i < ParameterPrefixes.Length; i++) { if (path.EndsWith(ParameterPrefixes[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)) { var controller = property.objectReferenceValue as AnimatorController; if (controller != null) { for (int i = 0; i < Parameters.Length; i++) { property = rootProperty.FindPropertyRelative(Parameters[i]); var parameterName = property.stringValue; 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 && name == parameters[i].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 /************************************************************************************************************************/ } }