// 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;
/// Serialized data used to create s at runtime.
public SerializableParameterBindings SerializedParameterBindings
{
get => _SerializedParameterBindings;
set
{
_SerializedParameterBindings = value;
DeserializeParameterBindings();
}
}
/// Deserializes the .
private void DeserializeParameterBindings()
{
if (Graph == null)
return;
DisposeParameterBindings();
_SerializedParameterBindings?.Deserialize(this);
}
/************************************************************************************************************************/
private List _ParameterBindings;
///
/// Adds an object to a list for
/// to be called in .
///
private void AddParameterBinding(IDisposable disposable)
{
_ParameterBindings ??= new();
_ParameterBindings.Add(disposable);
}
/// Disposes everything added by .
private void DisposeParameterBindings()
{
if (_ParameterBindings == null)
return;
for (int i = _ParameterBindings.Count - 1; i >= 0; i--)
_ParameterBindings[i].Dispose();
_ParameterBindings.Clear();
}
/************************************************************************************************************************/
///
/// Configures all parameters in the
/// to follow the value of a parameter with the same name in the .
///
public void BindAllParameters()
{
var count = Playable.GetParameterCount();
for (int i = 0; i < count; i++)
{
var parameter = Playable.GetParameter(i);
BindParameter(parameter.name, parameter);
}
}
/************************************************************************************************************************/
///
/// Configures a parameter in the
/// to follow the value of a parameter with the same name in the .
///
public void BindParameter(StringReference name)
=> BindParameter(name, name);
///
/// Configures a parameter in the
/// to follow the value of a parameter in the .
///
public void BindParameter(StringReference animancerParameter, string controllerParameterName)
=> BindParameter(animancerParameter, Animator.StringToHash(controllerParameterName));
///
/// Configures a parameter in the
/// to follow the value of a parameter in the .
///
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;
}
}
}
/************************************************************************************************************************/
///
/// Configures all parameters in the
/// to follow the value of a parameter with the same name in the .
///
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;
}
}
/************************************************************************************************************************/
///
/// Configures a parameter in the
/// to follow the value of a parameter with the same name in the .
///
public ParameterBinding BindBool(StringReference name)
=> BindBool(name, name);
///
/// Configures a parameter in the
/// to follow the value of a parameter in the .
///
public ParameterBinding BindBool(StringReference animancerParameter, string controllerParameterName)
=> BindBool(animancerParameter, Animator.StringToHash(controllerParameterName));
///
/// Configures a parameter in the
/// to follow the value of a parameter in the .
///
public ParameterBinding BindBool(StringReference animancerParameter, int controllerParameterHash)
{
var parameter = Graph.Parameters.GetOrCreate(animancerParameter);
var binding = new ParameterBinding(
parameter,
value => Playable.SetBool(controllerParameterHash, value));
AddParameterBinding(binding);
return binding;
}
/************************************************************************************************************************/
///
/// Configures a parameter in the
/// to follow the value of a parameter with the same name in the .
///
public ParameterBinding BindFloat(StringReference name)
=> BindFloat(name, name);
///
/// Configures a parameter in the
/// to follow the value of a parameter in the .
///
public ParameterBinding BindFloat(StringReference animancerParameter, string controllerParameterName)
=> BindFloat(animancerParameter, Animator.StringToHash(controllerParameterName));
///
/// Configures a parameter in the
/// to follow the value of a parameter in the .
///
public ParameterBinding BindFloat(StringReference animancerParameter, int controllerParameterHash)
{
var parameter = Graph.Parameters.GetOrCreate(animancerParameter);
var binding = new ParameterBinding(
parameter,
value => Playable.SetFloat(controllerParameterHash, value));
AddParameterBinding(binding);
return binding;
}
/************************************************************************************************************************/
///
/// Configures a parameter in the
/// to follow the value of a parameter with the same name in the .
///
public ParameterBinding BindInt(StringReference name)
=> BindInt(name, name);
///
/// Configures a parameter in the
/// to follow the value of a parameter in the .
///
public ParameterBinding BindInt(StringReference animancerParameter, string controllerParameterName)
=> BindInt(animancerParameter, Animator.StringToHash(controllerParameterName));
///
/// Configures a parameter in the
/// to follow the value of a parameter in the .
///
public ParameterBinding BindInt(StringReference animancerParameter, int controllerParameterHash)
{
var parameter = Graph.Parameters.GetOrCreate(animancerParameter);
var binding = new ParameterBinding(
parameter,
value => Playable.SetInteger(controllerParameterHash, value));
AddParameterBinding(binding);
return binding;
}
/************************************************************************************************************************/
/// An binding to .
/// https://kybernetik.com.au/animancer/api/Animancer/ParameterBinding_1
public class ParameterBinding : IDisposable
{
/************************************************************************************************************************/
/// The parameter being watched.
public readonly Parameter Parameter;
/// The callback to invoke when the parameter changes.
public readonly Action OnParameterChanged;
/************************************************************************************************************************/
///
/// Invokes `onParameterChanged` and adds it to the
/// to be removed by .
///
public ParameterBinding(
Parameter parameter,
Action onParameterChanged)
{
Parameter = parameter;
OnParameterChanged = onParameterChanged;
OnParameterChanged(Parameter.Value);
Parameter.OnValueChanged += OnParameterChanged;
}
/************************************************************************************************************************/
///
/// Removes from the .
///
public void Dispose()
{
Parameter.OnValueChanged -= OnParameterChanged;
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
///
/// A serializable array of data which can create s at runtime.
///
///
/// This data contains a array and flag:
///
///
/// -
/// If the array is empty,
/// true will bind all parameters by name
/// and false will bind nothing.
///
///
/// -
/// Otherwise, true will bind [i * 2] in the
/// to [i * 2 + 1] in the .
///
///
/// -
/// And false will bind each of its parameters to the same name in both systems.
///
///
///
///
/// https://kybernetik.com.au/animancer/api/Animancer/SerializableParameterBindings
[Serializable]
public class SerializableParameterBindings :
ICloneable
{
/************************************************************************************************************************/
[SerializeField]
private bool _Mode;
/// []
/// Modifies the way the array is interpreted.
///
/// See the class for details.
public ref bool Mode
=> ref _Mode;
#if UNITY_EDITOR
/// [Editor-Only] The name of the serialized backing field of .
public const string ModeFieldName = nameof(_Mode);
#endif
/************************************************************************************************************************/
/// []
/// Should all parameters in the be bound by name?
///
/// See the class for details.
public bool BindAllParameters
{
get => _Mode && _Bindings.Length == 0;
set
{
_Mode = value;
if (value)
{
_Bindings = Array.Empty();
}
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.");
}
}
}
/************************************************************************************************************************/
/// []
/// Should the be grouped into pairs
/// to bind each parameter
/// to the subsequent parameter in ?
///
/// See the class for details.
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();
/// []
/// Parameter names used to have parameters in the
/// follow the value of parameters in the .
///
/// See the class for details.
public StringAsset[] Bindings
{
get => _Bindings;
set
{
Debug.Assert(
value != null,
$"{nameof(Bindings)} can't be null. Use Array.Empty() instead.");
_Bindings = value;
}
}
#if UNITY_EDITOR
/// [Editor-Only] The name of the serialized backing field of .
public const string BindingsFieldName = nameof(_Bindings);
#endif
/************************************************************************************************************************/
/// Creates runtime bindings for the `state`.
/// See the class for details.
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);
}
}
}
}
/************************************************************************************************************************/
///
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;
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
}
}