Detect when device changes and get corresponding control scheme

Hello guys,

I made Controls screen where user can change key binding. At the moment it is only keyboard based, So I created separate control schemes for Keyboard and Gamepad. Now I want to change binding to the corresponding control scheme when device changes, anybody can provide any help with that?

P.S I know that if you have PlayerInput you can implement method OnControlsChanged() which will trigger when device has changed, and also currentControlScheme property which will give you currently used scheme, but I’m not using PlayerInput in the project.

It’s actually really easy to do. You can do this with following code:

++InputUser.listenForUnpairedDeviceActivity;
InputUser.onUnpairedDeviceUsed += (ctx, event_ptr) =>
{
    var device = ctx.device;
}

In there you can check whether the device is some kind of a gamepad, keyboard, etc. Here’s a simple script/class with an event action, which you then can subscribe to. You can also grab the CurrentControlScheme variable if you wish to get access to the current InputScheme without being bound to events.

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Users;

/*Helper class for input management. Only have exactly one permanently active object
in your scene at any time holding an instance of this - or create a singleton if not possible otherwise*/
public class InputExtensions : MonoBehaviour
{
// Subscribe to this event
public static event Action<ControlScheme> OnInputSchemeChanged;
public static ControlScheme CurrentControlScheme { get; private set; };
public InputUser user;

void Awake()
{  
                user = InputUser.CreateUserWithoutPairedDevices();
               ++InputUser.listenForUnpairedDeviceActivity;
                InputUser.onUnpairedDeviceUsed += (ctrl, eventPtr) =>
                {
                    var device = ctrl.device;

                    if ((CurrentControlScheme == ControlScheme.KeyboardMouse) &&
                         ((device is Pointer) || (device is Keyboard)))
                    {
                         InputUser.PerformPairingWithDevice(device, user);
    if (OnInputSchemeChanged != null)           OnInputSchemeChanged(ControlScheme.KeyboardMouse);
SetUserControlScheme(ControlScheme.KeyboardMouse);
                         return;
                    }

                    if (device is Gamepad)
                    {
                         if (OnInputSchemeChanged != null) OnInputSchemeChanged(ControlScheme.Gamepad);
                         CurrentControlScheme = ControlScheme.Gamepad;
SetUserControlScheme(ControlScheme.Gamepad);
                    }
                    else if ((device is Keyboard) || (device is Pointer))
                    {
                        if (OnInputSchemeChanged != null) OnInputSchemeChanged(ControlScheme.KeyboardMouse);
                        CurrentControlScheme = ControlScheme.KeyboardMouse;
                        SetUserControlScheme(ControlScheme.KeyboardMouse);
                    }
                    else return;

                    input_source.user.UnpairDevices();
                    InputUser.PerformPairingWithDevice(device, user);
                };
                input_source.user.UnpairDevices();
}

public void SetUserControlScheme(ControlScheme scheme)
{
     /*IMPORTANT NOTE - For this to work correctly, your InputSchemes within the Input
     Settings must be spelled exactly "KeyboardMouse" and "Gamepad", or you just rename 
     the enum properties of ControlScheme*/
     user.ActivateControlScheme(scheme.ToString());
}

public enum ControlScheme
{
      KeyboardMouse, Gamepad
}
}

If you have any further questions - feel free to ask! :smile:

[Edit: I know you don’t want to use PlayerInputs in your project. At line 17, we’re creating a new user for testing the changes on the input. The code was only tested with the user of a playerinput instead of a one that was created manually. If you run into problems, you may have to look into the custom user we create for pairing the devices!]

2 Likes

Thank you! Automatic scheme switching works great (keyboad and mouse or gamepad). Here is my modified version which works for me and my manually created single player (without PlayerInput script):

using System;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Users;

/*Helper class for input management. Only have exactly one permanently active object
in your scene at any time holding an instance of this - or create a singleton if not possible otherwise*/
public class InputExtensions : MonoBehaviour
{
    // Subscribe to this event
    public static event Action<ControlScheme> OnInputSchemeChanged;
    public static ControlScheme CurrentControlScheme { get; private set; }
    public InputActionAsset inputActions;

    public InputUser user;
    private void Start() => StartAutoControlSchemeSwitching();
    private void OnDestroy() => StopAutoControlSchemeSwitching();
   
    void StartAutoControlSchemeSwitching()
    {
        user = InputUser.CreateUserWithoutPairedDevices();
        user.AssociateActionsWithUser(inputActions.actionMaps[0]); // need to be there at least one actionmap defined in InputActionAsset, otherwise rises exception during paring process
        ++InputUser.listenForUnpairedDeviceActivity;
        InputUser.onUnpairedDeviceUsed += InputUser_onUnpairedDeviceUsed;
        user.UnpairDevices();
    }
    private void StopAutoControlSchemeSwitching()
    {
        InputUser.onUnpairedDeviceUsed -= InputUser_onUnpairedDeviceUsed;       
        if (InputUser.listenForUnpairedDeviceActivity>0)
                --InputUser.listenForUnpairedDeviceActivity;       
        user.UnpairDevicesAndRemoveUser();
    }   

    private void InputUser_onUnpairedDeviceUsed(InputControl ctrl, UnityEngine.InputSystem.LowLevel.InputEventPtr eventPtr)
    {
        var device = ctrl.device;

        if ((CurrentControlScheme == ControlScheme.KeyboardMouse) &&
             ((device is Pointer) || (device is Keyboard)))
        {
            InputUser.PerformPairingWithDevice(device, user);
            if (OnInputSchemeChanged != null) OnInputSchemeChanged(ControlScheme.KeyboardMouse);
            SetUserControlScheme(ControlScheme.KeyboardMouse);
            return;
        }

        if (device is Gamepad)
        {
            if (OnInputSchemeChanged != null) OnInputSchemeChanged(ControlScheme.Gamepad);
            CurrentControlScheme = ControlScheme.Gamepad;
            SetUserControlScheme(ControlScheme.Gamepad);
        }
        else if ((device is Keyboard) || (device is Pointer))
        {
            if (OnInputSchemeChanged != null) OnInputSchemeChanged(ControlScheme.KeyboardMouse);
            CurrentControlScheme = ControlScheme.KeyboardMouse;
            SetUserControlScheme(ControlScheme.KeyboardMouse);
        }
        else return;

        user.UnpairDevices();
        InputUser.PerformPairingWithDevice(device, user);
    }
    public void SetUserControlScheme(ControlScheme scheme)
    {
        //user.ActivateControlScheme(scheme.ToString());
        user.ActivateControlScheme(inputActions.controlSchemes[(int)scheme]); // this should be faster and not vulnerable to scheme string names
    }
    public enum ControlScheme
    {
        KeyboardMouse = 0, Gamepad = 1 // just need to be same indexes as defined in inputActionAsset
    }
}

Note: Later I moved this code into scriptable object which makes things even easier.

3 Likes

I’m not sure this is the right way to do it. It appears like it works, but unpairing and repairing every time you switch controls? That seems not right.

It also causes an issue with input where if you’re inputting both keyboard&mouse input and gamepad input simultaneously, lots of input gets lost. But it doesn’t when using PlayerInput, so they must do it a different way internally.

**EDIT: But apparently after checking the InputUser.onChanged event, PlayerInput also appears to unpair/re-pair the devices when changing control schemes. So maybe that is the way they do it as well. But it doesn’t explain why your way causes input loss when pressing two different devices simultaneously.

Hello!
It’s been almost 2 years when I made the script for detecting input changes, and it always worked perfectly. It actually took me almost an eternity to figure out something that worked, I was not able to find any other way to achieve the same results (if ur concerned the performance impact isn’t very high because the script is based on events from the inputsystem itself). Input loss is also probably caused by the inputsystem, but in most cases you don’t play with keyboard/mouse AND a gamepad (at least for me this wasn’t an issue). Unity should implement this in their input system, as input-scheme detection is very common in games (e.g. when you want to swap buttonhints/tooltips,…).

Hope the script (also the one from jackieczech) helps anyway if ur using it! :slight_smile:

void Start() {
       InputSystem.onEvent += OnDeviceChange;
}
void OnDestroy() {
       InputSystem.onEvent -= OnDeviceChange;
}
private void OnDeviceChange(InputEventPtr eventPtr, InputDevice device)  {
        if (_lastDevice == device) return;

        if (eventPtr.type != StateEvent.Type) return;

        bool validPress = false;
        foreach (InputControl control in eventPtr.EnumerateChangedControls(device, 0.01F))
        {
            validPress = true;
            break;
        }
        if (validPress is false) return;

        if (device is Keyboard || device is Mouse)
        {
            if (_controlScheme == ControlScheme.KeyboardMouse) return;
            _controlScheme = ControlScheme.KeyboardMouse;
            OnControlSchemeChanged?.Invoke(_controlScheme);
        }
        else if (device is Joystick)
        {
            if (_controlScheme == ControlScheme.Joystick) return;
            _controlScheme = ControlScheme.Joystick;
            OnControlSchemeChanged?.Invoke(_controlScheme);
        } else if (device is Gamepad)
        {
            if (_controlScheme == ControlScheme.Gamepad) return;
            _controlScheme = ControlScheme.Gamepad;
            OnControlSchemeChanged?.Invoke(_controlScheme);
        }

    }

This for me seems to most accurately replicate PlayerInput’s OnControlsChanged without causing the input loss when two devices are giving input simultaneously.

Based it on a response from this thread https://discussions.unity.com/t/760071 . I altered it a bit because the one they had used Linq and generated a lot of garbage each frame, but mine doesn’t.

1 Like

Just wanted to chime in and say that there is the InputControlScheme.FindControlSchemeForDevice() method.
https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/api/UnityEngine.InputSystem.InputControlScheme.html#UnityEngine_InputSystem_InputControlScheme_FindControlSchemeForDevice__1_UnityEngine_InputSystem_InputDevice___0_

I’m not saying this is better, just that there’s a built-in method for finding the correct control scheme.

Here’s a quick example of how to use it.

//The inputActionsAsset contains your control schemes.
public InputActionAsset inputActions;

private void SomeMethod(InputDevice device)
{
    //Cast is necessary because the method returns type 'InputControlScheme?'
    //Requires the device and the Collection of controlSchemes you want to iterate through.
    InputControlScheme scheme = (InputControlScheme)InputControlScheme.FindControlSchemeForDevice(device, inputActions.controlSchemes);

        //Do stuff with scheme.
}

Ok, So upon further testing the method mentioned above does not know how to properly deal with “Keyboard&Mouse” as it sees them as two separate control schemes because they are two separate devices. I’m not sure why that’s how it works.

Here’s what I’m going with for now inspired by those above:

private string GetCorrespondingControlScheme(InputDevice device)
{
    if (device is Gamepad)
    {
        return "Gamepad";
    }
    if (device is Keyboard)
    {
        return "Keyboard&Mouse";
    }
    if (device is Mouse)
    {
        return "Keyboard&Mouse";
    }
    return null;
}