A game that stays silent is only half a game, so a sound is an asset like any other (#45)
game-maker / suite (push) Successful in 12m1s
game-maker / suite (push) Successful in 12m1s
Sounds follow the road fonts already paved: the file goes to the server content-addressed under its sha256, the project keeps a named asset that points at it, and nothing is ever re-encoded. The format is read from the file's own signature, not from what the browser claims - the same .m4a arrives as audio/mp4, audio/x-m4a or as nothing at all. Playing is two nodes. "Play sound" takes the asset and may raise or lower its volume; every call is its own voice, so two jumps in a row are two jumps heard. Its loop input is a choice of three - from the asset, on, off - because a checkbox has to spend one of its two positions on "not set" and would lose a case: an asset that loops could then never be played once. Below the graph core stays browser-free: NodeContext hands out an asset id, BehaviorSystem resolves it against the project and folds the asset's own volume and loop into the call, and only viewport/SoundLibrary knows about Web Audio. A run without a sink plays nothing instead of throwing. The CI browser is muted, so the ear is no witness: the debug hook counts started and stopped voices, and step 34 reads that counter.
This commit is contained in:
1 parent
7babf88636
commit
5671a38044
47 files changed
+2066
-42
No files matched your search
@@ -16,6 +16,7 @@ import { gameClock } from '../core/time';
|
||||
import { Input } from '../runtime/systems/Input';
|
||||
import { World } from '../runtime/World';
|
||||
import { ui } from '../ui';
|
||||
import { soundLibrary } from '../viewport/SoundLibrary';
|
||||
import type { GameViewport } from './viewport/GameViewport';
|
||||
import type { GraphEditor } from './graph/GraphEditor';
|
||||
import type { LiveMachineState } from './graph/GraphCanvas';
|
||||
@@ -72,6 +73,9 @@ export class PlaySession {
|
||||
togglePause(): void {
|
||||
if (!this.world) return;
|
||||
this.state.setPaused(!this.state.paused);
|
||||
// A held note is part of the picture a pause freezes: the sounds stop where they are and carry
|
||||
// on mid-note, rather than playing to their end over a still image.
|
||||
soundLibrary.setSuspended(this.state.paused);
|
||||
ui().focus.blur();
|
||||
this.state.setNotice(this.playNotice(), 'quiet');
|
||||
}
|
||||
@@ -109,7 +113,7 @@ export class PlaySession {
|
||||
const world = World.instantiate(runtime, scene.id);
|
||||
this.world = world;
|
||||
this.input.attach();
|
||||
this.behaviors = addGameSystems(world, runtime, this.input);
|
||||
this.behaviors = addGameSystems(world, runtime, this.input, undefined, soundLibrary);
|
||||
this.host.gameView.setWorld(world);
|
||||
this.host.showGame();
|
||||
this.state.setNotice(this.playNotice(), 'quiet');
|
||||
@@ -126,6 +130,9 @@ export class PlaySession {
|
||||
stop(): void {
|
||||
// The run's seconds end with it: the editor goes back to the picture of a freshly loaded scene.
|
||||
gameClock.reset();
|
||||
// And its sounds end with it, looping music included – nothing may outlive the world.
|
||||
soundLibrary.setSuspended(false);
|
||||
soundLibrary.stopAll();
|
||||
const world = this.world;
|
||||
this.world = null;
|
||||
this.behaviors = null;
|
||||
|
||||
Reference in new issue
Block a user