Search Unity

Feedback Input System - Generate C# Code: What Its Good For

Discussion in 'Input System' started by UncleAlias, Oct 28, 2020.

  1. UncleAlias

    UncleAlias

    Joined:
    Aug 18, 2017
    Posts:
    27
    Okay, I've been trying to understand the script that you get from the "Generate C# Class" button (I'm going to call it the Wrapper Script) but I'm not an expert and not experienced enough to really understand the script itself, so I welcome feedback on whether I have all this right:
    1. It is meant as an alternative to the PlayerInput component, but isn't good for local multiplayer.
    2. It creates a set of properties to easily refer to the actions and action maps, but you can do the same thing with PlayerInput using FindAction and FindActionMap, you just have to use strings, which is not great, but workable.
    3. There is no good documentation for it.
    4. You can't rely on the documentation for things like ̶I̶n̶p̶u̶t̶A̶c̶t̶i̶o̶n̶ ̶a̶n̶d̶ (edit: Actions are actually InputActions) InputActionMap, because those things are of a different type in the Wrapper Script. MyGeneratedClass.SomeActionMap is of type SomeActionMapActions, which is different from an InputActionMap. Of course, these made-up names can't be looked up.
    If all this is the case, I guess my big question is: Why are so many tutorials and guides touting the Wrapper Script as the way to handle c# events? What's the point of giving a beginner a short example of something that they can't easily learn more about, and that will be discarded if they want to do local multiplayer? I didn't even realize I could do C# events with just PlayerInput at first. I had to scour the documentation for PlayerInput before I realised "Oh, this does all the things the Wrapper Script does!". But at least I was able to figure that out from the documentation.
    I feel like I'm missing something. Is there something really important that the Wrapper Script does?
     
    Last edited: Oct 28, 2020
    dval, JKrypto and MirageOwl like this.
  2. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Correct.

    "Good" is relative. PlayerInput doesn't do anything per se that can't be done with the generated code. But yeah, it does require some additional scripting. There's plans to further build out the code generation feature and add some more convenience.

    Correct.

    Correct.

    The generated code is really just a thin wrapper on top of InputActionAsset/InputActionMap/InputAction. So whatever applies to the latter, also applies to the former. In a way, the same goes for PlayerInput. They just wrap around functionality provided by the various InputAction-related classes.

    Comes down to the development history. PlayerInput materialized much later then the code generation feature so early tutorials all used code generation. This was exacerbated by us releasing tutorials using the code generation approach when really they should have used PlayerInput.

    In a way, PlayerInput can be seen as a result of this. Earlier versions of the input system led to feedback indicating there's too much setup and detail work involved in setting up input so PlayerInput emerged as a simple toplevel entry point.

    Note that our own material (both docs and tutorials/demos) no longer use code generation. If you look at our quick start, for example, it uses PlayerInput.

    Other than improving code generation, there's plans to integrate the two approaches more. There's no good reason why PlayerInput and the generated wrappers can't work better together. To some extent, this can already be done as each wrapper simply contains an InputActionAsset that can be used with a PlayerInput again, does require some scripting.
     
    Last edited: Oct 28, 2020
  3. JoNax97

    JoNax97

    Joined:
    Feb 4, 2016
    Posts:
    611
    Hey thanks for the insights!
    I've implemented input last week and found myself with the same questions. I mostly wanted the codegen path for the strong typing, but in the end the lack of docs and the sheer usefulness of the PlayerInput draw me away from it.

    The perfect hybrid for me would be just a strongly typed PlayerInput, where I can wire everything in code like with current C# events option.
     
    NotaNaN likes this.
  4. UncleAlias

    UncleAlias

    Joined:
    Aug 18, 2017
    Posts:
    27
    Okay, I'm a little confused about this. If I refer to an action map using the wrapper script, my IDE says it doesn't have any of the properties or methods unique to InputActionMap. For instance, using my previous example, MyGeneratedClass.SomeActionMap.name won't auto-complete. But if I cache it to an actual InputActionMap, I have access to them. So
    Code (CSharp):
    1. InputActionMap someActionMap = MyGeneratedClass.SomeActionMap
    2. string name=InputActionMap.name;
    works fine.

    Thank you, this makes a lot of sense, and mostly answers my question.

    Yeah, I came across a lot of info about PlayerInput, but very little about using it for C# events in particular. For instance, I would expect to find more about it in GameObject Components For Input, but when it gets to C# events, it just kind of peters out. Since there's more guides talking about the generated code, I ended up thinking that was just how you took take advantage of the C# event option of the PlayerInput component, since I also couldn't find anything to position them as alternatives to each other. But maybe that's just my experience.

    Yeah, I've been playing with using the id property to use the generated code types to Find things on PlayerInput, though it still has the same multiplayer limitations. Conversely, if I could pass the InputControlScheme from PlayerInput to the generated code instead of specifying a physical controller, I might be more inclined to use it.

    Thanks for your response, it was very helpful!
     
  5. UncleAlias

    UncleAlias

    Joined:
    Aug 18, 2017
    Posts:
    27
    Thanks! I'm really trying to contribute something useful and not just gripe, so I'm glad you got something out of it.
     
    JoNax97 likes this.
  6. NibbleByte3

    NibbleByte3

    Joined:
    Aug 9, 2017
    Posts:
    81
    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:
    Code (CSharp):
    1.  
    2. void Awake()
    3. {
    4.     PlayerControls = new PlayerControls(); // implements IInputActionCollection
    5.  
    6.     PlayerInput = GetComponent<PlayerInput>();
    7.     PlayerInput.defaultActionMap = PlayerControls.UI.Get().name;
    8.     PlayerInput.actions = PlayerControls.asset;
    9.  
    10.  
    11.     var uiInputModule = gameInputObject.GetComponentInChildren<InputSystemUIInputModule>();
    12.     uiInputModule.actionsAsset = playerControls.asset;
    13.  
    14.     // EDIT: no need for these, assigning the actionsAsset above does the same.
    15.     //uiInputModule.point = InputActionReference.Create(playerControls.UI.Point);
    16.     //uiInputModule.leftClick = InputActionReference.Create(playerControls.UI.Click);
    17.     //uiInputModule.middleClick = InputActionReference.Create(playerControls.UI.MiddleClick);
    18.     //uiInputModule.rightClick = InputActionReference.Create(playerControls.UI.RightClick);
    19.     //uiInputModule.scrollWheel = InputActionReference.Create(playerControls.UI.ScrollWheel);
    20.     //uiInputModule.move = InputActionReference.Create(playerControls.UI.Navigate);
    21.     //uiInputModule.submit = InputActionReference.Create(playerControls.UI.Submit);
    22.     //uiInputModule.cancel = InputActionReference.Create(playerControls.UI.Cancel);
    23.     //uiInputModule.trackedDevicePosition = InputActionReference.Create(playerControls.UI.TrackedDevicePosition);
    24.     //uiInputModule.trackedDeviceOrientation = InputActionReference.Create(playerControls.UI.TrackedDeviceOrientation);
    25.  
    26.     PlayerInput.uiInputModule = uiInputModule
    27.  
    28.     // Set listeners or manually subscribe for specific actions.
    29.     PlayerControls.GameInput.SetCallbacks(gameController);
    30. }
    31.  
    32. void OnEnable()
    33. {
    34.     // Creates users using your controls class & overrides the InputSystemUIInputModule with it.
    35.     PlayerInput.ActivateInput();
    36. }
    37.  
    38. // This is how you should the switch currently active action map
    39. public void SwitchInputToUI()
    40. {
    41.     PlayerInput.currentActionMap = PlayerControls.UI.Get();
    42. }
    43.  
    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.
     
    Last edited: May 24, 2021
    chrisall76, bzxo and gasppol like this.
  7. UncleAlias

    UncleAlias

    Joined:
    Aug 18, 2017
    Posts:
    27
    Thanks for all the info. This looks really useful!
     
  8. gasppol

    gasppol

    Joined:
    Feb 16, 2020
    Posts:
    33
    This was really fun to read. All my struggles were reflected here.
    I like the wrapper approach for his strong typed manners, and I really like to use the automatic generated interfaces to callback to my own methods. On the other hand, I found very frustrating that the wrapper properties aren't one-to-one match of the asset (InputActionMap != ActionMaps structure on the wrapper).
    My ideal approach will include the asset referenced in the editor via the inspector, and incorporate the callbacks in my scripts, switching between Action Maps as desired. (Not using strings!)
    Currently, you have suggested the closest solution. Cheers.
     
    UncleAlias likes this.
  9. gasppol

    gasppol

    Joined:
    Feb 16, 2020
    Posts:
    33
    It works really well. Thanks.
     
  10. UncleAlias

    UncleAlias

    Joined:
    Aug 18, 2017
    Posts:
    27
    Glad I'm not the only one who encountered that. Well, not glad, I guess, but you get the idea.
     
    gasppol likes this.
  11. NibbleByte3

    NibbleByte3

    Joined:
    Aug 9, 2017
    Posts:
    81
    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.

    Updated my original post.
     
  12. DevViktoria

    DevViktoria

    Joined:
    Apr 6, 2021
    Posts:
    94
    This is a great discussion, but I am really confused now. I completed the Using the Input System in Unity project on the Unity learn website, and it has the Taking Advantage of the Input System Scripting API part where we have to generate the C# code for the Input Action asset. So that counts as bad practice now?
     
  13. Mirvini

    Mirvini

    Joined:
    Jul 25, 2013
    Posts:
    11
    Reading this thread has been cathartic for me, as the input system's documentation is so scattered and incomplete I feel like I've been repeatedly re-learning it every time I encounter an issue.
    NibbleByte3's solution is pretty much what I've been looking for. It's a real shame that Send Messages is locked only to InputValue instead of the context.
     
    ImreT01 likes this.
  14. Simon-O

    Simon-O

    Joined:
    Jan 22, 2014
    Posts:
    51
    This is an incredibly frustrating choice. PlayerInput doesn't seem to be much use unless you want to have a single root object for any GameObject or UI element that might possibly want to capture input.

    Yes, you can scatter PlayerInputs around your scene, but then switching controls becomes an exercise in keeping track of and updating each of them.

    [Why go to ALL this effort to create a data-driven loosely coupled input system then force really tightly coupled implementations?]

    So I'm being forced to fall back to code generation and there's now barely any useful documentation.

    Even worse... How do I half-and-half so UI components still work? Or do I have to recreate all the UI functionality myself?
     
    josessito and florianBrn like this.
  15. cyanlink

    cyanlink

    Joined:
    Sep 13, 2018
    Posts:
    1
    I'm afraid you get it wrong, one PlayerInput component represents one player (their input), not an actionmap. Same goes for wrapper class, one instance = one player input source, you cannot create instances here and there and expect them to share the same enabled/disabled states, you have to use (get reference of) the very same instance everywhere.
    But I don't buy the idea of PlayerInput also. BroadcastMessage or SendMessage is PITA, separating logics and unable to handle the dependency injection or callback register can be a trouble, indeed they are like "mind your own game logic and leave rest to us", but they are less manageable.
    About the half-and-half (do u mean like dark souls where you can walk or roll while you can navigate through menu if menu is open or switch weapon if menu is closed?), you can have multiple actionmaps enabled at the same time (yes this is a thing). so you can separate menu and switch weapon into two action maps and have both of them enabled.
    As soon as you get the point of input system, generated wrapper class is straight forward. To workaround the Awake and ScriptableObject and null reference drama (i.e. you have to create the wrapper instance in Awake in your input action manager class, but you cannot access it in other scripts in Awake, because it gives you NRE, hacking via static field is no-go and gives error, try them yourself. use OnEnabled will not work (awake and OnEnabled are not two stages, on different objects they are not executed in order), and Start will totally mess up your logic, you have to check null everywhere) It is adviced to use lazy loading pattern, see https://forum.unity.com/threads/do-i-need-to-create-a-new-input-instance-every-time.726521/