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.
 
 
 
 
 
 

789 lines
40 KiB

// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 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"/>.</summary>
/// <remarks>
/// You can control this state very similarly to an <see cref="Animator"/> via its <see cref="Playable"/> property.
/// <para></para>
/// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/animator-controllers">Animator Controllers</see>
/// </remarks>
/// https://kybernetik.com.au/animancer/api/Animancer/ControllerState
///
public class ControllerState : AnimancerState
{
/************************************************************************************************************************/
/// <summary>An <see cref="ITransition{TState}"/> that creates a <see cref="ControllerState"/>.</summary>
public interface ITransition : ITransition<ControllerState> { }
/************************************************************************************************************************/
#region Fields and Properties
/************************************************************************************************************************/
private RuntimeAnimatorController _Controller;
/// <summary>The <see cref="RuntimeAnimatorController"/> which this state plays.</summary>
public RuntimeAnimatorController Controller
{
get => _Controller;
set => ChangeMainObject(ref _Controller, value);
}
/// <summary>The <see cref="RuntimeAnimatorController"/> which this state plays.</summary>
public override Object MainObject
{
get => Controller;
set => Controller = (RuntimeAnimatorController)value;
}
/// <summary>The internal system which plays the <see cref="RuntimeAnimatorController"/>.</summary>
public AnimatorControllerPlayable Playable
{
get
{
Validate.AssertPlayable(this);
return _Playable;
}
}
private new AnimatorControllerPlayable _Playable;
/************************************************************************************************************************/
private bool _KeepStateOnStop;
/// <summary>
/// If false, <see cref="Stop"/> will reset all layers to their default state. Default False.
/// <para></para>
/// The <see cref="DefaultStateHashes"/> will only be gathered the first time this property is set to false or
/// <see cref="GatherDefaultStates"/> is called manually.
/// </summary>
public bool KeepStateOnStop
{
get => _KeepStateOnStop;
set
{
_KeepStateOnStop = value;
if (!value && DefaultStateHashes == null && _Playable.IsValid())
GatherDefaultStates();
}
}
/// <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 if <see cref="KeepStateOnStop"/> is true.
/// </summary>
public int[] DefaultStateHashes { get; set; }
/************************************************************************************************************************/
#if UNITY_ASSERTIONS
/// <summary>[Assert-Only] Animancer Events doesn't work properly on <see cref="ControllerState"/>s.</summary>
protected override string UnsupportedEventsMessage =>
"Animancer Events on " + nameof(ControllerState) + "s will probably not work as expected." +
" The events will be associated with the entire Animator Controller and be triggered by any of the" +
" states inside it. If you want to use events in an Animator Controller you will likely need to use" +
" Unity's regular Animation Event system.";
/// <summary>[Assert-Only]
/// <see cref="PlayableExtensions.SetSpeed"/> does nothing on <see cref="ControllerState"/>s.
/// </summary>
protected override string UnsupportedSpeedMessage =>
nameof(PlayableExtensions) + "." + nameof(PlayableExtensions.SetSpeed) + " does nothing on " + nameof(ControllerState) +
"s so there is no way to directly control their speed." +
" The Animator Controller Speed page explains a possible workaround for this issue:" +
" https://kybernetik.com.au/animancer/docs/bugs/animator-controller-speed";
#endif
/************************************************************************************************************************/
/// <summary>IK cannot be dynamically enabled on a <see cref="ControllerState"/>.</summary>
public override void CopyIKFlags(AnimancerNode node) { }
/************************************************************************************************************************/
/// <summary>IK cannot be dynamically enabled on a <see cref="ControllerState"/>.</summary>
public override bool ApplyAnimatorIK
{
get => false;
set
{
#if UNITY_ASSERTIONS
if (value)
OptionalWarning.UnsupportedIK.Log($"IK cannot be dynamically enabled on a {nameof(ControllerState)}." +
" You must instead enable it on the desired layer inside the Animator Controller.", _Controller);
#endif
}
}
/************************************************************************************************************************/
/// <summary>IK cannot be dynamically enabled on a <see cref="ControllerState"/>.</summary>
public override bool ApplyFootIK
{
get => false;
set
{
#if UNITY_ASSERTIONS
if (value)
OptionalWarning.UnsupportedIK.Log($"IK cannot be dynamically enabled on a {nameof(ControllerState)}." +
" You must instead enable it on the desired state inside the Animator Controller.", _Controller);
#endif
}
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Public API
/************************************************************************************************************************/
/// <summary>Creates a new <see cref="ControllerState"/> to play the `controller`.</summary>
public ControllerState(RuntimeAnimatorController controller, bool keepStateOnStop = false)
{
if (controller == null)
throw new ArgumentNullException(nameof(controller));
_Controller = controller;
_KeepStateOnStop = keepStateOnStop;
}
/************************************************************************************************************************/
/// <summary>Creates and assigns the <see cref="AnimatorControllerPlayable"/> managed by this state.</summary>
protected override void CreatePlayable(out Playable playable)
{
playable = _Playable = AnimatorControllerPlayable.Create(Root._Graph, _Controller);
if (!_KeepStateOnStop)
GatherDefaultStates();
}
/************************************************************************************************************************/
/// <summary>
/// Stores the values of all parameters, calls <see cref="AnimancerNode.DestroyPlayable"/>, then restores the
/// parameter values.
/// </summary>
public override void RecreatePlayable()
{
if (!_Playable.IsValid())
{
CreatePlayable();
return;
}
var parameterCount = _Playable.GetParameterCount();
var values = new object[parameterCount];
for (int i = 0; i < parameterCount; i++)
{
values[i] = AnimancerUtilities.GetParameterValue(_Playable, _Playable.GetParameter(i));
}
base.RecreatePlayable();
for (int i = 0; i < parameterCount; i++)
{
AnimancerUtilities.SetParameterValue(_Playable, _Playable.GetParameter(i), values[i]);
}
}
/************************************************************************************************************************/
/// <summary>
/// The current state on layer 0, or the next state if it is currently in a transition.
/// </summary>
public AnimatorStateInfo StateInfo
{
get
{
Validate.AssertPlayable(this);
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 RawTime
{
get
{
var info = StateInfo;
return info.normalizedTime * info.length;
}
set
{
Validate.AssertPlayable(this);
_Playable.PlayInFixedTime(0, 0, value);
}
}
/************************************************************************************************************************/
/// <summary>The current <see cref="AnimatorStateInfo.length"/> (on layer 0).</summary>
public override float Length => 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 => StateInfo.loop;
/************************************************************************************************************************/
/// <summary>Gathers the <see cref="DefaultStateHashes"/> from the current states.</summary>
public void GatherDefaultStates()
{
Validate.AssertPlayable(this);
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()
{
if (_KeepStateOnStop)
{
base.Stop();
}
else
{
ResetToDefaultStates();
// Don't call base.Stop(); because it sets Time = 0; which uses PlayInFixedTime and interferes with
// resetting to the default states.
Weight = 0;
IsPlaying = false;
Events = null;
}
}
/// <summary>
/// Resets all layers to their default state.
/// </summary>
/// <exception cref="NullReferenceException"><see cref="DefaultStateHashes"/> is null.</exception>
/// <exception cref="IndexOutOfRangeException">
/// The size of <see cref="DefaultStateHashes"/> is larger than the number of layers in the
/// <see cref="Controller"/>.
/// </exception>
public void ResetToDefaultStates()
{
Validate.AssertPlayable(this);
for (int i = DefaultStateHashes.Length - 1; i >= 0; i--)
_Playable.Play(DefaultStateHashes[i], i, 0);
// Allowing the RawTime to be applied prevents the default state from being played because
// Animator Controllers don't properly respond to multiple Play calls in the same frame.
CancelSetTime();
}
/************************************************************************************************************************/
/// <inheritdoc/>
public override void GatherAnimationClips(ICollection<AnimationClip> clips)
{
if (_Controller != null)
clips.Gather(_Controller.animationClips);
}
/************************************************************************************************************************/
/// <inheritdoc/>
public override void Destroy()
{
_Controller = null;
base.Destroy();
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Animator Controller Wrappers
/************************************************************************************************************************/
#region Cross Fade
/************************************************************************************************************************/
/// <summary>
/// The default constant for fade duration parameters which causes it to use the
/// <see cref="AnimancerPlayable.DefaultFadeDuration"/> instead.
/// </summary>
public const float DefaultFadeDuration = -1;
/************************************************************************************************************************/
/// <summary>
/// Returns the `fadeDuration` if it is zero or positive. Otherwise returns the
/// <see cref="AnimancerPlayable.DefaultFadeDuration"/>.
/// </summary>
public static float GetFadeDuration(float fadeDuration)
=> fadeDuration >= 0 ? fadeDuration : AnimancerPlayable.DefaultFadeDuration;
/************************************************************************************************************************/
/// <summary>Starts a transition from the current state to the specified state using normalized times.</summary>
/// <remarks>If `fadeDuration` is negative, it uses the <see cref="AnimancerPlayable.DefaultFadeDuration"/>.</remarks>
public void CrossFade(int stateNameHash,
float fadeDuration = DefaultFadeDuration,
int layer = -1,
float normalizedTime = float.NegativeInfinity)
=> Playable.CrossFade(stateNameHash, GetFadeDuration(fadeDuration), layer, normalizedTime);
/************************************************************************************************************************/
/// <summary>Starts a transition from the current state to the specified state using normalized times.</summary>
/// <remarks>If `fadeDuration` is negative, it uses the <see cref="AnimancerPlayable.DefaultFadeDuration"/>.</remarks>
public void CrossFade(string stateName,
float fadeDuration = DefaultFadeDuration,
int layer = -1,
float normalizedTime = float.NegativeInfinity)
=> Playable.CrossFade(stateName, GetFadeDuration(fadeDuration), layer, normalizedTime);
/************************************************************************************************************************/
/// <summary>Starts a transition from the current state to the specified state using times in seconds.</summary>
/// <remarks>If `fadeDuration` is negative, it uses the <see cref="AnimancerPlayable.DefaultFadeDuration"/>.</remarks>
public void CrossFadeInFixedTime(int stateNameHash,
float fadeDuration = DefaultFadeDuration,
int layer = -1,
float fixedTime = 0)
=> Playable.CrossFadeInFixedTime(stateNameHash, GetFadeDuration(fadeDuration), layer, fixedTime);
/************************************************************************************************************************/
/// <summary>Starts a transition from the current state to the specified state using times in seconds.</summary>
/// <remarks>If `fadeDuration` is negative, it uses the <see cref="AnimancerPlayable.DefaultFadeDuration"/>.</remarks>
public void CrossFadeInFixedTime(string stateName,
float fadeDuration = DefaultFadeDuration,
int layer = -1,
float fixedTime = 0)
=> Playable.CrossFadeInFixedTime(stateName, GetFadeDuration(fadeDuration), layer, fixedTime);
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Play
/************************************************************************************************************************/
/// <summary>Plays the specified state immediately, starting from a particular normalized time.</summary>
public void Play(int stateNameHash,
int layer = -1,
float normalizedTime = float.NegativeInfinity)
=> Playable.Play(stateNameHash, layer, normalizedTime);
/************************************************************************************************************************/
/// <summary>Plays the specified state immediately, starting from a particular normalized time.</summary>
public void Play(string stateName,
int layer = -1,
float normalizedTime = float.NegativeInfinity)
=> Playable.Play(stateName, layer, normalizedTime);
/************************************************************************************************************************/
/// <summary>Plays the specified state immediately, starting from a particular time (in seconds).</summary>
public void PlayInFixedTime(int stateNameHash,
int layer = -1,
float fixedTime = 0)
=> Playable.PlayInFixedTime(stateNameHash, layer, fixedTime);
/************************************************************************************************************************/
/// <summary>Plays the specified state immediately, starting from a particular time (in seconds).</summary>
public void PlayInFixedTime(string stateName,
int layer = -1,
float fixedTime = 0)
=> Playable.PlayInFixedTime(stateName, layer, fixedTime);
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Parameters
/************************************************************************************************************************/
/// <summary>Gets the value of the specified boolean parameter.</summary>
public bool GetBool(int id) => Playable.GetBool(id);
/// <summary>Gets the value of the specified boolean parameter.</summary>
public bool GetBool(string name) => Playable.GetBool(name);
/// <summary>Sets the value of the specified boolean parameter.</summary>
public void SetBool(int id, bool value) => Playable.SetBool(id, value);
/// <summary>Sets the value of the specified boolean parameter.</summary>
public void SetBool(string name, bool value) => Playable.SetBool(name, value);
/// <summary>Gets the value of the specified float parameter.</summary>
public float GetFloat(int id) => Playable.GetFloat(id);
/// <summary>Gets the value of the specified float parameter.</summary>
public float GetFloat(string name) => Playable.GetFloat(name);
/// <summary>Sets the value of the specified float parameter.</summary>
public void SetFloat(int id, float value) => Playable.SetFloat(id, value);
/// <summary>Sets the value of the specified float parameter.</summary>
public void SetFloat(string name, float value) => Playable.SetFloat(name, value);
/// <summary>Gets the value of the specified integer parameter.</summary>
public int GetInteger(int id) => Playable.GetInteger(id);
/// <summary>Gets the value of the specified integer parameter.</summary>
public int GetInteger(string name) => Playable.GetInteger(name);
/// <summary>Sets the value of the specified integer parameter.</summary>
public void SetInteger(int id, int value) => Playable.SetInteger(id, value);
/// <summary>Sets the value of the specified integer parameter.</summary>
public void SetInteger(string name, int value) => Playable.SetInteger(name, value);
/// <summary>Sets the specified trigger parameter to true.</summary>
public void SetTrigger(int id) => Playable.SetTrigger(id);
/// <summary>Sets the specified trigger parameter to true.</summary>
public void SetTrigger(string name) => Playable.SetTrigger(name);
/// <summary>Resets the specified trigger parameter to false.</summary>
public void ResetTrigger(int id) => Playable.ResetTrigger(id);
/// <summary>Resets the specified trigger parameter to false.</summary>
public void ResetTrigger(string name) => Playable.ResetTrigger(name);
/// <summary>Gets the details of one of the <see cref="Controller"/>'s parameters.</summary>
public AnimatorControllerParameter GetParameter(int index) => Playable.GetParameter(index);
/// <summary>Gets the number of parameters in the <see cref="Controller"/>.</summary>
public int GetParameterCount() => Playable.GetParameterCount();
/// <summary>Indicates whether the specified parameter is controlled by an <see cref="AnimationClip"/>.</summary>
public bool IsParameterControlledByCurve(int id) => Playable.IsParameterControlledByCurve(id);
/// <summary>Indicates whether the specified parameter is controlled by an <see cref="AnimationClip"/>.</summary>
public bool IsParameterControlledByCurve(string name) => Playable.IsParameterControlledByCurve(name);
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Misc
/************************************************************************************************************************/
// Layers.
/************************************************************************************************************************/
/// <summary>Gets the weight of the layer at the specified index.</summary>
public float GetLayerWeight(int layerIndex) => Playable.GetLayerWeight(layerIndex);
/// <summary>Sets the weight of the layer at the specified index.</summary>
public void SetLayerWeight(int layerIndex, float weight) => Playable.SetLayerWeight(layerIndex, weight);
/// <summary>Gets the number of layers in the <see cref="Controller"/>.</summary>
public int GetLayerCount() => Playable.GetLayerCount();
/// <summary>Gets the index of the layer with the specified name.</summary>
public int GetLayerIndex(string layerName) => Playable.GetLayerIndex(layerName);
/// <summary>Gets the name of the layer with the specified index.</summary>
public string GetLayerName(int layerIndex) => Playable.GetLayerName(layerIndex);
/************************************************************************************************************************/
// States.
/************************************************************************************************************************/
/// <summary>Returns information about the current state.</summary>
public AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex = 0) => Playable.GetCurrentAnimatorStateInfo(layerIndex);
/// <summary>Returns information about the next state being transitioned towards.</summary>
public AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex = 0) => Playable.GetNextAnimatorStateInfo(layerIndex);
/// <summary>Indicates whether the specified layer contains the specified state.</summary>
public bool HasState(int layerIndex, int stateID) => Playable.HasState(layerIndex, stateID);
/************************************************************************************************************************/
// Transitions.
/************************************************************************************************************************/
/// <summary>Indicates whether the specified layer is currently executing a transition.</summary>
public bool IsInTransition(int layerIndex = 0) => Playable.IsInTransition(layerIndex);
/// <summary>Gets information about the current transition.</summary>
public AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex = 0) => Playable.GetAnimatorTransitionInfo(layerIndex);
/************************************************************************************************************************/
// Clips.
/************************************************************************************************************************/
/// <summary>Gets information about the <see cref="AnimationClip"/>s currently being played.</summary>
public AnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layerIndex = 0) => Playable.GetCurrentAnimatorClipInfo(layerIndex);
/// <summary>Gets information about the <see cref="AnimationClip"/>s currently being played.</summary>
public void GetCurrentAnimatorClipInfo(int layerIndex, List<AnimatorClipInfo> clips) => Playable.GetCurrentAnimatorClipInfo(layerIndex, clips);
/// <summary>Gets the number of <see cref="AnimationClip"/>s currently being played.</summary>
public int GetCurrentAnimatorClipInfoCount(int layerIndex = 0) => Playable.GetCurrentAnimatorClipInfoCount(layerIndex);
/// <summary>Gets information about the <see cref="AnimationClip"/>s currently being transitioned towards.</summary>
public AnimatorClipInfo[] GetNextAnimatorClipInfo(int layerIndex = 0) => Playable.GetNextAnimatorClipInfo(layerIndex);
/// <summary>Gets information about the <see cref="AnimationClip"/>s currently being transitioned towards.</summary>
public void GetNextAnimatorClipInfo(int layerIndex, List<AnimatorClipInfo> clips) => Playable.GetNextAnimatorClipInfo(layerIndex, clips);
/// <summary>Gets the number of <see cref="AnimationClip"/>s currently being transitioned towards.</summary>
public int GetNextAnimatorClipInfoCount(int layerIndex = 0) => Playable.GetNextAnimatorClipInfoCount(layerIndex);
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Parameter IDs
/************************************************************************************************************************/
/// <summary>A wrapper for the name and hash of an <see cref="AnimatorControllerParameter"/>.</summary>
public readonly struct ParameterID
{
/************************************************************************************************************************/
/// <summary>The name of this parameter.</summary>
public readonly string Name;
/// <summary>The name hash of this parameter.</summary>
public readonly int Hash;
/************************************************************************************************************************/
/// <summary>
/// Creates a new <see cref="ParameterID"/> with the specified <see cref="Name"/> and uses
/// <see cref="Animator.StringToHash"/> to calculate the <see cref="Hash"/>.
/// </summary>
public ParameterID(string name)
{
Name = name;
Hash = Animator.StringToHash(name);
}
/// <summary>
/// Creates a new <see cref="ParameterID"/> with the specified <see cref="Hash"/> and leaves the
/// <see cref="Name"/> null.
/// </summary>
public ParameterID(int hash)
{
Name = null;
Hash = hash;
}
/// <summary>Creates a new <see cref="ParameterID"/> with the specified <see cref="Name"/> and <see cref="Hash"/>.</summary>
/// <remarks>This constructor does not verify that the `hash` actually corresponds to the `name`.</remarks>
public ParameterID(string name, int hash)
{
Name = name;
Hash = hash;
}
/************************************************************************************************************************/
/// <summary>
/// Creates a new <see cref="ParameterID"/> with the specified <see cref="Name"/> and uses
/// <see cref="Animator.StringToHash"/> to calculate the <see cref="Hash"/>.
/// </summary>
public static implicit operator ParameterID(string name) => new ParameterID(name);
/// <summary>
/// Creates a new <see cref="ParameterID"/> with the specified <see cref="Hash"/> and leaves the
/// <see cref="Name"/> null.
/// </summary>
public static implicit operator ParameterID(int hash) => new ParameterID(hash);
/************************************************************************************************************************/
/// <summary>Returns the <see cref="Hash"/>.</summary>
public static implicit operator int(ParameterID parameter) => 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.UnityEditor)]
public void ValidateHasParameter(RuntimeAnimatorController controller, AnimatorControllerParameterType type)
{
#if UNITY_EDITOR
Editor.AnimancerEditorUtilities.InitializeCleanDictionary(ref _ControllerToParameterHashAndType);
// Get the parameter details.
if (!_ControllerToParameterHashAndType.TryGetValue(controller, out var parameterDetails))
{
var editorController = (AnimatorController)controller;
var parameters = editorController.parameters;
var count = parameters.Length;
// Animator Controllers loaded from Asset Bundles only contain their RuntimeAnimatorController data
// but not the editor AnimatorController data which we need to perform this validation.
if (count == 0 &&
editorController.layers.Length == 0)// Double check that the editor data is actually empty.
{
_ControllerToParameterHashAndType.Add(controller, null);
return;
}
parameterDetails = new Dictionary<int, AnimatorControllerParameterType>();
for (int i = 0; i < count; i++)
{
var parameter = parameters[i];
parameterDetails.Add(parameter.nameHash, parameter.type);
}
_ControllerToParameterHashAndType.Add(controller, parameterDetails);
}
if (parameterDetails == null)
return;
// Check that there is a parameter with the correct hash and type.
if (!parameterDetails.TryGetValue(Hash, out var 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 $"{nameof(ControllerState)}.{nameof(ParameterID)}" +
$"({nameof(Name)}: '{Name}'" +
$", {nameof(Hash)}: {Hash})";
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Inspector
/************************************************************************************************************************/
/// <summary>The number of parameters being wrapped by this state.</summary>
public virtual int ParameterCount => 0;
/// <summary>Returns the hash of a parameter being wrapped by this state.</summary>
/// <exception cref="NotSupportedException">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 CreateDrawer() => new Drawer(this);
/************************************************************************************************************************/
/// <inheritdoc/>
public sealed class Drawer : Editor.ParametizedAnimancerStateDrawer<ControllerState>
{
/************************************************************************************************************************/
/// <summary>Creates a new <see cref="Drawer"/> to manage the Inspector GUI for the `state`.</summary>
public Drawer(ControllerState state) : base(state) { }
/************************************************************************************************************************/
/// <inheritdoc/>
protected override void DoDetailsGUI()
{
GatherParameters();
base.DoDetailsGUI();
}
/************************************************************************************************************************/
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)
{
Validate.AssertPlayable(Target);
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;
}
/************************************************************************************************************************/
/// <inheritdoc/>
public override int ParameterCount => Parameters.Count;
/// <inheritdoc/>
public override string GetParameterName(int index) => Parameters[index].name;
/// <inheritdoc/>
public override AnimatorControllerParameterType GetParameterType(int index) => Parameters[index].type;
/// <inheritdoc/>
public override object GetParameterValue(int index)
{
Validate.AssertPlayable(Target);
return AnimancerUtilities.GetParameterValue(Target._Playable, Parameters[index]);
}
/// <inheritdoc/>
public override void SetParameterValue(int index, object value)
{
Validate.AssertPlayable(Target);
AnimancerUtilities.SetParameterValue(Target._Playable, Parameters[index], value);
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}