AK056/Packages/com.kybernetik.animancer/Runtime/Controller States/ControllerState.ParameterBinding.cs
2025-05-09 15:40:34 +08:00

473 lines
20 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Animancer
{
/// https://kybernetik.com.au/animancer/api/Animancer/ControllerState
partial class ControllerState
{
/************************************************************************************************************************/
private SerializableParameterBindings _SerializedParameterBindings;
/// <summary>Serialized data used to create <see cref="ParameterBinding{T}"/>s at runtime.</summary>
public SerializableParameterBindings SerializedParameterBindings
{
get => _SerializedParameterBindings;
set
{
_SerializedParameterBindings = value;
DeserializeParameterBindings();
}
}
/// <summary>Deserializes the <see cref="SerializedParameterBindings"/>.</summary>
private void DeserializeParameterBindings()
{
if (Graph == null)
return;
DisposeParameterBindings();
_SerializedParameterBindings?.Deserialize(this);
}
/************************************************************************************************************************/
private List<IDisposable> _ParameterBindings;
/// <summary>
/// Adds an object to a list for <see cref="IDisposable.Dispose"/>
/// to be called in <see cref="Destroy"/>.
/// </summary>
private void AddParameterBinding(IDisposable disposable)
{
_ParameterBindings ??= new();
_ParameterBindings.Add(disposable);
}
/// <summary>Disposes everything added by <see cref="AddParameterBinding"/>.</summary>
private void DisposeParameterBindings()
{
if (_ParameterBindings == null)
return;
for (int i = _ParameterBindings.Count - 1; i >= 0; i--)
_ParameterBindings[i].Dispose();
_ParameterBindings.Clear();
}
/************************************************************************************************************************/
/// <summary>
/// Configures all parameters in the <see cref="Controller"/>
/// to follow the value of a parameter with the same name in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public void BindAllParameters()
{
var count = Playable.GetParameterCount();
for (int i = 0; i < count; i++)
{
var parameter = Playable.GetParameter(i);
BindParameter(parameter.name, parameter);
}
}
/************************************************************************************************************************/
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter with the same name in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public void BindParameter(StringReference name)
=> BindParameter(name, name);
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public void BindParameter(StringReference animancerParameter, string controllerParameterName)
=> BindParameter(animancerParameter, Animator.StringToHash(controllerParameterName));
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public void BindParameter(StringReference animancerParameter, int controllerParameterHash)
{
var count = Playable.GetParameterCount();
for (int i = 0; i < count; i++)
{
var parameter = Playable.GetParameter(i);
if (parameter.nameHash == controllerParameterHash)
{
BindParameter(animancerParameter, parameter);
break;
}
}
}
/************************************************************************************************************************/
/// <summary>
/// Configures all parameters in the <see cref="Controller"/>
/// to follow the value of a parameter with the same name in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public void BindParameter(
StringReference animancerParameter,
AnimatorControllerParameter controllerParameter)
{
switch (controllerParameter.type)
{
case AnimatorControllerParameterType.Float:
BindFloat(animancerParameter, controllerParameter.nameHash);
break;
case AnimatorControllerParameterType.Int:
BindInt(animancerParameter, controllerParameter.nameHash);
break;
case AnimatorControllerParameterType.Bool:
case AnimatorControllerParameterType.Trigger:
BindBool(animancerParameter, controllerParameter.nameHash);
break;
}
}
/************************************************************************************************************************/
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter with the same name in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<bool> BindBool(StringReference name)
=> BindBool(name, name);
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<bool> BindBool(StringReference animancerParameter, string controllerParameterName)
=> BindBool(animancerParameter, Animator.StringToHash(controllerParameterName));
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<bool> BindBool(StringReference animancerParameter, int controllerParameterHash)
{
var parameter = Graph.Parameters.GetOrCreate<bool>(animancerParameter);
var binding = new ParameterBinding<bool>(
parameter,
value => Playable.SetBool(controllerParameterHash, value));
AddParameterBinding(binding);
return binding;
}
/************************************************************************************************************************/
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter with the same name in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<float> BindFloat(StringReference name)
=> BindFloat(name, name);
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<float> BindFloat(StringReference animancerParameter, string controllerParameterName)
=> BindFloat(animancerParameter, Animator.StringToHash(controllerParameterName));
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<float> BindFloat(StringReference animancerParameter, int controllerParameterHash)
{
var parameter = Graph.Parameters.GetOrCreate<float>(animancerParameter);
var binding = new ParameterBinding<float>(
parameter,
value => Playable.SetFloat(controllerParameterHash, value));
AddParameterBinding(binding);
return binding;
}
/************************************************************************************************************************/
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter with the same name in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<int> BindInt(StringReference name)
=> BindInt(name, name);
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<int> BindInt(StringReference animancerParameter, string controllerParameterName)
=> BindInt(animancerParameter, Animator.StringToHash(controllerParameterName));
/// <summary>
/// Configures a parameter in the <see cref="Controller"/>
/// to follow the value of a parameter in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
public ParameterBinding<int> BindInt(StringReference animancerParameter, int controllerParameterHash)
{
var parameter = Graph.Parameters.GetOrCreate<int>(animancerParameter);
var binding = new ParameterBinding<int>(
parameter,
value => Playable.SetInteger(controllerParameterHash, value));
AddParameterBinding(binding);
return binding;
}
/************************************************************************************************************************/
/// <summary>An <see cref="IDisposable"/> binding to <see cref="Parameter{T}.OnValueChanged"/>.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer/ParameterBinding_1
public class ParameterBinding<T> : IDisposable
{
/************************************************************************************************************************/
/// <summary>The parameter being watched.</summary>
public readonly Parameter<T> Parameter;
/// <summary>The callback to invoke when the parameter changes.</summary>
public readonly Action<T> OnParameterChanged;
/************************************************************************************************************************/
/// <summary>
/// Invokes `onParameterChanged` and adds it to the <see cref="Parameter{T}.OnValueChanged"/>
/// to be removed by <see cref="Dispose"/>.
/// </summary>
public ParameterBinding(
Parameter<T> parameter,
Action<T> onParameterChanged)
{
Parameter = parameter;
OnParameterChanged = onParameterChanged;
OnParameterChanged(Parameter.Value);
Parameter.OnValueChanged += OnParameterChanged;
}
/************************************************************************************************************************/
/// <summary>
/// Removes <see cref="OnParameterChanged"/> from the <see cref="Parameter{T}.OnValueChanged"/>.
/// </summary>
public void Dispose()
{
Parameter.OnValueChanged -= OnParameterChanged;
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
/// <summary>
/// A serializable array of data which can create <see cref="ParameterBinding{T}"/>s at runtime.
/// </summary>
/// <remarks>
/// This data contains a <see cref="Bindings"/> array and <see cref="Mode"/> flag:
/// <list type="bullet">
///
/// <item>
/// If the array is empty,
/// <c>true</c> will bind all parameters by name
/// and <c>false</c> will bind nothing.
/// </item>
///
/// <item>
/// Otherwise, <c>true</c> will bind <c>[i * 2]</c> in the <see cref="RuntimeAnimatorController"/>
/// to <c>[i * 2 + 1]</c> in the <see cref="AnimancerGraph.Parameters"/>.
/// </item>
///
/// <item>
/// And <c>false</c> will bind each of its parameters to the same name in both systems.
/// </item>
///
/// </list>
/// </remarks>
/// https://kybernetik.com.au/animancer/api/Animancer/SerializableParameterBindings
[Serializable]
public class SerializableParameterBindings :
ICloneable<SerializableParameterBindings>
{
/************************************************************************************************************************/
[SerializeField]
private bool _Mode;
/// <summary>[<see cref="SerializeField"/>]
/// Modifies the way the <see cref="Bindings"/> array is interpreted.
/// </summary>
/// <remarks>See the <see cref="SerializableParameterBindings"/> class for details.</remarks>
public ref bool Mode
=> ref _Mode;
#if UNITY_EDITOR
/// <summary>[Editor-Only] The name of the serialized backing field of <see cref="Mode"/>.</summary>
public const string ModeFieldName = nameof(_Mode);
#endif
/************************************************************************************************************************/
/// <summary>[<see cref="SerializeField"/>]
/// Should all parameters in the <see cref="RuntimeAnimatorController"/> be bound by name?
/// </summary>
/// <remarks>See the <see cref="SerializableParameterBindings"/> class for details.</remarks>
public bool BindAllParameters
{
get => _Mode && _Bindings.Length == 0;
set
{
_Mode = value;
if (value)
{
_Bindings = Array.Empty<StringAsset>();
}
else
{
Debug.Assert(
_Bindings.Length == 0,
$"{nameof(BindAllParameters)} can't be disabled unless the {nameof(Bindings)}" +
$" array is empty because it changes the meaning of that array.");
}
}
}
/************************************************************************************************************************/
/// <summary>[<see cref="SerializeField"/>]
/// Should the <see cref="Bindings"/> be grouped into pairs
/// to bind each <see cref="RuntimeAnimatorController"/> parameter
/// to the subsequent parameter in <see cref="AnimancerGraph.Parameters"/>?
/// </summary>
/// <remarks>See the <see cref="SerializableParameterBindings"/> class for details.</remarks>
public bool RebindNames
{
get => _Mode && _Bindings.Length > 0;
set
{
_Mode = value;
if (value)
{
if (_Bindings.Length % 2 != 0)
Array.Resize(ref _Bindings, _Bindings.Length + 1);
}
else
{
Debug.Assert(
_Bindings.Length == 0,
$"{nameof(RebindNames)} can't be disabled unless the {nameof(Bindings)}" +
$" array is empty because it changes the meaning of that array.");
}
}
}
/************************************************************************************************************************/
[SerializeField]
private StringAsset[] _Bindings = Array.Empty<StringAsset>();
/// <summary>[<see cref="SerializeField"/>]
/// Parameter names used to have parameters in the <see cref="RuntimeAnimatorController"/>
/// follow the value of parameters in the <see cref="AnimancerGraph.Parameters"/>.
/// </summary>
/// <remarks>See the <see cref="SerializableParameterBindings"/> class for details.</remarks>
public StringAsset[] Bindings
{
get => _Bindings;
set
{
Debug.Assert(
value != null,
$"{nameof(Bindings)} can't be null. Use Array.Empty<StringAsset>() instead.");
_Bindings = value;
}
}
#if UNITY_EDITOR
/// <summary>[Editor-Only] The name of the serialized backing field of <see cref="Bindings"/>.</summary>
public const string BindingsFieldName = nameof(_Bindings);
#endif
/************************************************************************************************************************/
/// <summary>Creates runtime bindings for the `state`.</summary>
/// <remarks>See the <see cref="SerializableParameterBindings"/> class for details.</remarks>
public void Deserialize(ControllerState state)
{
if (_Bindings.Length == 0)
{
if (_Mode)
state.BindAllParameters();
// Else do nothing.
}
else
{
if (_Mode)
{
for (int i = 0; i < _Bindings.Length - 1; i += 2)
{
var controller = _Bindings[i];
var animancer = _Bindings[i + 1];
if (controller == null ||
animancer == null)
continue;
state.BindParameter(animancer, controller);
}
}
else
{
for (int i = 0; i < _Bindings.Length; i++)
{
var name = _Bindings[i];
if (name == null)
continue;
state.BindParameter(name);
}
}
}
}
/************************************************************************************************************************/
/// <inheritdoc/>
public SerializableParameterBindings Clone(CloneContext context)
{
var bindingCount = Bindings != null ? Bindings.Length : 0;
var clone = new SerializableParameterBindings()
{
BindAllParameters = BindAllParameters,
Bindings = new StringAsset[bindingCount],
};
for (int i = 0; i < bindingCount; i++)
clone.Bindings[i] = context.GetCloneOrOriginal(Bindings[i]);
return clone;
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
}
}