Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Player input component triggering events multiple times

Discussion in 'Input System' started by FynnLevy, Mar 23, 2020.

  1. FynnLevy

    FynnLevy

    Joined:
    Apr 7, 2017
    Posts:
    2
    I have set up my player input with the following:

    upload_2020-3-23_0-43-54.png

    I have then attached the Player Input component so that it invokes Unity events the following way:

    upload_2020-3-23_0-46-3.png

    I am finding that when I press a button the associated event is called multiple times (twice when the interaction type is set to Tap and three times when the interaction type is set to Press).

    Is this a bug? If not and this outcome is intentional, how do I go about making one button press = event is called once? I'm happy to provide more information or clarify what is happening if needed.

    On an aside, I've also noticed that adding the Input System package to my project has broken all of my old methods of input (Checking for a key being pressed via code, UI button clicks, etc.) - they remain broken even if I remove the package from my project, so I can't even use the old input system now.

    Any help would be very much appreciated. Thanks!
     
    jonny4th and DonutFork like this.
  2. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    This is intentional. Each action goes through a started -> performed -> canceled cycle with a callback triggered for each step in the process. The supplied context object supplies information about this.

    Code (CSharp):
    1. public void OnMyAction(InputAction.CallbackContext context)
    2. {
    3.     if (context.started)
    4.         Debug.Log("Action was started");
    5.     else if (context.performed)
    6.         Debug.Log("Action was performed");
    7.     else if (context.canceled)
    8.         Debug.Log("Action was cancelled");
    9. }
    10.  
    In the Player settings in your Project Settings, set "Active Input Handling" to "Input Manager" or "Both".
     
  3. Cenda

    Cenda

    Joined:
    Jun 3, 2010
    Posts:
    66
    @Rene-Damm Do you plan add some checkbox to "Player Input" for easier use? So I don't have to check in all method "context.phase" status? It is very uncomfortable.
     
  4. Rene-Damm

    Rene-Damm

    Unity Technologies

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    We're thinking about various ways to simplify and streamline the use of actions. The current setup is very open-ended, which I think at a foundational level is a good thing. But for simple, no-brainer setups, there's still too many ways to unknowingly shoot yourself in the foot. I expect this to improve over time.
     
    ModLunar likes this.
  5. CoCoNutti

    CoCoNutti

    Joined:
    Nov 30, 2009
    Posts:
    513
    I've just wasted two full days of work trying to figure this out. Reading the above is disappointing. Was it not tested?

    My setup, I have objects on screen that the player can click on to start an action.
    So, in player input I have events pointing to my InputActionAsset. Within the events I have all my objects in the scene hooked up to the callback, with each callback set to Runtime only, and pointing to a method on the (same) script on each object.

    When the game is run, through debug log I can see that the method above is being called TWICE every time the user presses the spacebar (which is set for this action).

    Please tell me I'm doing something wrong - not sure how to get around it aside from a crappy bandaid involving an Ienumerator perhaps? :( I really don't want to have to roll back to the old input system.
    Thanks.
     
  6. oldManPig

    oldManPig

    Joined:
    Oct 25, 2019
    Posts:
    13
    On one hand, I do think it's good design to keep it open-ended, but the workaround I use is to link the UnityEvent from Player Input to an intermediary script whose only job is to check the input action for the phase I want then fire another UnityEvent which I then link to the methods I want (eg, play sound effect/set variable/goto scene), which seems slightly problematic as its doubling the number of events.

    I don't know how performant UnityEvents are but does it seem like it would be helpful to add an enum in the PlayerInput for the required phase to actually invoke the Unity Event in the first place? I'll contest I can't really think of an elegant way to include that in the UnityEvent list though.
     
    ElijayOK likes this.
  7. FileThirteen

    FileThirteen

    Joined:
    Oct 23, 2012
    Posts:
    40
    If you want a really easy solution I made a paid asset that makes consuming the new input system as easy as the old input manager but it allows you to still have the power of the input action assets. It allows you to use Input.GetButtonDown, Input.GetButtonUp, and Input.GetButton like you used to with the old input manager. You can even drop the asset (and the new input system) into a project that is still using the old Input Manager and make no changes to your code and it will automatically be using the new input system. You just have to make an input action asset that matches the old input manager's settings and set it as your active one in the asset's settings, then any script that was using the old input manager will automatically work with the new input system. It comes with an input action asset that matches the default old input manager settings, and examples of how it works. Link my signature.
     
  8. GlenZPS

    GlenZPS

    Joined:
    Sep 12, 2019
    Posts:
    25
    Just wrap your code in a if(contex.performed) to ensure it only run 1 time
     
  9. Crisby

    Crisby

    Joined:
    Oct 1, 2019
    Posts:
    5
    I dont know if this is in any way helpful.

    But I was struggling to use the new Input System, code examples I would see looked overly complicated compared to "Input.GetButtonDown()" so I tried using the Player Input Component. I went straight for Behavior == Use Unity Events because it let me link Actions to Function/ Methods in my script. On paper this sounded good but then my functions were triggering multiple times.

    This is just how Unity Events work "started -> performed -> canceled".

    https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Components.html

    Says to: Set Behavior to Send Message and Set your Function/ Method to public void OnActionName () e.g. Action: Jump, Method: OnJump(). But it still triggered multiple times. So in the Input Action you need to set the Interaction to Press but bare in mind Lt and Rt are varriable (i.e. push a little to shoot a little, push harder to shoot . . . harder idk) so untick the Default next to Press Point and set that to 1.

    Whish someone had explained this to me 4 days ago, hope it helps someone.
     
    Last edited: Feb 19, 2021
    MartinAo likes this.
  10. AnthonyJackson81

    AnthonyJackson81

    Joined:
    May 30, 2019
    Posts:
    32
    yep, having just read this context wrapper, I like the idea of it, glad I know. Having 3 different points of input on one action is a great idea.
     
    TractGames likes this.
  11. Ucama

    Ucama

    Joined:
    Sep 30, 2020
    Posts:
    1
    thanks brother it helped me
     
  12. alti

    alti

    Joined:
    Jan 8, 2014
    Posts:
    94
    Why doesn't unity events just inherit the setting we put in the Input Actions Window/Settings?

    When you click the Action there, under properties > interactions you can say "press only" etc. Why doesn't unity events just respect that setting? Seems weird to completely ignore it. Unless press only is somehow different than performed. In which case I'd be delighted to be enlightened.

    upload_2021-10-1_20-30-4.png
     
  13. alti

    alti

    Joined:
    Jan 8, 2014
    Posts:
    94
    anyways, time to spray
    Code (CSharp):
    1. if(!context.performed) return;
    everywhere
     
  14. anilozmen

    anilozmen

    Joined:
    Nov 11, 2021
    Posts:
    3
    If you want to detect just single click, you can use cancelled or performed state.

    Code (CSharp):
    1.  public void OnScreenClick(InputAction.CallbackContext callbackContext)
    2. {
    3.          
    4.      if (callbackContext.performed || callbackContext.canceled )
    5.      {
    6.              // do it
    7.       }
    8.          
    9. }
     
  15. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254
    This only works if the input and method supports the context parm.
    EG, when using SendMessages to call:
    Code (CSharp):
    1. public void OnMouseScroll(InputAction.CallbackContext context, InputValue input) {
    2.     if(!context.performed) { return; }
    3. }
    4.  
    5. //Failed to call function OnMouseScroll of class Player Calling function OnMouseScroll with 1 parameter but the function requires 2
    @Rene-Damm So where do we add the context when using the SendMessages Behavior type?
     
    Hypertectonic likes this.
  16. cocoalovethax

    cocoalovethax

    Joined:
    Dec 20, 2016
    Posts:
    2
    How do you set this up? For registering the Actions? I'm searching everywhere and can't find it. Although this compiles, it gives me an error when run:

    Code (CSharp):
    1. controls.Battle.OK.performed += ctx => OKPressed(ctx.ReadValue<InputAction.CallbackContext>());
     
  17. Reahreic

    Reahreic

    Joined:
    Mar 23, 2011
    Posts:
    254

    This post covers the how rather succinctly: https://forum.unity.com/threads/using-playerinput-with-c-events.961129/#post-8168024

    That said, using the interface's events will result in them getting called for performed, started, and canceled. If you don't want to deal with determining the context you can just target the individual sub-events directly.

    Targeting the sub-events directly
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.InputSystem;
    4.  
    5. public class InputTestSpecific : MonoBehaviour{
    6.     MyInputActions inputActions;
    7.  
    8.     void OnEnable() => inputActions.RightController.Enable();
    9.  
    10.     void OnDisable() => inputActions.RightController.Disable();
    11.  
    12.     void Awake() {
    13.         inputActions = new LauncherInputActions();
    14.     }
    15.  
    16.     void Start() {
    17.         inputActions.MyNamedActionMap.MyNamedAction.performed += MyNamedAction_performed;
    18.         inputActions.MyNamedActionMap.MyNamedAction.canceled += MyNamedAction_canceled;
    19.         inputActions.MyNamedActionMap.MyNamedAction.started += MyNamedAction_started;
    20.  
    21.         InputSystem.onDeviceChange += OnInputDeviceChanged;
    22.     }
    23.  
    24.     void MyNamedAction_performed(InputAction.CallbackContext context) {
    25.         Debug.Log("Performed");
    26.     }
    27.  
    28.     void MyNamedAction_canceled(InputAction.CallbackContext context) {
    29.         Debug.Log("Performed");
    30.     }
    31.  
    32.     void MyNamedAction_started(InputAction.CallbackContext context) {
    33.         Debug.Log("Performed");
    34.     }
    35.  
    36.     void OnInputDeviceChanged(InputDevice device, InputDeviceChange change) {
    37.         Debug.Log(device.name + ": " + change.GetDescription());
    38.     }
    39. }
    40.  
    Using the Auto generated Interface
    Code (CSharp):
    1. public class InputTestWithInterface : MonoBehaviour, IMyNamedActionMapActions{
    2.     MyInputActions inputActions;
    3.      
    4.     void OnEnable() => inputActions.RightController.Enable();
    5.  
    6.     void OnDisable() => inputActions.RightController.Disable();
    7.      
    8.     void Awake() {
    9.         inputActions = new LauncherInputActions();
    10.         inputActions.MyNamedActionMap.SetCallbacks(this);
    11.      
    12.         InputSystem.onDeviceChange += OnInputDeviceChanged;
    13.     }
    14.  
    15.     void OnInputDeviceChanged(InputDevice device, InputDeviceChange change) {
    16.         Debug.Log(device.name + ": " + change.GetDescription());
    17.     }
    18.  
    19.     public void OnMyNamedAction(InputAction.CallbackContext context) {  
    20.         if(context.started) {
    21.             Debug.Log("MyNamedAction - Started: " + context);
    22.  
    23.         } else if(context.performed) {
    24.             Debug.Log("MyNamedAction - Performed: " + context);
    25.  
    26.         } else if(context.canceled) {
    27.             Debug.Log("MyNamedAction - Canceled: " + context);
    28.  
    29.         } else {
    30.             Debug.LogWarning("Unsupported context '"+ context.ToString() +"' for this event!");
    31.         }
    32.     }
    33. }
    34.  
    Additionally, the 'Actions' 'Interactions' foldout from the InputActions editor window can also be used to customize when the events are triggered. Leaving it empty uses the default 'button' behavior.

    Button Interactions
    • None - Default Behavior
      Pressed: started, performed
      Released: canceled
    • Press - Press Only
      Pressed: started, performed
      Released: canceled
    • Press - Release Only
      Pressed: started
      Released: performed, canceled
    • Press - Press & Release
      Pressed: started, performed
      Released: performed, canceled
    • Hold - Default Behavior
      Pressed: started
      Held: performed
      Released: canceled
    Trigger Interactions
    • Just use the default behavior, the others are designed for buttons not triggers and behave poorly as a trigger.
     
  18. Hypertectonic

    Hypertectonic

    Joined:
    Dec 16, 2016
    Posts:
    75
    Yes, it seems an oversight of the coders of the PlayerInput component to not have included the context in the messages. Seeing as how SendMessages is used with their starter assets player controllers, it's probably going to be giving headaches to a lot of people...
     
  19. Selek-of-Vulcan

    Selek-of-Vulcan

    Joined:
    Sep 23, 2021
    Posts:
    70
    Yep, like me. Send Messages works, but I get multiple events. Not sure what to do.

    I know I can filter out unwanted events if I Invoke Unity Events, but that option doesn't behave the way I want either. I'm trying just to click on a cloned game object and get some info about that object. The send-messages version gets this info correctly, but the invoke-unity-events version returns the prefab's generic values, not the cloned object's values (e.g., names, variables). I don't understand why.

    Edit: I figured out why the "invoke events" option doesn't raycast correctly. For whatever reason, the "send messages" option automatically uses the raycaster attached to the camera, whereas the "invoke events" option does not. Once I added a manual raycast to my "invoke events" method, it worked fine. I don't understand why the "invoke events" option lacks the auto-raycast functionality of the "send messages" option; is there a reason for this?

    I'm also still curious why there's no easy way to filter out "performed" and "started" events when using the "Send Message" option.
     
    Last edited: Sep 29, 2022
  20. xavierdhjr

    xavierdhjr

    Joined:
    Jan 20, 2020
    Posts:
    5
    Feedback: Needing to check InputContext to avoid multiple callbacks is not clear when using the PlayerInput component.

    Suggestion: Support flags on the PlayerInput component that allows choice of which callback triggers to handle. E.g.
    upload_2023-3-25_10-2-8.png

    For backwards compatibility it could default to 'Everything' but then folks have something to try changing when things aren't working as expected.

    An explanation tooltip that could probably be less wordy:
    upload_2023-3-25_10-2-24.png