UI buttons are triggered multiple times when rebinding controls

Hi everyone,

I am looking for a solution for something that might also be a bug. I am currently making a control rebinding screen. I use the “new” input system and the rebind example that comes with it. I’m on Unity 2021.3.39f1.

What I did is create a couple instances of their prefab with code. The prefab consists of a label and two buttons of which one triggers a method in their script that rebinds the control. The method in the script is called by using the default UI button “On click” that you assign in the inspector, it’s already setup in the prefab.

The problem is that the rebinding is often triggered more than one time. I figured out via debugging that it must be because the button gets triggered more than once, as not only the rebind method, but also the method that responds to the button is called more than once. The rebinding works the first time - when I cancel all the following rebinds, the button I used in the first rebind will work. Otherwise, the last time will count as the entire rebind process happens more than once as if I had clicked the button again immediately after the rebinding.

Then I figured out this only happens when I activate the button with the same device the rebinding is for and also only if the device is a gamepad - so every time I use X on my Playstation gamepad to trigger the button that triggers the rebind for a gamepad control, the button and hence the rebind will be triggered multiple times. It does not matter which button I press in the rebind process, it will get repeated a random couple of times. Except if I use X again as the control to bind, then it gets worse and more often - I assume because then even more wrong button presses are generated.

I assume it’s some processing problem of the input system. It does not happen with the Enter key or the mouse, only the gamepad. I guess the “X button pressed, trigger button” thing never gets processed completely and then rebind kicks in and messes with button inputs and then things go wild. I just don’t know what do about it - I tried setting “Interactable” to false for the canvas group my rebind buttons are in and I also tried deactivating the UI action map when remapping, both didn’t help. It does not matter if the gamepad is connected via bluetooth or cable.

I don’t want to implement some artificial block in the rebind function as this is clearly an issue with UI or input system and not my code. I just don’t know if there’s some setting I could utilize or if this is just a bug.

tl;dr: When I perform an interactive rebind for a gamepad button with Input System by pressing the submit button of my gamepad on a UI button, the UI button gets triggered multiple times. Deactivating “interactable” or the UI action map does not help. It’s not a connection issue. It’s not my code that’s calling itself. It does not happen when the UI button is selected via keyboard or mouse.

What can I do about that? Is there something like “discard remaining UI input” that I could call when initiating the rebind?

This is the code from the package that I mainly use: InputSystem/Assets/Samples/RebindingUI at develop · Unity-Technologies/InputSystem · GitHub

Does it activate on button press and again on release?
I just made a post about the same issue. Forum link

I also noticed it with the key rebinding samples but then it was happening when submitting any UI button.

No. It does not always happen twice. It does not even always happen the same amount of times.

Further information:

When I replace the code in the function that gets called from the UI button with a Debug.Log(), the log entry only appears once for every gamepad button press. It appears when the gamepad button has been pressed (not after release) as expected. So here’s the entire code for the UI button:

    public void StartInteractiveRebind() {
        if (!ResolveActionAndBinding(out InputAction action, out int bindingIndex)) {
            return;
        }

        // If the binding is a composite, we need to rebind each part in turn.
        if (action.bindings[bindingIndex].isComposite) {
            int firstPartIndex = bindingIndex + 1;
            if (firstPartIndex < action.bindings.Count && action.bindings[firstPartIndex].isPartOfComposite) {
                PerformInteractiveRebind(action, firstPartIndex, true);
            }
        }
        else {
            PerformInteractiveRebind(action, bindingIndex);
        }
    }

    private void PerformInteractiveRebind(InputAction action, int bindingIndex, bool allCompositeParts = false) {
        ongoingRebind?.Cancel(); // Will null out m_RebindOperation.

        void CleanUp() {
            ongoingRebind?.Dispose();
            ongoingRebind = null;
        }

        action.Disable();

        // Configure the rebind.
        ongoingRebind = action.PerformInteractiveRebinding(bindingIndex)
            .WithCancelingThrough("<Keyboard>/escape")
            .OnCancel(
                operation => {
                    action.Enable();
                    m_RebindStopEvent?.Invoke(this, operation);
                    m_RebindOverlay?.SetActive(false);
                    UpdateBindingDisplay();
                    CleanUp();
                })
            .OnComplete(
                operation => {
                    // If there's more composite parts we should bind, initiate a rebind
                    // for the next part.
                    if (allCompositeParts) {
                        int nextBindingIndex = bindingIndex + 1;
                        if (nextBindingIndex < action.bindings.Count && action.bindings[nextBindingIndex].isPartOfComposite) {
                            PerformInteractiveRebind(action, nextBindingIndex, true);
                        }
                    }

                    action.Enable();
                    m_RebindOverlay?.SetActive(false);
                    m_RebindStopEvent?.Invoke(this, operation);
                    UpdateBindingDisplay();
                    CleanUp();
                });

        // If it's a part binding, show the name of the part in the UI.
        string partName = default;
        if (action.bindings[bindingIndex].isPartOfComposite) {
            partName = $"Binding '{action.bindings[bindingIndex].name}'. ";
        }

        // Bring up rebind overlay, if we have one.
        m_RebindOverlay?.SetActive(true);
        if (m_RebindText != null) {
            string text = !string.IsNullOrEmpty(ongoingRebind.expectedControlType)
                ? $"{partName}Waiting for {action.bindings[bindingIndex].groups} input (press ESC key or Start button to cancel)..."
                : $"{partName}Waiting for input...";
            m_RebindText.text = text;
        }

        // If we have no rebind overlay and no callback but we have a binding text label,
        // temporarily set the binding text label to "<Waiting>".
        if (m_RebindOverlay == null && m_RebindText == null && m_RebindStartEvent == null && m_BindingText != null) {
            m_BindingText.text = "<Waiting...>";
        }

        // Give listeners a chance to act on the rebind starting.
        m_RebindStartEvent?.Invoke(this, ongoingRebind);

        ongoingRebind.Start();
    }

StartInteractiveRebind() is bound to the UI button via the inspector. It’s also StartInteractiveRebind() which is called multiple times, which I confirmed with a simple Debug.Log() at its beginning. This method is not used anywhere else.

As mentioned, this is mostly the original code from the Input System example. I suspect that somewhere in there something happens that disrupts the Event System’s processing of the button press.

As this is Unity 2021.3.39f1, it’s Input System 1.7.0. I tried a forced upgrade to 1.11.2 but that did not help either.