Search Unity

New Input System : How to use the "Hold" interaction.

Discussion in 'Input System' started by Xynhay, Dec 31, 2018.

  1. DavidBaughan

    DavidBaughan

    Joined:
    Apr 24, 2021
    Posts:
    5
    So I have been starting to use the new input system for games and like a lot of people in this thread i misunderstood how the Hold Interaction worked. I understand it now, but it has left me trying to work out how to implement different behaviour when a button is held down.

    Following a suggestion from SomeGuy22 earlier on the page I tried to use a ReadValue<float>() check to see if a button was being held down, and that worked, to an extent. What i noticed is that the ReadValue returns a 1 as soon as the button is pressed even if I have a Hold interaction on the Binding.

    So while the Action may only perform after the button has been pressed for 0.5 seconds, the ReadValue will return a result instantly.

    I was hoping that it would only return a 1 after the action had been performed so i could use the Hold interaction to input a delay into registering the button press without using additional code for time delays.

    I suppose the other thing i could try is to read the Phase of the action, but I haven't been able to figure out how to do that.
     
  2. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Similar to the way you can call ReadValue() from an Input Action, you can just read the .phase property as explained in the API. i.e. myInputAction.phase. "Note that both interactions and the action type can affect the phases that an action goes through." So you should see that the Hold interaction will affect the value of phase, not sure if you should be checking against Started or Performed, but one of those might get you what you're looking for.
     
  3. DavidBaughan

    DavidBaughan

    Joined:
    Apr 24, 2021
    Posts:
    5
    Thank you for the reply, i appreciate your attempt to help. Unfortunately i had already read the API for the .phase property and it didn't help because i didn't understand it.

    I expect there is some knowledge of the format i am missing. I am able to understand how to use the ReadValue() because i can use it to set the value of a float, but whenever i tried that with .phase i ran into an error saying that it cannot be implicitly converted to float/ int/ string.

    I will have to come back to this in the future, hopefully then I will have learned what i am missing.
     
  4. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I recommend looking into C# or coding tutorials and such since utilizing the .phase property just means doing a comparison wherever you need in your code. If you're hoping to check every frame if .phase is "performed", all you need to stick in your Update() function would be:

    Code (CSharp):
    1. if(myInputAction.phase == InputActionPhase.Performed) {
    2.     //The button is currently performed
    3. }
    This should be close to what you were doing with ReadValue() if I'm understanding correctly.
     
  5. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    In case anyone's interested, I've whipped up a version of a Hold interaction where the
    .performed
    callback is constantly triggered for as long as the input is being held, allowing you to do a fully callback-based setup.

    It also supports setting a value threshold and minimum hold-time for the performed to be triggered.

    https://gist.github.com/Invertex/db99b1b16ca53805ae02697b1a51ea77

    Just place this script somewhere in your assets folder and it will become a selectable "Interaction" in the input system. (Don't put it in an 'Editor' directory)

    Hopefully people can test it and report any issues they run into :)
     
    Last edited: Jul 10, 2021
  6. Deleted User

    Deleted User

    Guest

    Works flawlessly.
    Thank you.
     
    Invertex and mikawendt like this.
  7. bwulff

    bwulff

    Joined:
    Jun 20, 2020
    Posts:
    12

    Thank you, sir!
    It's working flawlessly.
     
  8. ChrisNonyminus

    ChrisNonyminus

    Joined:
    Jul 29, 2021
    Posts:
    1
    It does not work at all for me. Doing an if statement on performed does nothing.
     
  9. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    This was designed to be used for callback. So you should be subscribing a method to the
    .performed
    event, like so:
    Code (CSharp):
    1. void OnEnable()
    2. {//Script is enabled, let's start listening for input event.
    3.     jumpAction.performed += Performing;
    4. }
    5. void OnDisable()
    6. { //Script is disabled, let's stop listening for input event.
    7.     jumpAction.performed -= Performing;
    8. }
    9. void Performing(InputAction.CallbackContext ctx)
    10. { //This method will be called every frame that the action is being held down.
    11.     Debug.Log($"Performing {ctx.action.name} action");
    12. }
    13. //Can also make a method just like that one,
    14. //but for .started and .canceled to know when the input first starts and ends.
    Don't even need an Update() method in your script.

    But, if you are just going to be checking the state from Update, then you would do:
    if(someActionReference.WasPerformedThisFrame()) { //do stuff }


    I've tested and this works perfectly fine. It may be that there's a general input configurable issue with your setup too if it's not giving a callback with other interactions as well.
     
  10. Synith

    Synith

    Joined:
    Apr 18, 2021
    Posts:
    1
    This is what I've done to check if the space bar is being held down in Unity's new Input System


    I set the action properties to Action Type = Value and Control Type = Axis
    inputactionsWhileHeldDown.jpg

    Now you can get a value back from that axis
    example.jpg
     
  11. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Yes, this is the typical way if you are using update. The issue that was previously being discussed was in respect to having that happen with the input callbacks. Not manually reading it from
    Update
    . There was no interaction method that would constantly call
    .performed
    when held. The script I provided creates such an interaction.
    inputAction.performed
    is an event you can subscribe methods to, and those methods will be called automatically for as long as the key is being held, as I showed in my example code at the bottom of the last comment.
     
    Synith and ALDUIIN like this.
  12. EdgarMtz1807

    EdgarMtz1807

    Joined:
    Sep 13, 2017
    Posts:
    4
    First of all, thanks for this extension! Great work.

    Now... this works for me but not the way I expected :(...
    Maybe I'm implementing the wrong way, but i just don't know what "Press Point" does mean, I thought that it was like a "timer" between each "performed" callback was executed, but if I have a value more than 1 it just does not execute anything, but if I have it at 0, it just executes an unholy amount of times, did I missunderstand how this extension works or this just works as intended and my specific case just don't apply here?
     
  13. VR_Junkie

    VR_Junkie

    Joined:
    Nov 26, 2016
    Posts:
    77
    Can someone explain to me how to access "IsPressed(), WasReleasedThisFrame(), ReadValueAsButton(), WasPerformedThisFrame()". I thought it would be InputActionAsset.InputActionMap.InputAction.IsPressed() but it doesnt come up (ex: playerActions.DefaultActions.Jump.IsPressed())
     
  14. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Sorry, thought I replied to this but I guess I never submitted haha.

    No, Press Point is the value the input must pass to be considered "actuated" or "pressed", think of it like "Dead Zone", so if you had it set to more than 1, then most standard inputs would not trigger, as they are usually in the 0-1 or -1-1 range. It is not a delay.
    The
    .performed
    and other callbacks will be called every time the Input system is updated, and thus trigger any methods you have subscribed to them when applicable.
    If you want a timer it would likely be better to implement that in your code, elapsing the timer while the input is Pressed, and each time you hit your delay amount you can trigger the action you want :)
     
    EdgarMtz1807 likes this.
  15. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    They should come up... They are part of the InputAction type. Do you have using
    UnityEngine.InputSystem;
    at the top of your script?
     
  16. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Make sure to be on the latest 1.1 preview (1.1-pre.6) package as 1.0.2 doesn't have these yet.

    1.1 final should be out shortly to finally get rid of this problem.
     
    Nefisto, nglasl and EdgarMtz1807 like this.
  17. motibaral

    motibaral

    Joined:
    Sep 19, 2020
    Posts:
    1
    I had a similar use-case, where I wanted a button to fire when:
    1. It's first pressed ("Perform Instantly")
    2. X seconds after it's pressed (X = "First Duration")
    3. Every Y seconds after that (Y = "General Duration")

    I made some adjustments to the code Invertex shared to support that:

    Code (CSharp):
    1. #if UNITY_EDITOR
    2. using UnityEditor;
    3. #endif
    4. using UnityEngine;
    5. using UnityEngine.InputSystem;
    6.  
    7. namespace Invertex.UnityInputExtensions.Interactions
    8. {
    9.     /// <summary>
    10.     /// Based on https://forum.unity.com/threads/new-input-system-how-to-use-the-hold-interaction.605587/page-4#post-7314433
    11.     /// </summary>
    12. #if UNITY_EDITOR
    13.     [InitializeOnLoad]
    14. #endif
    15.     public class HoldRepeatInteraction : IInputInteraction
    16.     {
    17.         public bool useDefaultSettingsPressPoint;
    18.         public float pressPoint;
    19.         public bool performInstantly = true;
    20.         public bool useDefaultSettingsFirstDuration;
    21.         public float firstDuration;
    22.         public bool useDefaultSettingsGeneralDuration;
    23.         public float generalDuration;
    24.  
    25.         private InputInteractionContext _context;
    26.         private double _lastPerformed;
    27.         private bool _didWaitFirstDuration;
    28.  
    29.         private float FirstDurationOrDefault => useDefaultSettingsFirstDuration ? InputSystem.settings.defaultHoldTime : firstDuration;
    30.  
    31.         private float GeneralDurationOrDefault => useDefaultSettingsGeneralDuration ? InputSystem.settings.defaultHoldTime : generalDuration;
    32.  
    33.         private float PressPointOrDefault => useDefaultSettingsPressPoint ? InputSystem.settings.defaultButtonPressPoint : pressPoint;
    34.  
    35.         private void OnUpdate()
    36.         {
    37.             var isActuated = _context.ControlIsActuated(PressPointOrDefault);
    38.             var phase = _context.phase;
    39.  
    40.             if (phase == InputActionPhase.Canceled || phase == InputActionPhase.Disabled || !isActuated)
    41.             {
    42.                 Cancel(ref _context);
    43.  
    44.                 return;
    45.             }
    46.  
    47.             float now = Time.time;
    48.  
    49.             if (phase != InputActionPhase.Performed)
    50.             {
    51.                 if (performInstantly)
    52.                 {
    53.                     Perform(now);
    54.                 }
    55.                 else if (now - _context.startTime >= FirstDurationOrDefault)
    56.                 {
    57.                     Perform(now);
    58.                     _didWaitFirstDuration = true;
    59.                 }
    60.  
    61.                 return;
    62.             }
    63.  
    64.             float duration = !_didWaitFirstDuration ? FirstDurationOrDefault : GeneralDurationOrDefault;
    65.  
    66.             if (now - _lastPerformed >= duration)
    67.             {
    68.                 Perform(now);
    69.                 _didWaitFirstDuration = true;
    70.             }
    71.         }
    72.  
    73.         private void Perform(float now)
    74.         {
    75.             _lastPerformed = now;
    76.             _context.PerformedAndStayPerformed();
    77.         }
    78.  
    79.         public void Process(ref InputInteractionContext context)
    80.         {
    81.             if (context.phase == InputActionPhase.Waiting && context.ControlIsActuated(PressPointOrDefault))
    82.             {
    83.                 context.Started();
    84.  
    85.                 InputSystem.onAfterUpdate += OnUpdate;
    86.             }
    87.  
    88.             // Ensure our Update always has access to the most recently updated context
    89.             _context = context;
    90.         }
    91.  
    92.         private void Cancel(ref InputInteractionContext context)
    93.         {
    94.             Reset();
    95.  
    96.             context.Canceled();
    97.         }
    98.  
    99.         public void Reset()
    100.         {
    101.             _lastPerformed = 0;
    102.             _didWaitFirstDuration = false;
    103.  
    104.             InputSystem.onAfterUpdate -= OnUpdate;
    105.         }
    106.  
    107.         static HoldRepeatInteraction()
    108.         {
    109.             InputSystem.RegisterInteraction<HoldRepeatInteraction>();
    110.         }
    111.     }
    112. }
    I set my interaction with the following:

    upload_2021-9-10_18-1-7.png
     
    Last edited: Sep 10, 2021
    all_iver, jujunosuke, AlejMC and 2 others like this.
  18. Krausladen

    Krausladen

    Joined:
    Nov 3, 2021
    Posts:
    9
    I found that outputting from within the input callback results in errors such as the one you are talking about, which makes a lot of sense.

    I used to call Move(); every update frame in the old input system. Now I call OutputMove(); every update frame in the new system.

    You register inputs, calculate, then take your data and output it in a separate function. The solution is really just creating a new function for your line of transform code separate from the callback reading your inputs.

    Have a gander at my move and aim:

    Code (CSharp):
    1.    
    2. void Update()
    3.     {
    4.         Bounds();
    5.         OutputMove();
    6.         OutputAim();
    7.         OutputFire();
    8.     }
    9.     public void InputMove(InputAction.CallbackContext value)
    10.     {
    11.         //
    12.         Vector2 inputMovement = value.ReadValue<Vector2>();
    13.         // Make movement speed frame-rate independent
    14.         moveThisFrame =
    15.         Time.deltaTime * moveSpeed *
    16.         (
    17.         (Vector3.right * inputMovement.x) +
    18.         (Vector3.forward * inputMovement.y)
    19.         );
    20.     }
    21.     public void OutputMove()
    22.     {
    23.         transform.position += moveThisFrame; //Move();
    24.     }
    25.     public void InputAim(InputAction.CallbackContext value)
    26.     {
    27.         Vector2 aim = value.ReadValue<Vector2>();
    28.         aimDirection = new Vector3(aim.x, 0, aim.y);
    29.     }
    30.     public void OutputAim()
    31.     {
    32.         transform.rotation = Quaternion.LookRotation(aimDirection);//aim();
    33.     }
    34.     }
     
    Last edited: Nov 8, 2021
  19. DAcKey

    DAcKey

    Joined:
    Feb 11, 2019
    Posts:
    9
    Three is my unfinished solution :)


    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using NaughtyAttributes;
    4. using UnityEngine.InputSystem;
    5.  
    6. [RequireComponent(typeof(PlayerInput))]
    7. public class CameraDriver : MonoBehaviour
    8. {
    9.     private Vector2 movementAcceleration;
    10.     private Vector2 lookingAcceleration;
    11.     [SerializeField] private CameraDriverMode cameraMode;
    12.     [SerializeField] [Range(0f,5f)] private float speedMultiplier;
    13.     [SerializeField] [Range(0f,5f)] private float sprintSpeedMultiplier;
    14.     [ShowIf("cameraMode", CameraDriverMode.TopDownTargetFollow)] public Transform target;
    15.    
    16.     void LateUpdate()
    17.     {
    18.         switch (cameraMode)
    19.         {
    20.             case CameraDriverMode.AbsoluteFree:
    21.                 FreeMovement();
    22.                 break;
    23.             case CameraDriverMode.FreeTopDown:
    24.                 FreeTopDownMovement();
    25.                 break;
    26.             case CameraDriverMode.TopDownTargetFollow:
    27.                 TargetTopDownFollow();
    28.                 break;
    29.         }
    30.     }
    31.  
    32.     public void Move(InputAction.CallbackContext callbackContext)
    33.     {
    34.         Debug.Log(callbackContext.phase);
    35.         if (callbackContext.phase == InputActionPhase.Performed)
    36.             movementAcceleration = callbackContext.ReadValue<Vector2>();
    37.         if (callbackContext.phase == InputActionPhase.Canceled)
    38.             movementAcceleration = Vector2.zero;
    39.     }
    40.  
    41.     public void Look(InputAction.CallbackContext callbackContext)
    42.     {
    43.         if (callbackContext.phase == InputActionPhase.Performed)
    44.             lookingAcceleration = callbackContext.ReadValue<Vector2>();
    45.         if (callbackContext.phase == InputActionPhase.Canceled)
    46.             lookingAcceleration = Vector2.zero;
    47.     }
    48.    
    49.     #region MovementBehaviour
    50.     void FreeMovement()
    51.     {
    52.         var acceleration = movementAcceleration * Time.deltaTime * speedMultiplier;
    53.         transform.position += new Vector3(acceleration.x, 0 , acceleration.y);
    54.     }
    55.  
    56.     void FreeTopDownMovement()
    57.     {
    58.        
    59.     }
    60.  
    61.     void TargetTopDownFollow()
    62.     {
    63.        
    64.     }
    65.     #endregion
    66.  
    67.  
    68.     private enum CameraDriverMode
    69.     {
    70.         AbsoluteFree,
    71.         FreeTopDown,
    72.         TopDownTargetFollow
    73.     }
    74. }
    75.  
     
  20. swedishfisk

    swedishfisk

    Joined:
    Oct 14, 2016
    Posts:
    57
    Hey, from all the boilerplate pasted in this long thread I assume we can't get a simple callback once the action with hold interaction is performed (without bookkeeping this ourselves and negating the usefulness of the input action UI that suggest this should work?)

    This is what I presume should work but I get no callback:

    Code (CSharp):
    1. [SerializeField]
    2. private InputActionReference actionRef;
    3.  
    4. void Awake()
    5. {
    6.     actionRef.action.performed += ctx => Debug.Log("Performed!");
    7. }
    I do get the started callback, but performed callback never fires.

    I have the hold interaction set on the action itself, and I have tried all combinations of Action Type and Control Type on the action to get it working since so many threads suggest mismatched configurations (that the inspector doesn't appear to warn about at all?)
     
    Last edited: Dec 27, 2021
  21. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    A couple things, and I apologize if some of this you've tried already:
    • Ensure that the correct action is being passed into actionRef.action via the Inspector. You can also try utilizing a reference of InputActionAsset instead, dragging your whole asset into the variable and then use Asset.FindAction(String) to acquire the action you want. Note that if you have multiple actions of the same name across multiple maps you may need to do FindActionMap(String) to get the map and then FindAction on that map to get the action. From there you can try adding the callback to the performed delegate.
    • Ensure that the Action is ActionType Button, or Pass Through. Add the hold interaction and make note of the Press Point and Hold Time. Note from the above posts in this thread that due to Control Disambiguation you can only reliably have 1 binding registered on the action if you use ActionType Button. This is because Input System tries to manually cull out inputs that are not active and cannot process both affective the interaction at the same time. If you want to use more than 1 binding, use Pass Through.
    • When pressing the button, ensure that your input value is higher than the press point. If it's a keyboard button it should be a value of 1 when you press the key. If it's a Gamepad trigger it could be from 0-1. Ensure that you hold the button for longer than the Hold Time for performed to register.
    • An odd quirk I've found about the InputActionAsset is that sometimes it just starts off being completely disabled. Make sure that everything is up and running by calling InputActionAsset.Enable() in Awake() before doing anything else.
    • If all else fails, try abstracting your Debug.Log statement into a function that calls Debug.Log. Weird things can happen with delegates and local variables due to C# closure. Although the code snippet you posted above shouldn't have this problem, if you're relying on local variables anywhere else it's good to keep in mind. It's definitely tripped me up when trying to use iterators passed into callbacks. Again, I don't think it will fix anything here, but something to be aware of for future use.
    If you're still unable to see the log let me know and I can investigate this further, since that exact setup has worked for me in the past.

    For your other points, the Inspector doesn't warn you of "mismatching configurations" since it has no idea what you intend to use it for. The testing results shown in this thread demonstrate that most combinations should make sense with what you'd expect from .performed for each type of ActionType and interaction.
     
    JohnnyGo-Time likes this.
  22. razzraziel

    razzraziel

    Joined:
    Sep 13, 2018
    Posts:
    396
  23. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    You can code your own Input behaviour type that will show up in the Input Action UI. I posted an example script that calls .performed every update that the input is held here: https://forum.unity.com/threads/new...e-hold-interaction.605587/page-4#post-7314433
    And some others posted some with slightly different behaviors later in this thread.

    Once the script is in your project, this will show up as an action type in your Input Manager just like the built-in ones.
     
  24. SmoothPulsar

    SmoothPulsar

    Joined:
    Apr 21, 2018
    Posts:
    3
    Your scripts does not work for me (Input System 1.2.0). Well, it works but when i press 2 buttons
    simultaneously (Like D for right and S for backwards), the call to .performed callback still continues, even when I have released the key. And when I exit Play Mode, i have a bunch of errors (because the update is still running somehow)

    There's 2 errors with the same line, one with the error message "Binding index out of range" and the other "Interaction index out of range" on this line

    if (phase == InputActionPhase.Performed || (phase != InputActionPhase.Performed && ctx.timerHasExpired)) { ctx.PerformedAndStayPerformed(); }


    It triggers a "NullReferenceException: Object reference not set to an instance of an object" on the line 2736 of the script InputActionState.cs

    get => ((Flags)m_Flags & Flags.TimerRunning) == Flags.TimerRunning;
     
    AlejMC likes this.
  25. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    I can't seem to replicate this on Unity 2021.2.6f1 with Input System 1.2.0, what Unity version are you on?

    Also how is your Player Input component setup, is it using the Send Messages behaviour or something else?

    And in your Project Settings > Input System Package, do you have non-default settings there at a ll?
     
  26. Deleted User

    Deleted User

    Guest

    After this being open for years, has Unity issued an official method for continuous hold? How does one "accelerate" a vehicle without this functionality?
     
    chriseborn likes this.
  27. Pixelith

    Pixelith

    Joined:
    Jun 24, 2014
    Posts:
    580
    You read the passthrough value in something like the Update method. A little bit a pseudo code would look like
    Vector2 move = controls.Player.Move.ReadValue<Vector2>();


    If you're wanting to detect a normal button being pressed, I like to subscribe events and use bools. Here's another quick pseudo code type

    Code (CSharp):
    1. void OnEnable(){
    2. controls.Player.Accelerate.Performed += ctc => accelerateButton = true;
    3.  
    4. controls.Player.Accelerate.Cancelled += ctx => accelerateButton = false;
    5. }
    I know there's many better ways to handle that, but it's the quickest way I can think of to enact a "hold" type function.
     
    Deleted User likes this.
  28. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    With polling:
    Code (CSharp):
    1. Gamepad pad = Gamepad.current;
    2. ...
    3. if (pad != null)
    4. ...
    5. float lt = pad.leftTrigger.ReadValue();
    Although it is actually covered in the docs, just easy to skim over.
     
  29. AlejMC

    AlejMC

    Joined:
    Oct 15, 2013
    Posts:
    149
    Had a question, this is working perfectly fine in editor but when making stand alone builds the InputSystem action bindings that use it throw an error.
    Wondering if maybe I'm missing something? there's an [InitializeOnLoad] on the class but only #if UNITY_EDITOR doesn't make sense maybe?

    Code (CSharp):
    1. Exception: InvalidOperationException: No interaction with name 'HoldRepeat' (mentioned in 'HoldRepeat(useDefaultSettingsPressPoint=true,pressPoint=0.5,firstDuration=0.25,generalDuration=0.11)') has been registered
    2.  
    3.  
    4. UnityEngine.InputSystem.InputBindingResolver.ResolveInteractions (System.String interactionString) (at <c86ba36cca454ba2b2780784a157ad32>:0)
    5. UnityEngine.InputSystem.InputBindingResolver.AddActionMap (UnityEngine.InputSystem.InputActionMap map) (at <c86ba36cca454ba2b2780784a157ad32>:0)
    6. UnityEngine.InputSystem.InputManager:OnNativeDeviceDiscovered(Int32, String)
    7. UnityEngineInternal.Input.NativeInputSystem:NotifyDeviceDiscovered(Int32, String)
    8.  
     
  30. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Because that one is only needed when in editor mode. For build, the initialization is taken care of with
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
    on the RegisterInteraction method.

    What version of Unity and Input System package are you on specifically?
     
    AlejMC likes this.
  31. AlejMC

    AlejMC

    Joined:
    Oct 15, 2013
    Posts:
    149
    Hello! thanks a lot for the follow-up.

    Unity 2021.1.28f1 (stuck on it of the time being)
    Input System 1.3

    But you are absolutely right, I must have messed up something between merging bits between the other examples and didn't have the static method with the RuntimeLoad attribute that registers. My bad.

    I think I'll end up just using your class as is from scratch, I'm just discovering this new input system (which I find to be amazing to be honest once understanding it more) and can't remember why I didn't leave your proposed interaction class alone, it looks clean, it uses the built-in timeout, etc.
     
    Invertex likes this.
  32. tblanchard001

    tblanchard001

    Joined:
    Aug 8, 2020
    Posts:
    1
    Could you post the link to the doc for this?
     
  33. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    683
    Has there still been no resolution to this issue? And yes, it is an issue. I've read what @hippocoder has said on the matter, and that's not a solution. That's a work around, that the developers at Unity themselves have stated shouldn't be used for anything more than prototyping. I'll have to see where I read it, but directly accessing components of the InputSystem (i.e.
    Vector2 mousePosition = Mouse.current.position;
    shouldn't be done for final production scenarios. And it's understandable why! Here's my own specific use scenario...

    Input Action is a Button Action Type with a Hold Interaction.

    The purpose of the action is to create a selection box on the screen. When the Hold interaction is performed, the code knows to start the selection marque process. And when the Action is canceled the code knows to stop the selection marque process. The problem here is there is no callback to hook into between performed and canceled. Without that callback, the only way to get the updated position of the Mouse, would be to tap into
    Mouse.current.position;
    , which we've already established shouldn't be done at the shipping stage of development; and again, with good reason.

    So, is there no solution to this "continuous" callback problem? And if not, is there one in the pipeline?
     
    kloot likes this.
  34. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    You can pass the context into a Coroutine on .performed and check the value of the context each coroutine update, and exit the routine when the context's
    .phase
    is .Canceled or .Disabled, or the context
    .canceled
    .

    There's also this I made https://forum.unity.com/threads/new...e-hold-interaction.605587/page-4#post-7314433 . It would be nice if they made something like this an official processor though.
     
  35. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    683
    Yep. I’m aware of all the work around methods. I just want to know if Unity has plans for a proper fix. The whole point of the new input system is to not need to write logic dealing with the input itself, but to just hook existing (or new) methods up to the input system either in the form of events or callbacks.

    For now though I’m using the custom hold interaction that you wrote.
     
    kloot, felipemullen and Invertex like this.
  36. toske_

    toske_

    Joined:
    Jul 21, 2018
    Posts:
    9
    Hey ! I found your custom interaction and it worked flawlessly, but recently i updated the unity version to the latest one as I changed pcs and now the interaction events are no longer firing. Would you happen to know what could be the problem? Thanks
     
  37. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Could you specify which version specifically? 2022.1, 2022.2, 2023.1? Hard to know if you mean latest stable, beta or alpha. Thanks.
     
  38. toske_

    toske_

    Joined:
    Jul 21, 2018
    Posts:
    9
    Sorry ! I meant the 2021.3.8f1 LTS.
     
  39. toske_

    toske_

    Joined:
    Jul 21, 2018
    Posts:
    9
    So one thing I noticed is that the context never changes to the performed once the condition of the hold is achieved. It get stuck on waiting. I am not sure, maybe i have the interaction settings wrong ? upload_2022-9-19_21-37-46.png
    upload_2022-9-19_21-37-57.png

    like i mentioned im on 2021.3.8f1 LTS and also Input system 1.4.1/1.4.2. Its really strange that it suddenly broke because of engine update.
     
  40. toske_

    toske_

    Joined:
    Jul 21, 2018
    Posts:
    9
    so basically what happens is that as soon as the duration timer expires the actuation resets and the interaction gets cancelled. meaning it never calls ctx.PerformedAndStayPerformed(). I assume something changed once the duration timer expires in the new Input system update.
     
  41. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Yeah they unfortunately changed some of the base functionality of the system. I'm in the process of re-writing it some but just havn't had the time. Soon
     
    pzolla and AlejMC like this.
  42. mattcoulter7

    mattcoulter7

    Joined:
    Aug 10, 2020
    Posts:
    1
    I came up with my own solution because many other methods I tried on this forum skipped the 'Hold Time' on the Hold interaction. Hope it may be of help to some people.

    Essentially my approach is using Coroutines that start and stop based on the Hold and press interaction.

    1. Paste this code into a new script, and add it as a component to the player input object

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.InputSystem;
    5. using UnityEngine.InputSystem.Interactions;
    6. using System.Linq;
    7.  
    8. public class HeldPlayerInputHelper : MonoBehaviour
    9. {
    10.     /// <summary>
    11.     /// Holds the mappings from action name to callbacks.
    12.     /// </summary>
    13.     public Dictionary<string, System.Action<InputAction.CallbackContext>> performed { get; private set; } = new Dictionary<string, System.Action<InputAction.CallbackContext>>();
    14.  
    15.     /// <summary>
    16.     /// The coroutines which run the callback every frame whilst the key is being held down
    17.     /// </summary>
    18.     private Dictionary<InputAction, Coroutine> actionCoroutines = new Dictionary<InputAction, Coroutine>();
    19.  
    20.     /// <summary>
    21.     /// Reference the all the input actions that are configured with a hold and a press interaction
    22.     /// </summary>
    23.     private List<InputAction> inputActions = new List<InputAction>();
    24.  
    25.     private PlayerInput playerInput;
    26.  
    27.     private void Awake()
    28.     {
    29.         playerInput = FindObjectOfType<PlayerInput>();
    30.         GetHoldInputActions();
    31.         InitialiseActions();
    32.     }
    33.     private void OnEnable()
    34.     {
    35.         BindHoldActions();
    36.     }
    37.  
    38.     private void OnDisable()
    39.     {
    40.         UnbindHoldActions();
    41.     }
    42.  
    43.     /// <summary>
    44.     /// Get all of the (cache) ActionInputs that are configured properly in order for the continuous hold callback to work
    45.     /// </summary>
    46.     private List<InputAction> GetHoldInputActions()
    47.     {
    48.         inputActions = playerInput.actions.Where(ia => ia.interactions.Contains("Hold") && ia.interactions.Contains("Press")).ToList();
    49.         return inputActions;
    50.     }
    51.  
    52.     /// <summary>
    53.     /// Set up a new System.Action callback for each of the actions.
    54.     /// This way we can bind and unbind various functions.
    55.     /// </summary>
    56.     private void InitialiseActions()
    57.     {
    58.         foreach (InputAction ia in inputActions)
    59.         {
    60.             performed[ia.name] = new System.Action<InputAction.CallbackContext>((ctx) => { });
    61.         }
    62.     }
    63.  
    64.     /// <summary>
    65.     /// Bind OnHold to listen to when the Hold Input is triggered
    66.     /// </summary>
    67.     private void BindHoldActions()
    68.     {
    69.         foreach (InputAction ia in inputActions)
    70.         {
    71.             ia.performed += OnHold;
    72.         }
    73.     }
    74.  
    75.     /// <summary>
    76.     /// Bind OnHold from listening to when the Hold Input is triggered
    77.     /// </summary>
    78.     private void UnbindHoldActions()
    79.     {
    80.         foreach (InputAction ia in inputActions)
    81.         {
    82.             ia.performed -= OnHold;
    83.         }
    84.     }
    85.  
    86.     /// <summary>
    87.     /// Handle Starting and Stopping the OnHoldCallback Coroutines for each InputAction.
    88.     /// </summary>
    89.     private void OnHold(InputAction.CallbackContext ctx)
    90.     {
    91.         float value = ctx.ReadValue<float>();
    92.         if (ctx.interaction is HoldInteraction)
    93.         {
    94.             actionCoroutines[ctx.action] = StartCoroutine(OnHoldCallback(ctx));
    95.         }
    96.         else if (ctx.interaction is PressInteraction)
    97.         {
    98.             if (value == 0)
    99.             {
    100.                 Coroutine heldCoroutine = null;
    101.                 actionCoroutines.TryGetValue(ctx.action,out heldCoroutine);
    102.                 if (heldCoroutine != null)
    103.                     StopCoroutine(heldCoroutine);
    104.             }
    105.         }
    106.         //Debug.Log(value);
    107.         //Debug.Log(ctx.interaction);
    108.     }
    109.  
    110.     /// <summary>
    111.     /// Invoke the Hold Callbacks continuously (every frame)
    112.     /// </summary>
    113.     private IEnumerator OnHoldCallback(InputAction.CallbackContext ctx)
    114.     {
    115.         while (true)
    116.         {
    117.             //Debug.Log(ctx.ReadValue<float>());
    118.             performed[ctx.action.name].Invoke(ctx);
    119.             yield return new WaitForEndOfFrame();
    120.         }
    121.     }
    122. }
    123.  
    2. Setup a new action for the input which will be held with interactions as per below
    upload_2022-10-14_20-34-3.png

    3. Listen to the event like below
    Code (CSharp):
    1. private void OnEnable()
    2.     {
    3.         heldPlayerInput = FindObjectOfType<HeldPlayerInput>();
    4.         heldPlayerInput.performed["HorizontalHold"] += DoHorizontalHeldMovement;
    5.     }
    6.  
    7.     private void DoHorizontalHeldMovement(InputAction.CallbackContext ctx)
    8.     {
    9.         float dir = ctx.ReadValue<float>();
    10.         if (Time.time - lastHorizontalMoveTime > horizontalMoveSpeed)
    11.             MoveHorizontal(dir);
    12.     }

    That's it! No more lines than that of the current Input System.


    Note: Since the ctx being passed through to the callback on every frame is the same one as the one that triggered the drag in the first place.
     
    Invertex and Lars-Steenhoff like this.
  43. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    I've updated it to properly handle the Hold time, the recent Input System changes that broke it, and added custom inspector code :)

    https://gist.github.com/Invertex/db99b1b16ca53805ae02697b1a51ea77
     
  44. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    Not necessarily the topic perse but has anyone tried holding a key and dragging another object?

    In my 2d game I want to drag with my virtual hand (like smash bros menu) another object, is that possible?

    Thanks
     
  45. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Don't see a reason why not. You can check the status of multiple inputs at the same time. One button being held doesn't make other buttons not be held anymore.
     
  46. Redphoenix666

    Redphoenix666

    Joined:
    Jul 12, 2016
    Posts:
    8
    Hey - I was testing your script, but it looks like there are some errors with the InputSystem 1.5.1 and Unity 2022.2.11.

    I added the errors below the gist.
     
  47. Deleted User

    Deleted User

    Guest

    I have checked the forks, and it seems like the one I found has fixed my null pointer issues:
    https://gist.github.com/aaronseng/63cf5ea706eb56e39015ebd13cbde5fa

    Though I use Unity 2021.3.4f1 with Input System 1.3.0
     
  48. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Hi I've taken a look and refactored the code some. It should handled a bunch of potential edge cases now.

    Also, .started will now only be called once the input has actually been held for the duration, not simply actuated. Same goes for .cancelled.

    https://gist.github.com/Invertex/db99b1b16ca53805ae02697b1a51ea77
     
    Redphoenix666 and Deleted User like this.
  49. Redphoenix666

    Redphoenix666

    Joined:
    Jul 12, 2016
    Posts:
    8
    Thank you very much!
     
  50. Rouddem

    Rouddem

    Joined:
    Dec 15, 2015
    Posts:
    14
    Hi !

    "Put simply, you should see "started" getting called as soon as a button goes down" : as long as we are looking for a hold interaction, I should make more sense to receive the "started" when the button goes down after x seconds down (when the hold is completed), and not the first time the button is down. Then the "started" becomes the real start of the "hold" feature. What is the reason it has been implemented this way ?
    Also, how can I detect the first frame the button has been hold for x seconds ? For now, I'm forced to use a boolean and on the first "performed", set it to true...

    Regards,
    Nicolas