Hi,
I was also trying to figure out the relation between C# generated controls class (IInputActionCollection) and the PlayerInput component. It seems I came late to the party and learned first about PlayerInput and then found out about the generated class. I think I figure it out now thanks to this topic and this video (the first two scenarios).
The problem:
I like that PlayerInput handles users and provides API for switching the “currently active action map”. It does one more important thing: it controls and overrides the InputSystemUIInputModule so UI won’t conflict with your game input. As you figured out, using it you’re bound to using strings for action names and maps which sucks.
If you create your own generated IInputActionCollection class (in my case doing “new PlayerControls()”), you get type safety but you have to use it everywhere and forget about PlayerInput as you can’t reference it as a project asset edit time*.* Or can it?
As far as I understand the generated IInputActionCollection creates a runtime ScriptableObject asset on its own which can be useful.
The solution:
It’s pretty simple:
-
Have your PlayerInput component with EMPTY reference for actions map asset.
-
On Awake() create and store your generated IInputActionCollection class.
-
Get the PlayerInput component and assign your instance of the action map asset from the IInputActionCollection class.
-
Assign the InputSystemUIInputModule to your PlayerInput if it is not referenced yet.
-
Activate the PlayerInput manually OnEnable() (NOT the IInputActionCollection class*)*.
-
The rest of the code should keep using the generated IInputActionCollection class.
-
Switch the currently active action map using the PlayerInput.
Basically something like this:
void Awake()
{
PlayerControls = new PlayerControls(); // implements IInputActionCollection
PlayerInput = GetComponent<PlayerInput>();
PlayerInput.defaultActionMap = PlayerControls.UI.Get().name;
PlayerInput.actions = PlayerControls.asset;
var uiInputModule = gameInputObject.GetComponentInChildren<InputSystemUIInputModule>();
uiInputModule.actionsAsset = playerControls.asset;
// EDIT: no need for these, assigning the actionsAsset above does the same.
//uiInputModule.point = InputActionReference.Create(playerControls.UI.Point);
//uiInputModule.leftClick = InputActionReference.Create(playerControls.UI.Click);
//uiInputModule.middleClick = InputActionReference.Create(playerControls.UI.MiddleClick);
//uiInputModule.rightClick = InputActionReference.Create(playerControls.UI.RightClick);
//uiInputModule.scrollWheel = InputActionReference.Create(playerControls.UI.ScrollWheel);
//uiInputModule.move = InputActionReference.Create(playerControls.UI.Navigate);
//uiInputModule.submit = InputActionReference.Create(playerControls.UI.Submit);
//uiInputModule.cancel = InputActionReference.Create(playerControls.UI.Cancel);
//uiInputModule.trackedDevicePosition = InputActionReference.Create(playerControls.UI.TrackedDevicePosition);
//uiInputModule.trackedDeviceOrientation = InputActionReference.Create(playerControls.UI.TrackedDeviceOrientation);
PlayerInput.uiInputModule = uiInputModule
// Set listeners or manually subscribe for specific actions.
PlayerControls.GameInput.SetCallbacks(gameController);
}
void OnEnable()
{
// Creates users using your controls class & overrides the InputSystemUIInputModule with it.
PlayerInput.ActivateInput();
}
// This is how you should the switch currently active action map
public void SwitchInputToUI()
{
PlayerInput.currentActionMap = PlayerControls.UI.Get();
}
Edit:
Just saw in the Warriors Demo that based on which player is pressing the input, they update the InputSystemUIInputModule in a better way - they just assign the InputSystemUIInputModule.actionsAsset directly. So, setting the actionsAsset to your PlayerControls.asset property will refresh references to your asset automatically (underneath it just searches for InputActions with the same name). This simplifies the code above greatly.
Final thoughts:
PlayerInput seems a good idea for prototyping, but doesn’t scale well if your game is more than one screen / level. The more I learn about it, the more I consider ditching it for IInputActionCollection class.
Example:
Having always a single active actions map is not a good idea actually. I want my gamepad navigation working on all my UI states / screens, but I don’t want to duplicate it in all their action maps. That would mean having two action maps active at the same time:
-
When level is playing: LevelInput
-
When level is paused: UIInput (navigation, submit, cancel, etc) & UILevelPausedInput (ESC for exit, etc)
-
When in options screen: UIInput & UIOptionsInput
Also, listening for actions is inconvenient:
-
SendMessage / Broadcast forces you to handle all possible input on the PlayerInput object
-
Unity events requires you to link edit time the desired handlers which is impossible if you build your game from different scenes, prefabs & modules loaded at runtime.
-
C# Events - use strings everywhere, very annoying.
It makes it had to split your logic in different controllers loaded runtime. Just check out this video showing how much more powerful this system can be (most notably the SetCallbacks() with action map interface):
Would love to hear your thoughts about it.
Cheers.