You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
869 lines
39 KiB
C#
869 lines
39 KiB
C#
3 months ago
|
// Animancer // Copyright 2020 Kybernetik //
|
||
|
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Animations;
|
||
|
using UnityEngine.Playables;
|
||
|
using Object = UnityEngine.Object;
|
||
|
|
||
|
#if UNITY_EDITOR
|
||
|
using UnityEditor;
|
||
|
using UnityEditor.Animations;
|
||
|
#endif
|
||
|
|
||
|
namespace Animancer
|
||
|
{
|
||
|
/// <summary>[Pro-Only]
|
||
|
/// An <see cref="AnimancerState"/> which plays a <see cref="RuntimeAnimatorController"/>.
|
||
|
/// You can control this state very similarly to an <see cref="Animator"/> via its <see cref="Playable"/> field.
|
||
|
/// </summary>
|
||
|
public class ControllerState : AnimancerState
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
#region Fields and Auto-Properties
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private RuntimeAnimatorController _Controller;
|
||
|
|
||
|
/// <summary>The <see cref="RuntimeAnimatorController"/> which this state plays.</summary>
|
||
|
public RuntimeAnimatorController Controller
|
||
|
{
|
||
|
get { return _Controller; }
|
||
|
set
|
||
|
{
|
||
|
if (ReferenceEquals(_Controller, value))
|
||
|
return;
|
||
|
|
||
|
if (ReferenceEquals(_Key, value))
|
||
|
Key = value;
|
||
|
|
||
|
if (_Playable.IsValid())
|
||
|
Root._Graph.DestroyPlayable(_Playable);
|
||
|
|
||
|
CreatePlayable(value);
|
||
|
SetWeightDirty();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>The <see cref="RuntimeAnimatorController"/> which this state plays.</summary>
|
||
|
public override Object MainObject
|
||
|
{
|
||
|
get { return Controller; }
|
||
|
set { Controller = (RuntimeAnimatorController)value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>The internal system which plays the <see cref="RuntimeAnimatorController"/>.</summary>
|
||
|
public AnimatorControllerPlayable Playable { get; private set; }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// If false, <see cref="Stop"/> will reset all layers to their default state. Default False.
|
||
|
/// <para></para>
|
||
|
/// If you set this value to false after the constructor, you must assign the <see cref="DefaultStateHashes"/>
|
||
|
/// or call <see cref="GatherDefaultStates"/> yourself.
|
||
|
/// </summary>
|
||
|
public bool KeepStateOnStop { get; set; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// The <see cref="AnimatorStateInfo.shortNameHash"/> of the default state on each layer, used to reset to
|
||
|
/// those states when <see cref="Stop"/> is called.
|
||
|
/// </summary>
|
||
|
public int[] DefaultStateHashes { get; set; }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Public API
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="ControllerState"/> to play the `animatorController` without connecting
|
||
|
/// it to the <see cref="PlayableGraph"/>.
|
||
|
/// </summary>
|
||
|
protected ControllerState(AnimancerPlayable root, RuntimeAnimatorController animatorController,
|
||
|
bool keepStateOnStop = false)
|
||
|
: base(root)
|
||
|
{
|
||
|
KeepStateOnStop = keepStateOnStop;
|
||
|
CreatePlayable(animatorController);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="ControllerState"/> to play the `animatorController` and connects it to
|
||
|
/// the `layer`.
|
||
|
/// </summary>
|
||
|
public ControllerState(AnimancerLayer layer, RuntimeAnimatorController animatorController,
|
||
|
bool keepStateOnStop = false)
|
||
|
: this(layer.Root, animatorController, keepStateOnStop)
|
||
|
{
|
||
|
layer.AddChild(this);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="ControllerState"/> to play the `animatorController` and
|
||
|
/// connects it to the `parent` at the specified `index`.
|
||
|
/// </summary>
|
||
|
public ControllerState(AnimancerNode parent, int index, RuntimeAnimatorController animatorController,
|
||
|
bool keepStateOnStop = false)
|
||
|
: this(parent.Root, animatorController, keepStateOnStop)
|
||
|
{
|
||
|
SetParent(parent, index);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private void CreatePlayable(RuntimeAnimatorController animatorController)
|
||
|
{
|
||
|
if (animatorController == null)
|
||
|
throw new ArgumentNullException("animatorController");
|
||
|
|
||
|
_Controller = animatorController;
|
||
|
Playable = AnimatorControllerPlayable.Create(Root._Graph, animatorController);
|
||
|
_Playable = Playable;
|
||
|
|
||
|
if (!KeepStateOnStop)
|
||
|
GatherDefaultStates();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// The current state on layer 0, or the next state if it is currently in a transition.
|
||
|
/// </summary>
|
||
|
public AnimatorStateInfo StateInfo
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return Playable.IsInTransition(0) ?
|
||
|
Playable.GetNextAnimatorStateInfo(0) :
|
||
|
Playable.GetCurrentAnimatorStateInfo(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// The <see cref="AnimatorStateInfo.normalizedTime"/> * <see cref="AnimatorStateInfo.length"/> of the
|
||
|
/// <see cref="StateInfo"/>
|
||
|
/// </summary>
|
||
|
protected override float NewTime
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var info = StateInfo;
|
||
|
return info.normalizedTime * info.length;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
Playable.PlayInFixedTime(0, 0, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The current <see cref="AnimatorStateInfo.length"/> (on layer 0).</summary>
|
||
|
public override float Length { get { return StateInfo.length; } }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Indicates whether the current state on layer 0 will loop back to the start when it reaches the end.
|
||
|
/// </summary>
|
||
|
public override bool IsLooping { get { return StateInfo.loop; } }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gathers the <see cref="DefaultStateHashes"/> from the current states.
|
||
|
/// </summary>
|
||
|
public void GatherDefaultStates()
|
||
|
{
|
||
|
var layerCount = Playable.GetLayerCount();
|
||
|
if (DefaultStateHashes == null || DefaultStateHashes.Length != layerCount)
|
||
|
DefaultStateHashes = new int[layerCount];
|
||
|
|
||
|
while (--layerCount >= 0)
|
||
|
DefaultStateHashes[layerCount] = Playable.GetCurrentAnimatorStateInfo(layerCount).shortNameHash;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Calls the base <see cref="AnimancerState.Stop"/> and if <see cref="KeepStateOnStop"/> is false it also
|
||
|
/// calls <see cref="ResetToDefaultStates"/>.
|
||
|
/// </summary>
|
||
|
public override void Stop()
|
||
|
{
|
||
|
base.Stop();
|
||
|
if (!KeepStateOnStop)
|
||
|
ResetToDefaultStates();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Resets all layers to their default state.
|
||
|
/// </summary>
|
||
|
/// <exception cref="NullReferenceException">Thrown if <see cref="DefaultStateHashes"/> is null.</exception>
|
||
|
/// <exception cref="IndexOutOfRangeException">
|
||
|
/// Thrown if the size of <see cref="DefaultStateHashes"/> is larger than the number of layers in the
|
||
|
/// <see cref="Controller"/>.
|
||
|
/// </exception>
|
||
|
public void ResetToDefaultStates()
|
||
|
{
|
||
|
for (int i = 0; i < DefaultStateHashes.Length; i++)
|
||
|
Playable.Play(DefaultStateHashes[i], i, 0);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a string describing the type of this state and the name of the <see cref="Controller"/>.
|
||
|
/// </summary>
|
||
|
public override string ToString()
|
||
|
{
|
||
|
if (_Controller != null)
|
||
|
return string.Concat(base.ToString(), " (", _Controller.name, ")");
|
||
|
else
|
||
|
return base.ToString() + " (null)";
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[<see cref="IAnimationClipCollection"/>]
|
||
|
/// Gathers all the animations in this state.
|
||
|
/// </summary>
|
||
|
public override void GatherAnimationClips(ICollection<AnimationClip> clips)
|
||
|
{
|
||
|
if (_Controller != null)
|
||
|
clips.Gather(_Controller.animationClips);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Destroys the <see cref="Playable"/>.</summary>
|
||
|
public override void Destroy()
|
||
|
{
|
||
|
_Controller = null;
|
||
|
base.Destroy();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Parameters
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// A wrapper for the name and hash of an <see cref="AnimatorControllerParameter"/> to allow easy access.
|
||
|
/// </summary>
|
||
|
public struct Parameter
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private string _Name;
|
||
|
private int _Hash;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// The name of the wrapped parameter. This will be null if the <see cref="Hash"/> was assigned directly.
|
||
|
/// </summary>
|
||
|
public string Name
|
||
|
{
|
||
|
get { return _Name; }
|
||
|
set
|
||
|
{
|
||
|
_Name = value;
|
||
|
_Hash = Animator.StringToHash(value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// The name hash of the wrapped parameter.
|
||
|
/// </summary>
|
||
|
public int Hash
|
||
|
{
|
||
|
get { return _Hash; }
|
||
|
set
|
||
|
{
|
||
|
_Name = null;
|
||
|
_Hash = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="Parameter"/> with the specified <see cref="Name"/> and uses
|
||
|
/// <see cref="Animator.StringToHash"/> to calculate the <see cref="Hash"/>.
|
||
|
/// </summary>
|
||
|
public Parameter(string name)
|
||
|
{
|
||
|
_Name = name;
|
||
|
_Hash = Animator.StringToHash(name);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="Parameter"/> with the specified <see cref="Hash"/> and leaves the
|
||
|
/// <see cref="Name"/> null.
|
||
|
/// </summary>
|
||
|
public Parameter(int hash)
|
||
|
{
|
||
|
_Name = null;
|
||
|
_Hash = hash;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="Parameter"/> with the specified <see cref="Name"/> and uses
|
||
|
/// <see cref="Animator.StringToHash"/> to calculate the <see cref="Hash"/>.
|
||
|
/// </summary>
|
||
|
public static implicit operator Parameter(string name)
|
||
|
{
|
||
|
return new Parameter(name);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="Parameter"/> with the specified <see cref="Hash"/> and leaves the
|
||
|
/// <see cref="Name"/> null.
|
||
|
/// </summary>
|
||
|
public static implicit operator Parameter(int hash)
|
||
|
{
|
||
|
return new Parameter(hash);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns the <see cref="Hash"/>.</summary>
|
||
|
public static implicit operator int(Parameter parameter)
|
||
|
{
|
||
|
return parameter._Hash;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
#if UNITY_EDITOR
|
||
|
private static Dictionary<RuntimeAnimatorController, Dictionary<int, AnimatorControllerParameterType>>
|
||
|
_ControllerToParameterHashAndType;
|
||
|
#endif
|
||
|
|
||
|
/// <summary>[Editor-Conditional]
|
||
|
/// Throws if the `controller` doesn't have a parameter with the specified <see cref="Hash"/>
|
||
|
/// and `type`.
|
||
|
/// </summary>
|
||
|
/// <exception cref="ArgumentException"/>
|
||
|
[System.Diagnostics.Conditional(Strings.EditorOnly)]
|
||
|
public void ValidateHasParameter(RuntimeAnimatorController controller, AnimatorControllerParameterType type)
|
||
|
{
|
||
|
#if UNITY_EDITOR
|
||
|
if (_ControllerToParameterHashAndType == null)
|
||
|
_ControllerToParameterHashAndType = new Dictionary<RuntimeAnimatorController, Dictionary<int, AnimatorControllerParameterType>>();
|
||
|
|
||
|
// Get the parameter details.
|
||
|
Dictionary<int, AnimatorControllerParameterType> parameterDetails;
|
||
|
if (!_ControllerToParameterHashAndType.TryGetValue(controller, out parameterDetails))
|
||
|
{
|
||
|
parameterDetails = new Dictionary<int, AnimatorControllerParameterType>();
|
||
|
|
||
|
var animatorController = controller as AnimatorController;
|
||
|
var parameters = animatorController.parameters;
|
||
|
var count = parameters.Length;
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
var parameter = parameters[i];
|
||
|
parameterDetails.Add(parameter.nameHash, parameter.type);
|
||
|
}
|
||
|
|
||
|
_ControllerToParameterHashAndType.Add(controller, parameterDetails);
|
||
|
}
|
||
|
|
||
|
// Check that there is a parameter with the correct hash and type.
|
||
|
|
||
|
AnimatorControllerParameterType parameterType;
|
||
|
if (!parameterDetails.TryGetValue(_Hash, out parameterType))
|
||
|
{
|
||
|
throw new ArgumentException(controller + " has no " + type + " parameter matching " + this);
|
||
|
}
|
||
|
|
||
|
if (type != parameterType)
|
||
|
{
|
||
|
throw new ArgumentException(controller + " has a parameter matching " + this + ", but it is not a " + type);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns a string containing the <see cref="Name"/> and <see cref="Hash"/>.</summary>
|
||
|
public override string ToString()
|
||
|
{
|
||
|
return string.Concat(
|
||
|
"ControllerState.Parameter(Name:'",
|
||
|
_Name,
|
||
|
"', Hash:",
|
||
|
_Hash.ToString(),
|
||
|
")");
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Inspector
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The number of parameters being wrapped by this state.</summary>
|
||
|
public virtual int ParameterCount { get { return 0; } }
|
||
|
|
||
|
/// <summary>Returns the hash of a parameter being wrapped by this state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if this state doesn't wrap any parameters.</exception>
|
||
|
public virtual int GetParameterHash(int index) { throw new NotSupportedException(); }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#if UNITY_EDITOR
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Editor-Only] Returns a <see cref="Drawer"/> for this state.</summary>
|
||
|
protected internal override Editor.IAnimancerNodeDrawer GetDrawer()
|
||
|
{
|
||
|
return new Drawer(this);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[Editor-Only] Draws the Inspector GUI for an <see cref="ControllerState"/>.</summary>
|
||
|
public sealed class Drawer : Editor.ParametizedAnimancerStateDrawer<ControllerState>
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructs a new <see cref="Drawer"/> to manage the Inspector GUI for the `state`.
|
||
|
/// </summary>
|
||
|
public Drawer(ControllerState state) : base(state) { }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary> Draws the details of the target state in the GUI.</summary>
|
||
|
protected override void DoDetailsGUI(IAnimancerComponent owner)
|
||
|
{
|
||
|
GatherParameters();
|
||
|
base.DoDetailsGUI(owner);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private readonly List<AnimatorControllerParameter>
|
||
|
Parameters = new List<AnimatorControllerParameter>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Fills the <see cref="Parameters"/> list with the current parameter details.
|
||
|
/// </summary>
|
||
|
private void GatherParameters()
|
||
|
{
|
||
|
Parameters.Clear();
|
||
|
|
||
|
var count = Target.ParameterCount;
|
||
|
if (count == 0)
|
||
|
return;
|
||
|
|
||
|
for (int i = 0; i < count; i++)
|
||
|
{
|
||
|
var hash = Target.GetParameterHash(i);
|
||
|
Parameters.Add(GetParameter(hash));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private AnimatorControllerParameter GetParameter(int hash)
|
||
|
{
|
||
|
var parameterCount = Target.Playable.GetParameterCount();
|
||
|
for (int i = 0; i < parameterCount; i++)
|
||
|
{
|
||
|
var parameter = Target.Playable.GetParameter(i);
|
||
|
if (parameter.nameHash == hash)
|
||
|
return parameter;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>The number of parameters being managed by the target state.</summary>
|
||
|
public override int ParameterCount { get { return Parameters.Count; } }
|
||
|
|
||
|
/// <summary>Returns the name of a parameter being managed by the target state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if the target state doesn't manage any parameters.</exception>
|
||
|
public override string GetParameterName(int index) { return Parameters[index].name; }
|
||
|
|
||
|
/// <summary>Returns the type of a parameter being managed by the target state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if the target state doesn't manage any parameters.</exception>
|
||
|
public override AnimatorControllerParameterType GetParameterType(int index) { return Parameters[index].type; }
|
||
|
|
||
|
/// <summary>Returns the value of a parameter being managed by the target state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if the target state doesn't manage any parameters.</exception>
|
||
|
public override object GetParameterValue(int index)
|
||
|
{
|
||
|
return Editor.AnimancerEditorUtilities.GetParameterValue(Target.Playable, Parameters[index]);
|
||
|
}
|
||
|
|
||
|
/// <summary>Sets the value of a parameter being managed by the target state.</summary>
|
||
|
/// <exception cref="NotSupportedException">Thrown if the target state doesn't manage any parameters.</exception>
|
||
|
public override void SetParameterValue(int index, object value)
|
||
|
{
|
||
|
Editor.AnimancerEditorUtilities.SetParameterValue(Target.Playable, Parameters[index], value);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endif
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Transition
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Base class for serializable <see cref="ITransition"/>s which can create a particular type of
|
||
|
/// <see cref="ControllerState"/> when passed into <see cref="AnimancerPlayable.Play(ITransition)"/>.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Unfortunately the tool used to generate this documentation does not currently support nested types with
|
||
|
/// identical names, so only one <c>Transition</c> class will actually have a documentation page.
|
||
|
/// <para></para>
|
||
|
/// Even though it has the <see cref="SerializableAttribute"/>, this class won't actually get serialized
|
||
|
/// by Unity because it's generic and abstract. Each child class still needs to include the attribute.
|
||
|
/// </remarks>
|
||
|
[Serializable]
|
||
|
public abstract new class Transition<TState> : AnimancerState.Transition<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 RuntimeAnimatorController Controller
|
||
|
{
|
||
|
get { return _Controller; }
|
||
|
set { _Controller = value; }
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
[SerializeField, Tooltip("If false, stopping this state will reset all its layers to their default state. Default False.")]
|
||
|
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 constructor, you must assign the <see cref="DefaultStateHashes"/>
|
||
|
/// or call <see cref="GatherDefaultStates"/> yourself.
|
||
|
/// </summary>
|
||
|
public bool KeepStateOnStop
|
||
|
{
|
||
|
get { return _KeepStateOnStop; }
|
||
|
set { _KeepStateOnStop = value; }
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>[<see cref="ITransitionDetailed"/>]
|
||
|
/// The maximum amount of time the animation is expected to take (in seconds).
|
||
|
/// </summary>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Returns the <see cref="Controller"/>.</summary>
|
||
|
public static implicit operator RuntimeAnimatorController(Transition<TState> transition)
|
||
|
{
|
||
|
return transition != null ? transition.Controller : null;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <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);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// A serializable <see cref="ITransition"/> which can create a <see cref="ControllerState"/> when
|
||
|
/// passed into <see cref="AnimancerPlayable.Play(ITransition)"/>.
|
||
|
/// <para></para>
|
||
|
/// This class can be implicitly cast to and from <see cref="RuntimeAnimatorController"/>.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
public class Transition : Transition<ControllerState>
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates and returns a new <see cref="ControllerState"/> connected to the `layer`.
|
||
|
/// <para></para>
|
||
|
/// This method also assigns it as the <see cref="AnimancerState.Transition{TState}.State"/>.
|
||
|
/// </summary>
|
||
|
public override ControllerState CreateState(AnimancerLayer layer)
|
||
|
{
|
||
|
return new ControllerState(layer, Controller, KeepStateOnStop);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Constructs a new <see cref="Transition"/>.</summary>
|
||
|
public Transition() { }
|
||
|
|
||
|
/// <summary>Constructs a new <see cref="Transition"/> with the specified Animator Controller.</summary>
|
||
|
public Transition(RuntimeAnimatorController controller)
|
||
|
{
|
||
|
Controller = controller;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Constructs a new <see cref="Transition"/> with the specified Animator Controller.</summary>
|
||
|
public static implicit operator Transition(RuntimeAnimatorController controller)
|
||
|
{
|
||
|
return new Transition(controller);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#region Drawer
|
||
|
#if UNITY_EDITOR
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// [Editor-Only] Draws the Inspector GUI for a <see cref="Transition{TState}"/> or
|
||
|
/// <see cref="Transition"/>.
|
||
|
/// </summary>
|
||
|
[CustomPropertyDrawer(typeof(Transition<>), true)]
|
||
|
[CustomPropertyDrawer(typeof(Transition), true)]
|
||
|
public class Drawer : Editor.TransitionDrawer
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
private readonly string[] Parameters;
|
||
|
private readonly string[] ParameterPrefixes;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>Constructs a new <see cref="Drawer"/> without any parameters.</summary>
|
||
|
public Drawer() : this(null) { }
|
||
|
|
||
|
/// <summary>Constructs a new <see cref="Drawer"/> and sets the <see cref="Parameters"/>.</summary>
|
||
|
public Drawer(params string[] parameters) : base("_Controller")
|
||
|
{
|
||
|
Parameters = parameters;
|
||
|
if (parameters == null)
|
||
|
return;
|
||
|
|
||
|
ParameterPrefixes = new string[parameters.Length];
|
||
|
|
||
|
for (int i = 0; i < ParameterPrefixes.Length; i++)
|
||
|
{
|
||
|
ParameterPrefixes[i] = "." + parameters[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Draws the `property` GUI in relation to the `rootProperty` which was passed into
|
||
|
/// <see cref="Editor.TransitionDrawer.OnGUI"/>.
|
||
|
/// </summary>
|
||
|
protected override void DoPropertyGUI(ref Rect area, SerializedProperty rootProperty, SerializedProperty property, GUIContent label)
|
||
|
{
|
||
|
if (ParameterPrefixes != null)
|
||
|
{
|
||
|
var controllerProperty = rootProperty.FindPropertyRelative(MainPropertyName);
|
||
|
var controller = controllerProperty.objectReferenceValue as AnimatorController;
|
||
|
if (controller != null)
|
||
|
{
|
||
|
var path = property.propertyPath;
|
||
|
|
||
|
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.DoPropertyGUI(ref area, rootProperty, property, label);
|
||
|
|
||
|
// When the controller changes, validate all parameters.
|
||
|
if (EditorGUI.EndChangeCheck() &&
|
||
|
Parameters != null &&
|
||
|
property.propertyPath.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;
|
||
|
|
||
|
var label = Editor.AnimancerGUI.TempContent(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;
|
||
|
|
||
|
var content = Editor.AnimancerGUI.TempContent(parameterName);
|
||
|
if (EditorGUI.DropdownButton(area, content, FocusType.Passive))
|
||
|
{
|
||
|
property = property.Copy();
|
||
|
|
||
|
var menu = new GenericMenu();
|
||
|
|
||
|
for (int i = 0; i < parameters.Length; i++)
|
||
|
{
|
||
|
var parameter = parameters[i];
|
||
|
Editor.AnimancerEditorUtilities.AddMenuItem(menu, parameter.name,
|
||
|
parameter.type == AnimatorControllerParameterType.Float, () =>
|
||
|
{
|
||
|
Editor.Serialization.ForEachTarget(property, (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
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
}
|
||
|
|