Search Unity

Resolved How to use PlayerInput with C# Events? Manual is very short and unclear

Discussion in 'Input System' started by leifgood, Aug 22, 2020.

  1. leifgood

    leifgood

    Joined:
    Nov 28, 2015
    Posts:
    12
    I want to use the New Input System with C# Events. The PlayerInput Component has 4 different behaviors that pass on the information that some Input Action has been triggered.

    Here are the notification behaviours in the manual.

    I want to use the C# Event option, because it is the only option that allows me to configure my code on a class that is not a MonoBehaviour.

    As it states in the manual, Invoke CSharp Events has three events available.

    What confuses me is that all actions are part of one collective event "onActionTriggered".

    Subscribing to that event could look loke this:

    Code (CSharp):
    1. private void Awake()
    2.     {
    3.         playerInput = GetComponent<PlayerInput>();
    4.         playerInput.onActionTriggered += OnTapJump;
    5.     }
    6.  
    7.     public void OnTapJump(InputAction.CallbackContext context)
    8.     {
    9.         Debug.Log(context.ReadValue<float>());
    10.     }
    Now the obvious issue here is that this will be called from every single input I put in (which is expected). So not just my jumping input, but also attack, movements, etc .

    So it would then be my job to make the decisions on how this input is handled? This feels very counter-intuitive, as this would mean I would have to create a single method that handles All incoming InputActions, and then use string bindings to sort that stuff out using the context that comes as a parameter (context.action.name will give me the name of the action that fired the event). I would have to check for every action by using a comparing that to a string.

    playerInput.actions.FindAction("MyAction") also works, but would need a reference to the Component.

    Am I missing something? If this is really the way to go here, I might as well just use SendMessage and use an Adapter class to pass on the information that an event has happened to my actual class that is handling the input. In this case I would not have worry about any strings that need to be changed.
     
  2. leifgood

    leifgood

    Joined:
    Nov 28, 2015
    Posts:
    12
    I went for a different approach.

    Instead of Using the PlayerInput Component, I use the generated InputActions Class. This way I can subscribe to each event individually.

    This is not a Monobehaviour, so I don't have to worry about any References to it on an Object in the Scene, instead I just instantiate it using new.

    I got tangled up on that PlayerInput Component. I feel like it complicates some things rather than simplifying them and I have read similar opinions here browsing the forum.
     
    jodiw, Daedolon and FlavioIT like this.
  3. VR_Junkie

    VR_Junkie

    Joined:
    Nov 26, 2016
    Posts:
    77
    Hey man, I'm using your method but am stuck on switching control schemes. Doesnt seem like the generated class has a method for this. How did you handle this?
     
  4. kolex023

    kolex023

    Joined:
    Oct 16, 2017
    Posts:
    5
    Just for the Record:

    It's not required to generate C# class (if you are doing a Rebind the new path will be omitted as the class is not refreshed in runtime if you reply on generated class events... well for now...), and you can still use Playerinput component. Make sure Behaviour Attribute has been set to "Invoke C Sharp Events", otherwise onActionTriggered is ignored.

    The trick is to subscribe your functions to onActionTriggered and then check the context and return if action name is not what you want:

    Code (CSharp):
    1.  
    2.     private void Start()
    3.     {
    4.         playerInput = GetComponent<PlayerInput>();
    5.         playerInput.onActionTriggered += Jump;
    6.     }
    7.    
    8.     public void Jump(InputAction.CallbackContext context)
    9.     {
    10.         if (context.action.name != "Jump") return;
    11.         if (context.performed)
    12.         {
    13.             Debug.Log("Jump");
    14.         }
    15.     }
     
    Last edited: Dec 8, 2021
  5. jodiw

    jodiw

    Joined:
    Feb 25, 2023
    Posts:
    3
    I've also been getting confused on this.. ditching PlayerInput is the way to go as soon as you need complex input handling interactions. The manual says it's meant to be a quick starter.
    Regarding the onActionTriggered, it seems that PlayerInput is offering a shorthand to be able to handle every action defined in an input definition asset. Though I think more normally, we expect to want to handle each action map separately. Well, you still could inside that one event by looking at:
    Code (CSharp):
    1. context.action.actionMap.name
    But, you can also set up a general handler for all actions within a map, with a construct like:
    Code (CSharp):
    1. _inputDefinitions.FindActionMap(_actionMapName).actionTriggered += OnActionMapEvent;
    For individual actions, you can attach to the subevent for the action itself, and this is what the generated C# script does:
    Code (CSharp):
    1. _inputDefinitions[$"{actionMap}/shoot"].started += myShootHandler;
    2. // or
    3. _actionMap = _inputDefinitions.FindActionMap(ActionMap);
    4. _actionMap["shoot"].performed += myShootHandler;
    5.  
    So you do end up with one handler, but your code probably ends up having to work out what to do with the button state within your handler. All this means you decide where you want to trade off performance.
    You have to manage the enabling/disabling of action maps without PlayerInput's actionmap switcher, otherwise all these bound events will be firing all the time. ActionMaps have methods for it, though I don't know if it's more performant than unbinding the events. The other thing you have to do is device binding with the control schemes.
     
    Last edited: Nov 3, 2023