Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Changing InputActions Bindings at runtime

Discussion in 'Input System' started by lucvw_1975, Mar 7, 2020.

  1. lucvw_1975

    lucvw_1975

    Joined:
    Dec 7, 2014
    Posts:
    20
    Hi guys, I need some help. I want to rebind my keyboard keys for a certain action, e.g. Fire. I can't seem to find the syntax to rebind my key to another key and I also have no clue how to save this to the InputActionAsset. I tried this code:

    _inputActions.GamePlay.Fire.ApplyBindingOverride("<Keyboard/space", "<Keyboard/delete");

    I was hoping this would change the binding of the Fire control from space to delete (just a random chosen key). But this doesn't seem to work. And for saving the ActionMapAsset from within code, so the user can keep his favorite keys without the need to rebind it every time he plays the game, I didn't find a thing about that. I even don't know if it's possible ? Any help would be greatly appreciated. Thanks !

    PS: I tried writing a parser to search for specific actions and bindings in the generated .cs file. The actions/bindings are stored in a Dictionary. Although this works, the problem is writing it back to the file on exact the same position and I have no idea if this would work at all...
     
  2. Voidsay

    Voidsay

    Joined:
    Jun 20, 2020
    Posts:
    6
    Hello

    Your post send me on a wild goose chase that shouldn't have taken so long to begin with. (Also more than a single line of code would be nice for context)

    My conclusion:
    • you use the wrong syntax, so the command that you suggest won't even work. Since I have no idea what other atrocities lie in your code and how they intertwine in the spaghetticode (no offense my code is usually a mess) and I am too dumb to read the sparse documentation I suggest to not press this matter further.
    • there is a convenient way suggested here: https://forum.unity.com/threads/performinteractiverebinding-not-working.891328/ although it might not fully answer your question (it only changes the binding in runtime), but should provide a solid foundation to build the missing features on top.
    • finally you can do what I realized far too late and just download the Samples, in this case the "Rebinding UI" file and use the provided scrip. You can find them in the package manager in the same place as the install files. (Just scroll, I am inpatient, I don't scroll, this screws me over every time) I still don't understand the scripts, but they are written by unity staff, so they must be good?
    Basically my suggestion do something like this:
    Code (CSharp):
    1.     using UnityEngine;
    2.     using UnityEngine.InputSystem;
    3.    
    4.     public class RebindScript : MonoBehaviour
    5.     {
    6.         public InputActionReference Action;
    7.         private InputActionRebindingExtensions.RebindingOperation rebindOperation; // this should be "optimised", since doing updates every frame is inefficient
    8.    
    9.         void Update()
    10.         {
    11.             if (rebindOperation != null)
    12.                 if (rebindOperation.completed)
    13.                 {
    14.                     Action.action.Enable();// after this you can use the new key
    15.                     Debug.Log("finished");
    16.                 }
    17.         }
    18.    
    19.         public void StartInteractiveRebind()
    20.         {
    21.             Action.action.Disable(); // critical before rebind!!!
    22.             rebindOperation = Action.action.PerformInteractiveRebinding()
    23.                 .WithControlsExcluding("Mouse")
    24.                 .WithCancelingThrough("<Keyboard>/escape")
    25.                 .OnMatchWaitForAnother(0.2f)
    26.                 .Start();
    27.         }
    28.    
    29.     }
    and than use
    PlayerPrefs.GetString() and PlayerPrefs.SerString() or your method of choice to save and read the relevant settings

    This should be trivial (as all things should be!)

    Cordially the noisy void
     
  3. lucvw_1975

    lucvw_1975

    Joined:
    Dec 7, 2014
    Posts:
    20
    Never thought I'd get an answer to my question, so thanks ! I just used the default way of input and will try to complete my project like so. But I'll certainly take a look at the new input system and your reaction to my post later on, when I find some time ;)
     
  4. Voidsay

    Voidsay

    Joined:
    Jun 20, 2020
    Posts:
    6
    So i figured out the code you need.

    First of all the correct syntax for your binding problem:
    (it uses the variables suggested in the solution above)
    Action.action.ApplyBindingOverride(0, <Keyboard>/delete);


    As you can see an index is required. How to get the correct one if you have more than one is a different question tho.
    Also note that this will only work if you only have one binding for each action. (And you will need at least one)

    Now to the save and load part:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.InputSystem;
    3. using UnityEngine.UI;
    4.  
    5. public class RebindScript : MonoBehaviour
    6. {
    7.     public InputActionReference thisaction;
    8.     private InputActionRebindingExtensions.RebindingOperation rebindOperation;
    9.  
    10.     void Start()
    11.     {
    12.         thisaction.action.ApplyBindingOverride(0, PlayerPrefs.GetString(thisaction.action.name, thisaction.action.bindings[0].path));// load and set prefered binding it there is none the preset button is used and saved
    13.     }
    14.  
    15.     public void StartInteractiveRebind()
    16.     {
    17.         //must despose of collection or else you will have a memory leak and error with crash!
    18.         rebindOperation?.Cancel();
    19.  
    20.         void CleanUp()
    21.         {
    22.             rebindOperation?.Dispose();
    23.             rebindOperation = null;
    24.         }
    25.  
    26.         thisaction.action.Disable(); // critical before rebind!!!
    27.         rebindOperation = thisaction.action.PerformInteractiveRebinding(0)
    28.             .WithCancelingThrough("<Keyboard>/escape")
    29.             .OnMatchWaitForAnother(0.2f)
    30.             .Start()
    31.             .OnCancel(something =>
    32.            {
    33.                thisaction.action.Enable();
    34.                Debug.Log("canceled");
    35.                CleanUp();
    36.            })
    37.            .OnComplete(something =>
    38.            {
    39.                thisaction.action.Enable();
    40.                Debug.Log("finished");
    41.                save();
    42.                CleanUp();
    43.            });
    44.     }
    45.  
    46.     private void save()
    47.     {
    48.         string device = string.Empty;
    49.         string key = string.Empty;
    50.         thisaction.action.GetBindingDisplayString(0, out device, out key);
    51.         PlayerPrefs.SetString(thisaction.action.name, "<" + device + ">/" + key);
    52.         PlayerPrefs.Save();// not necessery if you close the application properly or want to implement confirmation button
    53.     }
    54. }
    This works for simple buttons, but won't work with composites or even Vector 2 types. There is a solution for this (the example file dose it, I am just too dense to understand it), but for a very simple project this should suffice.

    Also check the thread here from time to time. They should drop some official binding save and loading functions there sometime.
     
  5. lucvw_1975

    lucvw_1975

    Joined:
    Dec 7, 2014
    Posts:
    20
    Thanks man for the time and effort you've put in this to figure out how it works. Much appreciated ! Trying to understand this is another thing... ;)
     
  6. Voidsay

    Voidsay

    Joined:
    Jun 20, 2020
    Posts:
    6
    Sort of doing it for myself and letting others partake in my findings. It is mostly me sifting through the example code and trying to understand it myself step by step.

    To anyone following along at home my personal recommendation is to modify the example code with the saving additions, since my script is sort of spotty and unreliable. The devs sort of promised to implement a save system some time, so all this will become obsolete in a matter of moths (my guess).

    Anyway... here is my composite saving code:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.InputSystem;
    3.  
    4. public class RebindScript : MonoBehaviour
    5. {
    6.     public InputActionReference thisaction;
    7.     public int index;
    8.  
    9.     private InputActionRebindingExtensions.RebindingOperation rebindOperation; // dosn't need to be global it's just how I wrote the code
    10.  
    11.     private int currentIndex;
    12.  
    13.     void Start()
    14.     {
    15.         if (thisaction.action.bindings[index].isComposite)
    16.         {
    17.             currentIndex = index + 1;// the bindings following a composite contain the actual bindings you can differentiate them with .ispartofcomposite
    18.  
    19.             for (int i = 1; i <= 4; i++)
    20.             {
    21.                 thisaction.action.ApplyBindingOverride(index + i, PlayerPrefs.GetString(thisaction.action.name + "/" + thisaction.action.bindings[index + i].name, thisaction.action.bindings[index + i].path));
    22.             }
    23.         }
    24.         else
    25.         {
    26. [INDENT]        //same as before
    27.  }[/INDENT]
    28.     }
    29.  
    30.     public void StartInteractiveRebindComposite()
    31.     {
    32.         rebindOperation?.Cancel(); // Will null out m_RebindOperation.
    33.  
    34.         void CleanUp()
    35.         {
    36.             rebindOperation?.Dispose();
    37.             rebindOperation = null;
    38.         }
    39.  
    40.         thisaction.action.Disable(); // critical before rebind!!!
    41.         rebindOperation = thisaction.action.PerformInteractiveRebinding(currentIndex)
    42.             .WithCancelingThrough("<Keyboard>/escape")
    43.             .OnMatchWaitForAnother(0.2f)
    44.             .Start()
    45.             .OnCancel(something =>
    46.             {
    47.                 thisaction.action.Enable();
    48.                 currentIndex = index + 1;
    49.                 Debug.Log("canceled");
    50.                 CleanUp();
    51.             })
    52.            .OnComplete(something =>
    53.            {
    54.                thisaction.action.Enable();
    55.                Debug.Log("one more");
    56.                UpdateBindingText();
    57.                CleanUp();
    58.                if (currentIndex < 4)
    59.                {
    60.                    currentIndex++;
    61.                    Debug.Log(currentIndex);
    62.                    StartInteractiveRebindComposite();
    63.                }
    64.                else
    65.                {
    66.                    currentIndex = index + 1;
    67.                    Debug.Log("finished");
    68.                }
    69.            });
    70.     }
    71.  
    72.     private void saveComposite()
    73.     {
    74.         for (int i = 1; i <= 4; i++)
    75.         {
    76.             string device = string.Empty;
    77.             string key = string.Empty;
    78.             thisaction.action.GetBindingDisplayString(index + i, out device, out key);
    79.             PlayerPrefs.SetString(thisaction.action.name + "/" + thisaction.action.bindings[i].name, "<" + device + ">/" + key);
    80.         }
    81.         PlayerPrefs.Save();
    82.     }
    83. }
    Note that this code is specifically designed for a composite with 4 components.
    If you have fewer components it will crash, if you have more it should work fine. A decent programmer would do a bindings.count and go thru all the bindings, but that's not my style.

    Now to the entering a Vector2 thing...

    Something I learned about joysticks. Turns out the joystick class has only one official stick (left stick) and the right stick must be archived through a composite with very specific values. Only Gamepads have a right and left stick.

    Long story short my generic controller is considered a joystick and rebinding with only one possible Vector2 proves rather difficult. So if anyone happens to have a xbox or ps controller they could test my theory easily.

    Basically I think that saving that should be just like saving a simple button on a keyboard, but I cant test this.

    Ps. I have been wondering what features make you stick to the old input system? I see the new one superior in every way. Is it just the difficulty of use?
     
  7. josenajarqs

    josenajarqs

    Joined:
    Jan 27, 2021
    Posts:
    22
    D
    Hi! Do you know any way to not rebind the same key? Something like WithControlExcluding(sameKeythatwaspressedBefore) ?
     
  8. Mystical_Pidgeon

    Mystical_Pidgeon

    Joined:
    Jun 7, 2016
    Posts:
    8
    Best option I've seen so far is to do it after the binding is complete - essentially, you check if any other actions are set to the same binding and reset the binding if they are. Another option could be to set a bunch of WithControlExcluding procedurally based on all other available actions. Not sure which is better, but using the first option would make it easier to do a swap of the controls if you wanted.