Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Join us on March 30, 2023, between 5 am & 1 pm EST, in the Performance Profiling Dev Blitz Day 2023 - Q&A forum and Discord where you can connect with our teams behind the Memory and CPU Profilers.
    Dismiss Notice

Detect most recent input device/type?

Discussion in 'Input System' started by Peter77, Sep 30, 2019.

  1. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,326
    I'm trying to integrate/switchto the new input system and I'm stuck at the following issue.

    My game supports gamepad, as well as mouse and keyboard controls when running on PC. Whatever input device the player used last, affects what the game displays for help texts.

    When the player makes any gamepad input, it causes the game to display gamepad sprites in various texts. If the player then switches from gamepad to keyboard, these texts are updated to no longer display gamepad sprites, but keyboard images for example.

    In order further support this, I need to figure out what device type the player used most recently and that's where I'm currently stuck.

    I thought I could use
    IsActuated
    to figure this out:
    Code (CSharp):
    1. Debug.LogFormat("gamepad: {0}, mouse: {1}, keyboard: {2}",
    2. Gamepad.current?.IsActuated(0),
    3. Mouse.current?.IsActuated(0),
    4. Keyboard.current?.IsActuated(0));
    ... but Gamepad and Mouse return true always, once input was made. Keyboard on the other hand resets as I expected after the key is released.

    How can I detect what device was used most recently?
     
  2. Jichaels

    Jichaels

    Joined:
    Dec 27, 2018
    Posts:
    237
    I think what you should do is have different control scheme for each devices (mouse and keyboard can be together though), and then, if you're using PlayerInput, you can get the current control scheme to display what you want to display based on that. I believe there is also an event to know when the player switched control scheme.
     
  3. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    For the simplest possible setup, I'd recommend using PlayerInput. It has built-in support for automatic control scheme switching and you'll get notified when a switch happens. Simply define both a keyboard and a gamepad scheme and listen to InputUser.onChange where you will get a InputUserChange.ControlSchemeChanged notification whenever the user switches. From there, you can trigger a refresh of the UI hints you are displaying to match the current control scheme (which you can query from PlayerInput.currentControlScheme or InputUser.controlScheme).

    As for detecting the most recently used device, InputDevice.lastUpdateTime will tell you the timestamp of the most recent state event received for the device.

    However, even if PlayerInput doesn't fit what you're looking for, I'd recommend using InputUser to explicitly track used devices via its pairing mechanism and use InputUser.listenForUnpairedDeviceActivity to detect when the user switches. This is what PlayerInput does internally.

    The problem with simply comparing timestamps is that it won't be very robust. A device being updated does not necessarily equate user interaction. PS4 controllers, for example, will just constantly send noise from their builtin gyro even if the gamepad is not used by anyone. InputUser/PlayerInput have builtin support for filtering that out.
     
    migwellian, jwinn, bobbaluba and 2 others like this.
  4. Lavidimus

    Lavidimus

    Joined:
    Sep 27, 2011
    Posts:
    75
    I'm speaking with limited experience using the new input system.

    I too would like an easy way to be notified on control scheme change. From my brief use, I have found a host of bugs in PlayerInput (it can even break UI functionality, seems like a big oversight). Considering this, I would prefer to avoid it at all costs but would like to be able to easily tell what scheme I'm using. Your solution above appears to require InputUser.onUnpairedDeviceUsed and InputUser.listenForUnpairedDeviceActivity which both seem to be driven by PlayerInput.

    Is there any way to get the current scheme easily without PlayerInput plaguing us, or do we need to strip PlayerInput for relevant code and roll our own solution?
     
  5. Jichaels

    Jichaels

    Joined:
    Dec 27, 2018
    Posts:
    237
    I'm also speaking with limited experience and a lot of problem myself, but I think you can create an InputUser and assign it devices and so on. So you'll be able to use Inputer.onChange etc. Again, not sure :(
     
  6. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    InputUser is an independent API. PlayerInput is built on top of it but the API can be used from anywhere.
     
  7. Lavidimus

    Lavidimus

    Joined:
    Sep 27, 2011
    Posts:
    75
    Does this require us to create a user then assign devices as Jichaels stated above? Simply registering for an event from the InputUser class will never fire said events unless PlayerInput is in the scene from my limited testing. If we are required to create a user is there a tutorial/documentation somewhere or do we need to dig through PlayerInput to see how that's happening?
     
  8. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Yes, you need to handle pairing yourself.

    Not ATM.
     
  9. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    905
    Control schemes are a high-level concept implemented in PlayerInput. If you use actions directly, then the concept of schemes doesn't really exist.

    Our game is only single player and I converted it from a custom system based on the old input manager to the new input system. The way I handled detecting the currently used device is to just use the action themselves.

    Without PlayerInput, what the schemes do in the input action editor is simply setting the group on the bindings of the action. I registered a handler with actionTriggered on my InputActionMap and used InputControlScheme.FindControlSchemeForDevice to figure out which scheme that activation belongs to. I use this to know which scheme was used last and when it changes.

    This will only work for the actual bound controls, i.e. it won't detect a change if you press any key on the keyboard or gamepad, you have to press a key that is actually used for your game.
     
    jmarsh411 likes this.
  10. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,326
    Which I think is a slick workaround, as you don't have to deal with "noise" such as from the builtin gyro on PS4, that could falsely cause a change.

    I came up with a similar workaround, but I don't test a single action, but all actions I configured for the game. But your method seems to be more clever, just using a single action. I will probably change it to a single action per device as well now, so that "actionTriggered" would include all buttons I want to consider for a valid device change.
     
  11. Lavidimus

    Lavidimus

    Joined:
    Sep 27, 2011
    Posts:
    75
    This is more or less what I did.
     
  12. spiritworld

    spiritworld

    Joined:
    Nov 26, 2014
    Posts:
    29
    Here's my solution for runtime changes of button sprites:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.InputSystem;
    4. using UnityEngine.InputSystem.Users;
    5.  
    6. // add this into built-in button you can create from menu: UI/Button
    7. public class InputDeviceChangeHandler : MonoBehaviour {
    8.    // refs to Button's components
    9.     private Image buttonImage;
    10.  
    11.    // refs to your sprites
    12.     public Sprite gamepadImage;
    13.     public Sprite keyboardImage;
    14.  
    15.     void Awake() {
    16.         buttonImage = GetComponent<Image>();
    17.         PlayerInput input = FindObjectOfType<PlayerInput>();
    18.         updateButtonImage(input.currentControlScheme);
    19.     }
    20.  
    21.     void OnEnable() {
    22.         InputUser.onChange += onInputDeviceChange;
    23.     }
    24.  
    25.     void OnDisable() {
    26.         InputUser.onChange -= onInputDeviceChange;
    27.     }
    28.  
    29.     void onInputDeviceChange(InputUser user, InputUserChange change, InputDevice device) {
    30.         if (change == InputUserChange.ControlSchemeChanged) {
    31.             updateButtonImage(user.controlScheme.Value.name);
    32.         }
    33.     }
    34.  
    35.     void updateButtonImage(string schemeName) {
    36.        // assuming you have only 2 schemes: keyboard and gamepad
    37.         if (schemeName.Equals("Gamepad")) {
    38.             buttonImage.sprite = gamepadImage;
    39.         }
    40.         else {
    41.             buttonImage.sprite = keyboardImage;
    42.         }
    43.     }
    44. }
    45.  
     
    Last edited: Oct 11, 2021
  13. vecima

    vecima

    Joined:
    Jun 6, 2017
    Posts:
    16
    Thanks spiritworld,
    That was super helpful for getting my approach to this set up, though I took it a little bit further because I'm supporting device-specific icons, as much as I can.

    This made me notice though that it seems every time a scene change happens (at least running in the editor) the PlayerInput.currentControlScheme seems to get reset to "Keyboard&Mouse". Shouldn't this value stay unpopulated until something actually gets used?
     
  14. Daerst

    Daerst

    Joined:
    Jun 16, 2016
    Posts:
    275
    I'm trying to find a valid control scheme for given user input. I tried to use an approach like @Adrian, @Peter77 and @Lavidimus described it: Get notified about any Action that is triggered (either explicitly subscribing to the ActionMaps'
    actionTriggered
    callbacks, or globally to
    InputSystem.onActionChange
    ), then react to that. However, just querying the device and using that to determine the scheme does not work for me since I would like to differentiate between KeyboardWASD and KeyboardArrow schemes.

    Here's how far I got:
    Code (CSharp):
    1. private void ActionMap_actionTriggered(InputAction.CallbackContext obj)
    2. {
    3.     var inputAction = obj.action;
    4.     var binding = inputAction.GetBindingForControl(inputAction.activeControl).Value;
    5.     Debug.Log(binding.groups);
    6. }
    ...which actually works fine for simple Actions, but fails for Composites because they don't have the groups (= ControlSchemes) themselves.

    Any idea how I could get the actual 'leaf' binding from the Composite, or find the Control Scheme for my activeControl in an altogether different way?
     
    Irfan_Swag likes this.
  15. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    We're definitely missing a method that simply gives you an index of the currently active binding. Maybe we should just have an InputAction.activeBinding getter just like InputAction.activeControl.

    Hmm, that one hits a peculiarity in how the current data structure is set up. Leaning towards considering this one a bug.

    The problem is that in the data, *all* controls from a composite are associated with it. So when the code looks up the binding for the control, it comes across the composite before it comes across the part bindings. So it ends up returning the index of the composite instead proceeding to look at the part bindings.

    I'm thinking, this is something we should fix on our side. GetBindingForControl() should return the part binding here, not the composite.
     
  16. vecima

    vecima

    Joined:
    Jun 6, 2017
    Posts:
    16
    Here's how I'm doing it (If I understood your question correctly). I've set mine up to support only a composite that's a "button with one modifier" type. I wanted to show "Tab" and "Shift + Tab" navigation and I have icons for those two keyboard keys. I also intended for this class to be on a child of the button, and have children called "Control Image" and "Modifier Image" - adjust to your needs, of course. The below code is also missing some extra classes I use for storing preferences (PreferenceData) and configuring control icons (ControlDevices), but hopefully gives a sense of how I accomplished these things.

    EDIT: Keep in mind that some of this code is because I wanted to support Keyboard&Mouse/Gamepad/Touchscreen as well as allowing the user to decide which Icons they want to see (ie, playing with any gamepad but showing xbox icons if you prefer)

    EDIT2: sorry for all the "this.xyz" ... I'm a Java software developer by trade and brought some of my habits with me.

    Code (CSharp):
    1.  
    2.     public class InputActionIcon : MonoBehaviour
    3.     {
    4.         // this is required to work around the fact that OnActionChange is static.
    5.         private static List<InputActionIcon> s_InputActionIcons;
    6.  
    7.         private Image controlImage;
    8.         private Image modifierImage;
    9.         private List<string> controls;
    10.         private List<string> modifierControls;
    11.  
    12.         /// <summary>
    13.         /// Reference to the action that is to be rebound.
    14.         /// </summary>
    15.         public InputActionReference actionReference
    16.         {
    17.             get => m_Action;
    18.             set
    19.             {
    20.                 m_Action = value;
    21.                 //UpdateActionLabel();
    22.                 UpdateControlDisplay();
    23.             }
    24.         }
    25.         [Tooltip("Reference to action that is to be rebound from the UI.")]
    26.         [SerializeField]
    27.         private InputActionReference m_Action;
    28.  
    29.         [Tooltip("Control path to use if no action is provided above.")]
    30.         public string controlPath;
    31.  
    32. //preference data is a scriptable object that stores preferences
    33.         public PreferenceData preferenceData;
    34. //control devices is a scriptable object that maps input devices to collections of sprites
    35.         public ControlDevices controlDevices;
    36.  
    37.         void Awake()
    38.         {
    39.             this.controls = new List<string>();
    40.             this.modifierControls = new List<string>();
    41.             this.controlImage = this.transform.Find("Control Image").GetComponent<Image>();
    42.             this.modifierImage = this.transform.Find("Modifier Image").GetComponent<Image>();
    43.         }
    44.  
    45.         protected void OnEnable()
    46.         {
    47.             if (s_InputActionIcons == null)
    48.                 s_InputActionIcons = new List<InputActionIcon>();
    49.             s_InputActionIcons.Add(this);
    50.             if (s_InputActionIcons.Count == 1) {
    51.                 InputSystem.onActionChange += OnActionChange;
    52.                 InputUser.onChange += OnInputDeviceChange;
    53.             }
    54.             UpdateControlDisplay();
    55.         }
    56.  
    57.         protected void OnDisable()
    58.         {
    59.             s_InputActionIcons.Remove(this);
    60.             if (s_InputActionIcons.Count == 0) {
    61.                 s_InputActionIcons = null;
    62.                 InputSystem.onActionChange -= OnActionChange;
    63.                 InputUser.onChange -= OnInputDeviceChange;
    64.             }
    65.         }
    66.  
    67.         public void UpdateControlDisplay()
    68.         {
    69.             this.controls.Clear();
    70.             this.modifierControls.Clear();
    71.  
    72.             // examine bindings and separate controls and modifiers.
    73.             var action = m_Action?.action;
    74.             if (action != null) {
    75.                 for (int i = 0; i < action.bindings.Count; i++) {
    76.                     //Debug.Log("i: " + ", composite: " + action.bindings[i].isComposite + ", partOfComposite: " + action.bindings[i].isPartOfComposite + ", path: " + action.bindings[i].path + ", name: " + action.bindings[i].name);
    77.                     if (action.bindings[i].name == "modifier") {
    78.                         this.modifierControls.Add(action.bindings[i].effectivePath.Substring(action.bindings[i].effectivePath.IndexOf("/") + 1));
    79.                     } else {
    80.                         this.controls.Add(action.bindings[i].effectivePath.Substring(action.bindings[i].effectivePath.IndexOf("/") + 1));
    81.                     }
    82.                 }
    83.             } else {
    84.                 this.controls.Add(this.controlPath.Substring(this.controlPath.IndexOf("/") + 1));
    85.             }
    86.  
    87.            // here you'll need something that can return a sprite for the desired image given a device name and a control path.  I do it with some scriptable objects but the below section of code won't work if copy pasted.
    88.  
    89.  
    90.             // determine which icon set to draw from (either auto-detect or use preferred set).
    91.             ControlIcons controlIcons = null;
    92.             if (this.preferenceData.controlIconSelection == PreferenceData.PREFERENCE_CONTROL_ICONS_AUTO) {
    93.                 foreach (DeviceMapping deviceMapping in this.controlDevices.deviceMap) {
    94.                     if (this.preferenceData.lastUsedDevice.Contains(deviceMapping.deviceName)) {
    95.                         controlIcons = deviceMapping.controlIcons;
    96.                     }
    97.                 }
    98.  
    99.                 // If last device didn't match a mapping with the right controls, see if we have a control path,
    100.                 // and try to use that to match. The most likely scenario for this is when the last device is set
    101.                 // to keyboard, but the on-screen controls are being displayed.
    102.                 if (controlIcons == null && this.controlPath != null) {
    103.                     foreach (DeviceMapping deviceMapping in this.controlDevices.deviceMap) {
    104.                         if (this.controlPath.Contains(deviceMapping.deviceName)) {
    105.                             controlIcons = deviceMapping.controlIcons;
    106.                         }
    107.                     }
    108.                 }
    109.             } else {
    110.                 controlIcons = this.controlDevices.deviceMap[this.preferenceData.controlIconSelection].controlIcons;
    111.             }
    112.  
    113.             if (controlIcons != null) {
    114.                 foreach (string control in this.controls) {
    115.                     Sprite sprite = controlIcons.GetSprite(control);
    116.                     if (sprite != null) {
    117.                         this.controlImage.sprite = sprite;
    118.                     }
    119.                 }
    120.                 this.modifierImage.gameObject.SetActive(false);
    121.                 foreach (string control in this.modifierControls) {
    122.                     Sprite sprite = controlIcons.GetSprite(control);
    123.                     if (sprite != null) {
    124.                         this.modifierImage.sprite = sprite;
    125.                         this.modifierImage.gameObject.SetActive(true);
    126.  
    127.                     }
    128.                 }
    129.             }
    130.         }
    131.  
    132.         // When the action system re-resolves bindings, we want to update our UI in response. While this will
    133.         // also trigger from changes we made ourselves, it ensures that we react to changes made elsewhere. If
    134.         // the user changes keyboard layout, for example, we will get a BoundControlsChanged notification and
    135.         // will update our UI to reflect the current keyboard layout.
    136.         private static void OnActionChange(object obj, InputActionChange change)
    137.         {
    138.             if (change != InputActionChange.BoundControlsChanged)
    139.                 return;
    140.  
    141.             //Debug.Log("OnActionChange - change: " + change);
    142.  
    143.             var action = obj as InputAction;
    144.             var actionMap = action?.actionMap ?? obj as InputActionMap;
    145.             var actionAsset = actionMap?.asset ?? obj as InputActionAsset;
    146.  
    147.             for (var i = 0; i < s_InputActionIcons.Count; ++i)
    148.             {
    149.                 var component = s_InputActionIcons[i];
    150.  
    151.                 var referencedAction = component.actionReference?.action;
    152.                 if (referencedAction == null)
    153.                     return;
    154.  
    155.                 if (referencedAction == action ||
    156.                     referencedAction.actionMap == actionMap ||
    157.                     referencedAction.actionMap?.asset == actionAsset) {
    158.                     component.UpdateControlDisplay();
    159.                 }
    160.             }
    161.         }
    162.  
    163.         private static void OnInputDeviceChange(InputUser user, InputUserChange change, InputDevice device)
    164.         {
    165.             if (change == InputUserChange.DevicePaired
    166.                 && !device.ToString().Contains("Mouse")) {
    167.                 //Debug.Log("device paired: " + device);
    168.                 for (var i = 0; i < s_InputActionIcons.Count; ++i) {
    169.                     var component = s_InputActionIcons[i];
    170.                     if (device.ToString() != component.preferenceData.lastUsedDevice) {
    171.                         string previousControlScheme = component.preferenceData.lastUsedControlScheme;
    172.                         string previousDevice = component.preferenceData.lastUsedDevice;
    173.  
    174.                         component.preferenceData.lastUsedDevice = device.ToString();
    175.                     }
    176.                     component.UpdateControlDisplay();
    177.                 }
    178.             }
    179.  
    180.             if (change == InputUserChange.ControlSchemeChanged) {
    181.                 //Debug.Log("control scheme changed to: " + user.controlScheme.Value.name);
    182.                 for (var i = 0; i < s_InputActionIcons.Count; ++i) {
    183.                     var component = s_InputActionIcons[i];
    184.                     if (user.controlScheme.Value.name != component.preferenceData.lastUsedControlScheme) {
    185.                         string previousControlScheme = component.preferenceData.lastUsedControlScheme;
    186.                         string previousDevice = component.preferenceData.lastUsedDevice;
    187.          
    188.                         component.preferenceData.lastUsedControlScheme = user.controlScheme.Value.name;
    189.                     }
    190.                     component.UpdateControlDisplay();
    191.                 }
    192.             }
    193.         }
    194.     }
    195. }
     
    Last edited: Apr 17, 2020
  17. Daerst

    Daerst

    Joined:
    Jun 16, 2016
    Posts:
    275
    I found this other thread by @Adrian, suggesting to use
    InputControlPath.Matches
    , so here's where I'm at now - calling this from either the ActionMaps'
    actionTriggered
    or the global
    InputSystem.onActionChange
    callbacks:
    Code (CSharp):
    1. private static void GetPossibleSchemes(InputAction action)
    2. {
    3.     char[] separator = new char[] { ';' };
    4.     foreach (var binding in action.bindings)
    5.     {
    6.         if (InputControlPath.Matches(binding.effectivePath, action.activeControl))
    7.         {
    8.             // A binding can be assigned to multiple InputSchemes - loop over them
    9.             foreach (string group in binding.groups.Split(separator, StringSplitOptions.RemoveEmptyEntries))
    10.             {
    11.                 // Do whatever with the candidate InputScheme
    12.             }
    13.         }
    14.     }
    15. }
    For now, I think this works well enough for my case of 'I want to know which InputScheme was used most recently'.

    I agree, both ideas sound like they would be very helpful.

    Thanks for the code, I'll dig through it. My goal, too, is to display appropriate sprites for the current input configuration, and you mentioned one important point: If 'Gamepad' is the current InputScheme, I might still want to display different sprites based on the actual type of controller. I'll have to put some thought into this.
     
  18. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
    @Rene-Damm
    I have code that suddenly stopped working when I updated to 1.0.0 - preview 7

    The ControlSchemes are no longer found by InputControlScheme.FindControlSchemeForDevice. What's happened?

    Code (CSharp):
    1.     private static void OnActionTriggered(InputAction.CallbackContext _callbackContext)
    2.     {
    3.         var device = _callbackContext.control.device;
    4.         var actionName = _callbackContext.action.actionMap.name;
    5.         var scheme = InputControlScheme.FindControlSchemeForDevice(device, _callbackContext.action.actionMap.controlSchemes);
    6.         if (scheme.HasValue)
    7.         {
    8.             Debug.Log($"{actionName}'s scheme: {scheme.Value}");
    9.         }
    10.         else
    11.         {
    12.             Debug.Log($"{actionName} has no scheme!?"); // THIS IS ALWAYS CALLED NOW !
    13.         }
    14.      }
     
  19. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
    @Rene-Damm I still have the issue presented above. Are you aware of this bug?
     
  20. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    What's the device and the requirements of the control scheme you're expecting to match?

    IIRC there was a change where the matching now takes the full list of requirements into account. I'm thinking that API where you give it a single device should probably be extended to allow specifying something like "give me *any* one that matches the device even if there's additional requirements not met with just that one device".
     
  21. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
    Here are the supported devices:
    D351EUAyph.png

    I have 2 control schemes: "Gamepad" and "Keyboard & Mouse"
    ePxPIllsLN.png

    Previously, I would get the scheme each action was bound to.
    I would then use that scheme to define if I'm using a gamepad or the keyboard/mouse combo.
    Now, it always returns null so I can't know which scheme I'm using.

    How do I get the scheme from a device now?
     
    Last edited: May 14, 2020
  22. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
  23. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    758
    Still no documentation, i dont see any way to get this. I also need to know if the current input device is a controller or something else.
     
    harryjc, Bastienre4, Erethan and 2 others like this.
  24. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
    @Rene-Damm Can you fix the InputControlScheme.FindControlSchemeForDevice method of the new input system?
     
    reinfeldx likes this.
  25. Hookkshot

    Hookkshot

    Joined:
    Jan 11, 2013
    Posts:
    26
    Would be great if this was fixed.
     
  26. Hannibal_Leo

    Hannibal_Leo

    Joined:
    Nov 5, 2012
    Posts:
    526
    Lots of complicated code here, I actually had to dig into the API to find an easy solution - this one worked for me:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. namespace JL
    4. {
    5.     public class DeviceChange : MonoBehaviour
    6.     {
    7.         UnityEngine.InputSystem.PlayerInput _controls;
    8.         public static ControlDeviceType currentControlDevice;
    9.         public enum ControlDeviceType
    10.         {
    11.             KeyboardAndMouse,
    12.             Gamepad,
    13.         }
    14.         void Start()
    15.         {
    16.             _controls = GetComponent<UnityEngine.InputSystem.PlayerInput>();
    17.             _controls.onControlsChanged += OnControlsChanged;
    18.         }
    19.         private void OnControlsChanged(UnityEngine.InputSystem.PlayerInput obj)
    20.         {
    21.             if (obj.currentControlScheme == "Gamepad")
    22.             {
    23.                 if (currentControlDevice != ControlDeviceType.Gamepad)
    24.                 {
    25.                     currentControlDevice = ControlDeviceType.Gamepad;
    26.                     // Send Event
    27.                     // EventHandler.ExecuteEvent("DeviceChanged", currentControlDevice);
    28.                 }
    29.             }
    30.             else
    31.             {
    32.                 if (currentControlDevice != ControlDeviceType.KeyboardAndMouse)
    33.                 {
    34.                     currentControlDevice = ControlDeviceType.KeyboardAndMouse;
    35.                     // Send Event
    36.                     // EventHandler.ExecuteEvent("DeviceChanged", currentControlDevice);
    37.                 }
    38.             }
    39.         }
    40.     }
    41. }
    42.  
    upload_2020-6-29_4-11-32.png
     
    Last edited: Jun 29, 2020
  27. whitexroft

    whitexroft

    Joined:
    Oct 22, 2012
    Posts:
    48
    It seems that PlayerInput.onActionTriggered works pretty well as a solution, but im not getting why is it ignored for a case when Behaviour is set to "Invoke Unity Events". What if I want both: hooks for specific actions, AND a hook for "any action"?
     
    GilbertoBitt likes this.
  28. Pandanym

    Pandanym

    Joined:
    Dec 2, 2017
    Posts:
    17
    So there's no easy way to tell which device was used last without using PlayerInput ? Seems like a big oversight
     
  29. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
    @Rene-Damm any news?
     
    DebugLogError likes this.
  30. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
    If someone finds a solution when not using the PlayerInput that would be very useful.

    @Rene-Damn I read you're low on staff but it would be cool to take care of this, because this is a broken feature.
    This problem was created in the more recent versions, it's been almost a year and nothing is changing.
     
    TJHeuvel-net likes this.
  31. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    With the gamepad, FindControlSchemeForDevice should return the "Gamepad" scheme. With keyboard or mouse, with only one of the devices, FindControlSchemeForDevice should fail. However, SupportsDevice should return true for the control scheme.

    Code (CSharp):
    1. actions.controlSchemes.First(x => x.SupportsDevice(d));
    Just noticed FindControlSchemeForDevice now has incorrect docs, though, which still refer to its previous behavior. Will fix.

    As far as getting the device last used with a set of actions, there's no built-in API for that ATM. Made a note for that to be taken into consideration. Has come up a number of times that it would be desirable to be able to query the last used control and not just the active control from actions. Would go in the same direction as being able to generally query the last used device for a particular set of actions.

    As for what's currently available, one way to track it is from callbacks. Either globally

    Code (CSharp):
    1. InputDevice lastDevice;
    2.  
    3. InputSystem.onActionChange =>
    4.     (obj, change) =>
    5.     {
    6.         if (change == InputActionChange.Performed)
    7.             lastDevice = ((InputAction)obj).activeControl;
    8.     };
    Or locally

    Code (CSharp):
    1. InputDevice lastDevice;
    2.  
    3. actions.Gameplay.Get().actionTriggered +=
    4.     ctx => lastDevice = ctx.control?.device;
    ////EDIT: And sorry for the non-responsiveness :/
     
    Last edited: Nov 11, 2020
  32. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    968
    Thank you! Yea! It's good to fix a long time bug.

    Your code's idea works :)
    Here's the correct syntax

    Code (CSharp):
    1.             InputSystem.onActionChange += (obj, change) =>
    2.             {
    3.                 if (change == InputActionChange.ActionPerformed)
    4.                 {
    5.                     var inputAction = (InputAction) obj;
    6.                     var lastControl = inputAction.activeControl;
    7.                     var lastDevice = lastControl.device;
    8.                    
    9.                     Debug.Log($"device: {lastDevice.displayName}");
    10.                 }
    11.             };
     
  33. Pandanym

    Pandanym

    Joined:
    Dec 2, 2017
    Posts:
    17
  34. BonitaPersona

    BonitaPersona

    Joined:
    Feb 20, 2014
    Posts:
    16
    Another non-PlayerInput user here.


    This event doesn't seem to work with the Value Action Type. For example, the Vector2 we use for player and camera movement doesn't trigger an action change.

    Is this issue on the roadmap already, or is it still "on a note for being taken into consideration"? Posting this as an issue will help anyhow?
     
  35. Omti1990

    Omti1990

    Joined:
    Jun 22, 2018
    Posts:
    9
    This sort of works for me. It does recognise mouse and keyboard, but for some reason it doesn't react to control stick movement on my x-box controller (while pressing buttons works).
    I tried this in a new project with a 2020 unity version and it worked. (My project is in unity 2019). Could that be the reason? Alternatively I meddled around with the standard input actions for the UI, so the UI wouldn't react on gamepad stick movement. That said even in the new version this only seems to work for certain buttons, but not others. Which action map is actually refered to with InputSystem.onActionChange ?

    Also it returns "XBox-Controller" for a device name, which is more detailed than I actually need. Is there some way to make it return simply Gamepad or something like that?
    I looked into the API and apparently you can test that with InputDevice.description.deviceClass, but that returns an empty string for my gamepad (I assume that's a bug).
     
    Last edited: Apr 11, 2021
  36. Omti1990

    Omti1990

    Joined:
    Jun 22, 2018
    Posts:
    9
    Thanks for pointing this out. I had my own problems, where the control stick (a vector2) on the controller didn't work in my game where I implemented a custom UI controlling scheme, but did work in a new project where I was just using default everything.

    I believe the reason for this is that the default scheme doesn't outright assign the control sticks to a Vector2, but uses a Vector2 composite instead. Which does seem to trigger the actionChange. Still a pretty unnecessary workaround.

    Edit: Actually when I implemented a custom control scheme and didn't use the default one in the UI, it seems it also uses the normal stick. I just forgot to activate it...
     
    Last edited: Apr 11, 2021
  37. NibbleByte3

    NibbleByte3

    Joined:
    Aug 9, 2017
    Posts:
    61
    I think I've managed to do it properly. I'm making a simple single player game and wanted to go without the PlayerInput component (as I wanted to keep my type safety) and without InputUser (wanted to keep it as simple as possible). Still I wanted to refresh my UI icons on any device change. This is how I ended up using the InputSystem.onEvent() directly:

    Code (CSharp):
    1. InputSystem.onEvent += OnInputSystemEvent;
    2.  
    3. void OnInputSystemEvent(InputEventPtr eventPtr, InputDevice device)
    4. {
    5.     if (m_LastUsedDevice == device)
    6.         return;
    7.  
    8.     m_LastUsedDevice = device;
    9.     LastUsedDeviceChanged?.Invoke(0);
    10. }
    This worked at first, but after plugging in the PS4 controller on Windows PC, it started spamming non-stop events making my code think it is currently being used even when it was not. I couldn't pin-point the problem, but I suspect it was as @Rene-Damm said:
    So I decided to reverse-engineer how "InputUser/PlayerInput" is filtering that out. After inspecting the InputUser.OnEvent() method turns out the fix was quite simple:

    Code (CSharp):
    1. InputSystem.onEvent += OnInputSystemEvent;
    2.  
    3. void OnInputSystemEvent(InputEventPtr eventPtr, InputDevice device)
    4. {
    5.     if (m_LastUsedDevice == device)
    6.         return;
    7.  
    8.     // Some devices like to spam events like crazy.
    9.    // Example: PS4 controller on PC keeps triggering events without meaningful change.
    10.    var eventType = eventPtr.type;
    11.    if (eventType == StateEvent.Type) {
    12.        // Go through the changed controls in the event and look for ones actuated
    13.        // above a magnitude of a little above zero.
    14.        if (!eventPtr.EnumerateChangedControls(device: device, magnitudeThreshold: 0.0001f).Any())
    15.            return;
    16.    }
    17.  
    18.  
    19.     m_LastUsedDevice = device;
    20.     LastUsedDeviceChanged?.Invoke(0);
    21. }
    Haven't noticed this to affect the performance and even if it did - remember that PlayerInput/InputUser does the same thing!
    Please note that I'm using the 1.1.0-preview.3 version. This code was a bit different in the public release versions.

    Additionally, you may need this code below as well. It is triggered when the device configuration changes (e.g. keyboard layout/language change). It is NOT triggered on device switch (keyboard to gamepad) so you'll need to listen for both events.
    Code (CSharp):
    1. InputSystem.onDeviceChange += OnInputSystemDeviceChange;
    2.  
    3. void OnInputSystemDeviceChange(InputDevice device, InputDeviceChange change)
    4. {
    5.     if (m_LastUsedDevice == device)
    6.         return;
    7.  
    8.     m_LastUsedDevice = device;
    9.     LastUsedDeviceChanged?.Invoke(0);
    10. }
    NOTE: I don't recommend working without PlayerInput/InputUser. This will probably bite me / you by the ass if you plan to release on consoles. I'd recommend going hybrid, as I mentioned here.

    More boring comments:
    Subscribing for actions instead of any device events will probably be an issue if you plan to release on Google Stadia. They require that any device input event should switch the control scheme respectively at any point. In our old system we ended up adding mouse actions in screens where they were not required, just so the UI refreshes the control scheme. It was really annoying.
    Also, on Stadia (and maybe PC?) each controller needed to have it's own set of icons (XBox, PS4, Switch, Stadia) and refresh instantly on switching controllers. You can't just go with "Keyboard&Mouse" and "Gamepad" controller scheme. Controllers may change within the scheme, keyboards too.
     
    Last edited: Oct 7, 2022
  38. my_little_kafka

    my_little_kafka

    Joined:
    Feb 6, 2014
    Posts:
    63
    Is this still planned in the future updates?
     
  39. Wolfos

    Wolfos

    Joined:
    Mar 17, 2011
    Posts:
    906
    Here's a code sample for the current usage:
    Code (CSharp):
    1.  
    2. private void Awake()
    3. {
    4.    _playerInput = GetComponent<PlayerInput>();
    5.    UsingController = _playerInput.currentControlScheme.ToLower().Contains("gamepad");
    6.    InputUser.onChange += (_, change, _) =>
    7.    {
    8.       if (change is InputUserChange.ControlSchemeChanged)
    9.       {
    10.          UsingController = _playerInput.currentControlScheme.ToLower().Contains("gamepad");
    11.       }
    12.    };
    13. }
    14.  
    You need two input schemes with one containing the word "gamepad".

    I do wish this input system was simpler. Having 200 different ways to do something isn't all that indie friendly.
     
    GuirieSanchez likes this.
  40. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    355
    Could you please elaborate a little bit on how to set up the parameters? I'm aware I am probably asking something really basic, but nonetheless, I would highly appreciate any help here:

    I have my input actions set up (it's called UI Test), with its (Keyboard/Mouse) and ("gamepad") control schemes ready.
    My two problems are the following:

    (1) How can I reference this Input Actions? Is it done by generating a C# class off of the Input Actions Assets and referencing it in the Awake function?

    (2) I would like to know where do you establish these 2 parameters: "_playerInput" and "UsingController" because they don't work out of the box (they don't exist in the current context)


    My purpose is to detect if at any moment the keyboard/mouse scheme has been changed (used), in which case I would like to show the mouse cursor; and likewise, detect a switch to the "gamepad" scheme whenever any gamepad key is used in order to hide the mouse cursor.
    I'm not really sure if your sample code aims to do that (for my case), and if not, I would appreciate any guidance to do so.

    Thank you!
     
    Last edited: Jun 11, 2022
  41. seoyeon222222

    seoyeon222222

    Joined:
    Nov 18, 2020
    Posts:
    128

    There are several ways to use the new input system.
    This is the most helpful article I've ever seen. I recommend you read it
    https://gamedevbeginner.com/input-in-unity-made-easy-complete-guide-to-the-new-system/

    Wolfos uses the <PlayerInput> component way
    Using the PlayerInput component, it is easy to obtain information through the "currentControlScheme".
    However, if you don't use the PlayerInput component, there are difficulties, as many people have mentioned above.
    I also use the new input system without using the PlayerInput component for several reasons.
    I use the c# class generation way, and solve the problem by referring to the code written by NibbleByte3's way.

    To solve your problem,
    when a device is changed, you can set the cursor visible based on the changed device information.

    For example, set the cursor like this
    Code (CSharp):
    1.  
    2. private                  InputDevice       _lastUsedDevice;
    3. private                  bool              _lastCursorState;
    4.  
    5. private void OnDeviceChange(InputEventPtr eventPtr, InputDevice device)
    6. {
    7.     if (_lastUsedDevice == device)
    8.         return;
    9.  
    10.     // Some devices like to spam events like crazy.
    11.     // Example: PS4 controller on PC keeps triggering events without meaningful change.
    12.     var eventType = eventPtr.type;
    13.     if (eventType == StateEvent.Type)
    14.         // Go through the changed controls in the event and look for ones actuated
    15.         // above a magnitude of a little above zero.
    16.         if (!eventPtr.EnumerateChangedControls(device, 0.0001f).Any())
    17.             return;
    18.  
    19.     if (_lastUsedDevice is not Gamepad && device is not Gamepad)
    20.     {
    21.         _lastUsedDevice = device;
    22.         return;
    23.     }
    24.  
    25.     _lastUsedDevice = device;
    26.     SetCursorState(_lastCursorState);
    27. }
    28.  
    29. public void SetCursorState(bool state)
    30. {
    31.     _lastCursorState = state;
    32.     if (_lastUsedDevice is Gamepad)
    33.     {
    34.         Cursor.visible   = false;
    35.         Cursor.lockState = CursorLockMode.Locked;
    36.     }
    37.     else
    38.     {
    39.         Cursor.visible   = state;
    40.         Cursor.lockState = !state ? CursorLockMode.Locked : CursorLockMode.None;
    41.     }
    42. }
    43.  
    (It is a code that has been modified roughly to explain, so it may be a problem because it is a little different from what I am actually using.)
    - Set the status of the cursor when the device changes
    - Consider the state of the device when intentionally changing the state of the cursor
    Both work as intended.


    If you want to get help using the new input system through c# class generation way,
    It would be good to refer to the project below.
    https://github.com/UnityTechnologies/open-project-1
     
    GuirieSanchez likes this.
  42. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    355

    Thank you for the reply, I appreciate it!

    Before trying @Wolfos' method I was following the method mentioned by @spiritworld above, which is almost like the method you came up with, but I didn't have success because I didn't attach the script to the game object that possesses the "PlayerInput" component (I was trying to call it from another GameObj); so, even when my script didn't throw any errors at me, I wasn't able to get any input from the methods in the script. However, once I attached the script to the right GameObj it worked like a charm.

    PS: Thanks for the links. I remember going through this one: https://gamedevbeginner.com/input-in-unity-made-easy-complete-guide-to-the-new-system/
    before I started even thinking about switching to the new input system. Now that I got my hands dirty I think it's a great time for me to go through it once more and review it carefully. Ty!
     
  43. office_gamelab

    office_gamelab

    Joined:
    Nov 27, 2017
    Posts:
    46
    @Rene-Damm My problem is that neither InputUser.onChange nor playerInput.onControlsChanged get's called, when I'm switching from controller to touch or from touch to controller.

    -I'm pairing the controller before starting the game on Android.
    -switching controller to off
    -starting the game
    -touch works perfectly
    -switching controller to on
    -game switches intro controller scheme, playerInput.onControlsChanged get's called
    -if I touch the screen, neither InputUser.onChange nor playerInput.onControlsChanged get's called, and thus they're not working in game
    -if I switch controller off, playerInput.onControlsChanged get's called, touch works perfectly

    Why is this happening?
     
  44. Juneav

    Juneav

    Joined:
    Feb 18, 2017
    Posts:
    22
    Oh this, this is beautiful.
     
  45. NibbleByte3

    NibbleByte3

    Joined:
    Aug 9, 2017
    Posts:
    61
    Hi again, wanted to add some new findings. Added this to my original reply above as well.

    InputSystem.onEvent may not be enough. You may need this code below as well. It is triggered when the device configuration changes (e.g. keyboard layout/language change). It is NOT triggered on device switch (keyboard to gamepad) so you'll need to listen for both events.
    Code (CSharp):
    1. InputSystem.onDeviceChange += OnInputSystemDeviceChange;
    2.  
    3. void OnInputSystemDeviceChange(InputDevice device, InputDeviceChange change)
    4. {
    5.     if (m_LastUsedDevice == device)
    6.         return;
    7.  
    8.     m_LastUsedDevice = device;
    9.     LastUsedDeviceChanged?.Invoke(0);
    10. }
     
  46. pumpkinszwan

    pumpkinszwan

    Joined:
    Feb 6, 2014
    Posts:
    173
    I just do this:


    Code (CSharp):
    1. public  Enums.ControlMethod CurrentControlMethod
    2.     {
    3.         get
    4.         {
    5.             var input = FindObjectOfType<PlayerInput>();
    6.             if(input.currentControlScheme == "Gamepad") return Enums.ControlMethod.Gamepad;
    7.             return Enums.ControlMethod.Keyboard;
    8.         }
    9.     }  
    And my popup hints will display the icon based on retrieving this value before they popup. Nice and simple, though it might not be suitable for more complex scenarios (I only have about 6 input hints in the whole game as there are only two buttons).