Getting binding assigned to an action in the current scheme?

I’m having a lot of fun implementing the new Input System, and so far it’s been pretty easy going.

What I’m trying to figure out now, (and isn’t covered in the FAQ) is how to tell what binding (button, etc.) is assigned to each action in whatever the current scheme is. Basically, if the player last touched an Xbox controller, I want to show Xbox button prompts, and if the player last touched KBM, I want to show KBM button prompts, and then immediately change them if the player switches to another controller type. I’d also like to get the exact button rather than just a combination of current scheme and action, so that it’ll still match if the player rebinds the controls.

Ideally I want to know the current scheme, the button / stick / whatever bound to each action in the current scheme, and the type of controller the scheme is using (XB360, XBOne, PS4, etc.) if it’s using a controller.

Any suggestions?

1 Like

activeControl seemed promising, but looks like a dead end; it reports the path of the device that is currently sending the action, but it only works while the action is occurring, and otherwise it returns null. :frowning:

I was struggling with a similar issue. The problem is that bindings are only used for resolving the controls, afterwards there’s no straightforward way to figure out which binding was responsible for resolving a given control.

See my previous thread here: Displaying what an action is bound to

In the end, I’m going through InputAction’s controls and bindings and use InputControlPath.Matches to figure out which binding a control could belong to. This is not perfect, as multiple bindings could match a given control path, but works good enough for our use case.

OK, I solved this, but my solution feels super janky, and I hope that a better solution is implemented in a future version of New Input System.

First, I have a static MainGameInputHandler class. In that class, I define an Action enum that includes every one of my actions. Then I have this code:

    public static InputDevice currentDevice { get; private set; } = new Gamepad();

    private static int _currentControl = 0;
    private static MainGameInputActions _inputActions = new MainGameInputActions();

    public static InputAction InputForAction(Action action)
    {
        InputAction a = _inputActions.MainGame.Get().FindAction(action.ToString());

        if(a?.activeControl?.device != null)
        {
            currentDevice = a.activeControl.device;
            if(currentDevice is Keyboard)
            {
                _currentControl = 1;
            }
            else if(!(currentDevice is Mouse))
            {
                _currentControl = 0;
            }
        }

        if(a == null)
        {
            Debug.LogWarning("can't find action for " + action.ToString());
        }

        return a;
    }

    public static InputControl ControlForAction(Action action)
    {
        return InputForAction(action)?.controls[_currentControl];
    }

with this I can get the current input for an action with InputForAction, and I can get the current specific control assigned to an action with ControlForAction (note here that in my InputActions asset, the names of the actions are identical to the stringified entries of the Action enum, and the controls[0] schema is gamepad, and the controls[1] schema is KBM).

With me so far? Great!

Next, everywhere I want to show the control icon for an action, I have a ControlIconBehavior, which has a MainGameInputHandler.Action as a public member, and does this:

    void Update()
    {
        InputControl activeControl = MainGameInputHandler.ControlForAction(action);
        if(_control != activeControl)
        {
            _control = activeControl;
            SetControls(_control);
        }
    }

    public void SetControls(InputControl control)
    {
        if(control.device.name == "Keyboard" && action == MainGameInputHandler.Action.Move)
        {
            // special cheat to set kbm move icon to WASD
            icon.sprite = controlIconRoster.kbmSprites.wasdSprite;
            label.text = "";
        }
        else
        {
            (icon.sprite, label.text) = controlIconRoster.IconAndLabelForControl(control);
        }
    }

As you can see, for “keyboard move” it just directly grabs a “WASD” sprite, and for anything else it asks my ControlIconRoster for the proper sprite (and label if the proper sprite is a keyboard key).

Now onward to ControlIconRoster!

“Rosters” are scriptable objects I’ve been using for a long time now (since before there were even scriptable objects! I just used gameobjects that never got instantiated!) as sort of a static storage for Unity objects.

The ControlIconRoster contains a whole bunch of sprite references. It has references to KBM sprites (which are used when input is in KBM mode), and the code to fetch those looks like this:

        if(control.device.name.Contains("Keyboard"))
        {
            string label = control.name.Capitalize();
            if(label.Length > 1)
            {
                label = label.Replace("Left", "L.").Replace("Right", "R.");
                sprite = kbmSprites.wideKeySprite;
            }
            else
            {
                sprite = kbmSprites.keySprite;
            }

            return (sprite, label);
        }
        else if(control.device.name.Contains("Mouse"))
        {
            if(control.name.Contains("leftButton"))
            {
                return (kbmSprites.mouseLSprite, "");
            }
            else if(control.name.Contains("rightButton"))
            {
                return (kbmSprites.mouseRSprite, "");
            }
            else
            {
                return (kbmSprites.mouseSprite, "");
            }
        }

(keep in mind here that control is a UnityEngine.InputSystem.InputControl)

Hopefully that’s fairly self-explanatory.

Then the roster ALSO has “sets” of icons for different kinds of controllers; each control set has a name, and a list of icon sprites, and I currently have sets for “XInput”, “XboxOne”, and “DualShock” (i.e. PS4). The code to decide which of those to use based on what controller is plugged in looks like this:

        ControlSet set = controlSets[0];
        string devicename = control.device.name.Replace("XboxGamepad", "XInput");

        foreach(ControlSet c in controlSets)
        {
            if(devicename.Contains(c.name))
            {
                set = c;
            }
        }

        foreach(ControlSet.ControlIcon i in set.icons)
        {
            if(control.parent.name.Contains("dpad"))
            {
                if(i.input.ToString() == "dpad_" + control.name)
                {
                    sprite = i.sprite;
                }
            }
            else if(control.name.Contains(i.input.ToString()))
            {
                sprite = i.sprite;
            }
        }

        if(sprite == null)
        {
            Debug.LogWarning("couldn't find match for: " + control.path);
        }

        return (sprite, "");

so simple! :smile:

Incidentally, I’m using these controller / KBM icons: FREE Keyboard and controllers prompts pack | OpenGameArt.org

I hope that helps out others who need to implement a similar “control icons magically change to reflect whatever input device you touched last” system in their own games, and I hope that this is all made obsolete by a much more straightforward feature integrated into a future release of New Input System. :slight_smile:

2 Likes

since writing this I’ve replaced all instances of control.device.name.Contains("Keyboard") with control.device is Keyboard, and the same for Mouse, but otherwise it’s all the same.

1 Like

Thanks luvcraft.

In the old input manager, I had a setup for handling multiple sets of bindings (eg keyboard and mouse, gamepad, alt keyboard, etc) and raised an event whenever the user switched from one set to another so I could redraw the on-screen tooltips in the new scheme. It worked great.

I’ve been putting off redoing this since moving to the new input system many months ago, and finally decided to look at it over the weekend just gone. Long story short, the weekend was a bit of a waste.

Hopefully a nicer solution will become available after the initial release and the team can start looking at adding features and polish.

@luvcraft Is there already a better solution for this?

Some time ago I have seen an example from Unity Itself of a way of doing this, but I could not find this example anymore to see how they implemented it.

In case someone struggle with this problem, it is possible to get InputBindings as string like this:

inputActions.Player.Fire.GetBindingDisplayString(InputBinding.MaskByGroup("Gamepad"))

Fire is some action and GetBindingDisplayString returns all bindings separated by pipe for example “RT | LMB”.
The only problem was to get only single one for connected device, but it can be done with groups parameter.

There is documentation about this here Input Bindings | Input System | 1.0.2

2 Likes

String value=playerInputAction.currentActionMap[“Submit”].GetBindingDisplayString(InputBinding.MaskByGroup(“Keyboard&Mouse”));

this works for me you only need to have the playerInputAction

If you want the binding for the action for the currently used scheme, you can do it like this (with playerInput anyway)

int bindingIndex = action.GetBindingIndex(group: playerInput.currentControlScheme); 
var displayString = action.GetBindingDisplayString(bindingIndex, out string deviceLayoutName, out string controlPath);```
4 Likes

For those still looking for the answer, use .GetBindingDisplayString(InputBinding.MaskByGroup(“Your Control Scheme Name”) and it will display the correct binding based on which control scheme you provide. Alternatively, if you don’t use InputBinding.MaskByGroup, it will display both. For example, it may display “Press A | X to interact”

1 Like

if your GetBindingIndex call returns -1, ensure you’ve specified control scheme inside the binding

int bindingIndex = action.GetBindingIndex(group: playerInput.currentControlScheme); 
var displayString = action.GetBindingDisplayString(bindingIndex, out string deviceLayoutName, out string controlPath);```

![9875814--1424121--image_2024-06-06_11-59-25.png|548x242](upload://nAVCcHR8yZwNhg7D00ioCfTSGN3.png)

Hey, sorry for necroposting but the solutions given here work fine but are device specific. Meaning that if you’re using a playstation controller the result will be “Cross” but if you’re using an Xbox controller it will be “A”.

I wanted a solution that give me the universal face buttons (“buttonSouth”, “buttonWest”,etc) and I came up with this, not the most elegant but it works.

		string currentScheme = playerInput.currentControlScheme;

		foreach (InputBinding b in playerInput.actions[actionName].bindings)
		{
			if (b.groups.Contains(currentScheme))
			{
				bindingName = b.effectivePath; // effectivePath = "<Gamepad>/buttonSouth"

				bindingName = bindingName.Substring(bindingName.LastIndexOf('/') + 1);
			}
		} //Prints buttonSouth