Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question How to Apply Parameter Override to action itself (rather than binding)?

Discussion in 'Input System' started by CaseyHofland, Aug 17, 2022.

  1. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    565
    I've got this action:
    Look.png

    And this binding:
    See.png

    They both have a ScaleVector2 Processor. If I call "GetParameterValue" and "SetParameterValue", I would expect to manipulate the Processor on the action itself, but instead it calls them on the binding.

    Code (CSharp):
    1.     private void OnEnable()
    2.     {
    3.         slider.onValueChanged.AddListener(Thing);
    4.  
    5.         slider.value = Mathf.RoundToInt(lookInput.action.GetParameterValue(scaleVector2ProcessorX) ?? default); // Returns 0.05f
    6.     }
    7.  
    8.     private void OnDisable()
    9.     {
    10.         slider.onValueChanged.RemoveListener(Thing);
    11.     }
    12.  
    13.     private void Thing(float value)
    14.     {
    15.         textMeshProUGUI.text = slider.value.ToString();
    16.         lookInput.action.ApplyParameterOverride(scaleVector2ProcessorX, value); // Sets the mouse processor
    17.         lookInput.action.ApplyParameterOverride(scaleVector2ProcessorY, value); // Sets the mouse processor
    18.     }
    Even more frustrating is the fact that if I change the processor on a binding that does not have its own processor, like gamepad, that still doesn't scale the processor on the mouse.

    Is there no way to manipulate the processor on the base action itself?
     
  2. andrew_oc

    andrew_oc

    Unity Technologies

    Joined:
    Apr 16, 2021
    Posts:
    77
    The issue is that the processor on the action is a lie! The editor lets you add processors to actions but behind the scenes, the processor gets added to each binding individually. In this case, Delta already has a ScaleVector2 processor, so that prevents the one from the action being added. Making this more complicated is the fact that the overload of GetParameterValue you've used here always returns the parameter from the first processor it matches, in this case, the X parameter from the ScaleVector2 processor on the Delta binding.

    You can still do what you want here though by using the overloads of GetParameterValue/ApplyParameterOverride that take a binding mask argument, but you have to do it for each binding individually. There's some examples of how to do that here https://docs.unity3d.com/Packages/c...manual/ActionBindings.html#setting-parameters, using either the InputBinding.MaskByGroup("...") method to choose bindings by control scheme, or new InputBinding("<device>/control") to target a specific binding.
     
  3. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    565
    Know that this next part is written out of nothing but love for the Input System - I only wish the best for it. But this API, and there is no nice way of saying this, is completely overcooked spaghetti.

    Trying to establish the problem and see if I could fix it myself, I found the reason for why applying parameter overrides is such an ordeal: it's utter chaos in there! There are internals everywhere locking me out at every turn, in this type-based day-and-age the code still uses strings to instantiate controls (making saving these settings unnecessarily confusing and cumbersome through guesswork string-manipulation), state of 1 object may be held inside the state of a completely different object (as is the case with InputBindings and ActionMaps). I used reflection to solve my use-case, partly, but I never want to go through it again. It gave me a lot of insight into your codebase and it drives me mad with frustration, because if there's one thing I can't understand, it's why.

    Let's start with processors. First of, if the processors on the InputAction in the editor are a lie, but you still need InputAction.processors + InputAction.bindings[0].processors (string, btw) to find out what processors binding 0 has, then what does this API even want me to do? The actual processor state is held by the action map... for... some reason... but wouldn't it make much more sense to make InputAction.bindings[0].processors return an actual collection of InputProcessors? It would make everyone's job easier, because now people can just modify that list instead of using an extension method that: is not performant, is unnecessary, takes agency away from the user, frankly doesn't make a whole lot of sense. Because the InputBinding already processes the input, so why can't it actually contain the processors? Nevermind if InputAction.processors is a lie, InputBinding.processors is a lie as well!

    Then I tried to do this: action.GetParameterValue(someExpression, action.bindings[0]), to get the parameter value of my first binding. But this doesn't work... because action.bindings[0] isn't a correct InputBinding mask. Now, maybe I'm missing some big picture... but if an InputBinding is fundamentally different from an InputBindingMask, then they should be 2 different types, so then who let this decision slip by unnoticed? It wouldn't be hard to fix too: you can still create an InputBindingMask type that can implicitly convert from InputBinding and just takes its id.

    This one doesn't really matter, but GetParameterValue<TObject, TValue>(...) does a completely unnecessary conversion (which is even stated in the comments of the method) where a simple
    return (TValue)value;
    would suffice. It's just weird to me that these kinds of decisions pass by code review.

    There's just a lot of places where strings are used when they absolutely shouldn't. What's nicer: this?
    Code (CSharp):
    1. var action = new Action(processors: "invertVector2(invertX=false)");
    2. action.ApplyParameterOverride("invertX=true"); // tough luck if you have a binding with an invertVector2.
    Or this?
    Code (CSharp):
    1. var action = new InputAction(inputProcessors: new[] { new InvertVector2() {invertX: false} });
    2. action.inputProcessors[0].invertX = true; // handles its bindings by setting some internal m_ActionInputProcessors in the its InputBindings (or preferably process the input through the action before the binding processes it)

    Code (CSharp):
    1. #nullable enable
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using System.Linq.Expressions;
    7. using System.Reflection;
    8. using System.Text.RegularExpressions;
    9. using UnityEngine.InputSystem;
    10. using UnityEngine.InputSystem.Utilities;
    11.  
    12. public static partial class InputActionRebindingExtensions
    13. {
    14.     private const BindingFlags privateFlags = BindingFlags.Instance | BindingFlags.NonPublic;
    15.  
    16.     private static readonly MethodInfo GetOrCreateActionMap = typeof(InputAction).GetMethod(nameof(GetOrCreateActionMap), privateFlags);
    17.     private static readonly MethodInfo ResolveBindingsIfNecessary = typeof(InputActionMap).GetMethod(nameof(ResolveBindingsIfNecessary), privateFlags);
    18.     private static readonly MethodInfo ExtractParameterOverride = typeof(UnityEngine.InputSystem.InputActionRebindingExtensions).GetMethod(nameof(ExtractParameterOverride), BindingFlags.Static | BindingFlags.NonPublic);
    19.     private static readonly FieldInfo m_ParameterOverrides = typeof(InputActionMap).GetField(nameof(m_ParameterOverrides), privateFlags);
    20.     private static readonly FieldInfo m_ParameterOverridesCount = typeof(InputActionMap).GetField(nameof(m_ParameterOverridesCount), privateFlags);
    21.     private static readonly Type ArrayHelpers = Type.GetType("UnityEngine.InputSystem.Utilities." + nameof(ArrayHelpers) + ", Unity.InputSystem");
    22.     private static readonly Type ParameterOverride = Type.GetType("UnityEngine.InputSystem.InputActionRebindingExtensions+" + nameof(ParameterOverride) + ", Unity.InputSystem");
    23.     private static MethodInfo? _AppendWithCapacity;
    24.     private static MethodInfo AppendWithCapacity
    25.     {
    26.         get
    27.         {
    28.             if (_AppendWithCapacity == null)
    29.             {
    30.                 var ms = Array.FindAll(ArrayHelpers.GetMethods(BindingFlags.Static | BindingFlags.Public), x => x.Name == nameof(AppendWithCapacity)
    31.                     && x.IsGenericMethod
    32.                     && x.GetParameters().Length == 4);
    33.  
    34.                 _AppendWithCapacity = ms.Length == 1
    35.                     ? ms[0]
    36.                     : ArrayHelpers.GetMethod(nameof(AppendWithCapacity), BindingFlags.Static | BindingFlags.Public); // throws to make sure we get an error if more than one AppendWithCapacity method was found.
    37.  
    38.                 _AppendWithCapacity = _AppendWithCapacity.MakeGenericMethod(ParameterOverride);
    39.             }
    40.  
    41.             return _AppendWithCapacity;
    42.         }
    43.     }
    44.     private static readonly FieldInfo m_State = typeof(InputActionMap).GetField(nameof(m_State), privateFlags);
    45.     private static readonly FieldInfo m_MapIndexInState = typeof(InputActionMap).GetField(nameof(m_MapIndexInState), privateFlags);
    46.     private static readonly Type ParameterEnumerable = Type.GetType("UnityEngine.InputSystem.InputActionRebindingExtensions+" + nameof(ParameterEnumerable) + ", Unity.InputSystem");
    47.     private static readonly Type Parameter = Type.GetType("UnityEngine.InputSystem.InputActionRebindingExtensions+" + nameof(Parameter) + ", Unity.InputSystem");
    48.     private static readonly FieldInfo bindingIndex = Parameter.GetField(nameof(bindingIndex));
    49.     private static readonly FieldInfo field = Parameter.GetField(nameof(field));
    50.     private static readonly FieldInfo instance = Parameter.GetField(nameof(instance));
    51.     private static readonly FieldInfo bindingMask = ParameterOverride.GetField(nameof(bindingMask));
    52.     private static readonly PropertyInfo valuePtr = typeof(PrimitiveValue).GetProperty(nameof(valuePtr), privateFlags);
    53.     private static readonly FieldInfo objectRegistrationName = ParameterOverride.GetField(nameof(objectRegistrationName));
    54.     private static readonly FieldInfo parameter = ParameterOverride.GetField(nameof(parameter));
    55.     private static readonly object s_Processors = typeof(InputProcessor).GetField(nameof(s_Processors), BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
    56.     private static readonly MethodInfo FindProcessorNameForType = s_Processors.GetType().GetMethod(nameof(FindNameForType));
    57.     private static readonly Type InputInteraction = Type.GetType("UnityEngine.InputSystem.InputInteraction, Unity.InputSystem");
    58.     private static readonly object s_Interactions = InputInteraction.GetField(nameof(s_Interactions), BindingFlags.Static | BindingFlags.Public).GetValue(null);
    59.     private static readonly MethodInfo FindInteractionNameForType = s_Interactions.GetType().GetMethod(nameof(FindNameForType));
    60.     private static readonly object s_Composites = typeof(InputBindingComposite).GetField(nameof(s_Composites), BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
    61.     private static readonly MethodInfo FindCompositeNameForType = s_Composites.GetType().GetMethod(nameof(FindNameForType));
    62.     private static readonly FieldInfo m_Processors = typeof(InputAction).GetField(nameof(m_Processors), privateFlags);
    63.     private static readonly FieldInfo m_BindingProcessors = typeof(InputBinding).GetField(nameof(m_Processors), privateFlags);
    64.  
    65.     private static readonly Regex bracketsRegex = new (@"(\(.*\))");
    66.     //private static readonly Regex bracketsRegex = new (@"((?:\(.*\))?,)");
    67.  
    68.     private static string FindNameForType(Type type)
    69.     {
    70.         string objectRegistrationName;
    71.         if (typeof(InputProcessor).IsAssignableFrom(type))
    72.         {
    73.             objectRegistrationName = (InternedString)FindProcessorNameForType.Invoke(s_Processors, new[] { type });
    74.         }
    75.         else if (typeof(IInputInteraction).IsAssignableFrom(type))
    76.         {
    77.             objectRegistrationName = (InternedString)FindInteractionNameForType.Invoke(s_Interactions, new[] { type });
    78.         }
    79.         else if (typeof(InputBindingComposite).IsAssignableFrom(type))
    80.         {
    81.             objectRegistrationName = (InternedString)FindCompositeNameForType.Invoke(s_Composites, new[] { type });
    82.         }
    83.         else
    84.         {
    85.             throw new ArgumentException($"Given type must be an {nameof(InputProcessor)}, {nameof(IInputInteraction)}, or {nameof(InputBindingComposite)} (was {type.Name})", nameof(type));
    86.         }
    87.  
    88.         return objectRegistrationName;
    89.     }
    90.  
    91.     public static TValue GetParameterValueAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Index index)
    92.         where TValue : struct
    93.         => GetParameter<TValue>(action.GetParameters(expression)[index]);
    94.  
    95.     public static TValue[] GetParameterValueAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Range range)
    96.         where TValue : struct
    97.         => Array.ConvertAll(action.GetParameters(expression)[range], parameter => GetParameter<TValue>(parameter));
    98.  
    99.     public static TValue GetParameterValueAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Index index, Index bindingIndex)
    100.         where TValue : struct
    101.         => GetParameter<TValue>(action.GetParameters(expression, action.bindings[bindingIndex])[index]);
    102.  
    103.     public static TValue[] GetParameterValueAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Range range, Index bindingIndex)
    104.         where TValue : struct
    105.         => Array.ConvertAll(action.GetParameters(expression, action.bindings[bindingIndex])[range], parameter => GetParameter<TValue>(parameter));
    106.  
    107.     public static void ApplyParameterOverrideAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, Index index)
    108.         where TValue : struct
    109.     {
    110.         var parameters = action.GetApplyableParameters(expression, value);
    111.         var actionParameterCount = action.GetParameterCount<TObject>();
    112.  
    113.         for (int i = 0; i < parameters.Length; i += actionParameterCount)
    114.         {
    115.             var segment = new ArraySegment<object>(parameters, i, actionParameterCount);
    116.             SetParameter(segment[index], value);
    117.         }
    118.     }
    119.  
    120.     public static void ApplyParameterOverrideAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, Range range)
    121.         where TValue : struct
    122.     {
    123.         var parameters = action.GetApplyableParameters(expression, value);
    124.         var actionParameterCount = action.GetParameterCount<TObject>();
    125.  
    126.         for (int i = 0; i < parameters.Length; i += actionParameterCount)
    127.         {
    128.             var segment = new ArraySegment<object>(parameters, i, actionParameterCount);
    129.             Array.ForEach(segment[range].Array, parameter => SetParameter(parameter, value));
    130.         }
    131.     }
    132.  
    133.     public static void ApplyParameterOverrideAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, Index index, Index bindingIndex)
    134.         where TValue : struct
    135.         => SetParameter(action.GetApplyableParameters(expression, value, action.bindings[bindingIndex])[index], value);
    136.  
    137.     public static void ApplyParameterOverrideAt<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, Range range, Index bindingIndex)
    138.         where TValue : struct
    139.         => Array.ForEach(action.GetApplyableParameters(expression, value, action.bindings[bindingIndex])[range], parameter => SetParameter(parameter, value));
    140.  
    141.     public static int GetParameterCount<TObject>(this InputAction action)
    142.     {
    143.         if (typeof(InputProcessor).IsAssignableFrom(typeof(TObject)))
    144.         {
    145.             var processors = bracketsRegex.Replace(action.processors, string.Empty).Split(',');
    146.             return Array.FindAll(processors, processor => InputSystem.TryGetProcessor(processor) == typeof(TObject)).Length;
    147.         }
    148.         else if (typeof(IInputInteraction).IsAssignableFrom(typeof(TObject)))
    149.         {
    150.             var interactions = bracketsRegex.Replace(action.interactions, string.Empty).Split(',');
    151.             return Array.FindAll(interactions, interaction => InputSystem.TryGetInteraction(interaction) == typeof(TObject)).Length;
    152.         }
    153.  
    154.         return default;
    155.     }
    156.  
    157.     public static int GetParameterCount<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, Index bindingIndex)
    158.         where TValue : struct
    159.         => action.GetParameters(expression, action.bindings[bindingIndex]).Length;
    160.  
    161.     private static object[] GetParameters<TObject>(this InputAction action, InputActionMap actionMap, object parameterOverride, bool returnActionParameters)
    162.     {
    163.         var allParameters = actionMap.GetParameterEnumerable(parameterOverride, out _, out _);
    164.         var actionParameterCount = action.GetParameterCount<TObject>();
    165.  
    166.         object[] parameters;
    167.         if (returnActionParameters)
    168.         {
    169.             parameters = (from parameter in allParameters
    170.                          group parameter by (int)bindingIndex.GetValue(parameter) into groupedParameters
    171.                          from parameter in groupedParameters.TakeLast(actionParameterCount)
    172.                          select parameter).ToArray();
    173.         }
    174.         else
    175.         {
    176.             parameters = allParameters.SkipLast(actionParameterCount).ToArray();
    177.         }
    178.  
    179.         return parameters;
    180.     }
    181.  
    182.     private static object[] GetParameters<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, InputBinding binding = default)
    183.     {
    184.         var returnActionParameters = binding == default;
    185.         var bindingMask = returnActionParameters
    186.             ? new InputBinding(binding.path, action.name)
    187.             : new InputBinding { id = binding.id };
    188.  
    189.         var actionMap = (InputActionMap)GetOrCreateActionMap.Invoke(action, null);
    190.         ResolveBindingsIfNecessary.Invoke(actionMap, null);
    191.  
    192.         var ExtractParameterOverride = InputActionRebindingExtensions.ExtractParameterOverride.MakeGenericMethod(typeof(TObject), typeof(TValue));
    193.         var parameterOverride = ExtractParameterOverride.Invoke(null, new object[] { expression, bindingMask, ExtractParameterOverride.GetParameters()[2].DefaultValue });
    194.  
    195.         return action.GetParameters<TObject>(actionMap, parameterOverride, returnActionParameters);
    196.     }
    197.  
    198.     private static object[] GetApplyableParameters<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expression, TValue value, InputBinding binding = default)
    199.         where TValue : struct
    200.     {
    201.         var returnActionParameters = binding == default;
    202.         var bindingMask = returnActionParameters
    203.             ? new InputBinding(binding.path, action.name)
    204.             : new InputBinding { id = binding.id };
    205.  
    206.         var actionMap = (InputActionMap)GetOrCreateActionMap.Invoke(action, null);
    207.         ResolveBindingsIfNecessary.Invoke(actionMap, null);
    208.  
    209.         var ExtractParameterOverride = InputActionRebindingExtensions.ExtractParameterOverride.MakeGenericMethod(typeof(TObject), typeof(TValue));
    210.         var parameterOverride = ExtractParameterOverride.Invoke(null, new object[] { expression, bindingMask, PrimitiveValue.From(value) });
    211.  
    212.         UpdateParameterOverrides(actionMap, parameterOverride);
    213.  
    214.         return action.GetParameters<TObject>(actionMap, parameterOverride, returnActionParameters);
    215.     }
    216.  
    217.     private static TValue GetParameter<TValue>(object parameter)
    218.         where TValue : struct
    219.     {
    220.         object instance = InputActionRebindingExtensions.instance.GetValue(parameter);
    221.         var field = (FieldInfo)InputActionRebindingExtensions.field.GetValue(parameter);
    222.         var value = field.GetValue(instance);
    223.         return (TValue)value;
    224.     }
    225.  
    226.     private static void SetParameter<TValue>(object parameter, TValue value)
    227.         where TValue : struct
    228.     {
    229.         object instance = InputActionRebindingExtensions.instance.GetValue(parameter);
    230.         var field = (FieldInfo)InputActionRebindingExtensions.field.GetValue(parameter);
    231.         field.SetValue(instance, value);
    232.     }
    233.  
    234.     private static void UpdateParameterOverrides(InputActionMap actionMap, object parameterOverride)
    235.     {
    236.         var parameterOverrides = m_ParameterOverrides.GetValue(actionMap) as Array;
    237.         var parameterOverridesCount = (int)m_ParameterOverridesCount.GetValue(actionMap);
    238.  
    239.         if (parameterOverrides != null)
    240.         {
    241.             // Try to find existing override and opt out if we find it.
    242.             var objectRegistrationName = (string)InputActionRebindingExtensions.objectRegistrationName.GetValue(parameterOverride);
    243.             var parameter = (string)InputActionRebindingExtensions.parameter.GetValue(parameterOverride);
    244.             var bindingMask = (InputBinding)InputActionRebindingExtensions.bindingMask.GetValue(parameterOverride);
    245.             for (int i = 0; i < parameterOverridesCount; i++)
    246.             {
    247.                 var refParameterOverride = parameterOverrides.GetValue(i);
    248.                 var refObjectRegistrationName = (string)InputActionRebindingExtensions.objectRegistrationName.GetValue(refParameterOverride);
    249.                 var refParameter = (string)InputActionRebindingExtensions.parameter.GetValue(refParameterOverride);
    250.                 var refBindingMask = (InputBinding)InputActionRebindingExtensions.bindingMask.GetValue(parameterOverride);
    251.  
    252.                 if (string.Equals(refObjectRegistrationName, objectRegistrationName, StringComparison.OrdinalIgnoreCase)
    253.                     && string.Equals(refParameter, parameter, StringComparison.OrdinalIgnoreCase)
    254.                     && refBindingMask == bindingMask)
    255.                 {
    256.                     parameterOverrides.SetValue(parameterOverride, i);
    257.                     m_ParameterOverrides.SetValue(actionMap, parameterOverrides);
    258.                     return;
    259.                 }
    260.             }
    261.         }
    262.  
    263.         // The override doesn't exist, so add a new override.
    264.         var arguments = new object?[] { parameterOverrides, parameterOverridesCount, parameterOverride, AppendWithCapacity.GetParameters()[3].DefaultValue };
    265.         AppendWithCapacity.Invoke(null, arguments);
    266.         m_ParameterOverrides.SetValue(actionMap, arguments[0]);
    267.         m_ParameterOverridesCount.SetValue(actionMap, arguments[1]);
    268.     }
    269.  
    270.     private static IEnumerable<object> GetParameterEnumerable(this InputActionMap actionMap, object parameterOverride, out object state, out int mapIndex)
    271.     {
    272.         state = m_State.GetValue(actionMap);
    273.         mapIndex = (int)m_MapIndexInState.GetValue(actionMap);
    274.         return ((IEnumerable)Activator.CreateInstance(ParameterEnumerable, new object[] { state, parameterOverride, mapIndex })).OfType<object>();
    275.     }
    276. }
    277.  
     
    valentin56610 likes this.
  4. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    565
    This was not true, because it will still set the ScaleVector2 that was supplied by the InputAction setting both my mouse(x=0.05,y=0.05) and global(x=2,y=2) to something like mouse(x=3,y=3) global(x=3,y=3).

    It also wasn't my problem. I want mouse(x=0.05,y=0.05) global(x=3,y=3) but there is no way to distinguish the InputAction's supplied processor from the InputBinding one. The only way I could find in my own solution was counting occurrences of the type in the InputAction.processors string, I don't understand why there is no place that this information is stored, especially since it would be so easy to do with references: InputAction.inputProcessors and you're there. Things just... don't seem connected the way they should.
     
  5. JeffreyBennett

    JeffreyBennett

    Joined:
    May 18, 2017
    Posts:
    22
    Ok, I'm just finding this, and it looks like really bad news for me, since I am trying to do exactly the thing you appear to be trying to do, @CaseyHofland

    Namely: I'd like to give my user a menu button that can be clicked which would invert the Y axis on the Look action.

    It's the reason I put in the question here a little while ago. I was thinking it'd be only a line or two of code more than what I have there, but looking at your solution (behind the spoiler, above) I'm thinking I'm probably not going to be able to do this at all in Unity's New Input System.

    I assume your solution works for you, but it will take me a long time to go through that and try to apply it to my situation. I'm still very grateful that you posted it! Also, I think your notes that nobody asked for are pretty helpful to me, if dispiriting, so thanks also for those!

    Unity, please make this much simpler! I wouldn't think this is a big ask, to be able to invert the look action from inside the game. I've seen tons of games do this.
     
    valentin56610 likes this.
  6. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    565
    Hey @JeffreyBennett , I came up with a much better solution using extension methods. This I found is the way too go forward. Going through the whole reflection path is 1: a slippery slope because who knows how the API will change and 2: any small change I wanted to make takes days. Extension methods give me the control I need to implement input changes how I see fit.

    This here will latch onto InputActions to allow for that list I was talking about in my rant. Instead of calling
    action.processors
    you can simply call
    action.Reprocessors()
    and manipulate the returned list, than process the value in an InputAction.CallbackContext doing
    context.ReadRevalue<float>();

    There's no check in place to see if the processors are of the right type so be mindful of that.

    Code (CSharp):
    1. #nullable enable
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.InputSystem;
    5.  
    6. namespace UnityExtras.InputSystem
    7. {
    8.     public static partial class InputActionExtensions
    9.     {
    10.         [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    11.         private static void ClearReprocessors()
    12.         {
    13.             actionReprocessors.Clear();
    14.             bindingReprocessors.Clear();
    15.         }
    16.  
    17.         private static Dictionary<InputAction, List<InputProcessor>> actionReprocessors = new();
    18.         private static Dictionary<InputBinding, List<InputProcessor>> bindingReprocessors = new();
    19.  
    20.         public static List<InputProcessor> Reprocessors(this InputAction inputAction)
    21.         {
    22.             actionReprocessors.TryAdd(inputAction, new());
    23.             return actionReprocessors[inputAction];
    24.         }
    25.  
    26.         public static List<InputProcessor> Reprocessors(this InputBinding inputBinding)
    27.         {
    28.             bindingReprocessors.TryAdd(inputBinding, new());
    29.             return bindingReprocessors[inputBinding];
    30.         }
    31.  
    32.         public static TValue ReadRevalue<TValue>(this InputAction.CallbackContext context)
    33.             where TValue : struct
    34.         {
    35.             var value = context.ReadValue<TValue>();
    36.  
    37.             // Process the actions reprocessors.
    38.             if (actionReprocessors.TryGetValue(context.action, out var inputProcessors))
    39.             {
    40.                 for (int i = 0; i < inputProcessors.Count; i++)
    41.                 {
    42.                     var inputProcessor = (InputProcessor<TValue>)inputProcessors[i];
    43.                     value = inputProcessor.Process(value, context.control);
    44.                 }
    45.             }
    46.  
    47.             // Process the bindings reprocessors.
    48.             var bindingIndex = context.action.controls.IndexOf(control => control == context.control); // If this ever causes issues, use reflection to get context.bindingIndex instead.
    49.             var binding = context.action.bindings[bindingIndex];
    50.             if (bindingReprocessors.TryGetValue(binding, out inputProcessors))
    51.             {
    52.                 for (int i = 0; i < inputProcessors.Count; i++)
    53.                 {
    54.                     var inputProcessor = (InputProcessor<TValue>)inputProcessors[i];
    55.                     value = inputProcessor.Process(value, context.control);
    56.                 }
    57.             }
    58.  
    59.             return value;
    60.         }
    61.     }
    62. }
    63.  

    As a little bonus, it's time I won't get back anyway, here is a serializable processor! It's pretty unoptimized but since this is only really useful in option menus it shouldn't matter as it's not called upon often, nor manipulated by the dozens.

    Code (CSharp):
    1. #nullable enable
    2. using System;
    3. using UnityEngine;
    4. using UnityEngine.InputSystem;
    5.  
    6. namespace UnityExtras.InputSystem
    7. {
    8.     [Serializable]
    9.     public struct Processor
    10.     {
    11.         [SerializeField] private string? _inputProcessorAssemblyQualifiedName;
    12.         [SerializeField] private string[]? _inputProcessorValueStrings;
    13.  
    14.         public InputProcessor? inputProcessor
    15.         {
    16.             get
    17.             {
    18.                 Type? processorType;
    19.                 if (string.IsNullOrEmpty(_inputProcessorAssemblyQualifiedName) || (processorType = Type.GetType(_inputProcessorAssemblyQualifiedName)) == null)
    20.                 {
    21.                     return null;
    22.                 }
    23.  
    24.                 var inputProcessor = (InputProcessor)Activator.CreateInstance(processorType);
    25.  
    26.                 var processorFields = processorType.GetFields();
    27.                 for (int i = 0; i < processorFields.Length && i < _inputProcessorValueStrings?.Length; i++)
    28.                 {
    29.                     var processorField = processorFields[i];
    30.                     var processorValueString = _inputProcessorValueStrings[i];
    31.  
    32.                     try
    33.                     {
    34.                         var processorValue = Convert.ChangeType(processorValueString, processorField.FieldType);
    35.                         processorField.SetValue(inputProcessor, processorValue);
    36.                     }
    37.                     catch (Exception) { }
    38.                 }
    39.  
    40.                 return inputProcessor;
    41.             }
    42.             set
    43.             {
    44.                 var processorType = value?.GetType();
    45.                 _inputProcessorAssemblyQualifiedName = processorType?.AssemblyQualifiedName;
    46.  
    47.                 var processorFields = processorType?.GetFields();
    48.                 _inputProcessorValueStrings = new string[processorFields?.Length ?? 0];
    49.                 for (int i = 0; i < processorFields?.Length; i++)
    50.                 {
    51.                     var processorField = processorFields[i];
    52.                     _inputProcessorValueStrings[i] = processorField.GetValue(value).ToString();
    53.                 }
    54.             }
    55.         }
    56.  
    57.         public static implicit operator InputProcessor?(Processor processor) => processor.inputProcessor;
    58.         public static implicit operator Processor(InputProcessor? inputProcessor) => new() { inputProcessor = inputProcessor };
    59.     }
    60. }
    61.  
    Drawer (put in Editor folder yadayada you know the drill)
    Code (CSharp):
    1. #nullable enable
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using UnityEditor;
    6. using UnityEngine;
    7.  
    8. namespace UnityExtras.InputSystem.Editor
    9. {
    10. #pragma warning disable IDE0065 // Misplaced using directive
    11.     using InputSystem = UnityEngine.InputSystem.InputSystem;
    12. #pragma warning restore IDE0065 // Misplaced using directive
    13.  
    14.     [CustomPropertyDrawer(typeof(Processor))]
    15.     public class ProcessorDrawer : PropertyDrawer
    16.     {
    17.         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    18.         {
    19.             EditorGUI.BeginProperty(position, label, property);
    20.  
    21.             SerializedProperty _inputProcessorAssemblyQualifiedName = property.FindPropertyRelative(nameof(_inputProcessorAssemblyQualifiedName));
    22.  
    23.             position.height = EditorGUIUtility.singleLineHeight;
    24.             using var stringCheck = new EditorGUI.ChangeCheckScope();
    25.             var processorStrings = InputSystem.ListProcessors().ToArray();
    26.             var processorTypes = Array.ConvertAll(processorStrings, processorString => InputSystem.TryGetProcessor(processorString));
    27.             var index = Array.IndexOf(processorTypes, Type.GetType(_inputProcessorAssemblyQualifiedName.stringValue));
    28.             index = EditorGUI.Popup(position, index == -1 ? " " : processorStrings[index], index, processorStrings);
    29.             if (stringCheck.changed)
    30.             {
    31.                 var processorType = processorTypes[index];
    32.                 _inputProcessorAssemblyQualifiedName.stringValue = processorType?.AssemblyQualifiedName;
    33.             }
    34.  
    35.             property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, label, true);
    36.             if (property.isExpanded)
    37.             {
    38.                 EditorGUI.indentLevel++;
    39.  
    40.                 var target = property.serializedObject.targetObject;
    41.                 var value = fieldInfo.GetValue(target);
    42.  
    43.                 if (value != null)
    44.                 {
    45.                     if (!(value is Processor processor)
    46.                         && value is IList<Processor> processors)
    47.                     {
    48.                         var path = property.propertyPath;
    49.                         index = int.Parse(path.Substring(index = path.LastIndexOf('[') + 1, path.LastIndexOf(']') - index));
    50.                         processor = processors[index];
    51.                     }
    52.  
    53.                     SerializedProperty _inputProcessorValueStrings = property.FindPropertyRelative(nameof(_inputProcessorValueStrings));
    54.  
    55.                     if (stringCheck.changed)
    56.                     {
    57.                         for (int i = 0; i < _inputProcessorValueStrings.arraySize; i++)
    58.                         {
    59.                             _inputProcessorValueStrings.GetArrayElementAtIndex(i).stringValue = null;
    60.                         }
    61.  
    62.                         property.serializedObject.ApplyModifiedProperties();
    63.                     }
    64.  
    65.                     var processorFields = processor.inputProcessor?.GetType().GetFields();
    66.                     _inputProcessorValueStrings.ClearArray();
    67.                     _inputProcessorValueStrings.arraySize = processorFields?.Length ?? 0;
    68.  
    69.                     for (int i = 0; i < processorFields?.Length; i++)
    70.                     {
    71.                         var processorField = processorFields[i];
    72.                         _inputProcessorValueStrings.InsertArrayElementAtIndex(i);
    73.  
    74.                         value = processorField.GetValue(processor.inputProcessor);
    75.                         position.y += EditorGUIUtility.singleLineHeight + 2f;
    76.  
    77.                         switch (value)
    78.                         {
    79.                             case bool:
    80.                                 value = EditorGUI.Toggle(position, processorField.Name, (bool)value);
    81.                                 break;
    82.                             case float:
    83.                             case double:
    84.                             case decimal:
    85.                                 value = EditorGUI.FloatField(position, processorField.Name, (float)value);
    86.                                 break;
    87.                             case byte:
    88.                             case sbyte:
    89.                             case short:
    90.                             case ushort:
    91.                             case int:
    92.                             case uint:
    93.                                 value = EditorGUI.IntField(position, processorField.Name, (int)value);
    94.                                 break;
    95.                             case long:
    96.                             case ulong:
    97.                                 value = EditorGUI.LongField(position, processorField.Name, (long)value);
    98.                                 break;
    99.                             case char:
    100.                             case string:
    101.                                 value = EditorGUI.TextField(position, processorField.Name, (string)value);
    102.                                 break;
    103.                         }
    104.  
    105.                         _inputProcessorValueStrings.GetArrayElementAtIndex(i).stringValue = value.ToString();
    106.                     }
    107.                 }
    108.  
    109.                 EditorGUI.indentLevel--;
    110.             }
    111.  
    112.             EditorGUI.EndProperty();
    113.         }
    114.  
    115.         public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    116.         {
    117.             var height = base.GetPropertyHeight(property, label);
    118.  
    119.             if (property.isExpanded)
    120.             {
    121.                 var target = property.serializedObject.targetObject;
    122.                 var value = fieldInfo.GetValue(target);
    123.  
    124.                 if (value != null)
    125.                 {
    126.                     if (!(value is Processor processor)
    127.                         && value is IList<Processor> processors)
    128.                     {
    129.                         var path = property.propertyPath;
    130.                         int index = int.Parse(path.Substring(index = path.LastIndexOf('[') + 1, path.LastIndexOf(']') - index));
    131.                         processor = processors[index];
    132.                     }
    133.  
    134.                     height += (EditorGUIUtility.singleLineHeight + 2f) * processor.inputProcessor?.GetType().GetFields().Length ?? 0;
    135.                 }
    136.             }
    137.  
    138.             return height;
    139.         }
    140.     }
    141. }
    142.  

    Code (CSharp):
    1. public class Test : MonoBehaviour
    2. {
    3.     public InputInteractionReference input;
    4.     [SerializeField] private List<Processor> processors = new();
    5.  
    6.     private void OnEnable()
    7.     {
    8.         input.action.Reprocessors().AddRange(processors);
    9.         input.action.performed += Performed;
    10.     }
    11.  
    12.  
    13.     private void Update()
    14.     {
    15.         input.action.Reprocessors().Clear();
    16.         input.action.Reprocessors().AddRange(processors);
    17.     }
    18.  
    19.     private void Performed(InputAction.CallbackContext context)
    20.     {
    21.         Debug.Log(context.ReadRevalue<float>());
    22.     }
    23. }
     
  7. JeffreyBennett

    JeffreyBennett

    Joined:
    May 18, 2017
    Posts:
    22
    Oh, how very cool!

    Thank you for sharing! I need to go through this and check it out.

    My crappy solution was to duplicate my player control action map and change just the one setting for the processor on the Look action. Now, when my player selects the inverted look controls I change over to the "Inverted" action map (that is identical to the main Player action map in all ways except for its name and the inverted processor on the Look action).

    Ugh.
     
  8. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    565
    :eek: My condolences
     
  9. TheAverageHuman

    TheAverageHuman

    Joined:
    Dec 16, 2021
    Posts:
    2
    Does anybody have the code (Just the code and a simple and in-depth explanation of what it does) to change an inputAction to an inputActionReference?
     
  10. CaseyHofland

    CaseyHofland

    Joined:
    Mar 18, 2016
    Posts:
    565
    Just put your input action in an input action asset (via copy and paste), change InputAction to InputActionReference, select your reference. Ez

    All it does is reference an InputAction instead of embedding it. It just allows you to stay more organized: just call InputActionReference.action to work with InputAction in code
     
  11. valentin56610

    valentin56610

    Joined:
    Jan 22, 2019
    Posts:
    148
    Damn, this really hurts. Who designed this? Like seriously, I'm trying to override my processor for my Vector2 at runtime and I can't even do that! Jesus I've made a whole multiplayer game, and I cannot edit a processor for my player input! WTF!