Search Unity

Question Using PlayerInput with C# Events

Discussion in 'Input System' started by ZakareyaAlatoli, Aug 30, 2020.

  1. ZakareyaAlatoli

    ZakareyaAlatoli

    Joined:
    Aug 29, 2020
    Posts:
    4
    The "PlayerInput" component has a Behavior setting that can be set to "Invoke C Sharp Events". In my Monobehaviour that I want to do things with in response to input, I get the PlayerInput component via GetComponent() but the only input event I can subscribe to is "onActionTriggered". This fires when ANY input happens. I'm using the default input actions for the PlayerInput component, which has actions for "Move", "Look", and "Fire". Ideally I'd want to hook functions to those actions individually without using SendMessage or UnityEvents. But I can't find anything in the documentation about how to do that.
     
    Chambers88, GilbertoBitt and Vharz like this.
  2. jushiip

    jushiip

    Joined:
    Jul 3, 2012
    Posts:
    5
    I started learning the new Input System just last week, so I'm not sure if this is the de facto standard (if any even exists yet), but this works for me and I hope this will get you started as well!

    Code (CSharp):
    1.  
    2. public InputAction fire;
    3.  
    4. [SerializeField] private InputActionAsset controls;
    5.  
    6. private InputActionMap _inputActionMap;
    7.  
    8. private void Start()
    9. {
    10.     _inputActionMap = controls.FindActionMap("Gameplay");
    11.  
    12.     fire = _inputActionMap.FindAction("Fire");
    13.  
    14.     fire.performed += OnFireAction;
    15. }
    16.  
    17. private void OnFireAction(InputAction.CallbackContext obj)
    18. {
    19.     // do stuff
    20. }
    More details can be found in the Unity docs: https://docs.unity3d.com/Packages/c.../api/UnityEngine.InputSystem.InputAction.html
     
    Last edited: Aug 31, 2020
    Challseus likes this.
  3. ZakareyaAlatoli

    ZakareyaAlatoli

    Joined:
    Aug 29, 2020
    Posts:
    4
    How do I get this to work for local multiplayer? I probably should have specified that earlier. All the players should have access to the same inputs, but they should influence separate characters on the screen.
     
  4. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    Right now, PlayerInput can only do what you have described. One way to do what you want with it would be to roll your own that allows you to do what you want. Or the easiest solution would be to check the actions state or phase (started, performed, cancelled) in each event.

    I actually find that in most cases I wanted it to fire when any input happens regarding that action so I can handle the different phases of input. An example would be OnMove, I want to know it was cancelled so I can set my move vector to zero to stop movement or it will continue moving in the last direction of input. It really does depend on what you want to do with it though. I find it way better than the old system, even with the current flaws.
     
  5. dwarfengine

    dwarfengine

    Joined:
    Aug 3, 2019
    Posts:
    13
    IMO, if you're not gonna use the multiplayer support of PlayerInput, writing your own instancing code would be better. I have been doing that since 3 months, and I just saw in the new webinar that there's a PlayerInput component. It actually helped me out with getting the current input scheme so I can calculate weapon aim correctly on mouse and gamepad input.

    If you use the multiplayer feature (I think you do), handling it like the comment above (in one big callback) would be your best bet.
     
  6. Nathanieljla

    Nathanieljla

    Joined:
    Apr 18, 2014
    Posts:
    97
    OOC what do you see as the benefit in using c# events vs send messages? I originally didn't know about the PlayerInput component and wrote my own, but after I discovered PlayerInput I moved my code over to that and it seems cleaner than having to += and -= all your functions. I just wrote a component called "PlayerInputHandler" that contains all of the required messages.

    As far as your question regarding local multiplayer. Have you tried the Player Input Manager?
     
  7. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    There are multiple reason to use the provided PlayerInput component over rolling your own.
    1. You don't need to write your own and only need to worry about writing methods for each action.
    2. Schemes, not sure how to do that myself but it is nice to have it switch between keyboard and mouse to controller to touch with ease and know when it does it so we can change things like onscreen prompts. Doesn't work with local multiplayer though, as each player will be using one of the schemes.
    3. No need to have it generate the Input scripts.
    4. Local Multiplayer support when used with the Player Input Manager.
    Reasons to not use it. Not sure why you would want to reinvent the wheel, but it would be because you want to.
     
  8. Chambers88

    Chambers88

    Joined:
    Feb 25, 2018
    Posts:
    6
    I ran into the same problem. You can use
    playerInput.onActionTriggered
    to check if a specific PlayerInput has triggered a specific InputAction.

    Code (CSharp):
    1. public class Character : MonoBehaviour
    2.     {
    3.         public PlayerInput playerInput;
    4.         public InputAction fireAction;
    5.  
    6.         private bool fire;
    7.  
    8.         private void Awake()
    9.         {
    10.             playerInput = GetComponen<PlayerInput>();
    11.             playerInput.onActionTriggered += ReadAction;
    12.             fireAction = playerInput.currentActionMap.FindAction("Fire");
    13.         }
    14.  
    15.         private void ReadAction(InputAction.CallbackContext context)
    16.         {
    17.             if (context.action == fireAction)
    18.             {
    19.                 if (context.started) fire = true;
    20.                 if (context.canceled) fire = false;
    21.             }
    22.         }
    23.     }
    I wish I could do this as easily as we get actions directly from an InputActionMap, I'm still not sure if this is the best way of doing this.

    I hope this helps.
     
    DSivtsov, ben4d85 and dCalle like this.
  9. DSivtsov

    DSivtsov

    Joined:
    Feb 20, 2019
    Posts:
    151
    As another Variant, which can be helpful in some cases

    Code (CSharp):
    1.     PlayerInput playerInput;
    2.     InputAction fireAction;
    3.     InputAction moveAction;
    4.  
    5.     private void Awake()
    6.     {
    7.         playerInput = GetComponent<PlayerInput>();
    8.         fireAction = playerInput.currentActionMap.FindAction("Fire");
    9.  
    10.         fireAction.performed += FireAction_performed;
    11.         fireAction.canceled += FireAction_canceled;
    12.         fireAction.started += FireAction_started;
    13.  
    14.         moveAction = playerInput.currentActionMap.FindAction("move");
    15.     }
    16.  
    17.     private void FireAction_started(InputAction.CallbackContext context)
    18.     {
    19.         throw new System.NotImplementedException();
    20.     }
    21.  
    22.     private void FireAction_canceled(InputAction.CallbackContext context)
    23.     {
    24.         throw new System.NotImplementedException();
    25.     }
    26.  
    27.     private void FireAction_performed(InputAction.CallbackContext context)
    28.     {
    29.         throw new System.NotImplementedException();
    30.     }
    31.  
    32.     public void Update()
    33.     {
    34.         Vector2 m_Look = moveAction.ReadValue<Vector2>();
    35.         //...
    36.     }
     
    Last edited: Jun 18, 2021
  10. DSivtsov

    DSivtsov

    Joined:
    Feb 20, 2019
    Posts:
    151
    Time has passed and I prefer, if i want to use the
    "PlayerInput" component has a Behavior setting that can be set to "Invoke C Sharp Events"
    the other variant based on Using Input Action Assets without create the PlayerInput component (see the first part of link the "Auto-generating script code for Actions"). It's a good example with little mistypes.
    Below I Show shortly my worked:
    1. Input Action asset
    InputActionAsset.JPG
    2. Generation C# Script
    GenerateC#Script.JPG
    3. Code
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.InputSystem;
    3.  
    4. public class MyCharacterController : MonoBehaviour, MyControls.IMoveActions
    5. {
    6.     private MyControls inputs;
    7.  
    8.     private void Awake()
    9.     {
    10.         inputs = new MyControls();
    11.         inputs.Move.SetCallbacks(this);
    12.     }
    13.  
    14.     private void OnEnable() =>  inputs.Move.Enable();
    15.     private void OnDisable() => inputs.Move.Disable();
    16.  
    17.     public void OnJump(InputAction.CallbackContext context){...}
    18.     public void OnRun(InputAction.CallbackContext context)
    19.     {
    20.         switch (context.phase)
    21.         {
    22.             case InputActionPhase.Started:
    23.             case InputActionPhase.Performed:
    24.                 IsRun = true;
    25.                 break;
    26.             case InputActionPhase.Canceled:
    27.                 IsRun = false;
    28.                 break;
    29.         }
    30.     }
    31.     public void OnStart(InputAction.CallbackContext context) {...}
    32. }
    Note
    The code in this example are exist in the main character controller. Sometime is more appropriate the made additional "abstraction level" and put it to the separated Class especial useful use for it the ScriptableObject.
    See the example from the Unity OpenProject.
    I additionally recommend to the study this GREAT project at whole, it contain many great professional ideas and information (good video & GitHub wiki and other materials) from Unity Community and Unity Learning.
    P.S>
    This variant did the same as previous, but not demands the PlayerInput component at all. If you know what you do and not planing to change the parameters in Unity Inspector, this variant will be more useful
     
    Last edited: May 30, 2022
  11. Snowdrama

    Snowdrama

    Joined:
    May 10, 2014
    Posts:
    28
    I found my way here when looking for a solution and this is the closest I've come to a solution. The main issue I have with the variant that does not use player Input is that the callback context doesn't easily expose what control scheme triggered the callback. So If I want say, different sensitivity for X and Y axis turning in a first person game to differ between mouse and keyboard and Gamepad, there's no easy way to know if the "Turn" callback was triggered by a gamepad or mouse. All solutions when looking this up says "Use Player Input component and then use the currentControlScheme string to figure out which control scheme it is"
    Code (CSharp):
    1.  
    2.     Controls controls;
    3.     private void Awake()
    4.     {
    5.         controls = new Controls();
    6.         inputs.Player.SetCallbacks(this);
    7.     }
    8.     private void OnEnable() =>  inputs.Player.Enable();
    9.     private void OnDisable() => inputs.Player.Disable();
    10.  
    11.     public void Turn(InputAction.CallbackContext ctx)
    12.     {
    13.         //ctx.controlScheme?
    14.         //ctx.control.controlScheme?
    15.         //ctx.action.controlScheme?
    16.         //ctx.action.actionMap.ControlScheme?
    17.     }
    18.  
    And so if I want to be able to use C# events and use PlayerInput for the problem is that I think that using the "FindAction" setup to be really ugly when you select "Use C# Events" on PlayerInput
    Code (CSharp):
    1.  
    2.     PlayerInput playerInput;
    3.     private void Awake()
    4.     {
    5.         playerInput.onControlsChanged += PlayerInput_onControlsChanged;
    6.         var turn = playerInput.currentActionMap.FindAction("Turn");
    7.         turn.started += Turn;
    8.         turn.performed += Turn;
    9.         turn.canceled += Turn;
    10.     }
    11.     string controlScheme;
    12.     private void PlayerInput_onControlsChanged(PlayerInput pi)
    13.     {
    14.         controlScheme = pi.currentControlScheme;
    15.     }
    16.  
    17.     public void Turn(InputAction.CallbackContext ctx)
    18.     {
    19.         switch (controlScheme)
    20.         {
    21.             case "Keyboard&Mouse":
    22.                 //do kbm things
    23.                 break;
    24.             case "Gamepad":
    25.                 //do gamepad things
    26.                 break;
    27.             default:
    28.                 break;
    29.         }
    30.     }
    31.  
    32.  
    And while the above technically works, the downside to this is that I need to use "FindAction" and then assign stuff that way vs just implementing the interface and using SetCallbacks so this scales poorly and could introduce errors when I change the input binding but then don't update one of the scripts to include the updated "FindAction" and/or just add a new action and then don't implement it. Implementing the interface forces you to implement all the functions of the interface so it makes sure that you touch every script that implements the interface.

    If you could somehow use SetCallbacks on the PlayerInput component, it would solve like 99% of my complaints with using PlayerInput, and I'd really like to be able to get the controlScheme from the CallbackContext

    Small update: my current middle ground solution that's working for me right now and is what I'm going to use in the project I'm releasing soon is to use the ActionMap name, and then just implement the Gamepad and KBM as separate action maps that use the same actions. See how I have Player and Player_Gamepad which implement the same actions.

    upload_2022-9-29_11-56-29.png upload_2022-9-29_11-56-58.png
    Now in the code I can use:
    Code (CSharp):
    1.  
    2. public class PlayerController: MonoBehaviour, Controls.IPlayerActions, Controls.IPlayer_GamepadActions
    3. {
    4.     private void OnEnable()
    5.     {
    6.         if (controls == null)
    7.         {
    8.             controls = new Controls();
    9.             controls.Player.SetCallbacks(this);
    10.             controls.Player_Gamepad.SetCallbacks(this);
    11.             controls.Enable();
    12.         }
    13.     }
    14.  
    15.     private void OnDisable()
    16.     {
    17.         if (controls != null)
    18.         {
    19.             controls.Disable();
    20.             controls = null;
    21.         }
    22.     }
    23.     public void OnLook(InputAction.CallbackContext ctx)
    24.     {
    25.         var read = ctx.ReadValue<Vector2>();
    26.         lookDirection = read;
    27.  
    28.         //compare the action map name to see if it is the gamepad map
    29.         if (ctx.action.actionMap.name == "Player_Gamepad")
    30.         {
    31.             usingGamepad = true;
    32.         }
    33.         else
    34.         {
    35.             usingGamepad = false;
    36.         }
    37.     }
    38. //... rest of implementation here
    39.  
     
    Last edited: Sep 29, 2022
    alfredbaudisch likes this.
  12. strexet

    strexet

    Joined:
    Sep 30, 2016
    Posts:
    5
    It feels like another solution to this problem may be Input Actions' Processors:
    https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Processors.html

    You could use different Scales for different input sources. SCR-20231024-lif.png