Hi, I just had a quick look into this and it seems the behavior is inconsistent between my Unity 2020 (with Input System 1.7.0) and Unity 2022 (with Input System 1.6.3) versions.
There is an option called “Preferred Start Device” on the InputIconsManager which is supposed to handle the displayed icon types on startup.
This is the behavior I noticed while testing on Windows:
In Unity 2020
Auto with gamepads connected: Displays keyboard sprites
Gamepad with gamepads connected: Displays gamepad sprites
In Unity 2022
Auto with gamepads connected: Displays keyboard sprites
Gamepad with gamepads connected: Displays gamepad sprites but within one second changes to keyboard sprites. (However contrary to what you experienced I can switch again to gamepad icons without having to use the keyboard/mouse first. Quite strange …)
I’m not sure why this would happen, but I definitely need to look deeper into this.
A workaround would be to call InputIconsManagerSO.ShowGamepadIconsIfGamepadAvailable(); after a short delay once the game starts.
This method is also being called by the InputIconsManager object itself through the OnEnable method (which gets called when the manager is first referenced in a scene - which is usually done with the II_InputIconsActivator prefab in the first scene).
@runevision I just tried something and it seems to work well in my test environment. In the InputIconsManagerSO.cs in the HandleDeviceChange method, please try changing the " < 0.2f" to something larger like " < 2f". This will prevent the manager from automatically changing the displayed icons for a longer period of time after loading a new scene. Then also set Preferred Start Device type on the manager to Gamepad to automatically display gamepad icons on start if a gamepad is connected.
Hmm. The way you talk about things in your replies - “Auto with gamepads connected”, “Gamepad with gamepads connected” - makes me think you are looking at what prompts are displayed before the user has triggered any input. That’s not what I’m talking about though. The initial prompts displayed before the user has even touched any input could never be correct in 100% of cases, and that’s not a big deal.
I’m talking about the prompts still being incorrect when the user has already triggered an input. E.g., the prompts display keyboard keys after the user has already started using the gamepad to play the game (or select a menu item, like Start Game).
The worst part was that the prompts stayed wrong no matter how much the user kept using the gamepad. They never switched to gamepad prompts.
Your proposed workaround does fix the worst part, but the behavior is still not quite right. Here’s what happens when I use it:
If the user only uses gamepad:
The prompts work correctly. Gamepad prompts are displayed all the time.
If the user only uses keyboard:
The user starts the game and presses the keyboard Enter key to select Continue Game in the main menu. This loads the main scene. In the main scene the prompts display gamepad buttons until the user starts using the keyboard again, even though they already used the keyboard in the main menu and never touched the gamepad at all.
Some additional info:
I enabled the Debug.Log line at the end of HandleDeviceChange and played the game and found this odd thing:
If the preferred input is set to Gamepad, the prompts in the main game always display gamepad icons initially, no matter if keyboard or gamepad was used to select Continue Game in the main menu.
When selecting Continue Game with a keyboard, I get a logged entry from HandleDeviceChange showing that the change to Keyboard went through. However, the prompts in the game still show gamepad buttons. If I press a keyboard key now, HandleDeviceChange once again logs a change to Keyboard, even though that was also the last thing it logged. The prompts then correctly show keyboard keys.
If the preferred input is set to Auto, the prompts in the main game always display keyboard icons, no matter if keyboard or gamepad was used to select Continue Game in the main menu.
When selecting Continue Game with a gamepad, I get a logged entry from HandleDeviceChange showing that the change to Gamepad went through. However, the prompts in the game still show keyboard keys. If I press gamepad buttons now, nothing changes with the prompts; they continue to display keyboard keys.
So it appears this is the fundamental issue here: No amount of changing the default device or tweaking which events HandleDeviceChange reacts to is going to fix the issue when the prompts don’t respect the latest successful HandleDeviceChange anyway.
I admit I am quite a few releases behind the current one, so maybe things have changed in the past months. When I start my game, a new object is created with the name “II_DeviceDisplayChanger” and an Input Icons device updater component. This all happens in a scene that I never unload, so its lifetime is the entire app session. The Input Icons system keeps the correct device state through all other scene transitions, because that object stays alive and all my scene transitions use additive scene loading.
You’re expecting information-- ANY information-- to survive a scene change. I would suggest trying to flag the “II_DeviceDisplayChanger” object as Don’t Destroy On Load, so that it remains present through all your other scene switching. This is the same approach most people recommend to keep track of your player information through scene changes. Anything marked DDOL is essentially kept alive through scene changes by moving it to a special system-defined scene that is NOT unloaded, just like my game does with a master scene.
I just added DontDestroyOnLoad(obj); in the code in InputIconsManagerSO.cs which creates the “II_DeviceDisplayChanger” GameObject with the II_ShowDeviceIconsAfterDelay component. I don’t know why that’s not implemented as the default behavior?
The docs only mention that the II_InputIconsActivator Prefab should be in an early scene and I had already marked that one as DontDestroyOnLoad for good measure. I didn’t know it’s the II_DeviceDisplayChanger GameObject that carries the state about current active control scheme since it doesn’t appear to be mentioned anywhere.
I can’t speak for @tobias_froihofer but there are people who are a bit squeamish about DDOL in general. It feels like the debate around Singletons in architecture; it’s a complicated topic, but mostly a non-technical topic. However, it’s pretty much the only mechanism to keep any UnityObject alive across a non-additive scene transition.
Thank you @halley1 for the suggestion about adding DontDestroyOnLoad to the II_DeviceDisplayChanger object, I will add that to the next update as it can not do any harm. In theory it should not be necessary to keep the II_DeviceDisplayChanger alive between scene switches, but maybe I am missing something which is causing a bug (see below).
Let me shed some light into how switching displayed icons works. By going through this maybe we can find some flaws in my thinking process. There are two main ways of switching the icons and both are handled through the InputIconsManagerSO.
1. Using the static SetDeviceAndRefreshDisplayedIcons(InputDevice device) method.
This immediately sets the new device and shows the icons for this InputDevice.
If ‘device’ is null, it will use the ShowDeviceIconsBasedOnSettings method to show the device. This method is also called when the InputIconsManagerSO object gets enabled (through the II_InputIconsActivator prefab in the scene). If the preferred start device is either KeyboardAndMouse or Gamepad, the ShowDeviceIconsBasedOnSettings spawns a new II_ShowDeviceIconsAfterDelay object which - after a delay of 0.1 seconds - uses the SetDeviceAndRefreshDisplayedIcons(InputDevice device) method to set the device. This II_ShowDeviceIconsAfterDelay object does not get used again.
2. Through the HandleDeviceChange(object obj, InputActionChange change) method which gets called when the InputSystem.onActionChange event gets triggered.
This method grabs the InputDevice through the obj parameter and first checks if a device change is necessary. If a device change is necessary, we schedule a device change after a short delay (default 0.2 seconds) through the II_ShowDeviceIconsAfterDelay which gets spawned in the RequestDeviceDisplayChange method.
We do not want to switch devices when:
The user uses mouse and keyboard. Keyboard and mouse are considered the same device when considering the displayed icons.
One or more gamepads are connected and one of their sticks is stuck a little bit off center and constantly sending signals, but the user actually wants to use keyboard and mouse. We use a deadzone for this - only switch if a stick is moved significantly. (maybe I should add something similar for the mouse as well, this is not yet handled)
We ignore Virtual Mouse completely. It is not considered an Input Device for switching icons.
If the conditions above don’t apply, and we already scheduled a device change for this device, then we don’t schedule another device change.
Moreover: If a device change was scheduled but we detect that the user is still using the currently used device, we cancel the device change. For example: the gamepad got bumped a little bit during playing, but the user is engaged with keyboard and mouse, we don’t want to switch to gamepad.
Here are some things I noticed while going through this in detail.
The ShowDeviceIconsBasedOnSettings method which spawns a II_ShowDeviceIconsAfterDelay object can cause problems as it updates the currently displayed icons after a 0.1 second delay. If this happens after a device change schedule has gone through we might end up in an unwanted state.
=> Possible solution: Use the same II_ShowDeviceIconsAfterDelay object as we use for scheduling in the HandleDeviceChange(object obj, InputActionChange change) method.
When scheduling a device change, the variable inputDeviceToChangeTo on the InputIconsManagerSO gets set to the newly desired InputDevice. But when we cancel through CancelRequestedDeviceDisplayChange, the inputDeviceToChangeTo keeps its value. This probably messes with the 4th condition of when we don’t want to switch devices.
=> Possible solution: set inputDeviceToChangeTo to null in CancelRequestedDeviceDisplayChange method
also in the HandleSceneLoaded method call CancelRequestedDeviceDisplayChange, then call SetDeviceAndRefreshDisplayedIcons(GetCurrentInputDevice()) to update all active prompts.
There might be things I overlooked so please if you find a flaw in my thinking, let me know.
Thanks everyone.
Edit:
I’m generally quite fond of singletons and for the II_DeviceDisplayChanger it would make sense to apply it as there should never be more than one II_DeviceDisplayChanger active in the scene.
Just a quick feedback after the installation of the plugin. We are using Steam but the plugin is currently disabled and we have errors with InputIconsSteamworksExtensionSO.cs which triggers an error in this case. Steam provide a compilation directive to disable the API with DISABLESTEAMWORKS. You probably need to add this to properly handle disabling the API. On my side, I have replaced #if STEAMWORKS_NET by #if STEAMWORKS_NET && !DISABLESTEAMWORKS
I’m having an issue populating up ‘textPromptDatas’ for an II_TextPrompt at runtime.
I’m not sure if there’s something special that needs to be done, but I first clear the list, and populate it with new elements setting the ‘actionReference’ and ‘actionDisplayType’, I then call SetText to set my content.
When my UI shows, all the keys are shown as question marks. If i click on the II_TextPrompt component and change anything to mark the editor dirty, the prompts show the correct icons. I looked into the editor and it seems to just call OnValidate, which just calls UpdateDisplayedSprites, which I’ve tried to call myself explicitly immediately after SetText (which also calls it internally), after a delay, after next frame, end of frame, multiple times in a loop, etc. it didn’t do anything. I’m at a loss here and would appreciate any pointers.
I’m on the latest II version 3.2.01 Unity version 2023 2.16f1
[Edit] Looking into it through the debugger, InputIconsUtility.GetSpriteName returns empty string when I call it from my code, but returns valid value when called from editor…
I figured it out, GetIndexOfBindingType was failing to return in my case, when looking at the code path in both cases (mine vs editor), mine had Composite (default value) while editor was Non-Composite, so I just needed to set the CompositeType to NonComposite in my code when populating textPromptDatas.
It would be nice to have a formal public interface for adding datas at runtime.
Hi @DTAli
I see you figured it out already Did you set a new text using a list of TextPromptData? This can be quite a hassle unfortunately.
There are Scriptable Objects (II_TextPromptDataSO) with a custom inspector available which should make changing texts at runtime easier. You can create one through the Asset menu “Assets - Create - Input Icons - TextPromptDataSO”. Can’t say if they work for every type of binding yet, but generally they should work fine. You might also want to have a look at this example scene: InputIcons_ExampleScene3_2_TextPromptsRuntimeUpdates
Cheer, Tobias
Edit: The guide has a short section about changing text prompts at runtime (chapter “4.1.3 Changing displayed Bindings in II_TextPrompts”)
Hey guys, just got this asset recently, and super happy with it!
My inquiry is, I tried searching this thread for Steam Deck support, and I see that one person got it working somehow.
For me, I see an issue where after building my game, putting it on Steam, and then playing it on Steam Deck, the InputIcons code seems to think it’s a keyboard/mouse and not a controller?
I have control schemes set up for keyboard/mouse, and also for controller. And when I play on my PC, it works perfectly. When I plug in my controller and use it, the InputIcons switches over to controller icons, and when I use keyboard, vice versa.
But when I play on Steam Deck, at all stages of gameplay, it always only displays keyboard/mouse control scheme despite me pressing all the controller buttons that Steam Deck has, how can I force InputIcons to think that the current device you’re playing on is controller-only?
I want to be able to add something like, “if (playingOnSteamDeck) InputIcons.ForceControllerScheme();” if you know what I mean. I know that code doesn’t exist, but something like that would work.
I might not be a great help in this area but maybe I can lead you in the right direction.
From what you describe it seems the underlying issue might be that Unity’s InputSystem does not correctly detect the SteamDeck as a gamepad when playing through Steam. Can you display the name of the detected device in a UI text somewhere to confirm what device is detected? This code should give you the device name:
InputSystem.devices[0].displayName
You could then try to add this code somewhere to try and display gamepad sprites:
if(InputSystem.devices[0].displayName.Contains("Steam Virtual Gamepad")) //replace "Steam Virtual Gamepad" if necessary
{
InputIconsManagerSO.ShowGamepadIconsIfGamepadAvailable();
InputIconsManagerSO.StopUpdatingIconsBehaviour(); //this will stop InputIcons to automatically switch icons back and forth between keyboard and gamepad
}
This code will only work if SteamDeck actually got detected as a gamepad. You could add another static method to the manager like “ForceSteamDeckIcons”. This could look like this:
public static void ForceSteamDeckIcons()
{
SetDeviceAndRefreshDisplayedIcons(InputIconSetConfiguratorSO.InputIconsDevice.SteamDeck);
}
Also, since InputIcons displays the correct icons in the editor and in build, but not when played on Steam, have a look at the InputIconsSteamworksExtensionSO class. This class reacts when the InputIconsManager changes devices and Steamworks_net is active. It will then override the displayed icons. It could be that you miss something like:
else if (inputType == ESteamInputType.k_ESteamInputType_SteamDeckController)
{
InputIconSetConfiguratorSO.SetCurrentIconSet(InputIconSetConfiguratorSO.Instance.SteamDeckIconSet);
}
That’s some wonderful ideas, and thank you so much for the response!
I just imported the Steamworks API package into the game… and that did the trick… Apparently that’s what was missing.
Now, when I play my game on the Steam Deck (with nothing else changed, but just having Steamworks API in the game), it now properly detects Steam Deck as a controller-based game!
Ah, yes Steamworks.NET is required unfortunately to display the correct icons on Steam. From my experience, the Unity Input System somewhat loses control over the connected devices and can’t correctly detect the devices when playing through the Steam launcher. The InputIconsSteamworksExtensionSO class will take over when playing on Steam and will be responsible for assigning the icon set.
To start rebinding, I can either mouse-click on this rebind button, or use Enter key.
Problem: It listens to any keyboard inputs well, but doesn’t let me use “mouse click” itself as the action. Or any mouse buttons for that matter.
Reproduction steps:
This keybind already has mouse-click as the bind, so ignore that.
During gameplay, I start rebinding, it shows “listening for input…”, I rebind it to, for example, “T” key
I start rebinding on it again, it shows “listening for input…”, but mouse-clicking itself won’t register as an input for the keybind. In fact, none of the mouse buttons count (not scrolling, not middle click, nor right click). It stays on the listening part until I tap on a keyboard key.
Do you know how I can make it register the mouse buttons as keybinds?
this is an odd behavior and I currently don’t know why it won’t let you bind the mouse buttons. I just tested again in the example scenes and I can assign my mouse buttons and mouse scrolling as well. Please try if you can rebind to these buttons in the example scenes: open ExampleScene1_SpritePrompts, enter play mode, press escape which should open a small UI window in the top right. Clicking on settings will open a rebind menu where you can test the rebind buttons.
If the example scene works, there might be something different in your scene. What it would be is difficult to say. Maybe the EventSystem is different or a Virtual Mouse is causing some troubles(?)
The tests I just did were on
Unity 2020.3.23f1 with Input System 1.7.0
Unity 2022.3.4f1 with Input System 1.6.3
The rebind operation should definitely not block the mouse buttons from being assigned. Here is the relevant code from the rebind script: