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.
 
 
 
 
 
 

381 lines
16 KiB

// 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
{
/// <inheritdoc/>
/// 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<ControllerTransition>
{
/// <inheritdoc/>
[Serializable]
public class UnShared :
AnimancerTransitionAsset.UnShared<ControllerTransitionAsset, ControllerTransition, ControllerState>,
ControllerState.ITransition
{ }
}
/************************************************************************************************************************/
/// <inheritdoc/>
/// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransition_1
[Serializable]
public abstract class ControllerTransition<TState> : AnimancerTransition<TState>, IAnimationClipCollection
where TState : ControllerState
{
/************************************************************************************************************************/
[SerializeField]
private RuntimeAnimatorController _Controller;
/// <summary>[<see cref="SerializeField"/>]
/// The <see cref="ControllerState.Controller"/> that will be used for the created state.
/// </summary>
public ref RuntimeAnimatorController Controller => ref _Controller;
/// <inheritdoc/>
public override Object MainObject => _Controller;
#if UNITY_EDITOR
/// <summary>[Editor-Only] The name of the serialized backing field of <see cref="Controller"/>.</summary>
public const string ControllerFieldName = nameof(_Controller);
#endif
/************************************************************************************************************************/
[SerializeField]
[Tooltip(Strings.Tooltips.NormalizedStartTime)]
[AnimationTime(AnimationTimeAttribute.Units.Normalized)]
[DefaultValue(0, float.NaN)]
private float _NormalizedStartTime;
/// <inheritdoc/>
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;
/// <summary>[<see cref="SerializeField"/>]
/// If false, <see cref="Stop"/> will reset all layers to their default state.
/// <para></para>
/// If you set this value to false after the <see cref="Playable"/> is created, you must assign the
/// <see cref="DefaultStateHashes"/> or call <see cref="GatherDefaultStates"/> yourself.
/// </summary>
public ref bool KeepStateOnStop => ref _KeepStateOnStop;
/************************************************************************************************************************/
/// <inheritdoc/>
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;
}
}
/************************************************************************************************************************/
/// <inheritdoc/>
public override bool IsValid => _Controller != null;
/************************************************************************************************************************/
/// <summary>Returns the <see cref="Controller"/>.</summary>
public static implicit operator RuntimeAnimatorController(ControllerTransition<TState> transition)
=> transition?._Controller;
/************************************************************************************************************************/
/// <inheritdoc/>
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);
}
/************************************************************************************************************************/
/// <summary>Adds all clips in the <see cref="Controller"/> to the collection.</summary>
void IAnimationClipCollection.GatherAnimationClips(ICollection<AnimationClip> clips)
{
if (_Controller != null)
clips.Gather(_Controller.animationClips);
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
/// <inheritdoc/>
/// https://kybernetik.com.au/animancer/api/Animancer/ControllerTransition
[Serializable]
public class ControllerTransition : ControllerTransition<ControllerState>, ControllerState.ITransition
{
/************************************************************************************************************************/
/// <inheritdoc/>
public override ControllerState CreateState() => State = new ControllerState(Controller, KeepStateOnStop);
/************************************************************************************************************************/
/// <summary>Creates a new <see cref="ControllerTransition"/>.</summary>
public ControllerTransition() { }
/// <summary>Creates a new <see cref="ControllerTransition"/> with the specified Animator Controller.</summary>
public ControllerTransition(RuntimeAnimatorController controller) => Controller = controller;
/************************************************************************************************************************/
/// <summary>Creates a new <see cref="ControllerTransition"/> with the specified Animator Controller.</summary>
public static implicit operator ControllerTransition(RuntimeAnimatorController controller)
=> new ControllerTransition(controller);
/************************************************************************************************************************/
#region Drawer
#if UNITY_EDITOR
/************************************************************************************************************************/
/// <inheritdoc/>
[CustomPropertyDrawer(typeof(ControllerTransition<>), true)]
[CustomPropertyDrawer(typeof(ControllerTransition), true)]
public class Drawer : Editor.TransitionDrawer
{
/************************************************************************************************************************/
private readonly string[] Parameters;
private readonly string[] ParameterPrefixes;
/************************************************************************************************************************/
/// <summary>Creates a new <see cref="Drawer"/> without any parameters.</summary>
public Drawer() : this(null) { }
/// <summary>Creates a new <see cref="Drawer"/> and sets the <see cref="Parameters"/>.</summary>
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];
}
}
/************************************************************************************************************************/
/// <inheritdoc/>
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;
}
}
}
}
}
/************************************************************************************************************************/
/// <summary>
/// Draws a dropdown menu to select the name of a parameter in the `controller`.
/// </summary>
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
/************************************************************************************************************************/
}
}