Search Unity

Actions with modifiers?

Discussion in 'Input System' started by jwvanderbeck, Oct 22, 2019.

  1. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Let's say I want to make the mouse delta be used to pan a camera, but only when the right mouse button is pressed down.

    Or more abstractly, I want a Vector2 input but only when a button is pressed.

    I can't see a way to define such in this system. The best I've worked out is to have multiple actions, one for the Vector2 binding and one for the Button binding, then in the Vector2 action I have to poll to see if the other binding is active or not. And this feels, dirty, and possibly hard to make flexible for different inputs or user mappings.

    Is there a better way I am missing?
     
  2. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
  3. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    So this isn't something available "out of the box"? Seems like something pretty much every game would need. Its a very common situation.

    I will look at the docs. Thanks.
     
  4. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    This was a missed opportunity that will get rectified after 1.0.

    Basically, ButtonWithOneModifier is what you're looking for *except* it only works with buttons. For no good reason. It's easy to generalize the composite to work with a control of any type (i.e. "modifier" remains a button but the other control can be of any type). I simply had a bit of tunnel vision when adding ButtonWithOneModifier.

    It's on the list to generalize this after 1.0 (ticket ISX-279).
     
  5. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Hahah fair enough with the tunnel vision. We've all been there.

    Definitely seems like a common use case though, especially in Keyboard & Mouse schemes. Right click to pan a camera (Vector2 with button modifier), middle click and drag to orbit (Vectro2 and modifier). Or think of the scene camera in Unity or Maya where where you hold Alt (modifier) then a button on the mouse (second modifier) and then move the mouse.

    Now this gets more complicated if that modifier is also used in other actions. Right Click + Drag to pan the camera, but just Right click and release to Cancel an action.

    Frankly this is why I would like the Input System to handle the abstraction for me and just fire off the appropriate event after it has figured out the player's intent.
     
  6. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Yup, agree. Been popping up again and again.

    This one will get addressed after 1.0. ATM we're indeed lacking any kind of support for resolving ambiguities between actions. This goes for the situation you mentioned as well as for even more common setups where action input may be consumed by an in-game UI and should not reach the gameplay action layer.

    After 1.0, *something* will have to fill this hole. Not sure yet what form this will take but it's obvious that without it, the action system gives rise to some rather hard to deal with scenarios.
     
  7. LDiCesare

    LDiCesare

    Joined:
    Apr 20, 2018
    Posts:
    52
    Is this issue still not fixed?
    I'm not findign a way to do a "shift-mouse click" input action directly (have to do separate shift and mouse click actions).
     
  8. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    ButtonWithOneModifier can be used for shift-clicks.
     
  9. fstorehaug

    fstorehaug

    Joined:
    Apr 3, 2017
    Posts:
    10
    Hey, this is a custom binding that lets you detect movement as a vector2 if you bind it to a mouses delta value. its a hack but it works for now. just put this in a script and the binding should appear. hope this is helpful.

    Code (CSharp):
    1. using UnityEngine.InputSystem.Layouts;
    2. using UnityEngine.InputSystem.Utilities;
    3.  
    4. #if UNITY_EDITOR
    5. using UnityEditor;
    6. using UnityEngine.InputSystem.Editor;
    7. #endif
    8.  
    9. namespace UnityEngine.InputSystem.Composites
    10. {
    11.  
    12. #if UNITY_EDITOR
    13.     [InitializeOnLoad] // Automatically register in editor.
    14. #endif
    15.  
    16.     [DisplayStringFormat("{MouseButton}+{MouseVector}")]
    17.     public class ClickAndDragBinding : InputBindingComposite<Vector2>
    18.     {
    19.         [InputControl(layout = "Button")]
    20.         public int MouseButton;
    21.  
    22.         [InputControl(layout = "Vector2")]
    23.         public int MouseVector;
    24.  
    25.         public override Vector2 ReadValue(ref InputBindingCompositeContext context)
    26.         {
    27.             if (context.ReadValueAsButton(MouseButton))
    28.                 return context.ReadValue<Vector2,Vector2MagnitudeComparer>(MouseVector); //hack
    29.          
    30.             return default;
    31.         }
    32.  
    33.         public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
    34.         {
    35.             return ReadValue(ref context).magnitude;
    36.         }
    37.  
    38.         static ClickAndDragBinding()
    39.         {
    40.             InputSystem.RegisterBindingComposite<ClickAndDragBinding>();
    41.         }
    42.  
    43.         [RuntimeInitializeOnLoadMethod]
    44.         static void Init() { } // Trigger static constructor.
    45.     }
    46. }
    47.  
     
  10. Ishkur

    Ishkur

    Joined:
    Nov 18, 2011
    Posts:
    26
    It is, thanks a lot!
     
  11. BihtSift

    BihtSift

    Joined:
    Aug 25, 2017
    Posts:
    13
    I came here setting up Add button with one modifier (Middle mouse and mouse delta) and wondered why it wasn't working, and found a solution until support is added. Thank you so much!
     
  12. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    Is this available yet?
     
    quint_unity likes this.
  13. quint_unity

    quint_unity

    Joined:
    Oct 11, 2020
    Posts:
    13
    Is this functionality supported yet?
     
  14. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    1.1-preview.2 (released last week) has a new OneModifierComposite and a new TwoModifiersComposite that now allow gating an arbitrary control type by a button.
     
    bjornsyse likes this.
  15. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    Just out of curiosity, why not make any composite take an array of modifiers? Is there a technical limitation there?
     
    quint_unity likes this.
  16. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    Another thing to consider for ModifierComposites is the option to make the modifiers negatable. That would make it way simpler make bindings that share a control mutually exclusive (i.e. two composite modifier bindings where one is gated with the press of a modifier and one gated by not pressing the modifier). This is something I personally use in my projects extensively.
     
    LeandroExHuMeD and rboerdijk like this.
  17. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    Actually disregard my last post as I have just realized that you can simply use InputProcessors to negate modifiers.
     
  18. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    I was playing around with the Input System package, and I just figured out a custom way to do this using comparers.
    Code (CSharp):
    1. using UnityEngine;
    2. using static UnityEngine.InputSystem.InputSystem;
    3.  
    4. #if UNITY_EDITOR
    5. using UnityEditor;
    6.  
    7. [InitializeOnLoad]
    8. #endif
    9. public static class CustomInputRegistration
    10. {
    11. #if UNITY_EDITOR
    12.     static CustomInputRegistration()
    13.     {
    14.         Register();
    15.     }
    16. #endif
    17.  
    18.     [RuntimeInitializeOnLoadMethod]
    19.     private static void Register()
    20.     {
    21.         RegisterBindingComposite<AxisWithModifiersComposite>();
    22.     }
    23. }
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3.  
    4. public struct ClosestFloatToZeroComparer : IComparer<float>
    5. {
    6.     public int Compare(float x, float y)
    7.     {
    8.         float lenx = Math.Abs(x);
    9.         float leny = Math.Abs(y);
    10.         if (lenx < leny)
    11.             return 1;
    12.         if (lenx > leny)
    13.             return -1;
    14.         return 0;
    15.     }
    16. }
    Code (CSharp):
    1. using UnityEngine.InputSystem;
    2. using UnityEngine.InputSystem.Layouts;
    3.  
    4. public class AxisWithModifiersComposite : InputBindingComposite<float>
    5. {
    6.     [InputControl(layout = "Axis")]
    7.     public int axis;
    8.  
    9.     [InputControl(layout = "Button")]
    10.     public int modifier;
    11.  
    12.     public override float ReadValue(ref InputBindingCompositeContext context)
    13.     {
    14.         return context.ReadValue<float, ClosestFloatToZeroComparer>(modifier) != 0f ? context.ReadValue<float>(axis) : default;
    15.     }
    16. }
    If you need to add more modifiers, just right click on the existing modifier and select duplicate.
     
  19. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    I guess I found a bug?

    Code (CSharp):
    1. input.UICustom.MasterVolume.performed += d => Debug.Log( d.ReadValue<float>() );
    throws:
    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. UnityEngine.InputSystem.InputActionState.ReadValue[TValue] (System.Int32 bindingIndex, System.Int32 controlIndex, System.Boolean ignoreComposites) (at Library/PackageCache/com.unity.inputsystem@1.1.0-preview.2/InputSystem/Actions/InputActionState.cs:2288)
    3. UnityEngine.InputSystem.InputAction+CallbackContext.ReadValue[TValue] () (at Library/PackageCache/com.unity.inputsystem@1.1.0-preview.2/InputSystem/Actions/InputAction.cs:1790)
    ReadValueAsObject works correctly:
    Code (CSharp):
    1. input.UICustom.MasterVolume.performed += d => Debug.Log( d.ReadValueAsObject() );
    and
    Code (CSharp):
    1. input.UICustom.MasterVolume.performed += d => Debug.Log( d.ReadValueAsObject().GetType() );
    shows that it is float.
     
    Last edited: Oct 30, 2020
    rboerdijk likes this.
  20. quint_unity

    quint_unity

    Joined:
    Oct 11, 2020
    Posts:
    13
    This bug was also in the 1.0.0 version. Ended up utilizing fstorehaug's custom binding he posted and it functions fine.
     
  21. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    I ended up using unsafe ReadValue(void*, int)

    Code (CSharp):
    1.     public static float AsFloat( this InputAction.CallbackContext d )
    2.     {
    3.         float value;
    4.         void* ptr = &value;
    5.         d.ReadValue( ptr, sizeof( float ) );
    6.         return value;
    7.     }
     
  22. rboerdijk

    rboerdijk

    Joined:
    Aug 4, 2018
    Posts:
    96
    Tried doing that, but this doesn't seem to work.. also tried with a bool-variation returning !value....

    If you break in the debugger, you can see the control having the key "not-pressed", and going down in the callstack you can see it eventually accesses that. The processor can manipulate the value and see values in 'control', but it's probably a bad idea to try and manipulate anythign besides returning a value different from the input 'value'.

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine.Scripting;
    3.  
    4. namespace UnityEngine.InputSystem.Processors
    5. {
    6. #if UNITY_EDITOR
    7.     [InitializeOnLoad]
    8. #endif
    9.     public class TestProcessor : InputProcessor<float>
    10.     {
    11. #if UNITY_EDITOR
    12.         static TestProcessor ()
    13.         {
    14.             Initialize();
    15.         }
    16. #endif
    17.  
    18.         [RuntimeInitializeOnLoadMethod]
    19.         static void Initialize()
    20.         {
    21.             InputSystem.RegisterProcessor<TestProcessor >();
    22.         }
    23.  
    24.         public TestProcessor ()
    25.         {
    26.         }
    27.  
    28.         public override float Process(float value, InputControl control)
    29.         {
    30.             return value < 0.01f ? 1.0f : 0.0f;
    31.         }
    32.  
    33.         public override string ToString()
    34.         {
    35.             return "TestProcessor";
    36.         }
    37.     }
    38. }
    Any hints on how you got it to work
     
    Last edited: Nov 9, 2020
  23. bjornsyse

    bjornsyse

    Joined:
    Mar 28, 2017
    Posts:
    102
    I can't really figure out how to use this, is there an example somewhere?
     
  24. rboerdijk

    rboerdijk

    Joined:
    Aug 4, 2018
    Posts:
    96
    You can find it here, just add it and set the modifier to whatever control you want as a 'gate' for the binding.
    In my case, you need to hold right mousebutton, before you get mouse-delta-events.
    upload_2020-11-10_23-3-21.png

    It gets more interesting if you want "the opposite" gate, so getting mouse-delta-events when explicitly not pressing right mousebutton (as opposed to "not caring" whether or not right mousebutton is pressed). If you figure that one out via the editor/ui, let me know ;)

    (only solution I can think of is a separate RMB-event, set a boolean state and always handle mouse-delta events depending on that state... which puts all "complexity" in the code again, where I'd rather have an "invert-modifier" boolean in the editor ui... or an invert-processor... or something like that ).
     
    bjornsyse likes this.
  25. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    @rboerdijk so the built-in modifier composites use ReadValueAsButton on the modifier, which apparently ignores any processors added to the modifier. If you copy/paste my code in the spoiler above into your project and use AxisWithModifiers instead of OneModifier, it should work fine.
     
  26. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    My posted code also has the added benefit of supporting multiple modifiers (just right-click the modifier entry, hit duplicate, and assign a different binding)
     
  27. rboerdijk

    rboerdijk

    Joined:
    Aug 4, 2018
    Posts:
    96
    @TRS6123 Thanks for the reply, I tried putting your spoiler-code in 3 separate files in my project, then configured it like this (the UnitMoveUpDown is identical, except it doesn't have the invert set and it also reacts to the scrollwheel):
    upload_2020-11-13_0-52-12.png

    The UnitMoveHorizontal should be the default (shift not pressed) and when pressing shift the movement should change to Up/Down. This doesn't seem to work and still executes both code-paths. I did understand that the invert should now work correctly, right? (or does something else need to be done with your solution)
     
  28. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    The built-in invert processor just multiplies the value by -1.
    Here's the custom processor I use to negate button presses:
    Code (CSharp):
    1. using UnityEngine.InputSystem;
    2. using UnityEngine.Scripting;
    3.  
    4. [Preserve]
    5. public class NegateButtonProcessor : InputProcessor<float>
    6. {
    7.     public override float Process(float value, InputControl control)
    8.     {
    9.         return 1f - value;
    10.     }
    11. }
    12.  
     
  29. rboerdijk

    rboerdijk

    Joined:
    Aug 4, 2018
    Posts:
    96
    Tried it, with adding the register-call in the CustomInputRegistration, adding it as a processor (instead of Invert in my screenshot above), but NegateButtonProcess.Process is never called (at least it doesn't hit a breakpoint), resulting in both codepaths to be executed. Tried with my code above swapping out Process with your function, same result.

    Changing back to "One Modifier" it does get called (weird, not sure why that would be different to your AxisWithModifier), but it still doesn't work. Going down the callstack it ends up here, while value is correct (changed from 0/nonpressed-shift to invert to 1/pressedshift) it seems to ignore it, and instead takes button.isPressed (which still is false, because shift isn't really pressed).

    upload_2020-11-13_20-6-54.png

    I guess it can only possibly work for an "InputControl" and not for a "ButtonControl"...
    Any more ideas anyone?

    Update: For now went back to the oldfashioned way of doing it, just have separate events, set a boolean on the modifier (now also a binding) and check that boolean on the binding. Not as elegant as I hoped it would be with the new system, but oh well.
     
    Last edited: Nov 14, 2020
  30. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    519
    Using this :
    upload_2020-11-21_12-19-39.png

    With a coroutin pulling the values of MoveCamera with ReadValue<Vector2> But it fails with the One Modifier. I believe the Delta binding is not correctly retrieved with ReadValue, will try a workaround and post it here, but I believe it should get fixed.
     
  31. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    519
    I believe it's the same issue as you posted here, my work around :

    Code (CSharp):
    1.         /// <summary>
    2.         /// A read value that also read binding composited with modifiers.
    3.         /// </summary>
    4.         /// <typeparam name="T"></typeparam>
    5.         /// <returns></returns>
    6.         public static T ReadValueBetter<T>(this InputAction action)
    7.         {
    8.             var value = action.ReadValueAsObject();
    9.             if (value == null) return default;
    10.             if (value.GetType() == typeof(T)) return (T) value;
    11.             IndusLogger.Error(nameof(ReadValueBetter), $"Can't cast <{value.GetType().Name}> to <{typeof(T).Name}>");
    12.             return default;
    13.         }
     
  32. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    226
    Yeah I was experimenting the same issue...
     
  33. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    @rboerdijk are the action types of your actions using modifiers set to pass through? If not, then I'm pretty stumped with why it's not working on your end.
     
  34. andrescuevas

    andrescuevas

    Joined:
    Apr 23, 2019
    Posts:
    24
    hello! whats the best way to do this in 2021? is there an official solution?
     
    eumelzocker and robomarti like this.
  35. Stevens-R-Miller

    Stevens-R-Miller

    Joined:
    Oct 20, 2017
    Posts:
    677
    The click-to-drag solution I've been using is to use code and "go behind" the Unity Events so I can add handlers to actions on a per-phase basis. I create a Click action bound to a button, and a Drag action bound to a mouse delta. Then I add an OnClickStarted method to the click.started event, and an OnClickCanceled method to the click.canceled event.

    In the OnClickStarted method, I add an OnDragPerformed method to the drag.performed event, and in the OnClickCanceled method, I remove the OnDragPerformed method from the drag.performed event.

    To get to the events, you need to do some drill-down in your Start method. I just posted the start of this approach in another thread. Would love to know what people think.
     
  36. VR_Junkie

    VR_Junkie

    Joined:
    Nov 26, 2016
    Posts:
    77
    Did this get fixed yet? I cant figure out how to set this up
     
  37. wanfei

    wanfei

    Joined:
    Dec 22, 2018
    Posts:
    19
    @Rene-Damm how to use InputSystem.QueueStateEvent to trigger my InputAction.
     
  38. Wawwaa

    Wawwaa

    Joined:
    Sep 30, 2017
    Posts:
    165
    whenever I use a button with 2 modifiers, unity throws an exception when controller changes. the error indicates modifier 2 (and sometimes modifier 1) could not be found, but asset seems to be ok. also it does not execute the function if the button key is used alone in other actions, firing that action instead of the action with 2 modifier. using unity 2021.1.7f1. The input asset file was coppied from a project on unity 2020.2.3. it was working fine there. and this new key is completely produced in 2021.1.7. any ideas whats happening?
     
  39. dmytro_at_unity

    dmytro_at_unity

    Unity Technologies

    Joined:
    Feb 12, 2021
    Posts:
    212
    SinDeSiecle, would you mind to creating a bug report (help->report a bug) for that? thanks!
     
  40. Maselino

    Maselino

    Joined:
    Aug 12, 2014
    Posts:
    1
    I am trying to install the 1.1 preview package of the input system, but fail to find it. The package manager will only let me install 1.0.2, and doesn't even show any preview release (even though I switched it on in the project settings). Any ideas?
    Edit: Running 2021.1.7f1, Documentary says state of 1.1 is "compatible"
    Edit 2: Downloaded the git repo and imported the package from there. Using the new binding didn't work though, so I went back and just used @fstorehaug suggestion instead.
     
    Last edited: Aug 25, 2021
  41. eumelzocker

    eumelzocker

    Joined:
    Dec 2, 2021
    Posts:
    2
    2022 now! Did I miss the official solution?

    I'm just starting with Unity but I've been a professional coder for more than 30 years. That there is no simple solution for such a basic problem really makes me wonder...
     
    Last edited: Jan 8, 2022
  42. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    226
    Yeah well, welcome aboard. Unity engine has some really good things but then a LOT of details that are surprisingly complex, or require a 3rd party asset or are still work in progress...
     
  43. Wye43

    Wye43

    Joined:
    Dec 22, 2021
    Posts:
    4
    I needed MouseRightButton to set destination and Control+MouseRightButton to add a destination.
    They both got called when control was pressed - so I went ahead with some ugliness like this make it work ok:

    Code (CSharp):
    1.  
    2.    private bool isAddDestination = false;
    3.  
    4.     public void OnAddDestination(InputAction.CallbackContext context)
    5.     {
    6.         switch(context.phase)
    7.         {
    8.             case InputActionPhase.Started:
    9.                 isAddDestination = true;
    10.                 break;
    11.             case InputActionPhase.Performed:
    12.                 Debug.Log("OnAddDestination: Do work");
    13.                 break;
    14.             case InputActionPhase.Canceled:
    15.                 isAddDestination = false;
    16.                 break;
    17.         }
    18.  
    19.     }
    20.  
    21.     public void OnSetDestination(InputAction.CallbackContext context)
    22.     {
    23.         if (isAddDestination)
    24.         {
    25.             return;
    26.         }
    27.         if (context.phase == InputActionPhase.Performed)
    28.         {
    29.             Debug.Log("OnSetDestination: Do work");
    30.         }
    31.     }
    I share eumelzocker's feelings of disappointment with Unity regarding this debacle. 30 years ago it was one line of code to detect if control was pressed or not in so many languages. It still is in 2022 in other languages/frameworks.
    Unity makes this basic scenario incredibly hard, I question the design of the input system.
     
  44. Wye43

    Wye43

    Joined:
    Dec 22, 2021
    Posts:
    4
    The only reason I migrated to the InputSystem is because I needed a regular action and a 2nd action with a modifier. Which doesn't work by default with Input System.
    Who had the ridiculous idea to call both actions with modifiers and without modifiers when modifiers are active?
    Why do you think we set up modifiers? to get them ignored?

    Sorry about the rant but this was several days and nights of painful investigations and debugging.
    The problem with having to restart the computer from time to time to make InputSystem work is a walking disaster too, is anyone fixing that horrific zombie with the InputSystem?
     
  45. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    226
    Try adding a negated ctrl modifier to the other action. It's a bit (more than a bit sometimes) convoluted but most use cases should work.

    Btw, u can still read the state of buttons and keys directly w/o actions.

    Mouse.current.leftButton....
    Keyboard.current.ctrlKey.isPressed..
     
  46. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    Still doesn't work with latest release. All processors placed on modifiers are ignored.
     
  47. Wye43

    Wye43

    Joined:
    Dec 22, 2021
    Posts:
    4
    Thanks for the tips TieSKey, I'll keep that in mind.
    Keyboard.current.ctrlKey.isPressed works for 2 of the 3 times the handler is called.
    For the 3rd one (the one with context.phase == InputActionPhase.Canceled) Keyboard.current.ctrlKey.isPressed returns false even if CTRL is still being pressed.
     
    Last edited: Jan 13, 2022
  48. Wye43

    Wye43

    Joined:
    Dec 22, 2021
    Posts:
    4
    I'm not 100% sure what you mean by "negated", I assume you mean attaching an invert processor.

    I tried putting an invert processor on the action, on the composite binding, on the modifier part of the composite, on the binding part of the composite, setting action type to button, Value, Control Type to Axis and composite type Button with one modifier or 1D axis. And all the combinations between them. They pretty much all resulted in the handler being call regardless of the modifier, and in one of them never being called.
     
  49. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246