Search Unity

Saving user bindings

Discussion in 'Input System' started by Redrag, Jan 8, 2020.

  1. Redrag

    Redrag

    Joined:
    Apr 27, 2014
    Posts:
    181
    With all the great options for a user to change and add multiple bindings I cannot find any guidance for how and where the actions and bindings are stored. Also I am a bit surprised there is not even a basic UI for this built in. Is there a sample somewhere I have missed?
     
    tigerleapgorge likes this.
  2. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    There's no API yet to assist directly in persisting bindings. It's something we'd like to take a look at after 1.0. An overall sample for a rebinding screen has landed and should be in the next package.

    For persisting binding customizations, you can traverse of the set of bindings in each action map using InputActionMap.bindings and for each one that has an overridePath set, save it along with the ID of the binding. When loading, you can look up the bindings by ID and put the override back in place.

    Code (CSharp):
    1. // Saving.
    2. var overrides = new Dictionary<Guid, string>();
    3. foreach (var map in asset.actionMaps)
    4.     foreach (var binding in map.bindings)
    5.     {
    6.         if (!string.IsNullOrEmpty(binding.overridePath))
    7.             overrides[binding.id] = binding.overridePath;
    8.     }
    9.  
    10. // Loading.
    11. foreach (var map in asset.actionMaps)
    12. {
    13.     var bindings = map.bindings;
    14.     for (var i = 0; i < bindings.Count; ++i)
    15.     {
    16.         if (overrides.TryGetValue(bindings[i].id, out var overridePath)
    17.             map.ApplyBindingOverride(i, new InputBinding { overridePath = overridePath });
    18.     }
    19. }
    20.  
    There'll be a more elegant API for this later on. What I think would be great to have would be a simple API that just returns a string in JSON format that you can store anywhere and that you can then hand back to the API and it restores all overrides for you.
     
    Last edited: Mar 16, 2020
  3. Wiechering

    Wiechering

    Joined:
    Jun 22, 2015
    Posts:
    9
    I took the suggestion and made it work with json and the PlayerPrefs system. Thanks for the start that was indeed a big big help!
    I am calling LoadControlOverrides on Start. I am also using the rebinding UI elements with a little rework. For them I added a Start method to call UpdateBindingDisplay so they show the correct override.

    Code (CSharp):
    1.  
    2.  
    3.        public InputActionAsset control;
    4.  
    5.         /// <summary>
    6.         /// Private wrapper class for json serialization of the overrides
    7.         /// </summary>
    8.         [System.Serializable]
    9.         class BindingWrapperClass {
    10.             public List<BindingSerializable> bindingList = new List<BindingSerializable> ();
    11.         }
    12.  
    13.         /// <summary>
    14.         /// internal struct to store an id overridepath pair for a list
    15.         /// </summary>
    16.         [System.Serializable]
    17.         private struct BindingSerializable {
    18.             public string id;
    19.             public string path;
    20.  
    21.             public BindingSerializable(string bindingId, string bindingPath ) {
    22.                 id = bindingId;
    23.                 path = bindingPath;
    24.             }
    25.         }
    26.  
    27.         /// <summary>
    28.         /// stores the active control overrides to player prefs
    29.         /// </summary>
    30.         public void StoreControlOverrides () {
    31.             //saving
    32.             BindingWrapperClass bindingList = new BindingWrapperClass ();
    33.             foreach ( var map in control.actionMaps ) {
    34.                 foreach ( var binding in map.bindings ) {
    35.                     if ( !string.IsNullOrEmpty ( binding.overridePath ) ) {
    36.                         bindingList.bindingList.Add ( new BindingSerializable ( binding.id.ToString (), binding.overridePath ) );
    37.                     }
    38.                 }
    39.             }
    40.  
    41.             PlayerPrefs.SetString( "ControlOverrides", JsonUtility.ToJson ( bindingList ) );
    42.             PlayerPrefs.Save ();
    43.         }
    44.  
    45.         /// <summary>
    46.         /// Loads control overrides from playerprefs
    47.         /// </summary>
    48.         public void LoadControlOverrides () {
    49.             if (PlayerPrefs.HasKey( "ControlOverrides") ) {
    50.                 BindingWrapperClass bindingList = JsonUtility.FromJson ( PlayerPrefs.GetString ( "ControlOverrides" ), typeof ( BindingWrapperClass ) ) as BindingWrapperClass;
    51.  
    52.                 //create a dictionary to easier check for existing overrides
    53.                 Dictionary<System.Guid, string> overrides = new Dictionary<System.Guid, string> ();
    54.                 foreach ( var item in bindingList.bindingList) {
    55.                     overrides.Add ( new System.Guid ( item.id ), item.path );
    56.                 }
    57.  
    58.                 //walk through action maps check dictionary for overrides
    59.                 foreach ( var map in control.actionMaps ) {
    60.                     var bindings = map.bindings;
    61.                     for ( var i = 0; i < bindings.Count; ++i ) {
    62.                         if ( overrides.TryGetValue ( bindings[i].id, out string overridePath ) ) {
    63.                             //if there is an override apply it
    64.                             map.ApplyBindingOverride ( i, new InputBinding { overridePath = overridePath } );
    65.                         }
    66.                     }
    67.                 }
    68.             }
    69.         }
     
  4. Schneddi

    Schneddi

    Joined:
    Nov 12, 2017
    Posts:
    8
    Thanks a lot @Rene-Damm and @Wiechering, I tried the code of @Wiechering and it worked without any fixes. I can use this with a few modifications, so it fits into my system.

    Also a method to reset:
    Code (CSharp):
    1.         public void ResetKeybindings(InputActionAsset control)
    2.         {
    3.             foreach (var map in control.actionMaps)
    4.             {
    5.                 map.RemoveAllBindingOverrides();
    6.             }
    7.             PlayerPrefs.DeleteKey("ControlOverrides");
    8.         }
     
    Last edited: Mar 31, 2020
  5. abdo400

    abdo400

    Joined:
    Feb 12, 2016
    Posts:
    44
    @Wiechering @Schneddi Hey, did the new key bindings work in trigger their action? I am getting the correct override path in logs but no action is triggered with it, still the old keys...
     
    tigerleapgorge likes this.
  6. celestialsurfer

    celestialsurfer

    Joined:
    Jan 31, 2020
    Posts:
    1
    @abdo400 same behavior here, I can log that the override is stored but it never triggers the action. Has anyone figured out a way around this?
     
    tigerleapgorge likes this.
  7. blakelowe

    blakelowe

    Joined:
    May 22, 2020
    Posts:
    1
    @Rene-Damm I'm getting the same issue as the previous two users posted. I'm using the code from the Rebinding UI sample and the overrides are definitely stored as JSON in PlayerPrefs and they even get loaded correctly into the UI after a restart. The issue comes after I switch scenes and begin playing my game. I attempt to load the overrides using a reference to the same InputActionAsset, but the callbacks are still triggered by the original keybinds. I made sure to register to the callbacks (Start) after loading the overrides (Awake). I tried to look for the documentation for ApplyBindingOverride but wasn't able to find anything super helpful here. I'm using 1.0.0 version of Input System and Unity 2019.3.6f1. I attached the code I'm using to save and load. Please let me know if I can include any other info.
     

    Attached Files:

    Tsnk7 likes this.
  8. misanthropowitsch

    misanthropowitsch

    Joined:
    Sep 30, 2016
    Posts:
    23
    That would be great. Right now I LOVE the inputsystem and the example, but it lacks a possibility to store the keybinds.
     
  9. ProceduralCatMaker

    ProceduralCatMaker

    Joined:
    Mar 9, 2020
    Posts:
    108
    Any news on this issue of storing the keybindings? Does 1.1 have a timing set?
     
  10. Clavus

    Clavus

    Joined:
    Jun 6, 2014
    Posts:
    62
    1.1 is in preview and has functionality to save / load all the input binding overrides as a json string, looking at the changelog.
     
  11. Roboserg

    Roboserg

    Joined:
    Jun 3, 2018
    Posts:
    83
    moritzcerst likes this.
  12. moritzcerst

    moritzcerst

    Joined:
    Dec 6, 2020
    Posts:
    4
    Now:
    Code (CSharp):
    1. void SaveUserRebinds(PlayerInput player)
    2. {
    3.     var rebinds = player.actions.SaveBindingOverridesAsJson();
    4.     PlayerPrefs.SetString("rebinds", rebinds);
    5. }
    6.  
    7. void LoadUserRebinds(PlayerInput player)
    8. {
    9.     var rebinds = PlayerPrefs.GetString("rebinds");
    10.     player.actions.LoadBindingOverridesFromJson(rebinds);
    11. }
    12.  
     
  13. Ceraph

    Ceraph

    Joined:
    May 16, 2013
    Posts:
    19
    Has anyone run into issues with Unity saving the JSON but not loading it back into the actions with 1.1 preview 3? I have confirmed that my PlayerPrefs are saving the JSON as a string but when I load the overrides nothing seems to happen. This is my code for loading:


    Code (CSharp):
    1. playerActionsAsset.LoadBindingOverridesFromJson(Options.PlayerRebinds);
    2.         _playerInput.actions.LoadBindingOverridesFromJson(Options.UIRebinds);
    I'm trying two different methods of loading the overrides JSON into the InputActions but neither approach is working. In the first line,
    playerActionsAsset
    is a publicly defined
    InputActionAsset
    that I assign in the inspector. I'm also attempting to get a reference via PlayerInput.actions, but that's not working either. I don't see any errors or warnings in the console, but when I open the input debugger for PlayerInput I still see the default button mappings, not the saved overrides. This is the JSON that is being generated when I save the overrides (I have two separate strings for the two InputActions I'm using):


    Code (CSharp):
    1. {"bindings":[{"action":"DefaultActions/Trick3","id":"67d6dcd5-cafc-4781-b58f-91c13efc4197","path":"<Gamepad>/leftShoulder","interactions":"","processors":""},{"action":"DefaultActions/Trick3","id":"9093fff7-3dfe-4ac4-b49a-f31f1700eb17","path":"<Gamepad>/leftShoulder","interactions":"","processors":""}]}
    2.  
    3. {"bindings":[{"action":"UI/Rename","id":"96a3b044-76ee-4d2b-9358-dbec8350d728","path":"<Gamepad>/rightShoulder","interactions":"","processors":""},{"action":"UI/Rename","id":"2e8d8c27-e64f-402c-a747-a2ffe4db43a9","path":"<Gamepad>/rightShoulder","interactions":"","processors":""}]}
    4.  
    5.  
    Does anyone have any idea why that wouldn't be loading? Am I missing a step or am I just doing something wrong? Any help would be super appreciated. Thanks!

    EDIT: I forgot to mention, but I'm running this in 2019.4.23f1.
     
    Last edited: Mar 29, 2021
  14. Ceraph

    Ceraph

    Joined:
    May 16, 2013
    Posts:
    19
    I was able to figure out what my problem was, and for the sake of anyone who finds this later I'll document the problem and how I got around it.

    As I mentioned in my previous post, I had been using a mix of the Unity class
    PlayerInput
    and manually instantiating my own
    InputActionAsset 
    classes. This was the fundamental cause behind
    LoadBindingOverridesFromJson
    not working, as creating a new instance of my Input Actions was overwriting my loading with the defaults.

    I need to make use of the events from different phases of
    InputAction 
    (.started, .performed, and .canceled) and as near as I can tell, there's no way to get that data directly from the messages and events that
    PlayerInput 
    broadcasts. So instead of creating a new instance and adding events like this:


    Code (CSharp):
    1. _playerInputs = new PlayerInputActions();
    2.  
    3. _playerInputs.DefaultActions.Movement.performed += ctx => moveVector = ctx.ReadValue<Vector2>();
    4.         _playerInputs.DefaultActions.Jump.started += ctx => ButtonStarted(InputType.Jump);
    I subscribe to the event directly from
    PlayerInput 
    like this:

    Code (CSharp):
    1. _playerInput.actions["Movement"].performed -= ctx => moveVector = ctx.ReadValue<Vector2>();
    2.         _playerInput.actions["Jump"].started -= ctx => ButtonStarted(InputType.Jump);
    I don't love this approach because it relies on matching the string to the Action name and the compiler won't catch this, but it gets the job done. I can listen for specific phase events and saving / loading my remapped inputs works now.
     
  15. lejean

    lejean

    Joined:
    Jul 4, 2013
    Posts:
    392
    What version are you using because LoadBindingOverridesFromJson isn't available for me in playerinput?
     
  16. Ceraph

    Ceraph

    Joined:
    May 16, 2013
    Posts:
    19
    According to my package lock, "com.unity.inputsystem" is using version "1.1.0-preview.3". Hope that helps!
     
  17. FireHawkX

    FireHawkX

    Joined:
    Apr 26, 2016
    Posts:
    28
    This is most likely my problem as well... been searching for a fix what seems 20 hours now...
    I'm using version 1.1.1 of the input system, got a save/load working perfectly, but the changes are only "applied" when I restart the game...
    In my case however, I use the following code :

    Code (CSharp):
    1. using UnityEngine.InputSystem;
    2.  
    3. PlayerInput playerInput;
    4.  
    5.     private void Awake()
    6.     {
    7.         playerInput = GetComponent<PlayerInput>();
    8.     }
    9.  
    10. //then later on in Update and another function I use the following lines to read values...
    11.  
    12. Vector2 plMove = playerInput.actions["Move"].ReadValue<Vector2>();
    13. //...
    14.  
    15. Ray camRay = nightcam.ScreenPointToRay(playerInput.actions["MouseLoc"].ReadValue<Vector2>());
    16.  
    17. //...
    18. if (playerInput.actions["Sprint"].ReadValue<float>() == 1)
    I have absolutely no idea how I would go to change all of my codes .ReadValue to actually work with the rebindsOverrides... cause right now having a rebinding menu that you need to restart the game isnt quite optimal...

    Any suggestion would be GREATLY appreciated :)

    EDIT : its been more than a week without a single reply... guess i'll just do like everyone else and buy "Rewired"...
    At least I know that will work properly straight away and i'll get support if needed....
     
    Last edited: Oct 25, 2021
  18. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Needs to be applied to the
    InputActionAsset
    instance used by a specific player. For single player, can be done at any point and just applied to the given source asset as is. For multiple players, needs to be applied after the player's
    OnEnable
    . When using
    PlayerInputManager
    , listening for player joins and doing it there is one way. But many others. E.g. putting this right next to the PlayerInput component on the same GO.

    Code (CSharp):
    1. public class SaveAndRestoreBindingOverrides : MonoBehaviour
    2. {
    3.     private void OnEnable()
    4.     {
    5.         var player = GetComponent<PlayerInput>();
    6.        
    7.         // Fetch overrides. Not very robust to do it by playerIndex but
    8.         // serves as an illustration.
    9.         var rebinds = PlayerPrefs.GetString("Rebinds" + player.playerIndex);
    10.  
    11.         // Apply.
    12.         player.actions.LoadBindingOverridesFromJson(rebinds);
    13.     }
    14.  
    15.     private void OnDisable()
    16.     {
    17.         // Save rebinds.
    18.         var player = GetComponent<PlayerInput>();
    19.         var rebinds = player.actions.SaveBindingOverridesAsJson();
    20.         PlayerPrefs.SetString("Rebinds" + player.playerIndex, rebinds);
    21.     }
    22. }
     
    AndersonMarquess likes this.
  19. unity_zvvTgm_VSMTmrg

    unity_zvvTgm_VSMTmrg

    Joined:
    Dec 14, 2020
    Posts:
    2
    for me the SaveBindingOverridesAsJson() and LoadBindingOverridesFromJson() just dont exist for me on my playerinput component, i have a "ToJson()" and "LoadFromJson()" method though
     
    ATpartytime likes this.
  20. FlightOfOne

    FlightOfOne

    Joined:
    Aug 1, 2014
    Posts:
    668
    Hello, any idea why this is giving me an empty string? Do we still need to loop through?
     
  21. FlightOfOne

    FlightOfOne

    Joined:
    Aug 1, 2014
    Posts:
    668
    NVM! I understand now. Overrides are runtime values, I was expecting what I had set in editor (just path) to show up.