Let On key down repeat while the key stays held
A held key fired once and then nothing, so sliding a Tetris piece sideways or walking a menu meant building a timer next to every key node. Two settings on the node do it now: Repeat after and Repeat interval, both in milliseconds so the usual values stay whole numbers (300, 50) instead of 0.3 and 0.05. A delay of 0 keeps the node exactly as it was, an interval of 0 means every frame. The browser's own auto-repeat is deliberately not used - Input.press has always dropped it - because its delay and rate are an operating-system setting of the player's, not a game's. The repeat is built from the measured hold time instead, per node, so two nodes on one key keep their own clocks. The rule itself is a pure function in keyRepeat.ts and is checked without a world. Both rows stay off the node body while the delay is 0: two lines of "0" on every key node in every graph would be noise, and the screenshot said so. NodeDef gains hiddenParams for that; the inspector still shows them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4b0919a224
commit
3ab8d88ad6
13 files changed
+552
-3
No files matched your search
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* The auto-repeat of `On key down`, kept apart from the runtime so it can be checked without a
|
||||
* world: given how long the key has been held, how often does the node fire this frame?
|
||||
*
|
||||
* The browser's own key auto-repeat is deliberately not used (`Input.press` drops it): its delay
|
||||
* and rate are an operating-system setting, not something a game can tune.
|
||||
*/
|
||||
|
||||
/** Smallest repeat gap, mirroring `On timer`; 0 keeps its own meaning of "every frame". */
|
||||
export const MIN_REPEAT_INTERVAL = 0.01;
|
||||
|
||||
export interface KeyRepeatState {
|
||||
/** Seconds since the key went down. */
|
||||
held: number;
|
||||
/** Repeats so far in this press; the next is due at `after + repeats × interval`. */
|
||||
repeats: number;
|
||||
}
|
||||
|
||||
export interface KeyRepeatStep {
|
||||
/** The key went down *this frame*, as opposed to `down`, which is the whole hold. */
|
||||
pressed: boolean;
|
||||
down: boolean;
|
||||
dt: number;
|
||||
/** Seconds of holding before the first repeat; 0 switches repeating off. */
|
||||
after: number;
|
||||
/** Seconds between repeats; 0 fires every frame. */
|
||||
interval: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* `state` is null while the key is not being tracked. A key that is already held when tracking
|
||||
* starts stays untracked until it is pressed again, so a repeat never runs without its first fire.
|
||||
*/
|
||||
export function stepKeyRepeat(
|
||||
state: KeyRepeatState | null,
|
||||
step: KeyRepeatStep,
|
||||
): { state: KeyRepeatState | null; fires: number } {
|
||||
if (step.pressed) return { state: { held: 0, repeats: 0 }, fires: 1 };
|
||||
if (!step.down) return { state: null, fires: 0 };
|
||||
if (!state) return { state: null, fires: 0 };
|
||||
|
||||
const after = Math.max(0, step.after);
|
||||
const held = state.held + Math.max(0, step.dt);
|
||||
if (after <= 0 || held < after) return { state: { ...state, held }, fires: 0 };
|
||||
|
||||
if (step.interval <= 0) return { state: { ...state, held }, fires: 1 };
|
||||
|
||||
const interval = Math.max(MIN_REPEAT_INTERVAL, step.interval);
|
||||
let repeats = state.repeats;
|
||||
let fires = 0;
|
||||
while (held >= after + repeats * interval) {
|
||||
repeats += 1;
|
||||
fires += 1;
|
||||
}
|
||||
return { state: { held, repeats }, fires };
|
||||
}
|
||||
Reference in new issue
Block a user