WithCancelingThrough not accepting more than one binding

Hi all,

I’ve been using the new input system for a couple days or so, and I am just figuring out how should everything work. This time, I am facing a problem on my rebinding system. I support gamepad and keyboard, and when I use the PerformInteractiveRebinding function from the InputAction, I see that only the last WithCancelingThrough is the one that matters, as repeated calls to this will override the info (from the docs: Note that every rebind operation has only one such path. Calling this method repeatedly will overwrite the path set from prior calls). Any workaround in my case, where I want to have more than one possible cancel button (one for keyboard and the other for gamepad)?

Thanks!

2 Likes

Hey just wondering if you found a solution to this issue as I seem to have it

@Szappy I ended up using a dirty little hack. Instead of using WithCancelingThrough, I intercepted certain cancel keys in the OnComplete call like this:

if (action.bindings[bindingIdx].overridePath == "<Gamepad>/select" || action.bindings[bindingIdx].overridePath == "<Keyboard>/escape")
            InputActionRebindingExtensions.ApplyBindingOverride(action, bindingIdx, new InputBinding{overridePath = ""});

That way both the gamepad select as well as keyboard escape keys would immediately save the binding as empty.

1 Like

I wanted to add to this that if you use .WithControlsHavingToMatchPath(“”) and have different key binding fields for keyboard and gamepad you can still use @Memoriesin8bit hack but you will have keyboard excluded for gamepad for instance. So in this case you can add .WithCancelingThrough(“/escape”) to the rebindingOperation for your gamepad.

Found a less dirty hack. Instead we can save the devicepaths and ask each device that we have for the last input time. In my case I have just a Gamepad and a Mouse + Keyboard. This will only cause issues if the player opens the rebinding for one button with a mouse and tries to cancel with controller. Because we predict, if we started the rebind operation with a controller, that we will try to cancel with the keyboard. Fine enough for me!

var excludingGamepadDevice = "<Gamepad>/select";
            var excludingKeyboardDevice = "<Keyboard>/escape";
            var excludingDevice = InputUtils.WasLastInputController() ? excludingGamepadDevice : excludingKeyboardDevice;

            var rebind = actionToRebind.PerformInteractiveRebinding(bindingIndex)
                .WithCancelingThrough(excludingDevice)

This is what the Method would look like for Checking, if the last Input device was a controller:

public static class InputUtils {
        public static bool WasLastInputController() {
            var lastInputTime = Keyboard.current.lastUpdateTime;

            if (Mouse.current.lastUpdateTime > lastInputTime) {
                lastInputTime = Mouse.current.lastUpdateTime;
            }

            return Gamepad.all.Count > 0 && Gamepad.all.Any(gamepad => gamepad.lastUpdateTime > lastInputTime);
        }
    }
1 Like

If you have one rebinding button for keyboard/mouse and one for gamepad and want to be able to cancel both with either input device:

PlayerInput playerInput;
string controlScheme = "Gamepad";
string inputName = "Jump";
var inputAction = playerInput.actions.FindAction(inputName);
var bindingIndex = inputAction.GetBindingIndex(InputBinding.MaskByGroup(controlScheme));

var rebind = inputAction.PerformInteractiveRebinding(bindingIndex)
	.WithCancelingThrough(controlScheme == "Gamepad" ? "<Keyboard>/escape" : "<Gamepad>/start")
	.OnPotentialMatch(operation =>
	{
		string path = operation.selectedControl.path;
		if (path == "/Keyboard/escape" || path.Contains("/start"))
		{
			operation.Cancel();
		}
	})
	// ...

The trick is to use the OPPOSITE input device for WithCancelingThrough, and catch the correct device with OnPotentialMatch.

1 Like

Thanks that works perfectly and is much cleaner than other suggestions IMO.