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.
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).
// In case there's a Hold first and a Press second:
lightToggleAction.performed +=
ctx =>
{
if (ctx.interaction is HoldInteraction)
ToggleLight2();
else // Could check for PressInteraction but easier to just assume it's a press.
ToggleLight1();
};
// In case there's a Tap first and a SlowTap second:
// (Probably want to set duration on both Tap and SlowTap to be equal so that they
// butt up against each other)
lightToggleAction.performed +=
ctx =>
{
if (ctx.interaction is SlowTapInteraction)
ToggleLight2();
else // Could check for TapInteraction but easier to just assume it's a press.
ToggleLight1();
};
// In case there's just a Hold, it's more complicated.
// NOTE: This isn't a good setup. An action may get cancelled for reasons other than input
// and it's much trickier to robustly differentiate this in the callback than to just put
// another interaction on there that represents the "shorter-than-hold" input on its own.
lightToggleAction.performed +=
ctx =>
{
// We know we will only get here on a successful hold.
ToggleLight2();
};
lightToggleAction.canceled +=
ctx =>
{
if (ctx.interaction is HoldInteraction && ctx.ReadValue<float> > 0)
ToggleLight1();
};
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.
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 (I guess its multiple times, because there is a touch of a flicker visible, like on-off-on)
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.
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.
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.
if I use âPress onlyâ then only the Hold interaction is executed
if I use âRelease onlyâ then the Press interaction is only executed every second press (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)
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)
<< 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.
if (myButton && _theTimer > 0.5f) DoTheThing();
if (myButton) MyHoldTimer();
else _theTimer = 0;
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).
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:
âRelease Onlyâ was what did the trick for me. I am driving the interactions with the following code:
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;
...
public void OnEscape(InputAction.CallbackContext context)
{
if (context.performed)
{
Debug.Log(context.interaction);
if (context.interaction is PressInteraction) CloseTopWindow();
else if (context.interaction is HoldInteraction) CloseAllWindows();
// This exception should never get thrown, but in case it does then at least we know where it came from
else throw new System.Exception("OnEscape received unrecognized button interaction '" + context.interaction + "'");
}
}
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.
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.
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.