Why held and pressed are separate
A jump should fire once, on the frame the key goes down, not every frame the key stays held. A move-left, though, should keep running while the key is down.
// Edge-triggered: fire once
if (input.pressedJump()) player.jump();
// Level-triggered: keep doing it
if (input.isDown('ArrowLeft')) player.move(-1);
The two sets give you both behaviors from the same keys. pressedJump reads the one-shot edge set; isDown reads the persistent keys set. Clear the edge set at the end of each frame with consume(), and the next press starts fresh.
Mixing them up is a classic bug: using isDown for a jump makes the player jump every frame; using pressed for movement makes the player stutter.
The full menu → play → pause → game over flow
Put the states and their transitions together:
const sm = new StateMachine();
sm.add('menu', menu);
sm.add('playing', play);
sm.add('pause', pause);
sm.add('gameover', gameover);
sm.setState('menu');
Transitions:
- menu → playing: Enter or jump.
- playing → pause:
por Escape. - playing → gameover: the player died.
- gameover → menu/playing: Enter restarts.
- pause → playing:
presumes.
Each state's onEnter sets up its world (start a new level, freeze the score) and its onExit cleans up. The state machine guarantees both run exactly once per transition. The main loop then does nothing but get time → update → draw, and which behavior runs is entirely decided by sm.currentName.
flowchart TD
A[menu] -->|Enter| B[playing]
B -->|p / Esc| C[pause]
C -->|p| B
B -->|player dead| D[game over]
D -->|Enter| B
D -->|Enter (reset run)| A
Input is the raw signal; the state machine turns that signal into meaning.