From 01736c3bb03ba815b6836cd5527ae01d7f0e3893 Mon Sep 17 00:00:00 2001 From: AlexZ005 Date: Thu, 1 Oct 2026 00:45:10 +0300 Subject: [PATCH] [docs] 1.18 game shell: the pause menu, levels, per-game settings (K3) - module-sdk.md: api.game.levels / addSetting / setting / setSetting / onSettingsChange / setHelp / onRestart / openMenu / closeMenu / menuOpen, the core rows core obeys, and how a module with its own WebAudio graph obeys the sfx row. - build-a-game.md: the shared game menu every game gets (Esc / corner Menu / VR left X). Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/build-a-game.md | 13 ++++++-- docs/module-sdk.md | 78 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/docs/build-a-game.md b/docs/build-a-game.md index ee7aee9..ad85ab4 100644 --- a/docs/build-a-game.md +++ b/docs/build-a-game.md @@ -179,8 +179,17 @@ no jump. !!! info "Keyboard and gamepad" Any menu is navigable without a mouse: arrows or D-pad move the highlight, Enter or - **A** activates, and a highlighted slider or dropdown takes left/right. **Esc** always - leaves play mode entirely — it is the guaranteed way out. + **A** activates, and a highlighted slider or dropdown takes left/right. + +!!! tip "You also get the app's own game menu for free (1.18)" + In any game, **Esc** (or the **Menu** button in the corner) opens the shared game menu: + **Resume · Restart · Levels · Settings · How to play · Main menu**, plus *Back to editor* + on the desktop. In VR it opens with **X** on the left controller or the **Menu** button + on the game board and the wrist card. Its **Settings** are per game and per device — + music and sound effects on/off with their volumes, controller vibration, **Show FPS**, + VR turning (snap / smooth / off and the snap angle), a comfort vignette and a quality + preset (Auto / Low / Medium / High). Your own Pause screen keeps working beside it; a + scene that is not a game still leaves play on Esc. --- diff --git a/docs/module-sdk.md b/docs/module-sdk.md index 10fe6b9..257aec6 100644 --- a/docs/module-sdk.md +++ b/docs/module-sdk.md @@ -316,6 +316,84 @@ A board, puzzle or instrument game can ask for the **free cursor** in Play (no p by publishing `userData.play.cursor = 'free'` on its scene-root group. `api.pointerRay()` is then the cursor's ray; in a pointer-locked game it is the crosshair ray. +### The game shell: pause menu, levels, per-game settings (1.18) + +Every game gets ONE pause menu from core — **Resume · Restart · Levels · Settings · How to +play · Main menu** — on the desktop (Escape in Play, or the corner Menu button) and in VR +(the left controller's **X**, or Menu on the game board / wrist card). You do not draw it; +you feed it. A scene counts as a game when it has a state-bound HUD screen, a spawn, or a +module publishing `userData.play` — or when your module registers levels. All of it is +feature-detected (`api.game.levels?.(…)`), LOCAL, and torn down with your module. + +**Levels** — the picker on the desktop and on the VR board. Re-call it whenever a level +unlocks or earns stars; `onPick` never hears a locked level. + +```js +const refresh = () => + api.game.levels?.({ + list: LEVELS.map((l, i) => ({ id: l.id, label: 'Level ' + (i + 1), locked: i > unlocked, stars: best[l.id] ?? 0 })), + current: currentLevel.id, + onPick: (id) => loadLevel(id) + }); +refresh(); +onLevelWon(() => { unlocked++; refresh(); }); +``` + +**Your own settings rows** — shown under the core rows, persisted per game on this device +(`tp:game::`). `type` is `'toggle'`, `'choice'` (`options`, optional `optionLabels`) +or `'range'` (`min`, `max`, `step`). A core id is refused. + +```js +api.game.addSetting?.({ + id: 'board', label: 'Board', type: 'choice', + options: ['globe', '2d'], optionLabels: ['Globe', '2D board'], default: 'globe', + onChange: (v) => setBoard(v) +}); +setBoard(api.game.setting?.('board') ?? 'globe'); // the stored choice at load +api.game.setSetting?.('board', '2d'); // your in-game button writes the same row +api.game.onSettingsChange?.((values) => console.log(values.board, values.sfx)); +``` + +**The core rows every game has** — `music`, `musicVolume` (0..100), `sfx`, `sfxVolume`, +`haptics`, `showFps`, `turning` (`default` | `snap` | `smooth` | `off`), `turnAngle` +(`default` | `15` | `30` | `45` | `90`), `vignette`, `quality` (`auto` | `low` | `medium` | +`high`). Core obeys them itself: the per-game volumes land on the audio BUSES (so +`api.playSound`, `api.music`, flow sound nodes and the scene's track all follow), haptics, +VR turning, the comfort vignette and the quality governor read them live. A module that +plays audio through its OWN WebAudio graph should read them: + +```js +const gain = ctx.createGain(); +const apply = () => (gain.gain.value = api.game.setting?.('sfx') === false ? 0 : (api.game.setting?.('sfxVolume') ?? 100) / 100); +apply(); +api.game.onSettingsChange?.(apply); +``` + +**How to play** — your words first, then the device's controls (keyboard or controllers). +Without it the menu shows the game's Games-tab description. + +```js +api.game.setHelp?.([ + 'Drag the dots until no two lines cross.', + 'Finish a level to unlock the next — stars for fewer moves.' +]); +``` + +**Restart** — the menu resets the game shell (host / alone; others carry on as it is), +respawns the player and runs your hook: + +```js +api.game.onRestart?.(() => { resetBoard(); spawnWave(1); }); +``` + +**Your own Menu button** (a module HUD, a VR board of yours): + +```js +api.game.openMenu?.(); // only while playing a game; false otherwise +api.game.closeMenu?.(); +if (api.game.menuOpen?.()) pauseMyTimers(); +``` + ### Utilities ```js