Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Hold and press on the same button/path without performing both actions when holding

Discussion in 'Input System' started by sebastianzander, Aug 18, 2019.

  1. sebastianzander

    sebastianzander

    Joined:
    Mar 22, 2018
    Posts:
    37
    First of all I want to say that you guys do a great job on the new input system. I really like the versatility, the clarity of the different input devices and the code paradigm, and not to forget the action maps.

    I am currently trying to achieve a double mapping for a leftStickPress [Gamepad] to toggle on/off two different lights. The headlights shall be toggled via a normal press interaction and a secondary light shall be toggled via a hold interaction. Having read the documentation on GitHub I did not find the correct way to do this without implementing my very own "hold" time measuring.

    The best result that I achieve is that both lights toggle, i.e. if I want only the secondary lights to turn on I have to 1) hold and both lights turn on and then 2) press once more to turn off the headlights. I tried it with two actions and two callbacks, I tried it with two actions in one callback and conditionals and I tried it with one action, one callback and conditionals. Conditionals being if(context.performed), if(context.canceled) and if(context.interaction is UnityEngine.InputSystem.Interactions.HoldInteraction)

    Is what I am after even possible with the new input system?
    I am very grateful for any hints and help.
     
  2. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Thanks. Glad to hear :)

    There's definitely need for better documentation here and especially the way Hold works seems to have led to confusion.

    There's several ways in which what you describe can be set up and is expected to work. One is with a Hold interaction first and a Press interaction second (so that if the Hold cancels, it becomes a Press). Another is with a Tap interaction first and a SlowTap interaction second (so that if the Tap cancels, it becomes a SlowTap). It should also work with just a Hold interaction in case the callbacks handle cancelation separately (much trickier setup, though).

    Code (CSharp):
    1. // In case there's a Hold first and a Press second:
    2. lightToggleAction.performed +=
    3.     ctx =>
    4.     {
    5.         if (ctx.interaction is HoldInteraction)
    6.             ToggleLight2();
    7.         else // Could check for PressInteraction but easier to just assume it's a press.
    8.             ToggleLight1();
    9.     };
    10.  
    11. // In case there's a Tap first and a SlowTap second:
    12. // (Probably want to set duration on both Tap and SlowTap to be equal so that they
    13. // butt up against each other)
    14. lightToggleAction.performed +=
    15.     ctx =>
    16.     {
    17.         if (ctx.interaction is SlowTapInteraction)
    18.             ToggleLight2();
    19.         else // Could check for TapInteraction but easier to just assume it's a press.
    20.             ToggleLight1();
    21.     };
    22.  
    23. // In case there's just a Hold, it's more complicated.
    24. // NOTE: This isn't a good setup. An action may get cancelled for reasons other than input
    25. //             and it's much trickier to robustly differentiate this in the callback than to just put
    26. //             another interaction on there that represents the "shorter-than-hold" input on its own.
    27. lightToggleAction.performed +=
    28.     ctx =>
    29.     {
    30.         // We know we will only get here on a successful hold.
    31.         ToggleLight2();
    32.     };
    33. lightToggleAction.canceled +=
    34.     ctx =>
    35.     {
    36.         if (ctx.interaction is HoldInteraction && ctx.ReadValue<float> > 0)
    37.             ToggleLight1();
    38.     };
    To shed a bit of light on this, when you stack interactions, they get processed top to bottom and interactions higher up in the stack get precedence over ones lower down the stack when it comes to driving the action.

    So, say you have a Tap and then a SlowTap. When the button goes down, *both* the Tap and the SlowTap start. But because Tap comes first, it gets to drive the action. So you see a callback on InputAction.started with the interaction being TapInteraction. As the button is held for longer than TapInteraction.duration (defaults to InputSettings.defaultTapTime), Tap will cancel. So you see a callback on InputAction.canceled and then a call on InputAction.started with SlowTapInteraction (because SlowTap already started). Then, when the button is released after SlowTapInteraction.duration (defaults to InputSetting.defaultSlowTapTime), you see a callback on InputAction.performed and then everything goes back to the beginning.

    Let me know if with this in mind, things aren't working as expected.
     
    iPhr3ak likes this.
  3. sebastianzander

    sebastianzander

    Joined:
    Mar 22, 2018
    Posts:
    37
    So with "stack" you mean the list of bindings of an action in the Input Actions asset? Like so: https://pasteboard.co/Itr2xuY.png

    This is my code:
    Code (CSharp):
    1.     // in Start()
    2.     toggleLightsInput = playerControls.Common.ToggleLights;
    3.     toggleLightsInput.performed += OnToggleLights;
    4. }
    5.  
    6. private void OnToggleLights(InputAction.CallbackContext context)
    7. {
    8.     if(context.interaction is HoldInteraction)
    9.         ToggleMastLights();
    10.     else
    11.         ToggleHeadlights();
    12. }
    ToggleHeadlights() is never executed. No matter how short or long (without being a hold) I press the left stick (or the L key for that matter)

    Edit #1: funny enough, ToggleHeadlights() is called multiple times when I double tap the stick (not the L key) and the lights do indeed toggle and switch their state o_O (I guess its multiple times, because there is a touch of a flicker visible, like on-off-on)
     
    Last edited: Aug 19, 2019
  4. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Stacked liked this:



    I recommend checking out SimpleControls.inputactions from the SimpleDemo samples. On the "fire" action, it has a simple two-interaction setup where Tap leads to it firing immediately and SlowTap leads to it charging (including some simple UI feedback) and then firing on release. See here.

    The setup there is likely going to confuse the action. If the action is set to "Pass-Through" type, it'll probably sort of work but there's bindings of mixed types (some buttons and one Vector2) on there plus one binding is repeated.

    ////EDIT: There's also some bits of explanations in the README for the sample.
     
  5. sebastianzander

    sebastianzander

    Joined:
    Mar 22, 2018
    Posts:
    37
    Sorry, but I don't get it to work. This is what happens:



    Input actions were: Hold (both lights turn on), Hold (both lights turn off), Press (headlights turn on), Hold (headlights off, mast lights on), Hold (headlights on, mast lights off)

    And I have it just like in your screenshot, but with Press instead of Tap and with Hold instead of Slow Tap. The problem I see is that when I have the Press interaction with trigger behavior "Press only", the input event will get consumed immediately and Hold will never be reached. But when I set trigger behavior "Release only" then the Press event continues past hold duration until I release the key/stick. In both cases the Press event is the only event that is being processed. Log messages confirm this.
     
  6. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Should work if you switch the order the other way round. If Press comes first, Hold never gets to do its thing as any Hold interaction can also be considered a Press interaction. With the two swapped, you should get the expected behavior. Only if the Hold fails should the Press start doing its thing.
     
  7. sebastianzander

    sebastianzander

    Joined:
    Mar 22, 2018
    Posts:
    37
    There are two possible outcomes now.
    1. if I use "Press only" then only the Hold interaction is executed
    2. if I use "Release only" then the Press interaction is only executed every second press o_O (hold works tough)
    In case of 2 I get these errors:

    Map index on trigger does not correspond to map index of trigger state
    UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
    UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at C:/buildslave/unity/build/Modules/Input/Private/Input.cs:117)

    Exception 'IndexOutOfRangeException' thrown from state change monitor 'InputActionState' on 'Button:/XInputControllerWindows/leftStickPress'
    UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
    UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at C:/buildslave/unity/build/Modules/Input/Private/Input.cs:117)

    IndexOutOfRangeException: Index was outside the bounds of the array.
    UnityEngine.InputSystem.InputActionState.ChangePhaseOfAction (UnityEngine.InputSystem.InputActionPhase newPhase, UnityEngine.InputSystem.InputActionState+TriggerState& trigger, UnityEngine.InputSystem.InputActionPhase phaseAfterPerformedOrCanceled) (at Library/PackageCache/com.unity.inputsystem@0.9.3-preview/InputSystem/Actions/InputActionState.cs:1525)
    UnityEngine.InputSystem.InputActionState.ChangePhaseOfInteraction (UnityEngine.InputSystem.InputActionPhase newPhase, UnityEngine.InputSystem.InputActionState+TriggerState& trigger, UnityEngine.InputSystem.InputActionPhase phaseAfterPerformed) (at Library/PackageCache/com.unity.inputsystem@0.9.3-preview/InputSystem/Actions/InputActionState.cs:1434)
    UnityEngine.InputSystem.InputInteractionContext.Canceled () (at Library/PackageCache/com.unity.inputsystem@0.9.3-preview/InputSystem/Actions/InputInteractionContext.cs:143)
    UnityEngine.InputSystem.Interactions.HoldInteraction.Process (UnityEngine.InputSystem.InputInteractionContext& context) (at Library/PackageCache/com.unity.inputsystem@0.9.3-preview/InputSystem/Actions/Interactions/HoldInteraction.cs:75)
    UnityEngine.InputSystem.InputActionState.ProcessInteractions (UnityEngine.InputSystem.InputActionState+TriggerState& trigger, System.Int32 interactionStartIndex, System.Int32 interactionCount) (at Library/PackageCache/com.unity.inputsystem@0.9.3-preview/InputSystem/Actions/InputActionState.cs:1267)
    UnityEngine.InputSystem.InputActionState.ProcessControlStateChange (System.Int32 mapIndex, System.Int32 controlIndex, System.Int32 bindingIndex, System.Double time, UnityEngine.InputSystem.LowLevel.InputEventPtr eventPtr) (at Library/PackageCache/com.unity.inputsystem@0.9.3-preview/InputSystem/Actions/InputActionState.cs:893)
    UnityEngine.InputSystem.InputActionState.UnityEngine.InputSystem.LowLevel.IInputStateChangeMonitor.NotifyControlStateChanged (UnityEngine.InputSystem.InputControl control, System.Double time, UnityEngine.InputSystem.LowLevel.InputEventPtr eventPtr, System.Int64 mapControlAndBindingIndex) (at Library/PackageCache/com.unity.inputsystem@0.9.3-preview/InputSystem/Actions/InputActionState.cs:785)
    UnityEngine.InputSystem.InputManager.FireStateChangeNotifications (System.Int32 deviceIndex, System.Double internalTime, UnityEngine.InputSystem.LowLevel.InputEvent* eventPtr) (at Library/PackageCache/com.unity.inputsystem@0.9.3-preview/InputSystem/InputManager.cs:2664)
    UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
    UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at C:/buildslave/unity/build/Modules/Input/Private/Input.cs:117)

    Map index on trigger does not correspond to map index of trigger state
    UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
    UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at C:/buildslave/unity/build/Modules/Input/Private/Input.cs:117)
     
    diogohartmann45 likes this.
  8. sebastianzander

    sebastianzander

    Joined:
    Mar 22, 2018
    Posts:
    37
    Do you guys have no idea then? Did I spot a bug there?
     
    diogohartmann45 likes this.
  9. Th0re

    Th0re

    Joined:
    Sep 4, 2015
    Posts:
    39
    << Newbie who couldn't get it to work, even just Hold without anything else. The new system is great, but not really that easy to use. I concur that the action that follows hold should probably be different than the one that is fired off from a normal press, but I couldn't get that to work either. Here's a band-aid with a simple timer.

    Code (CSharp):
    1. if (myButton && _theTimer > 0.5f) DoTheThing();
    2.  
    3. if (myButton) MyHoldTimer();
    4. else _theTimer = 0;
    and then in the method

    Code (CSharp):
    1. private void MyHoldTimer() => _theTimer += Time.deltaTime;
     
  10. laessnb

    laessnb

    Joined:
    Jun 10, 2014
    Posts:
    101
    I had to give up on trying to implement something fairly similar. I wanted pressing Button South on gamepad to advance dialogue, and press+holding Button South for a second to "accept incoming comms" (also had the M+K bindings for these). I set up a ContinueDialogue binding with Button South press, and AcceptComms binding with Button South + Hold interaction, but that wound up not working at all. Input was no longer going through. As soon as I mapped them to two separate buttons, it worked. So, it looks like I'll have to write custom logic for the hold. I'm sure I'm missing something but this has taken enough of my afternoon. One possibly relevant piece of info is that I'm also using the gamepad virtual cursor (though it's inactive during this sequence).
     
  11. Boof

    Boof

    Joined:
    Aug 11, 2014
    Posts:
    10
    For my case, I have a UI with windows and I wanted to close the top-most window when I only press Esc, but close all windows when I hold Esc. I was able to get both to work using the following settings:
    upload_2022-2-9_23-48-39.png
    "Release Only" was what did the trick for me. I am driving the interactions with the following code:
    Code (CSharp):
    1.  
    2. using UnityEngine.InputSystem;
    3. using UnityEngine.InputSystem.Interactions;
    4. ...
    5.     public void OnEscape(InputAction.CallbackContext context)
    6.     {
    7.         if (context.performed)
    8.         {
    9.             Debug.Log(context.interaction);
    10.             if (context.interaction is PressInteraction) CloseTopWindow();
    11.             else if (context.interaction is HoldInteraction) CloseAllWindows();
    12.             // This exception should never get thrown, but in case it does then at least we know where it came from
    13.             else throw new System.Exception("OnEscape received unrecognized button interaction '" + context.interaction + "'");
    14.         }
    15.     }
     
    dljdsandberg and sederfan like this.
  12. sederfan

    sederfan

    Joined:
    May 2, 2022
    Posts:
    4
    Thank you Boof "Release Only" is the trick,
    I was trying to do a normal jump with press and a high jump while I was holding the jump key down,
    the problem was Press Trigger Behavior was set on "Press Only" and because there was a Hold interaction before the Press interaction when I released the jump key early Hold would become canceled and there was no Press going on to Trigger Press Interaction,
    Anyway by changing Press Trigger Behavior to "Release Only" when I release the Jump key early Hold interaction will be canceled and the release will trigger Press action.
     
  13. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,364
    Expectation: InputSystem watches key or button. If the key is pressed and released within 0.2s, it will trigger the press action, and if the key is pressed and released after 0.4s, it will light up the hold action until they release it.

    Reality: InputSystem gives me no events for a short press, and will latch the hold action forever for a long press.

    Guess I gotta code this myself.
     
  14. wechat_os_Qy0xAlLYtE393D3sFdlh3sVN0

    wechat_os_Qy0xAlLYtE393D3sFdlh3sVN0

    Joined:
    Sep 7, 2020
    Posts:
    1
    In your order, when I just press, not hold, the Performed and Canel events of PressInteraction will be triggered at the same frame. For example, I want to play the FIST GRIP animation when the performed event trigger: handAnimator SetFloat("Grip", 1) . Play the fist unclamping animation: handAnimator SetFloat("Grip", 0) when the Canel event triggers; The FIST GRIP action can never be seen, because it is call the setFloat ("Grip", 0) at the same frame.