Held button is prematurely released (canceled)

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?

Well, the anticlimactic follow up is that I’m relatively confident (after a week of testing using both a brand new mouse, and a spare, but gently used mouse) this is a hardware issue - an incredibly subtle hardware issue(!). Apparently a number of different models of Logitech mice have issues with buttons wearing down over time causing unexpected behavior. My particular model was the M510, and is exactly 5 years old; I upgraded to the G305, which so far feels much looser than the M510 (I much prefer the physical feel and the onscreen movement of the M510). So far with the G305 there have been no misfires while holding down the right mouse button.

I’d still really like a way of directly reading the input value of a particular action, one with either no or the least amount of processing. Once one starts digging in to all the various ways of evaluating input actions it’s clear that there really needs to be a method that strips out most or all of the processing involved. This would provide those of us debugging input problems a more unambiguous idea as to what the problem is, instead of having to identify or rule out additional processing the Input System is applying to the raw input data.

This is very true. Any of the tiny tac switches in most electronics are really only rated for a few hundred thousand cycles, which adds up quick on mice.

As for directly reading the inputs, the legacy system is pretty stripped-down, but the whole industry is moving towards keeping an interpretive layer in between hardware and game commands, which lets players remap inputs. It may seem like it’s superfluous until you’re porting a Switch Joycon game feature to the Steam Deck’s touchpads, or you get fan mail from a player who has two fingers on their right hand but still wants to play.

I fully migrated to the new Input System when it was introduced, and understand it’s benefits over the old system, no need to sell me on it. What I’m speaking to is the ability to strip away the processing when needed and read the most raw data possible, which in my experience is really useful when it comes time to track down problems like this. I tried using the Input Debugger for this issue, but found that it was a cacophony of data, and not of much use (maybe more time/experience with it would prove otherwise). What I really needed was stripping away the processing so I could immediately drill in to whether the issue was software related (was I using the wrong API call?), or was this something close(r) to the hardware (would have been easier to verify or rule out with raw data).