Search Unity

Feature Request Rebinding Extension Methods

Discussion in 'Input System' started by _geo__, Feb 4, 2023.

  1. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,336
    Hi all,

    I have been wrapping my head around how to properly save user binding overrides. In the docs I read that one can use the id property of a binding (GUID) to reference them. So I am storing the overridePath and the guid and then use those to identify the binding and override. This is working fine.

    However the InputActionRebindingExtensions class does not seem to have a nice API to support this ID based workflow (hence my feature request). Here is what I use to make this easier.

    I would love to see those added to the InputActionRebindingExtensions:
    Code (CSharp):
    1.  
    2. public static class InputActionRebindingExtensionsExtensions
    3. {
    4.    /// <summary>
    5.    /// Find the binding by GUI in the given input actions asset.<br />
    6.    /// Returns true if found, false otherwise.
    7.    /// </summary>
    8.    /// <param name="inputActionAsset"></param>
    9.    /// <param name="bindingId"></param>
    10.    /// <param name="binding">The out variable taking the found binding.</param>
    11.    /// <returns>Returns true if found, false otherwise.</returns>
    12.    public static bool FindBinding(this InputActionAsset inputActionAsset, string bindingId, out InputBinding binding)
    13.    {
    14.        if (string.IsNullOrEmpty(bindingId))
    15.        {
    16.            binding = default;
    17.            return false;
    18.        }
    19.  
    20.        int mapCount = inputActionAsset.actionMaps.Count;
    21.        for (int m = 0; m < mapCount; m++)
    22.        {
    23.            var map = inputActionAsset.actionMaps[m];
    24.  
    25.            int bindingCount = map.bindings.Count;
    26.            for (int b = 0; b < bindingCount; b++)
    27.            {
    28.                if (map.bindings[b].id.ToString() == bindingId)
    29.                {
    30.                    binding = map.bindings[b];
    31.                    return true;
    32.                }
    33.            }
    34.        }
    35.  
    36.        binding = default;
    37.        return false;
    38.    }
    39.  
    40.    /// <summary>
    41.    /// Finds the action of the given binding id in the given input actions asset.<br />
    42.    /// </summary>
    43.    /// <param name="inputActionAsset"></param>
    44.    /// <param name="bindingId"></param>
    45.    /// <returns></returns>
    46.    public static InputAction GetActionOfBinding(this InputActionAsset inputActionAsset, string bindingId)
    47.    {
    48.        if (string.IsNullOrEmpty(bindingId))
    49.        {
    50.            return null;
    51.        }
    52.  
    53.        int mapCount = inputActionAsset.actionMaps.Count;
    54.        for (int m = 0; m < mapCount; m++)
    55.        {
    56.            var map = inputActionAsset.actionMaps[m];
    57.  
    58.            int actionCount = map.actions.Count;
    59.            for (int a = 0; a < actionCount; a++)
    60.            {
    61.                var action = map.actions[a];
    62.  
    63.                int bindingCount = action.bindings.Count;
    64.                for (int b = 0; b < bindingCount; b++)
    65.                {
    66.                    if (action.bindings[b].id.ToString() == bindingId)
    67.                    {
    68.                        return action;
    69.                    }
    70.                }
    71.            }
    72.        }
    73.  
    74.        return null;
    75.    }
    76.  
    77.    /// <summary>
    78.    /// Finds the action map which the given binding id is part of.<br />
    79.    /// </summary>
    80.    /// <param name="inputActionAsset"></param>
    81.    /// <param name="bindingId"></param>
    82.    /// <returns></returns>
    83.    public static InputActionMap GetActionMapOfBinding(this InputActionAsset inputActionAsset, string bindingId)
    84.    {
    85.        if (string.IsNullOrEmpty(bindingId))
    86.        {
    87.            return null;
    88.        }
    89.  
    90.        int mapCount = inputActionAsset.actionMaps.Count;
    91.        for (int m = 0; m < mapCount; m++)
    92.        {
    93.            var map = inputActionAsset.actionMaps[m];
    94.  
    95.            int actionCount = map.actions.Count;
    96.            for (int a = 0; a < actionCount; a++)
    97.            {
    98.                var action = map.actions[a];
    99.  
    100.                int bindingCount = action.bindings.Count;
    101.                for (int b = 0; b < bindingCount; b++)
    102.                {
    103.                    if (action.bindings[b].id.ToString() == bindingId)
    104.                    {
    105.                        return map;
    106.                    }
    107.                }
    108.            }
    109.        }
    110.  
    111.        return null;
    112.    }
    113.  
    114.    /// <summary>
    115.    /// The binding index within the given input actions asset.
    116.    /// </summary>
    117.    /// <param name="inputActionAsset"></param>
    118.    /// <param name="bindingId"></param>
    119.    /// <returns>The index or -1 if not found.</returns>
    120.    public static int GetBindingIndexWithinActionMap(this InputActionAsset inputActionAsset, string bindingId)
    121.    {
    122.        if (string.IsNullOrEmpty(bindingId))
    123.        {
    124.            return -1;
    125.        }
    126.  
    127.        int mapCount = inputActionAsset.actionMaps.Count;
    128.        for (int m = 0; m < mapCount; m++)
    129.        {
    130.            var map = inputActionAsset.actionMaps[m];
    131.  
    132.            // Index of all bindings in the action map.
    133.            // See: https://docs.unity3d.com/Packages/com.unity.inputsystem@1.5/api/UnityEngine.InputSystem.InputActionRebindingExtensions.html
    134.            int bindingCount = map.bindings.Count;
    135.            for (int b = 0; b < bindingCount; b++)
    136.            {
    137.                if (map.bindings[b].id.ToString() == bindingId)
    138.                {
    139.                    return b;
    140.                }
    141.            }
    142.        }
    143.  
    144.        return -1;
    145.    }
    146.  
    147.    /// <summary>
    148.    /// The binding index within the ACTION of the binding.
    149.    /// </summary>
    150.    /// <param name="inputActionAsset"></param>
    151.    /// <param name="bindingId"></param>
    152.    /// <returns>The index or -1 if not found.</returns>
    153.    public static int GetBindingIndexWithinAction(this InputActionAsset inputActionAsset, string bindingId)
    154.    {
    155.        if (string.IsNullOrEmpty(bindingId))
    156.        {
    157.            return -1;
    158.        }
    159.  
    160.        int mapCount = inputActionAsset.actionMaps.Count;
    161.        for (int m = 0; m < mapCount; m++)
    162.        {
    163.            var map = inputActionAsset.actionMaps[m];
    164.  
    165.            int actionCount = map.actions.Count;
    166.            for (int a = 0; a < actionCount; a++)
    167.            {
    168.                var action = map.actions[a];
    169.  
    170.                int bindingCount = action.bindings.Count;
    171.                for (int b = 0; b < bindingCount; b++)
    172.                {
    173.                    if (action.bindings[b].id.ToString() == bindingId)
    174.                    {
    175.                        return b;
    176.                    }
    177.                }
    178.            }
    179.        }
    180.  
    181.        return -1;
    182.    }
    183.  
    184.    /// <summary>
    185.    /// Overrides the binding based on the given input action asset and binding id.
    186.    /// </summary>
    187.    /// <param name="inputActionAsset"></param>
    188.    /// <param name="bindingId"></param>
    189.    /// <param name="overridePath"></param>
    190.    /// <param name="overrideInteractions"></param>
    191.    /// <param name="overrideProcessors"></param>
    192.    public static void ApplyBindingOverride(this InputActionAsset inputActionAsset, string bindingId, string overridePath, string overrideInteractions = null, string overrideProcessors = null)
    193.    {
    194.        inputActionAsset.ApplyBindingOverrideWithResult(bindingId, overridePath, overrideInteractions, overrideProcessors);
    195.    }
    196.  
    197.    /// <summary>
    198.    /// Overrides the binding based on the given input action asset and binding id.
    199.    /// </summary>
    200.    /// <param name="inputActionAsset"></param>
    201.    /// <param name="bindingId"></param>
    202.    /// <param name="overridePath"></param>
    203.    /// <param name="overrideInteractions"></param>
    204.    /// <param name="overrideProcessors"></param>
    205.    /// <returns>True if override succeeded, false otherwise.</returns>
    206.    public static bool ApplyBindingOverrideWithResult(this InputActionAsset inputActionAsset, string bindingId, string overridePath, string overrideInteractions = null, string overrideProcessors = null)
    207.    {
    208.        int bindingIndex = inputActionAsset.GetBindingIndexWithinActionMap(bindingId);
    209.        if (bindingIndex >= 0)
    210.        {
    211.            var actionMap = inputActionAsset.GetActionMapOfBinding(bindingId);
    212.            if (actionMap != null)
    213.            {
    214.                InputBinding overrideBinding = new InputBinding();
    215.                overrideBinding.overridePath = overridePath;
    216.                overrideBinding.overrideInteractions = overrideInteractions;
    217.                overrideBinding.overrideProcessors = overrideProcessors;
    218.  
    219.                actionMap.ApplyBindingOverride(bindingIndex, overrideBinding);
    220.  
    221.                return true;
    222.            }
    223.            else
    224.            {
    225.                return false;
    226.            }
    227.        }
    228.        else
    229.        {
    230.            return false;
    231.        }
    232.    }
    233.  
    234.    /// <summary>
    235.    /// Clears the override on the binding.
    236.    /// </summary>
    237.    /// <param name="inputActionAsset"></param>
    238.    /// <param name="bindingId"></param>
    239.    public static void ClearOverride(this InputActionAsset inputActionAsset, string bindingId)
    240.    {
    241.        int bindingIndex = inputActionAsset.GetBindingIndexWithinAction(bindingId);
    242.        if (bindingIndex >= 0)
    243.        {
    244.            var action = inputActionAsset.GetActionOfBinding(bindingId);
    245.            if (action != null)
    246.            {
    247.                action.RemoveBindingOverride(bindingIndex);
    248.            }
    249.        }
    250.    }
    251. }
    252.  

    Here is how I use it for override:
    Code (CSharp):
    1. InputActionAsset inputActionAsset = getItFromSomeWhere(); // <- The action asset we are operating on.
    2. string bindingId = "a36f2f76-092b..."; // <- I get the ID from my user save game or player prefs.
    3. string overridePath = "<Keyboard>/w"; // <- Whatever the user chose as override for the binding.
    4.  
    5. var result = inputActionAsset.ApplyBindingOverrideWithResult(bindingId, overridePath);
    6. if (result)
    7. {
    8.     Debug.Log("Yay");
    9. }
    And clear:
    Code (CSharp):
    1. // Clear it later
    2. inputActionAsset.ClearOverride(bindingId);
    I am relatively new to the InputSystem API so maybe I have just missed some obvious way of doing this?

    I also have a question about the ID (GUID) in a binding: Are those IDs guaranteed to remain the same over time? I am planning on using them as identifiers during editing, building and in the final builds. However this only works if the IDs are persistent across all platforms and build environments. I assume they are since it basiaclly is a json string saved in an asset but maybe they are regenerated at some point?

    Thank you.
     
    Last edited: Feb 4, 2023
    Onigiri likes this.