I have button press and hold behavior that was working up until recently. The behavior was simply press the button and hold it down to charge up an action. On release the action would activate. The user can hold the button down indefinitely, so the button does not have a predefined “hold time”. The button in question is just a mouse button (or gamepad shoulder button), so the input values are always either 0 or 1.
This behavior has been working flawlessly since switching over to the Input System when it was initially released (~1.0.0-preview.X).
Unfortunately, beginning a couple months ago I started observing sporadic and random misfires where the held button will be released (while it is being held). This results in the button’s action being activated prematurely. I think this started around the time of Input System 1.4.0.
To make this a little more concrete, image that holding the button down makes the player jump higher. Now imagine playing the game, everything works fine 8 out of 10 times, perfect jumps corresponding to how long the button has been held down, but then randomly when holding the button down the character jumps a small amount.
I started doing some serious fix/debugging on the issue a couple days ago, and unfortunately have not found a permutation of context callback call, nor polling that fixes the problem. Here are a number of different approaches to detecting the held button using a context callback (I’ve tried other methods, just including these since they’re the most recently tested and straightforward):
pressed = context.ReadValue<float>() > 0);
pressed = context.control.IsPressed(buttonPressPoint: 1f));
pressed = context.control.IsActuated(threshold: 0f);
pressed = context.control.EvaluateMagnitude() > 0);
pressed = context.action.WasPressedThisFrame());
pressed = (context.action.WasPressedThisFrame() || context.action.WasPerformedThisFrame()));
I’ve tried different buttonPressPoint values for IsPressed (ranging from 0 to 1) and the same for IsActuated, but haven’t found a value that fixes the problem.
I’ve also changed the values of InputSystem.settings.buttonReleaseThreshold, InputSystem.settings.defaultButtonPressPoint, and InputSystem.settings.defaultHoldTime; none have fixed the issue.
I’ve tried all three Action types: Button, Value and Pass Through; none of them make any difference in effecting a positive change. I’ve tried both the Press and the Hold Interactions types, and again have not found any setting that fixes the problem.
I’ve even tried just polling the button during Update using button.ReadValue, but still something is getting in the way and resetting the value of the held down button sporadically.
It seems to me that some internal processor in the Input System is resetting the button’s phase to either Cancelled or Waiting while it is still being held down resulting in the value of the action to be 0.
You can see in the log below that on the third line the phase goes to Canceled, and context.action.WasReleasedThisFrame has a value of True; this occurs while the player is still holding the button down. This log was captured using the Button ActionType, but the behavior is the same when the ActionType is Value or PassThrough (with PassThrough the phase value is always Performed, as expected):
InputSystem.settings: (UnityEngine.InputSystem.InputSettings) settings defined as: {“m_SupportedDevices”:[ ],“m_UpdateMode”:1,“m_MaxEventBytesPerUpdate”:5242880,“m_MaxQueuedEventsPerUpdate”:1000,“m_CompensateForScreenOrientation”:true,“m_BackgroundBehavior”:0,“m_EditorInputBehaviorInPlayMode”:0,“m_DefaultDeadzoneMin”:0.125,“m_DefaultDeadzoneMax”:0.925000011920929,“m_DefaultButtonPressPoint”:0.5,“m_ButtonReleaseThreshold”:1,“m_DefaultTapTime”:0.20000000298023225,“m_DefaultSlowTapTime”:0.5,“m_DefaultHoldTime”:0.4000000059604645,“m_TapRadius”:5.0,“m_MultiTapDelayTime”:0.75,“m_DisableRedundantEventsMerging”:false,“m_iOSSettings”:{“m_MotionUsage”:{“m_Enabled”:false,“m_Description”:“”}}}
Context.phase: Started; context: { action=PlayerKBM/SecondaryAttack[/Mouse/rightButton] phase=Started time=258.112396800003 control=Button:/Mouse/rightButton value=1 interaction= }; context.control.IsActuated: True; context.action.IsPressed: True, context.action.WasPressedThisFrame: True, context.action.WasReleasedThisFrame: False, context.ReadValue: 1, context.control.IsPressed(buttonPressPoint: 1f): True, context.control.EvaluateMagnitude(): 1, context.action.WasPerformedThisFrame(): False
Context.phase: Performed; context: { action=PlayerKBM/SecondaryAttack[/Mouse/rightButton] phase=Performed time=258.112396800003 control=Button:/Mouse/rightButton value=1 interaction= }; context.control.IsActuated: True; context.action.IsPressed: True, context.action.WasPressedThisFrame: True, context.action.WasReleasedThisFrame: False, context.ReadValue: 1, context.control.IsPressed(buttonPressPoint: 1f): True, context.control.EvaluateMagnitude(): 1, context.action.WasPerformedThisFrame(): True
Context.phase: Canceled; context: { action=PlayerKBM/SecondaryAttack[/Mouse/rightButton] phase=Canceled time=258.161336799996 control=Button:/Mouse/rightButton value= interaction= }; context.control.IsActuated: False; context.action.IsPressed: False, context.action.WasPressedThisFrame: False, context.action.WasReleasedThisFrame: True, context.ReadValue: 0, context.control.IsPressed(buttonPressPoint: 1f): False, context.control.EvaluateMagnitude(): 0, context.action.WasPerformedThisFrame(): False
Context.phase: Started; context: { action=PlayerKBM/SecondaryAttack[/Mouse/rightButton] phase=Started time=258.209436399993 control=Button:/Mouse/rightButton value=1 interaction= }; context.control.IsActuated: True; context.action.IsPressed: True, context.action.WasPressedThisFrame: True, context.action.WasReleasedThisFrame: False, context.ReadValue: 1, context.control.IsPressed(buttonPressPoint: 1f): True, context.control.EvaluateMagnitude(): 1, context.action.WasPerformedThisFrame(): False
Ideally, I’d like to just read the current value of the input action without any processing at all (especially since this is a button, and the value is always either 0 or 1). It seems like context.ReadValue should do that, but looking at InputAction.ReadValue reveals that it evaluates InputExtensions.IsInProgress (which returns true if the phase is either Started or Performed). So, again, it seems like something is inadvertently setting the phase to something other than Started or Performed (probably Canceled) while the button is being held down, and my guess is that the most previous button state is somehow what is affecting the current button state. Maybe the previous button state is being interpreted as released, and then the Input System sets the currently held down button to a phase of Canceled (ie the Input System reads the previous button state as the current state).
I filed a bug report on this since previously it was working flawlessly (no changes on my end prior to 1.4.0, and/or observing the misfires), but it’s entirely possible that I have a setting wrong now due to changes in the Input System.
@Rene-Damm , @Schubkraft , do either of you have a suggestion for the most robust way to get the value of a button over time?