// 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; } /************************************************************************************************************************/ } /************************************************************************************************************************/ } }