UnityGame/Library/PackageCache/com.unity.inputsystem/InputSystem/Actions/InputActionParameters.cs
2024-10-27 10:53:47 +03:00

1067 lines
58 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.InputSystem.Utilities;
namespace UnityEngine.InputSystem
{
partial class InputActionRebindingExtensions
{
/// <summary>
/// Return the current value of the given parameter as found on the processors, interactions, or composites
/// of the action's current bindings.
/// </summary>
/// <param name="action">Action on whose bindings to look for the value of the given parameter.</param>
/// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
/// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
/// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
/// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
/// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
/// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will be taken into account.</param>
/// <returns>The current value of the given parameter or <c>null</c> if the parameter could not be found.</returns>
/// <remarks>
/// Parameters are found on interactions (<see cref="IInputInteraction"/>), processors (<see cref="InputProcessor"/>), and
/// composites (see <see cref="InputBindingComposite"/>) that are applied to bindings. For example, the following binding
/// adds a <c>Hold</c> interaction with a custom <c>duration</c> parameter on top of binding to the gamepad's A button:
///
/// <example>
/// <code>
/// new InputBinding
/// {
/// path = "&lt;Gamepad&gt;/buttonSouth",
/// interactions = "hold(duration=0.6)"
/// };
/// </code>
/// </example>
///
/// In the editor UI, parameters are set graphically from the properties sections in the right-most pane
/// in the action editor when an action or a binding is selected.
///
/// When the binding above is applied to an action, the <c>duration</c> parameter from the <c>Hold</c> interaction can be
/// queried like so:
///
/// <example>
/// <code>
/// action.GetParameterValue("duration") // Returns 0.6
/// </code>
/// </example>
///
/// Note that if there are multiple objects on the action that use the same parameter name, the value of the <em>first</em> parameter
/// that is encountered is returned. Also note that this method will create GC heap garbage.
///
/// The type of object to query the parameter from can be include in the <paramref name="name"/> parameter. For example, if
/// an action has both a <see cref="Interactions.TapInteraction"/> and a <see cref="Interactions.HoldInteraction"/> on it, the
/// <c>duration</c> parameter can be queried independently like so:
///
/// <example>
/// <code>
/// // Query "duration" from "hold":
/// action.GetParameterValue("hold:duration");
///
/// // Query "duration" from "tap":
/// action.GetParameterValue("tap:duration");
/// </code>
/// </example>
///
/// The names used here to identify the object holding the parameter are the same used by <see cref="InputSystem.RegisterInteraction"/>,
/// <see cref="InputSystem.RegisterBindingComposite"/>, and <see cref="InputSystem.RegisterProcessor"/>.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c></exception>
/// <seealso cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
/// <seealso cref="ApplyBindingOverride(InputAction,string,string,string)"/>
/// <seealso cref="Editor.InputParameterEditor"/>
public static PrimitiveValue? GetParameterValue(this InputAction action, string name, InputBinding bindingMask = default)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
return action.GetParameterValue(new ParameterOverride(name, bindingMask));
}
private static PrimitiveValue? GetParameterValue(this InputAction action, ParameterOverride parameterOverride)
{
parameterOverride.bindingMask.action = action.name;
var actionMap = action.GetOrCreateActionMap();
actionMap.ResolveBindingsIfNecessary();
foreach (var parameter in new ParameterEnumerable(actionMap.m_State, parameterOverride, actionMap.m_MapIndexInState))
{
var value = parameter.field.GetValue(parameter.instance);
return PrimitiveValue.FromObject(value);
}
return null;
}
/// <summary>
/// Return the current value of the given parameter as found on the processors, interactions, or composites
/// of the action's current bindings.
/// </summary>
/// <param name="action">Action on whose bindings to look for the value of the given parameter.</param>
/// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
/// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
/// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
/// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
/// <param name="bindingIndex">Index of the binding in <paramref name="action"/>'s <see cref="InputAction.bindings"/>
/// to look for processors, interactions, and composites on.</param>
/// <returns>The current value of the given parameter or <c>null</c> if the parameter not could be found.</returns>
/// <remarks>
/// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
/// to specifically target a single binding by index. Otherwise, the method is identical in functionality.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c></exception>
public static PrimitiveValue? GetParameterValue(this InputAction action, string name, int bindingIndex)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
if (bindingIndex < 0)
throw new ArgumentOutOfRangeException(nameof(bindingIndex));
var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(bindingIndex);
var bindingMask = new InputBinding { id = action.GetOrCreateActionMap().bindings[indexOnMap].id };
return action.GetParameterValue(name, bindingMask);
}
/// <summary>
/// Return the current value of the given parameter as found on the processors, interactions, or composites
/// of the action's current bindings.
/// </summary>
/// <param name="action">Action on whose bindings to look for the value of the given parameter.</param>
/// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the
/// name and type of the parameter being looked for.</param>
/// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
/// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will be taken into account.</param>
/// <returns>The current value of the given parameter or <c>null</c> if the parameter not could be found.</returns>
/// <remarks>
/// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
/// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is
/// type-safe and does not involve strings.
///
/// <example>
/// <code>
/// // Get the "duration" parameter from a TapInteraction.
/// // This is equivalent to calling GetParameterValue("tap:duration")
/// // but will return a float? instead of a PrimitiveValue?.
/// action.GetParameterValue((TapInteraction x) => x.duration)
/// </code>
/// </example>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c></exception>
/// <seealso cref="ApplyParameterOverride{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},TValue,InputBinding)"/>
public static unsafe TValue? GetParameterValue<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expr, InputBinding bindingMask = default)
where TValue : struct
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (expr == null)
throw new ArgumentNullException(nameof(expr));
var parameterOverride = ExtractParameterOverride(expr, bindingMask);
var value = action.GetParameterValue(parameterOverride);
if (value == null)
return null;
// Type is guaranteed to match but just in case,
// make extra sure with a check here.
if (Type.GetTypeCode(typeof(TValue)) == value.Value.type)
{
// Can't just cast here so use UnsafeUtility to work around that.
var v = value.Value;
var result = default(TValue);
UnsafeUtility.MemCpy(UnsafeUtility.AddressOf(ref result),
v.valuePtr,
UnsafeUtility.SizeOf<TValue>());
return result;
}
// Shouldn't get here but just in case, do a conversion using C#'s Convert
// machinery as a fallback.
return (TValue)Convert.ChangeType(value.Value.ToObject(), typeof(TValue));
}
/// <summary>
/// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
/// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>.
/// </summary>
/// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set
/// the parameter value on.</param>
/// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the
/// name and type of the parameter whose value to set.</param>
/// <param name="value">New value to assign to the parameter.</param>
/// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
/// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c>
/// or empty.</exception>
/// <remarks>
/// This method is a variation of <see cref="ApplyParameterOverride(InputAction,string,PrimitiveValue,InputBinding)"/>
/// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is
/// type-safe and does not involve strings.
///
/// <example>
/// <code>
/// // Override the "duration" parameter from a TapInteraction.
/// // This is equivalent to calling ApplyParameterOverride("tap:duration", 0.4f).
/// action.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f);
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/>
public static void ApplyParameterOverride<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expr, TValue value,
InputBinding bindingMask = default)
where TValue : struct
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (expr == null)
throw new ArgumentNullException(nameof(expr));
var actionMap = action.GetOrCreateActionMap();
actionMap.ResolveBindingsIfNecessary();
bindingMask.action = action.name;
var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value));
ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState,
ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount,
parameterOverride);
}
/// <summary>
/// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
/// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/>.
/// </summary>
/// <param name="actionMap">An action on whose <see cref="InputActionMap.bindings"/> to look for objects to set
/// the parameter value on.</param>
/// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the
/// name and type of the parameter whose value to set.</param>
/// <param name="value">New value to assign to the parameter.</param>
/// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
/// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param>
/// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c>
/// or empty.</exception>
/// <remarks>
/// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/>
/// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is
/// type-safe and does not involve strings.
///
/// <example>
/// <code>
/// // Override the "duration" parameter from a TapInteraction.
/// // This is equivalent to calling mApplyParameterOverride("tap:duration", 0.4f).
/// actionMap.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f);
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/>
public static void ApplyParameterOverride<TObject, TValue>(this InputActionMap actionMap, Expression<Func<TObject, TValue>> expr, TValue value,
InputBinding bindingMask = default)
where TValue : struct
{
if (actionMap == null)
throw new ArgumentNullException(nameof(actionMap));
if (expr == null)
throw new ArgumentNullException(nameof(expr));
actionMap.ResolveBindingsIfNecessary();
var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value));
ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState,
ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount,
parameterOverride);
}
/// <summary>
/// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
/// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of the <see cref="InputActionAsset.actionMaps"/>
/// in <paramref name="asset"/>.
/// </summary>
/// <param name="asset">An asset on whose <see cref="InputActionMap.bindings"/> to look for objects to set
/// the parameter value on.</param>
/// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the
/// name and type of the parameter whose value to set.</param>
/// <param name="value">New value to assign to the parameter.</param>
/// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only
/// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c>
/// or empty.</exception>
/// <remarks>
/// This method is a variation of <see cref="ApplyParameterOverride(InputActionAsset,string,PrimitiveValue,InputBinding)"/>
/// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is
/// type-safe and does not involve strings.
///
/// <example>
/// <code>
/// // Override the "duration" parameter from a TapInteraction.
/// // This is equivalent to calling mApplyParameterOverride("tap:duration", 0.4f).
/// asset.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f);
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/>
public static void ApplyParameterOverride<TObject, TValue>(this InputActionAsset asset, Expression<Func<TObject, TValue>> expr, TValue value,
InputBinding bindingMask = default)
where TValue : struct
{
if (asset == null)
throw new ArgumentNullException(nameof(asset));
if (expr == null)
throw new ArgumentNullException(nameof(expr));
asset.ResolveBindingsIfNecessary();
var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value));
ApplyParameterOverride(asset.m_SharedStateForAllMaps, -1,
ref asset.m_ParameterOverrides, ref asset.m_ParameterOverridesCount,
parameterOverride);
}
private static ParameterOverride ExtractParameterOverride<TObject, TValue>(Expression<Func<TObject, TValue>> expr,
InputBinding bindingMask = default, PrimitiveValue value = default)
{
if (!(expr is LambdaExpression lambda))
throw new ArgumentException($"Expression must be a LambdaExpression but was a {expr.GetType().Name} instead", nameof(expr));
if (!(lambda.Body is MemberExpression body))
{
// If the field type in the lambda doesn't match the TValue type being used,
// but there is a coercion, the compiler will automatically insert a Convert(x.name, TValue)
// expression.
if (lambda.Body is UnaryExpression unary && unary.NodeType == ExpressionType.Convert && unary.Operand is MemberExpression b)
{
body = b;
}
else
{
throw new ArgumentException(
$"Body in LambdaExpression must be a MemberExpression (x.name) but was a {expr.GetType().Name} instead",
nameof(expr));
}
}
string objectRegistrationName;
if (typeof(InputProcessor).IsAssignableFrom(typeof(TObject)))
objectRegistrationName = InputProcessor.s_Processors.FindNameForType(typeof(TObject));
else if (typeof(IInputInteraction).IsAssignableFrom(typeof(TObject)))
objectRegistrationName = InputInteraction.s_Interactions.FindNameForType(typeof(TObject));
else if (typeof(InputBindingComposite).IsAssignableFrom(typeof(TObject)))
objectRegistrationName = InputBindingComposite.s_Composites.FindNameForType(typeof(TObject));
else
throw new ArgumentException(
$"Given type must be an InputProcessor, IInputInteraction, or InputBindingComposite (was {typeof(TObject).Name})",
nameof(TObject));
return new ParameterOverride(objectRegistrationName, body.Member.Name, bindingMask, value);
}
/// <summary>
/// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
/// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/>.
/// </summary>
/// <param name="actionMap">An action map on whose <see cref="InputActionMap.bindings"/> to look for objects to set
/// the parameter value on.</param>
/// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
/// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
/// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
/// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
/// <param name="value">New value to assign to the parameter.</param>
/// <param name="bindingMask">A binding mask that determines which of <paramref name="actionMap"/>'s <see cref="InputActionMap.bindings"/>
/// to apply the override to. By default this is empty which leads to the override to be applied to all bindings in the map.</param>
/// <remarks>
/// This method both directly applies the new value and also stores the override internally.
///
/// If an override for the same parameter <paramref name="name"/> and with the same <paramref name="bindingMask"/> already exists,
/// its value is simply updated. No new override will be created.
///
/// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created
/// from bindings.
///
/// <example>
/// <code>
/// // Create an action map with two actions.
/// var map = new InputActionMap();
/// var action1 = map.AddAction("action1");
/// var action2 = map.AddAction("action2");
///
/// // Add a binding to each action to which a "ClampProcessor" is applied.
/// // This processor has two parameters:
/// // - "min" (float)
/// // - "max" (float)
/// action1.AddBinding("&gt;Gamepad&gt;/rightTrigger", processors: "clamp(min=0.2,max=0.8)");
/// action2.AddBinding("&gt;Gamepad&gt;/leftTrigger", processors: "clamp(min=0.2,max=0.8)");
///
/// // Apply parameter overrides to set the values differently.
/// // This will apply the setting to *both* the bindings on action1 *and* action2.
/// map.ApplyParameterOverride("min", 0.3f);
/// map.ApplyParameterOverride("max", 0.9f);
/// </code>
/// </example>
///
/// An override can optionally be directed at a specific type of object.
///
/// <example>
/// <code>
/// map.ApplyParameterOverride("clamp:min", 0.3f);
/// map.ApplyParameterOverride("clamp:max", 0.9f);
/// </code>
/// </example>
///
/// By default, the parameter override will apply to all bindings in the map. To limit the override
/// to specific bindings, you can supply a <paramref name="bindingMask"/>.
///
/// <example>
/// <code>
/// // Apply a parameter override only to action1.
/// map.ApplyBindingOverride("clamp:min", 0.25f, new InputBinding { action = action1.name });
///
/// // Apply a parameter override only to a specific binding path.
/// map.ApplyBindingOverride("clamp:min", 0.4f, new InputBinding { path = "&lt;Gamepad&gt;/leftTrigger" });
/// </code>
/// </example>
///
/// If multiple overrides exist for the same parameter, an attempt is made to choose the override that is most specific.
/// Say, that you apply an override for <c>"duration"</c> on an entire <see cref="InputActionAsset"/> using
/// <see cref="ApplyParameterOverride(InputActionAsset,String,PrimitiveValue,InputBinding)"/>. But then you also apply
/// an override to just an individual <see cref="InputAction"/> inside the asset. In this case, the <c>"duration"</c>
/// override for just that action will be applied to bindings of that action and the override inside the asset will
/// be applied to bindings of all other actions. Note that if multiple overrides exist that could all be considered
/// equally valid, the behavior is undecided.
///
/// Note that parameter overrides stay in place on the map. Like binding overrides, however, they are not
/// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied
/// automatically to bindings added to the action in the future as well as whenever bindings are resolved to controls.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c>
/// or empty.</exception>
/// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/>
public static void ApplyParameterOverride(this InputActionMap actionMap, string name, PrimitiveValue value, InputBinding bindingMask = default)
{
if (actionMap == null)
throw new ArgumentNullException(nameof(actionMap));
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
actionMap.ResolveBindingsIfNecessary();
ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState,
ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount,
new ParameterOverride(name, bindingMask, value));
}
/// <summary>
/// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
/// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of each of the <see cref="InputActionAsset.actionMaps"/>
/// in <paramref name="asset"/>.
/// </summary>
/// <param name="asset">An <c>.inputactions</c> asset on whose <see cref="InputActionMap.bindings"/> to look for objects to set
/// the parameter value on.</param>
/// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
/// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
/// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
/// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
/// <param name="value">New value to assign to the parameter.</param>
/// <param name="bindingMask">A binding mask that determines which of the <see cref="InputActionMap.bindings"/>
/// to apply the override to. By default this is empty which leads to the override to be applied to all bindings in the asset.</param>
/// <remarks>
/// This method both directly applies the new value and also stores the override internally.
///
/// If an override for the same parameter <paramref name="name"/> and with the same <paramref name="bindingMask"/> already exists,
/// its value is simply updated. No new override will be created.
///
/// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created
/// from bindings.
///
/// <example>
/// <code>
/// // Create an asset with one action map and two actions.
/// var asset = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;();
/// var map = asset.AddActionMap("map");
/// var action1 = map.AddAction("action1");
/// var action2 = map.AddAction("action2");
///
/// // Add a binding to each action to which a "ClampProcessor" is applied.
/// // This processor has two parameters:
/// // - "min" (float)
/// // - "max" (float)
/// action1.AddBinding("&gt;Gamepad&gt;/rightTrigger", processors: "clamp(min=0.2,max=0.8)");
/// action2.AddBinding("&gt;Gamepad&gt;/leftTrigger", processors: "clamp(min=0.2,max=0.8)");
///
/// // Apply parameter overrides to set the values differently.
/// // This will apply the setting to *both* the bindings on action1 *and* action2.
/// asset.ApplyParameterOverride("min", 0.3f);
/// asset.ApplyParameterOverride("max", 0.9f);
/// </code>
/// </example>
///
/// An override can optionally be directed at a specific type of object.
///
/// <example>
/// <code>
/// asset.ApplyParameterOverride("clamp:min", 0.3f);
/// asset.ApplyParameterOverride("clamp:max", 0.9f);
/// </code>
/// </example>
///
/// By default, the parameter override will apply to all bindings in the asset. To limit the override
/// to specific bindings, you can supply a <paramref name="bindingMask"/>.
///
/// <example>
/// <code>
/// // Apply a parameter override only to action1.
/// asset.ApplyBindingOverride("clamp:min", 0.25f, new InputBinding { action = action1.name });
///
/// // Apply a parameter override only to a specific binding path.
/// asset.ApplyBindingOverride("clamp:min", 0.4f, new InputBinding { path = "&lt;Gamepad&gt;/leftTrigger" });
/// </code>
/// </example>
///
/// If multiple overrides exist for the same parameter, an attempt is made to choose the override that is most specific.
/// Say, that you apply an override for <c>"duration"</c> on an entire <see cref="InputActionAsset"/> using
/// <see cref="ApplyParameterOverride(InputActionAsset,String,PrimitiveValue,InputBinding)"/>. But then you also apply
/// an override to just an individual <see cref="InputAction"/> inside the asset. In this case, the <c>"duration"</c>
/// override for just that action will be applied to bindings of that action and the override inside the asset will
/// be applied to bindings of all other actions. Note that if multiple overrides exist that could all be considered
/// equally valid, the behavior is undecided.
///
/// Note that parameter overrides stay in place on the map. Like binding overrides, however, they are not
/// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied
/// automatically to bindings added to the action in the future as well as whenever bindings are resolved to controls.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c>
/// or empty.</exception>
/// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/>
public static void ApplyParameterOverride(this InputActionAsset asset, string name, PrimitiveValue value, InputBinding bindingMask = default)
{
if (asset == null)
throw new ArgumentNullException(nameof(asset));
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
asset.ResolveBindingsIfNecessary();
ApplyParameterOverride(asset.m_SharedStateForAllMaps, -1,
ref asset.m_ParameterOverrides, ref asset.m_ParameterOverridesCount,
new ParameterOverride(name, bindingMask, value));
}
/// <summary>
/// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
/// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>.
/// </summary>
/// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set
/// the parameter value on.</param>
/// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
/// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
/// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
/// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
/// <param name="value">New value to assign to the parameter.</param>
/// <param name="bindingMask">A binding mask that determines which of <paramref name="action"/>'s <see cref="InputAction.bindings"/>
/// to apply the override to. By default this is empty which leads to the override to be applied to all bindings of the action.</param>
/// <remarks>
/// This method both directly applies the new value and also stores the override internally.
///
/// If an override for the same parameter <paramref name="name"/> on the same <paramref name="action"/> and with the same
/// <paramref name="bindingMask"/> already exists, its value is simply updated. No new override will be created.
///
/// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created
/// from bindings.
///
/// <example>
/// <code>
/// // Create an action with a binding that has a "ClampProcessor" applied to it.
/// // This processor has two parameters:
/// // - "min" (float)
/// // - "max" (float)
/// var action = new InputAction(binding: "&gt;Gamepad&gt;/rightTrigger", processors: "clamp(min=0.2,max=0.8)");
///
/// // Apply parameter overrides to set the values differently.
/// action.ApplyParameterOverride("min", 0.3f);
/// action.ApplyParameterOverride("max", 0.9f);
/// </code>
/// </example>
///
/// An override can optionally be directed at a specific type of object.
///
/// <example>
/// <code>
/// // Create an action with both a "tap" and a "hold" interaction. Both have a
/// // "duration" parameter.
/// var action = new InputAction(binding: "&lt;Gamepad&gt;/buttonSouth", interactions: "tap;hold");
///
/// // Apply parameter overrides individually to the two.
/// action.ApplyParameterOverride("tap:duration", 0.6f);
/// action.ApplyParameterOverride("hold:duration", 4f);
/// </code>
/// </example>
///
/// By default, the parameter override will apply to all bindings on the action. To limit the override
/// to specific bindings, you can supply a <paramref name="bindingMask"/>.
///
/// <example>
/// <code>
/// // Create a "look" style action with a mouse and a gamepad binding.
/// var lookAction = new InputAction();
/// lookAction.AddBinding("&lt;Mouse&gt;/delta", processors: "scaleVector2", groups: "Mouse");
/// lookAction.AddBinding("&lt;Gamepad&gt;/rightStick", processors: "scaleVector2", groups: "Gamepad");
///
/// // Override scaling of the mouse delta individually.
/// lookAction.ApplyBindingOverride("scaleVector2:x", 0.25f, InputBinding.MaskByGroup("Mouse"));
/// lookAction.ApplyBindingOverride("scaleVector2:y", 0.25f, InputBinding.MaskByGroup("Mouse"));
///
/// // Can also do that by path.
/// lookAction.ApplyBindingOverride("scaleVector2:x", 0.25f, new InputBinding("&lt;Mouse&gt;/delta"));
/// lookAction.ApplyBindingOverride("scaleVector2:y", 0.25f, new InputBinding("&lt;Mouse&gt;/delta"));
/// </code>
/// </example>
///
/// Note that parameter overrides stay in place on the action. Like binding overrides, however, they are not
/// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied
/// automatically to bindings added to the action in the future as well as whenever bindings for the action are resolved.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c>
/// or empty.</exception>
/// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/>
public static void ApplyParameterOverride(this InputAction action, string name, PrimitiveValue value, InputBinding bindingMask = default)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (name == null)
throw new ArgumentNullException(nameof(name));
var actionMap = action.GetOrCreateActionMap();
actionMap.ResolveBindingsIfNecessary();
bindingMask.action = action.name;
ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState,
ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount,
new ParameterOverride(name, bindingMask, value));
}
/// <summary>
/// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>,
/// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>.
/// </summary>
/// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set
/// the parameter value on.</param>
/// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the
/// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the
/// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c>
/// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param>
/// <param name="value">New value to assign to the parameter.</param>
/// <param name="bindingIndex">Index of the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> to which
/// to restrict the parameter override to.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bindingIndex"/> is negative or equal or greater than the number of
/// <see cref="InputAction.bindings"/> of <paramref name="action"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c>
/// or empty.</exception>
/// <remarks>
/// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> which
/// allows specifying a binding by index. It otherwise behaves identically to that method.
/// </remarks>
public static void ApplyParameterOverride(this InputAction action, string name, PrimitiveValue value, int bindingIndex)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
if (bindingIndex < 0)
throw new ArgumentOutOfRangeException(nameof(bindingIndex));
var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(bindingIndex);
var bindingMask = new InputBinding { id = action.GetOrCreateActionMap().bindings[indexOnMap].id };
action.ApplyParameterOverride(name, value, bindingMask);
}
private static void ApplyParameterOverride(InputActionState state, int mapIndex,
ref ParameterOverride[] parameterOverrides, ref int parameterOverridesCount, ParameterOverride parameterOverride)
{
// Update the parameter overrides on the map or asset.
var haveExistingOverride = false;
if (parameterOverrides != null)
{
// Try to find existing override.
for (var i = 0; i < parameterOverridesCount; ++i)
{
ref var p = ref parameterOverrides[i];
if (string.Equals(p.objectRegistrationName, parameterOverride.objectRegistrationName, StringComparison.OrdinalIgnoreCase) &&
string.Equals(p.parameter, parameterOverride.parameter, StringComparison.OrdinalIgnoreCase) &&
p.bindingMask == parameterOverride.bindingMask)
{
haveExistingOverride = true;
// Update value on existing override.
p = parameterOverride;
break;
}
}
}
if (!haveExistingOverride)
{
// Add new override.
ArrayHelpers.AppendWithCapacity(ref parameterOverrides, ref parameterOverridesCount, parameterOverride);
}
// Set value on all current processor and/or interaction instances that use the parameter.
foreach (var parameter in new ParameterEnumerable(state, parameterOverride, mapIndex))
{
// We cannot just blindly apply the parameter here as the override we have set may be less
// specific than an override we already have applied. So instead, we look up the most specific
// override and set that.
var actionMap = state.GetActionMap(parameter.bindingIndex);
ref var binding = ref state.GetBinding(parameter.bindingIndex);
var overrideToApply = ParameterOverride.Find(actionMap, ref binding, parameterOverride.parameter,
parameterOverride.objectRegistrationName);
if (overrideToApply.HasValue)
{
var fieldTypeCode = Type.GetTypeCode(parameter.field.FieldType);
parameter.field.SetValue(parameter.instance, overrideToApply.Value.value.ConvertTo(fieldTypeCode).ToObject());
}
}
}
internal struct Parameter
{
public object instance;
public FieldInfo field;
public int bindingIndex;
}
// Finds all instances of a parameter in one or more actions.
private struct ParameterEnumerable : IEnumerable<Parameter>
{
private InputActionState m_State;
private ParameterOverride m_Parameter;
private int m_MapIndex;
public ParameterEnumerable(InputActionState state, ParameterOverride parameter, int mapIndex = -1)
{
m_State = state;
m_Parameter = parameter;
m_MapIndex = mapIndex;
}
public ParameterEnumerator GetEnumerator()
{
return new ParameterEnumerator(m_State, m_Parameter, m_MapIndex);
}
IEnumerator<Parameter> IEnumerable<Parameter>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
private struct ParameterEnumerator : IEnumerator<Parameter>
{
private InputActionState m_State;
private int m_MapIndex;
private int m_BindingCurrentIndex;
private int m_BindingEndIndex;
private int m_InteractionCurrentIndex;
private int m_InteractionEndIndex;
private int m_ProcessorCurrentIndex;
private int m_ProcessorEndIndex;
private InputBinding m_BindingMask;
private Type m_ObjectType;
private string m_ParameterName;
private bool m_MayBeInteraction;
private bool m_MayBeProcessor;
private bool m_MayBeComposite;
private bool m_CurrentBindingIsComposite;
private object m_CurrentObject;
private FieldInfo m_CurrentParameter;
public ParameterEnumerator(InputActionState state, ParameterOverride parameter, int mapIndex = -1)
: this()
{
m_State = state;
m_ParameterName = parameter.parameter;
m_MapIndex = mapIndex;
m_ObjectType = parameter.objectType;
m_MayBeComposite = m_ObjectType == null || typeof(InputBindingComposite).IsAssignableFrom(m_ObjectType);
m_MayBeProcessor = m_ObjectType == null || typeof(InputProcessor).IsAssignableFrom(m_ObjectType);
m_MayBeInteraction = m_ObjectType == null || typeof(IInputInteraction).IsAssignableFrom(m_ObjectType);
m_BindingMask = parameter.bindingMask;
Reset();
}
private bool MoveToNextBinding()
{
// Find a binding that matches our mask.
while (true)
{
++m_BindingCurrentIndex;
if (m_BindingCurrentIndex >= m_BindingEndIndex)
return false; // Reached the end.
ref var binding = ref m_State.GetBinding(m_BindingCurrentIndex);
ref var bindingState = ref m_State.GetBindingState(m_BindingCurrentIndex);
// Skip any binding that has no associated objects with parameters.
if (bindingState.processorCount == 0 && bindingState.interactionCount == 0 && !binding.isComposite)
continue;
// If we're only looking for composites, skip any binding that isn't one.
if (m_MayBeComposite && !m_MayBeProcessor && !m_MayBeInteraction && !binding.isComposite)
continue;
// If we're only looking for processors, skip any that hasn't got any.
if (m_MayBeProcessor && !m_MayBeComposite && !m_MayBeInteraction && bindingState.processorCount == 0)
continue;
// If we're only looking for interactions, skip any that hasn't got any.
if (m_MayBeInteraction && !m_MayBeComposite && !m_MayBeProcessor && bindingState.interactionCount == 0)
continue;
if (m_BindingMask.Matches(ref binding))
{
if (m_MayBeComposite)
m_CurrentBindingIsComposite = binding.isComposite;
// Reset interaction and processor count.
m_ProcessorCurrentIndex = bindingState.processorStartIndex - 1; // Minus one to account for first MoveNext().
m_ProcessorEndIndex = bindingState.processorStartIndex + bindingState.processorCount;
m_InteractionCurrentIndex = bindingState.interactionStartIndex - 1; // Minus one to account for first MoveNext().
m_InteractionEndIndex = bindingState.interactionStartIndex + bindingState.interactionCount;
return true;
}
}
}
private bool MoveToNextInteraction()
{
while (m_InteractionCurrentIndex < m_InteractionEndIndex)
{
++m_InteractionCurrentIndex;
if (m_InteractionCurrentIndex == m_InteractionEndIndex)
break;
var interaction = m_State.interactions[m_InteractionCurrentIndex];
if (FindParameter(interaction))
return true;
}
return false;
}
private bool MoveToNextProcessor()
{
while (m_ProcessorCurrentIndex < m_ProcessorEndIndex)
{
++m_ProcessorCurrentIndex;
if (m_ProcessorCurrentIndex == m_ProcessorEndIndex)
break;
var processor = m_State.processors[m_ProcessorCurrentIndex];
if (FindParameter(processor))
return true;
}
return false;
}
private bool FindParameter(object instance)
{
if (m_ObjectType != null && !m_ObjectType.IsInstanceOfType(instance))
return false;
var field = instance.GetType().GetField(m_ParameterName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
if (field == null)
return false;
m_CurrentParameter = field;
m_CurrentObject = instance;
return true;
}
public bool MoveNext()
{
while (true)
{
if (m_MayBeInteraction && MoveToNextInteraction())
return true;
if (m_MayBeProcessor && MoveToNextProcessor())
return true;
if (!MoveToNextBinding())
return false;
if (m_MayBeComposite && m_CurrentBindingIsComposite)
{
var compositeIndex = m_State.GetBindingState(m_BindingCurrentIndex).compositeOrCompositeBindingIndex;
var composite = m_State.composites[compositeIndex];
if (FindParameter(composite))
return true;
}
}
}
public unsafe void Reset()
{
m_CurrentObject = default;
m_CurrentParameter = default;
m_InteractionCurrentIndex = default;
m_InteractionEndIndex = default;
m_ProcessorCurrentIndex = default;
m_ProcessorEndIndex = default;
m_CurrentBindingIsComposite = default;
if (m_MapIndex < 0)
{
m_BindingCurrentIndex = -1; // Account for first MoveNext().
m_BindingEndIndex = m_State.totalBindingCount;
}
else
{
m_BindingCurrentIndex = m_State.mapIndices[m_MapIndex].bindingStartIndex - 1; // Account for first MoveNext().
m_BindingEndIndex = m_State.mapIndices[m_MapIndex].bindingStartIndex + m_State.mapIndices[m_MapIndex].bindingCount;
}
}
public Parameter Current => new Parameter
{
instance = m_CurrentObject,
field = m_CurrentParameter,
bindingIndex = m_BindingCurrentIndex,
};
object IEnumerator.Current => Current;
public void Dispose()
{
}
}
internal struct ParameterOverride
{
public string objectRegistrationName; // Optional. Such as "hold" or "scale".
public string parameter;
public InputBinding bindingMask;
public PrimitiveValue value;
public Type objectType =>
InputProcessor.s_Processors.LookupTypeRegistration(objectRegistrationName)
?? InputInteraction.s_Interactions.LookupTypeRegistration(objectRegistrationName)
?? InputBindingComposite.s_Composites.LookupTypeRegistration(objectRegistrationName);
public ParameterOverride(string parameterName, InputBinding bindingMask, PrimitiveValue value = default)
{
var colonIndex = parameterName.IndexOf(':');
if (colonIndex < 0)
{
objectRegistrationName = null;
parameter = parameterName;
}
else
{
objectRegistrationName = parameterName.Substring(0, colonIndex);
parameter = parameterName.Substring(colonIndex + 1);
}
this.bindingMask = bindingMask;
this.value = value;
}
public ParameterOverride(string objectRegistrationName, string parameterName, InputBinding bindingMask, PrimitiveValue value = default)
{
this.objectRegistrationName = objectRegistrationName;
this.parameter = parameterName;
this.bindingMask = bindingMask;
this.value = value;
}
// Find the *most specific* override to apply to the given parameter.
public static ParameterOverride? Find(InputActionMap actionMap, ref InputBinding binding, string parameterName, string objectRegistrationName)
{
// Look at level of map.
var overrideOnMap = Find(actionMap.m_ParameterOverrides, actionMap.m_ParameterOverridesCount, ref binding, parameterName,
objectRegistrationName);
// Look at level of asset (if present).
var asset = actionMap.asset;
var overrideOnAsset = asset != null
? Find(asset.m_ParameterOverrides, asset.m_ParameterOverridesCount, ref binding, parameterName,
objectRegistrationName)
: null;
return PickMoreSpecificOne(overrideOnMap, overrideOnAsset);
}
private static ParameterOverride? Find(ParameterOverride[] overrides, int overrideCount,
ref InputBinding binding, string parameterName, string objectRegistrationName)
{
ParameterOverride? result = null;
for (var i = 0; i < overrideCount; ++i)
{
ref var current = ref overrides[i];
if (!string.Equals(parameterName, current.parameter, StringComparison.OrdinalIgnoreCase))
continue; // Different parameter name.
if (!current.bindingMask.Matches(binding))
continue;
if (current.objectRegistrationName != null && !string.Equals(current.objectRegistrationName, objectRegistrationName,
StringComparison.OrdinalIgnoreCase))
continue;
if (result == null)
{
// First match.
result = current;
}
else
{
// Already have a match. See which one is more specific.
result = PickMoreSpecificOne(result, current);
}
}
return result;
}
private static ParameterOverride? PickMoreSpecificOne(ParameterOverride? first, ParameterOverride? second)
{
if (first == null)
return second;
if (second == null)
return first;
// Having an objectRegistrationName always wins vs not having one.
if (first.Value.objectRegistrationName != null && second.Value.objectRegistrationName == null)
return first;
if (second.Value.objectRegistrationName != null && first.Value.objectRegistrationName == null)
return second;
// Targeting a specific path always wins vs not doing so.
if (first.Value.bindingMask.effectivePath != null && second.Value.bindingMask.effectivePath == null)
return first;
if (second.Value.bindingMask.effectivePath != null && first.Value.bindingMask.effectivePath == null)
return second;
// Targeting a specific actions always wins vs not doing so.
if (first.Value.bindingMask.action != null && second.Value.bindingMask.action == null)
return first;
if (second.Value.bindingMask.action != null && first.Value.bindingMask.action == null)
return second;
// Undecided. First wins by default.
return first;
}
}
}
}