Rebinding Keys Isn't reflected in existing controlSchemes

It really does suck but in their defence it is a preview feature so kind of our own faults for going with this solution. Do hope someone comes up with an example soon though, this puzzles been bugging me for ages.

@ZachariBarnes @pantang_1 @Vagabond
I’m going to necro this post, for a good cause at least I think. I managed to get remappable bindings working. I used [mention|v5m6ALs6O9hQDakv+d/dRA==] 's code, along with other things. Don’t know if they’ll be relevant, but part of having the whole system working was reading these threads: 1 2 . I’ll just focus on what you need for the remappable keybinds, if you’re not familiar with some methods I’m using check those two threads out.

I have a SettingsManager singleton (which will be SettingsManager.Instance from now on), to which I assign my InputActionAsset:

public InputActionAsset control;

The component that I use on my buttons to change keybinds has the following, here itsnottme’s code comes into play. I never use the unity asset’s input actions, aside from assigning actualAction from action:

public InputActionReference action = null;
    InputActionRebindingExtensions.RebindingOperation rebindOperation = null;

    InputAction actualAction;

    void Start()
    {
        foreach (var map in SettingsManager.Instance.control.actionMaps)
        {
            foreach (var tempAction in map.actions)
            {
                if (tempAction.name == action.action.name)
                {
                    actualAction = tempAction;
                    break;
                }
            }
            if (actualAction != null)
            {
                break;
            }
        }

    }

This is the code that runs from the button component when I want to listen for a new keybind.

public void Do()
    {
        if (actualAction == null) return;

        actualAction.Disable();

        rebindOperation = actualAction.PerformInteractiveRebinding(0)
                    .WithControlsExcluding("<Pointer>/position") // Don't bind to mouse position
                    .WithControlsExcluding("<Pointer>/delta") // To avoid accidental input from mouse motion
                    .WithCancelingThrough("<Keyboard>/escape")
                    .OnMatchWaitForAnother(0.1f)
                    .OnComplete(operation => { OnCompletion(); })
                    .Start();

    }

OnCompletion() may be whatever you need for your case. Don’t forget to dispose rebindOperation!

void OnCompletion()
    {
        if (rebindOperation != null)
            if (rebindOperation.completed)
            {
                actualAction.Enable();// after this you can use the new key
                rebindOperation.Dispose();
                rebindOperation = null;
                Debug.Log("Remapped "+actualAction.name+" to "+ actualAction.bindings[0].effectivePath);

                SettingsManager.Instance.StoreControlOverrides();
            }

    }

My input actions only need one binding, so I’m fine with actualAction.bindings[0].

When it comes to actually using the input, I don’t use the default asset at all, but SettingsManager’s.

InputActionMap Game;

    void InitControls()
    {
        Game = SettingsManager.Instance.control.actionMaps[0];

        Game["Weapon1"].performed += ctx => UseWeapon(ctx, 0);
        Game["Weapon2"].performed += ctx => UseWeapon(ctx, 1);
        Game["Weapon3"].performed += ctx => UseWeapon(ctx, 2);
        Game["Weapon4"].performed += ctx => UseWeapon(ctx, 3);
        Game["Weapon5"].performed += ctx => UseWeapon(ctx, 4);
        Game["Weapon6"].performed += ctx => UseWeapon(ctx, 5);

        //et cetera

    }

In my case I’m writing Game["Weapon1"] rather than Controls.Game.Weapon1, what I would do if I were using the default C# wrapper. I only have one action map, so ...actionMaps[0] is enough for me.

Hopefully I covered everything.

2 Likes

@ZachariBarnes @pantang_1 @Vagabond

A small update on this: I was finally able to get it to work (after repeatedly trying from scratch), but it only ever does work when using Player Input (I’m using Unity Events, haven’t tried the other options on Player Input), not with custom instances of the generated C# classes. And it also only works when I’m creating a new Input asset (containing the player and UI schemes) from the Player Input script, not with a custom asset created through Asset → Create → Input Actions.

I’m using the Sample Rebinding UI stuff, the only thing I had to change there was deactivating and reactivating the action when performing the interactive rebind, everything else just worked (again, when using Player Input over code and creating the Input Actions asset from the Player Input inspector).

Saving the binding through playerInput.actions.SaveBindingOverridesAsJson also works without issue (1.1 p1/p2).

My thread didn’t get any replies but in my experience, the bindings are applied on a per asset basis. The generated C# classes create new runtime only SO assets so they never get any rebinding effects. It’s interesting that you mention that it works for assets generated through player input.

Since you’ve mentioned interactive rebinding, are you using the default rebind button prefabs? Those use input action references, I can’t imagine anything being different even with assets created through player input with them. Unless you are calling the interactive rebind manually in code.

Hi, I don’t want to remap the same action with the same key, does anyone know a good way to do it? For example, that the same key for jumping is not for attacking

That seems to be why rebinds are never applied when instantiating based on the generated classes, I agree. It also does not (for me) work like that even with assets created through PlayerInput.

And yes, I have no idea how the assets manually created would be different from the ones created through PlayerInput. I’m just glad that I finally got something to work… whole thing is pretty messy. I’m using the prefabs from the samples, the only thing I had to modify was deactivating and reactivating the actions, no other code was changed by me.

No idea at the moment, but I’m trying to solve it as well. If I manage to do that, I’ll post back here (unless there is a thread dedicated to the topic).

I posted something similar but did not receive any response, I am remapping the keys in my main menu, but I am not sure if there is a function to avoid that, if the key is the same, ignores it.

@josenajarqs

I changed the OnComplete of PerformInteractiveRebind from the sample to:

.OnComplete(
                    operation =>
                    {
                        m_RebindOverlay?.SetActive(false);
                        m_RebindStopEvent?.Invoke(this, operation);
                        action.Enable();

                        // Remove this binding if a duplicate is found.

                        if(CheckDuplicateBinding(action.bindings[bindingIndex])){
                            Debug.Log("Duplicate binding found, restart interactive rebind");
                            action.RemoveBindingOverride(bindingIndex);
                            CleanUp();
                            PerformInteractiveRebind(action, bindingIndex, true);
                            return;
                        };

                        playerInputPersistence.Save();
                      
                        UpdateBindingDisplay();
                        CleanUp();

                        // If there's more composite parts we should bind, initiate a rebind
                        // for the next part.
                        if (allCompositeParts)
                        {
                            var nextBindingIndex = bindingIndex + 1;
                            if (nextBindingIndex < action.bindings.Count && action.bindings[nextBindingIndex].isPartOfComposite)
                                PerformInteractiveRebind(action, nextBindingIndex, true);
                        }
                    });

and used this method to check for duplicate bindings:

private bool CheckDuplicateBinding(InputBinding bindingToCheckAgainst){

            foreach (var binding in playerInputPersistence.playerInput.actions.bindings)
            {

                // Don't test against itself, would always return true.
                if(binding == bindingToCheckAgainst)
                {
                    continue;
                }

                if(binding.effectivePath == bindingToCheckAgainst.effectivePath)
                {
                    Debug.Log("Duplicate binding found: " + binding.effectivePath + ", " + bindingToCheckAgainst.effectivePath);
                    return true;
                }
            }

            return false;
        }

This does not cover all cases (you can for example still end up with duplicates after resets, but should get you started.

1 Like

Do I understand it correctly that if I use the generated input action C# .cs class (and not PlayerInput or the asset directly) the rebindings done via “PerformInteractiveRebinding()” won’t work?

From my experience, yes.

1 Like

Hehe im gona keep working on my other things and have a look in a week or two, looks like plenty to go over thank you! Who needs keybinds anyway :stuck_out_tongue:

Wait if the generated Input Action C# doesn’t work with interactive rebinds how is one supposed to get the rebound actions then? If the overrides only apply to the InputActionAsset, then how do I bind actions using the InputActionAsset? It only gives me arrays of action maps which aren’t really suited for binding specific stuff to them unless I specifically remember in what order what action in what map is and never change the order etc…

Is there anyway I can rebind actions dynamically at runtime while also using actionmapname.actionname.triggered etc to use the action in the code?

At the moment, it looks like you need to use the PlayerInput component and wire stuff up in the inspector. At least then it all worked for me.

Was one year enough time for you?

5 Likes

Im just hoping if we are patient the devs will drop us an awesome example scene

Doing this does not refresh the functions that were assigned before the rebind, only the ones after!

uiActions.Cancel.ApplyBindingOverride(0, Keyboard.current[Key.T].path);
I tried 1.0.2 and 1.1.0-preview.3.
The Input Debug window show it’s correctly rebound, but the old keybind is active and the new keybind is not working at all.

How do I rebind from code (not the interactive one)?

EDIT: I found the problem!
If you have “Generate C# Class” enabled, it will create a new InputActionAsset at runtime!! (Why is that needed? Really strange)
That means that the InputActionAsset in the InputSystemUIInputModule is a different one.
So you have to change the bindings twice, once for each asset.
Very confusing, not gonna lie.

2 Likes

Due to this (and other issues) the generated C# classes are pretty much unusable. I was able to get everything I need for proper rebinding to work, but only when wiring everything up in the inspector.

2 Likes

Did I miss this “gotcha” in the documentation somewhere?

Actually, I’ve just been working through this myself, and it definitely can work using the generated C# classes.

The catch is this:

Despite the documentation calling it a “C# wrapper class”, it does not “wrap” the asset itself but instead contains a completely separate, internal copy of its JSON content, and creates a whole new asset from this when instantiated. On top of seeming to be needlessly wasteful, this also means that any binding overrides which you apply to the original asset are effectively ignored, because that asset isn’t actually being used by the so-called “wrapper”.

With that in mind, if you’re using the generated C# then you need to apply the binding overrides individually on every single instance of the class, rather than just once on the underlying asset.

Naively, having read that it was a “wrapper” class and noting that they could be enabled / disabled individually, each one of my own components which used input had its own instance of that object. Once I realised what was going on I made my own wrapper to provide singleton access, and rebind overrides are applied to that.

Also, note: at some point recently the methods SaveBindingOverridesAsJson() and
LoadBindingOverridesFromJson(json, removeExisting) were added, so all you need to do is stash that string in PlayerPrefs and re-apply it when you instantiate your stuff.

Edit: @Rene-Damm , I’m curious. Why does it work like this?

1 Like