Hold a running game, and let the editor run reach into containers
Pause is a sub-state of play, not a third mode: the toolbar button and Ctrl+Enter stop ticking the world, the clock included. A held frame still drops the input edges, so resuming does not fire a pause worth of keys in one go. The editor run missed the case it is most wanted for. hasEditorRun only looked at the stored scene objects, so a behavior inside a placed container never started one; the world now builds from resolveObjects and writes back through objectRef, keeping the move as an instance edit instead of baking the instance out. Keys and clicks go to the run while the Game tab is on screen, where no key means anything to the editor, and are handed back on leaving it - held keys released, so nothing keeps walking unpressed.
This commit is contained in:
1 parent
8f31afac37
commit
9c4148dc1b
19 files changed
+838
-78
No files matched your search
@@ -1,17 +1,22 @@
|
||||
/**
|
||||
* Keyboard state tracker for play mode. Records which keys are held and which went down /
|
||||
* Keyboard state tracker for a running world. Records which keys are held and which went down /
|
||||
* up since the last `endFrame()`, so systems can react to edges as well as to held keys.
|
||||
*/
|
||||
export class Input {
|
||||
private readonly down = new Set<string>();
|
||||
private readonly pressed = new Set<string>();
|
||||
private readonly released = new Set<string>();
|
||||
/** See `attach`: while this answers false the keys belong to someone else. */
|
||||
private accepts: () => boolean = () => true;
|
||||
private readonly onKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.target as HTMLElement | null)?.tagName === 'INPUT') return;
|
||||
if (!this.accepts()) return;
|
||||
this.press(e.key);
|
||||
if (e.key.startsWith('Arrow') || e.key === ' ') e.preventDefault();
|
||||
};
|
||||
private readonly onKeyUp = (e: KeyboardEvent) => this.release(e.key);
|
||||
private readonly onKeyUp = (e: KeyboardEvent) => {
|
||||
if (this.accepts()) this.release(e.key);
|
||||
};
|
||||
/** Losing the window releases every key, so nothing stays "held" behind the user's back. */
|
||||
private readonly onBlur = () => this.releaseAll();
|
||||
|
||||
@@ -32,16 +37,26 @@ export class Input {
|
||||
this.down.clear();
|
||||
}
|
||||
|
||||
attach(): void {
|
||||
/**
|
||||
* Starts listening on the window. `accepts` decides per event whether the key belongs to this
|
||||
* world at all, so a rejected key keeps its default and reaches the editor instead. A gate that
|
||||
* closes while a key is held leaves it held: the caller has to `releaseAll()` on the change.
|
||||
*/
|
||||
attach(accepts: () => boolean = () => true): void {
|
||||
this.accepts = accepts;
|
||||
if (typeof window === 'undefined') return; // headless: tests drive the state machine directly
|
||||
window.addEventListener('keydown', this.onKeyDown);
|
||||
window.addEventListener('keyup', this.onKeyUp);
|
||||
window.addEventListener('blur', this.onBlur);
|
||||
}
|
||||
|
||||
detach(): void {
|
||||
window.removeEventListener('keydown', this.onKeyDown);
|
||||
window.removeEventListener('keyup', this.onKeyUp);
|
||||
window.removeEventListener('blur', this.onBlur);
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('keydown', this.onKeyDown);
|
||||
window.removeEventListener('keyup', this.onKeyUp);
|
||||
window.removeEventListener('blur', this.onBlur);
|
||||
}
|
||||
this.accepts = () => true;
|
||||
this.down.clear();
|
||||
this.pressed.clear();
|
||||
this.released.clear();
|
||||
|
||||
Reference in new issue
Block a user