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
+35
-6
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* The one thing that makes this different from Play: the world is built from the saved model, not
|
||||
* from a copy. What such a behavior moves or writes is a real edit – it is saved and it is not on
|
||||
* the undo stack. The keyboard and the mouse stay with the editor, so input nodes never fire here.
|
||||
* the undo stack.
|
||||
*/
|
||||
import { hasEditorRun, runsInEditor } from '../core/properties/editorRun';
|
||||
import { BehaviorSystem } from '../runtime/systems/BehaviorSystem';
|
||||
@@ -12,17 +12,31 @@ import { Input } from '../runtime/systems/Input';
|
||||
import { type ModelWrite, World } from '../runtime/World';
|
||||
import type { EditorState } from './state';
|
||||
|
||||
export interface EditorRunHooks {
|
||||
/**
|
||||
* Whether the keyboard and the mouse currently belong to the game rather than to the editor.
|
||||
* Without such a gate, typing a name would steer the game at the same time.
|
||||
*/
|
||||
focused: () => boolean;
|
||||
/** The world the run just built, null when it stopped. Lets the game view route clicks into it. */
|
||||
onWorld: (world: World | null) => void;
|
||||
}
|
||||
|
||||
export class EditorRun {
|
||||
private world: World | null = null;
|
||||
private behaviors: BehaviorSystem | null = null;
|
||||
/** Never attached: the editor owns the keyboard, so `Is key down` is always false in here. */
|
||||
private readonly input = new Input();
|
||||
/** Last answer of `hooks.focused`, to let held keys go when the game view loses them. */
|
||||
private hadFocus = false;
|
||||
/** The model changed, so *whether* anything runs is open again, not just the world's contents. */
|
||||
private stale = true;
|
||||
/** Set while our own write travels through the listeners, so it does not count as a change. */
|
||||
private writing = false;
|
||||
|
||||
constructor(private readonly state: EditorState) {}
|
||||
constructor(
|
||||
private readonly state: EditorState,
|
||||
private readonly hooks: EditorRunHooks,
|
||||
) {}
|
||||
|
||||
/** The model changed: a command, a load, or leaving play mode. */
|
||||
invalidate(): void {
|
||||
@@ -42,17 +56,26 @@ export class EditorRun {
|
||||
}
|
||||
const world = this.world;
|
||||
if (!world) return;
|
||||
// A key held while the game view loses the keyboard would stay held for good, and the object
|
||||
// would keep walking with nobody pressing anything.
|
||||
const focus = this.hooks.focused();
|
||||
if (focus !== this.hadFocus) {
|
||||
this.hadFocus = focus;
|
||||
this.input.releaseAll();
|
||||
}
|
||||
world.tick(Math.min(dt, 0.1));
|
||||
this.input.endFrame();
|
||||
if (this.control(world)) return;
|
||||
this.commit(world.syncToModel());
|
||||
}
|
||||
|
||||
/** Facts for the check scripts: object ids of the behaviors currently running without Play. */
|
||||
debugInfo(): { running: boolean; time: number; behaviors: string[] } {
|
||||
debugInfo(): { running: boolean; time: number; behaviors: string[]; keyboard: boolean } {
|
||||
return {
|
||||
running: this.world !== null,
|
||||
time: this.world?.time ?? 0,
|
||||
behaviors: this.behaviors?.runningIds() ?? [],
|
||||
keyboard: this.world !== null && this.hooks.focused(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,13 +94,16 @@ export class EditorRun {
|
||||
this.stop();
|
||||
const project = this.state.project;
|
||||
// Baking a container's content into the scene would be an edit nobody asked for, so it is off
|
||||
// here – which is why a behavior inside a placed container does not run outside play mode.
|
||||
// here: the content runs, but resolved from its template (see `WorldOptions.bakeContainers`).
|
||||
const fresh = World.instantiate(project, scene.id, { bakeContainers: false });
|
||||
const collisions = new CollisionSystem();
|
||||
this.behaviors = new BehaviorSystem(project, this.input, collisions, (e) => runsInEditor(e.props));
|
||||
fresh.addSystem(collisions);
|
||||
fresh.addSystem(this.behaviors);
|
||||
this.world = fresh;
|
||||
this.hadFocus = this.hooks.focused();
|
||||
this.input.attach(this.hooks.focused);
|
||||
this.hooks.onWorld(fresh);
|
||||
// `On start` already ran while the system started, and it may have failed.
|
||||
this.control(fresh);
|
||||
}
|
||||
@@ -106,8 +132,11 @@ export class EditorRun {
|
||||
}
|
||||
|
||||
private stop(): void {
|
||||
this.world?.destroy();
|
||||
if (!this.world) return;
|
||||
this.world.destroy();
|
||||
this.world = null;
|
||||
this.behaviors = null;
|
||||
this.input.detach();
|
||||
this.hooks.onWorld(null);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user