"Input.Pressed edges drop or double-fire inside OnFixedUpdate"
▸ SYMPTOM
Edge-triggered inputs (Input.Pressed("jump"), Input.Pressed("fire"), gear shifts) behave unreliably: sometimes a keypress is ignored entirely, other times it fires twice in one moment. The problem worsens at low or fluctuating framerates and only affects presses read inside OnFixedUpdate.
▸ CAUSE
Input.Pressed(...) is frame-scoped: the engine sets the "pressed this frame" flag once per rendered frame and clears it at the end of that frame. OnFixedUpdate runs at a fixed rate (50 Hz by default), which means:
- A frame with no fixed tick (framerate > tick rate): the press flag is set and cleared with no
OnFixedUpdateto read it -- input lost. - A frame with multiple fixed ticks (framerate < tick rate): the same press flag is read by every tick in that frame -- input duplicated.
Level-triggered reads (Input.Down("handbrake")) are fine in OnFixedUpdate because they reflect the held state, not a one-frame edge.
▸ FIX
Latch the edge in OnUpdate (per-frame), then consume it in the fixed-step logic:
private bool _jumpLatched;
protected override void OnUpdate()
{
// Latch the edge per-frame -- never lost, never duplicated
if (Input.Pressed("jump"))
_jumpLatched = true;
}
protected override void OnFixedUpdate()
{
if (_jumpLatched)
{
_jumpLatched = false; // consume it
DoJump();
}
// Level-triggered reads are fine here
if (Input.Down("forward"))
ApplyThrottle();
}If the same action can also come from a scripted source (e.g. a test pilot or AI input struct that holds a bit across ticks), edge-detect the combined request against a _prev bool so both sources shift exactly once:
bool wantShift = _shiftLatched || inputOverride.ShiftUp;
if (wantShift && !_prevShift)
DoShiftUp();
_prevShift = wantShift;
_shiftLatched = false;▸ WHY IT WORKS
The latch bridges the frame/tick timing mismatch. OnUpdate runs exactly once per frame (matching Input.Pressed's lifetime), so the latch captures the edge reliably. The fixed tick then reads and clears the latch on its own schedule -- a frame with no tick keeps the latch until the next one (no loss), and the clear-after-read ensures a frame with multiple ticks only fires once (no duplication).