@pix3/cli 1.6.4 → 2.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +722 -788
- package/dist/index.js +557 -421
- package/dist/runtime-types.json +1 -1
- package/dist/smoke/prebuilt/{build-7MIXIMSZ.mjs → build-7REMSA2S.mjs} +52 -52
- package/dist/smoke/prebuilt/chunk-4DGG2BKR.mjs +2 -0
- package/dist/smoke/prebuilt/chunk-YLBMUJ2H.mjs +238 -0
- package/dist/smoke/prebuilt/runtime.mjs +1 -1
- package/dist/smoke/prebuilt/smoke-worker.mjs +211 -2
- package/dist/smoke/prebuilt/tree-defaults.mjs +1 -1
- package/dist/validate/prebuilt/{build-AUGKXER3.js → build-ADF5EEE2.js} +52 -52
- package/dist/validate/prebuilt/bundle-main.js +223 -18
- package/dist/validate/prebuilt/chunk-VIHIF4G2.js +238 -0
- package/dist/validate/prebuilt/runtime.js +1 -1
- package/kit/files/.claude/skills/pix3-editor/SKILL.md +200 -0
- package/kit/files/.claude/skills/pix3-nodes/SKILL.md +13 -15
- package/kit/files/.claude/skills/pix3-nodes/reference.md +77 -71
- package/kit/files/.claude/skills/pix3-scene-format/SKILL.md +38 -41
- package/kit/files/.claude/skills/pix3-scene-format/pix3anim.md +185 -0
- package/kit/files/.claude/skills/pix3-scene-format/project-files.md +244 -0
- package/kit/files/.claude/skills/pix3-scene-format/reference.md +28 -16
- package/kit/files/.claude/skills/pix3-scripts/SKILL.md +34 -24
- package/kit/files/.claude/skills/pix3-scripts/reference.md +68 -141
- package/kit/files/.claude/skills/pix3-verify/SKILL.md +65 -326
- package/kit/files/.claude/skills/pix3-verify/reference.md +2 -14
- package/kit/files/AGENTS.md +76 -84
- package/kit/files/design/tests/bots/pix3-test-bot.d.ts +89 -0
- package/kit/kit.json +8 -5
- package/package.json +62 -54
- package/templates/2d/files/scenes/main.pix3scene +31 -0
- package/templates/2d/template.yaml +11 -0
- package/templates/3d/files/scenes/main.pix3scene +50 -0
- package/templates/3d/template.yaml +11 -0
- package/templates/base/files/README.md +39 -0
- package/templates/base/files/gitignore +11 -0
- package/templates/base/files/index.html +23 -0
- package/templates/base/files/package.json +28 -0
- package/templates/base/files/src/main.ts +3 -0
- package/templates/base/files/tsconfig.json +17 -0
- package/templates/base/files/vite.config.ts +10 -0
- package/dist/smoke/prebuilt/chunk-CBFYKDS7.mjs +0 -2
- package/dist/smoke/prebuilt/chunk-VJHJ5BCO.mjs +0 -239
- package/dist/validate/prebuilt/chunk-CFQFEI52.js +0 -239
- package/templates/empty-2d/files/README.md +0 -20
- package/templates/empty-2d/files/scenes/main.pix3scene +0 -68
- package/templates/empty-2d/files/sprites/pix3-logo.png +0 -0
- package/templates/empty-2d/template.yaml +0 -7
- package/templates/empty-3d/files/README.md +0 -20
- package/templates/empty-3d/files/scenes/main.pix3scene +0 -108
- package/templates/empty-3d/files/sprites/pix3-logo.png +0 -0
- package/templates/empty-3d/template.yaml +0 -7
- package/templates/idea-blank/files/README.md +0 -22
- package/templates/idea-blank/files/scenes/main.pix3scene +0 -37
- package/templates/idea-blank/template.yaml +0 -10
- package/templates/minigame-2d/files/README.md +0 -44
- package/templates/minigame-2d/files/design/tests/reachability.json +0 -1
- package/templates/minigame-2d/files/design/tests/routines/mute-music.json +0 -44
- package/templates/minigame-2d/files/scenes/main.pix3scene +0 -84
- package/templates/minigame-2d/files/scenes/menu.pix3scene +0 -118
- package/templates/minigame-2d/files/scenes/ui/settings-window.pix3scene +0 -138
- package/templates/minigame-2d/files/scripts/GameFlow.ts +0 -133
- package/templates/minigame-2d/files/scripts/MenuFlow.ts +0 -217
- package/templates/minigame-2d/files/scripts/SettingsWindow.ts +0 -198
- package/templates/minigame-2d/template.yaml +0 -10
- package/templates/playable-2d/files/README.md +0 -30
- package/templates/playable-2d/files/design/recipe.md +0 -83
- package/templates/playable-2d/files/design/tests/reachability.json +0 -4
- package/templates/playable-2d/files/design/tests/routines/intro-to-cta.json +0 -32
- package/templates/playable-2d/files/scenes/main.pix3scene +0 -111
- package/templates/playable-2d/files/scenes/ui/end-screen.pix3scene +0 -91
- package/templates/playable-2d/files/scenes/ui/intro.pix3scene +0 -65
- package/templates/playable-2d/files/scripts/CtaButton.ts +0 -55
- package/templates/playable-2d/files/scripts/GameFlow.ts +0 -256
- package/templates/playable-2d/files/sprites/pix3-logo.png +0 -0
- package/templates/playable-2d/template.yaml +0 -9
- package/templates/playable-3d/files/README.md +0 -32
- package/templates/playable-3d/files/design/recipe.md +0 -127
- package/templates/playable-3d/files/design/tests/reachability.json +0 -4
- package/templates/playable-3d/files/design/tests/routines/intro-to-cta.json +0 -32
- package/templates/playable-3d/files/scenes/main.pix3scene +0 -230
- package/templates/playable-3d/files/scenes/ui/end-screen.pix3scene +0 -91
- package/templates/playable-3d/files/scenes/ui/intro.pix3scene +0 -65
- package/templates/playable-3d/files/scripts/CtaButton.ts +0 -55
- package/templates/playable-3d/files/scripts/GameFlow.ts +0 -309
- package/templates/playable-3d/files/scripts/GameRules.ts +0 -290
- package/templates/playable-3d/files/scripts/ScoreHud.ts +0 -113
- package/templates/playable-3d/template.yaml +0 -9
- package/templates/recipe-arena-2d/files/README.md +0 -48
- package/templates/recipe-arena-2d/files/design/recipe.md +0 -127
- package/templates/recipe-arena-2d/files/design/tests/reachability.json +0 -4
- package/templates/recipe-arena-2d/files/design/tests/routines/terminal-retry.json +0 -23
- package/templates/recipe-arena-2d/files/scenes/main.pix3scene +0 -292
- package/templates/recipe-arena-2d/files/scenes/menu.pix3scene +0 -100
- package/templates/recipe-arena-2d/files/scenes/prefabs/hazard.pix3scene +0 -42
- package/templates/recipe-arena-2d/files/scenes/prefabs/pickup.pix3scene +0 -45
- package/templates/recipe-arena-2d/files/scenes/ui/result.pix3scene +0 -85
- package/templates/recipe-arena-2d/files/scripts/GameRules.ts +0 -368
- package/templates/recipe-arena-2d/files/scripts/MenuFlow.ts +0 -106
- package/templates/recipe-arena-2d/files/scripts/PlayerController.ts +0 -151
- package/templates/recipe-arena-2d/files/scripts/ScoreHud.ts +0 -94
- package/templates/recipe-arena-2d/files/scripts/Spawner.ts +0 -214
- package/templates/recipe-arena-2d/files/scripts/TouchRules.ts +0 -174
- package/templates/recipe-arena-2d/files/sprites/ph-avatar.png +0 -0
- package/templates/recipe-arena-2d/files/sprites/ph-hazard.png +0 -0
- package/templates/recipe-arena-2d/files/sprites/ph-pickup.png +0 -0
- package/templates/recipe-arena-2d/template.yaml +0 -10
- package/templates/recipe-blank-2d/files/README.md +0 -61
- package/templates/recipe-blank-2d/files/design/recipe.md +0 -148
- package/templates/recipe-blank-2d/files/design/tests/reachability.json +0 -4
- package/templates/recipe-blank-2d/files/design/tests/routines/restart.json +0 -27
- package/templates/recipe-blank-2d/files/design/tests/routines/terminal-retry.json +0 -23
- package/templates/recipe-blank-2d/files/scenes/main.pix3scene +0 -224
- package/templates/recipe-blank-2d/files/scenes/ui/result.pix3scene +0 -85
- package/templates/recipe-blank-2d/files/scripts/GameRules.ts +0 -350
- package/templates/recipe-blank-2d/files/scripts/ScoreHud.ts +0 -110
- package/templates/recipe-blank-2d/files/sprites/ph-bg.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-capsule.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-circle.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-orb.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-ring.png +0 -0
- package/templates/recipe-blank-2d/files/sprites/ph-star.png +0 -0
- package/templates/recipe-blank-2d/template.yaml +0 -10
- package/templates/recipe-bouncer-2d/files/README.md +0 -81
- package/templates/recipe-bouncer-2d/files/design/recipe.md +0 -146
- package/templates/recipe-bouncer-2d/files/design/tests/reachability.json +0 -4
- package/templates/recipe-bouncer-2d/files/design/tests/routines/terminal-retry.json +0 -23
- package/templates/recipe-bouncer-2d/files/scenes/main.pix3scene +0 -615
- package/templates/recipe-bouncer-2d/files/scenes/menu.pix3scene +0 -191
- package/templates/recipe-bouncer-2d/files/scenes/ui/result.pix3scene +0 -87
- package/templates/recipe-bouncer-2d/files/scripts/BallBody.ts +0 -298
- package/templates/recipe-bouncer-2d/files/scripts/GameRules.ts +0 -377
- package/templates/recipe-bouncer-2d/files/scripts/MenuFlow.ts +0 -109
- package/templates/recipe-bouncer-2d/files/scripts/PaddleController.ts +0 -112
- package/templates/recipe-bouncer-2d/files/scripts/ScoreHud.ts +0 -94
- package/templates/recipe-bouncer-2d/files/scripts/TouchRules.ts +0 -263
- package/templates/recipe-bouncer-2d/files/scripts/ball-collision.ts +0 -307
- package/templates/recipe-bouncer-2d/files/sprites/ph-ball.png +0 -0
- package/templates/recipe-bouncer-2d/files/sprites/ph-bg.png +0 -0
- package/templates/recipe-bouncer-2d/files/sprites/ph-bumper.png +0 -0
- package/templates/recipe-bouncer-2d/files/sprites/ph-paddle.png +0 -0
- package/templates/recipe-bouncer-2d/template.yaml +0 -10
- package/templates/recipe-grid-3d/files/README.md +0 -22
- package/templates/recipe-grid-3d/files/design/recipe.md +0 -110
- package/templates/recipe-grid-3d/files/scenes/main.pix3scene +0 -169
- package/templates/recipe-grid-3d/files/scenes/menu.pix3scene +0 -100
- package/templates/recipe-grid-3d/files/scenes/ui/result.pix3scene +0 -105
- package/templates/recipe-grid-3d/files/scripts/GridBoard.ts +0 -319
- package/templates/recipe-grid-3d/files/scripts/GridRules.ts +0 -278
- package/templates/recipe-grid-3d/files/scripts/MenuFlow.ts +0 -106
- package/templates/recipe-grid-3d/files/scripts/ScoreHud.ts +0 -94
- package/templates/recipe-grid-3d/template.yaml +0 -10
- package/templates/recipe-tapper-2d/files/README.md +0 -39
- package/templates/recipe-tapper-2d/files/design/recipe.md +0 -132
- package/templates/recipe-tapper-2d/files/design/tests/reachability.json +0 -4
- package/templates/recipe-tapper-2d/files/design/tests/routines/terminal-retry.json +0 -23
- package/templates/recipe-tapper-2d/files/scenes/main.pix3scene +0 -269
- package/templates/recipe-tapper-2d/files/scenes/menu.pix3scene +0 -100
- package/templates/recipe-tapper-2d/files/scenes/prefabs/hazard.pix3scene +0 -42
- package/templates/recipe-tapper-2d/files/scenes/prefabs/target.pix3scene +0 -45
- package/templates/recipe-tapper-2d/files/scenes/ui/result.pix3scene +0 -85
- package/templates/recipe-tapper-2d/files/scripts/GameRules.ts +0 -368
- package/templates/recipe-tapper-2d/files/scripts/MenuFlow.ts +0 -106
- package/templates/recipe-tapper-2d/files/scripts/ScoreHud.ts +0 -94
- package/templates/recipe-tapper-2d/files/scripts/Spawner.ts +0 -214
- package/templates/recipe-tapper-2d/files/scripts/TouchRules.ts +0 -192
- package/templates/recipe-tapper-2d/files/sprites/ph-hazard.png +0 -0
- package/templates/recipe-tapper-2d/files/sprites/ph-target.png +0 -0
- package/templates/recipe-tapper-2d/template.yaml +0 -10
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# Pix3 Scene File (YAML)
|
|
2
|
-
# Minigame 2D — the SETTINGS modal: a centered panel with Music/SFX checkboxes
|
|
3
|
-
# (wired to the audio buses by user:SettingsWindow) and a CLOSE button. It lives
|
|
4
|
-
# in its own file under scenes/ui/ so the menu scene opens clean. Host scenes
|
|
5
|
-
# carry it as a one-line `instance:` reference marked `visible: false`, which
|
|
6
|
-
# hides it in the EDITOR only; `initiallyVisible: false` on `settings-root`
|
|
7
|
-
# below is what play mode reads, and `user:MenuFlow` shows it at run time.
|
|
8
|
-
#
|
|
9
|
-
# Edit it here, full-screen, in its own tab. To preview it composited over the
|
|
10
|
-
# menu, tick the eye on `settings-window` in menu.pix3scene — just do not commit
|
|
11
|
-
# that tick, or every iteration starts behind the settings modal again.
|
|
12
|
-
#
|
|
13
|
-
# Instance it from any scene:
|
|
14
|
-
# - id: settings-window
|
|
15
|
-
# instance: res://scenes/ui/settings-window.pix3scene
|
|
16
|
-
# properties:
|
|
17
|
-
# visible: false
|
|
18
|
-
version: 1.0.0
|
|
19
|
-
metadata:
|
|
20
|
-
author: Pix3 Template
|
|
21
|
-
description: Reusable settings window (music/SFX toggles)
|
|
22
|
-
root:
|
|
23
|
-
- id: settings-root
|
|
24
|
-
type: Group2D
|
|
25
|
-
name: Settings Window
|
|
26
|
-
properties:
|
|
27
|
-
width: 640
|
|
28
|
-
height: 520
|
|
29
|
-
initiallyVisible: false
|
|
30
|
-
layout:
|
|
31
|
-
enabled: true
|
|
32
|
-
horizontalAlign: center
|
|
33
|
-
verticalAlign: center
|
|
34
|
-
components:
|
|
35
|
-
- id: settings-behavior
|
|
36
|
-
type: user:SettingsWindow
|
|
37
|
-
enabled: true
|
|
38
|
-
config:
|
|
39
|
-
musicToggle: music-toggle
|
|
40
|
-
sfxToggle: sfx-toggle
|
|
41
|
-
closeButton: close-button
|
|
42
|
-
children:
|
|
43
|
-
- id: settings-panel
|
|
44
|
-
type: ColorRect2D
|
|
45
|
-
name: Panel
|
|
46
|
-
properties:
|
|
47
|
-
width: 640
|
|
48
|
-
height: 520
|
|
49
|
-
color: "#1a1a2e"
|
|
50
|
-
opacity: 0.97
|
|
51
|
-
transform:
|
|
52
|
-
position: [0, 0]
|
|
53
|
-
scale: [1, 1]
|
|
54
|
-
rotation: 0
|
|
55
|
-
children: []
|
|
56
|
-
- id: settings-title
|
|
57
|
-
type: Label2D
|
|
58
|
-
name: Title
|
|
59
|
-
properties:
|
|
60
|
-
label: "SETTINGS"
|
|
61
|
-
labelFontSize: 48
|
|
62
|
-
labelColor: "#ffcf33"
|
|
63
|
-
labelAlign: center
|
|
64
|
-
transform:
|
|
65
|
-
position: [0, 190]
|
|
66
|
-
scale: [1, 1]
|
|
67
|
-
rotation: 0
|
|
68
|
-
children: []
|
|
69
|
-
- id: music-label
|
|
70
|
-
type: Label2D
|
|
71
|
-
name: Music Label
|
|
72
|
-
properties:
|
|
73
|
-
label: "Music"
|
|
74
|
-
labelFontSize: 36
|
|
75
|
-
labelColor: "#ffffff"
|
|
76
|
-
labelAlign: left
|
|
77
|
-
transform:
|
|
78
|
-
position: [-120, 70]
|
|
79
|
-
scale: [1, 1]
|
|
80
|
-
rotation: 0
|
|
81
|
-
children: []
|
|
82
|
-
- id: music-toggle
|
|
83
|
-
type: Checkbox2D
|
|
84
|
-
name: Music Toggle
|
|
85
|
-
properties:
|
|
86
|
-
size: 44
|
|
87
|
-
checked: true
|
|
88
|
-
checkedColor: "#ffcf33"
|
|
89
|
-
uncheckedColor: "#555577"
|
|
90
|
-
transform:
|
|
91
|
-
position: [150, 70]
|
|
92
|
-
scale: [1, 1]
|
|
93
|
-
rotation: 0
|
|
94
|
-
children: []
|
|
95
|
-
- id: sfx-label
|
|
96
|
-
type: Label2D
|
|
97
|
-
name: SFX Label
|
|
98
|
-
properties:
|
|
99
|
-
label: "Sound FX"
|
|
100
|
-
labelFontSize: 36
|
|
101
|
-
labelColor: "#ffffff"
|
|
102
|
-
labelAlign: left
|
|
103
|
-
transform:
|
|
104
|
-
position: [-120, -10]
|
|
105
|
-
scale: [1, 1]
|
|
106
|
-
rotation: 0
|
|
107
|
-
children: []
|
|
108
|
-
- id: sfx-toggle
|
|
109
|
-
type: Checkbox2D
|
|
110
|
-
name: SFX Toggle
|
|
111
|
-
properties:
|
|
112
|
-
size: 44
|
|
113
|
-
checked: true
|
|
114
|
-
checkedColor: "#ffcf33"
|
|
115
|
-
uncheckedColor: "#555577"
|
|
116
|
-
transform:
|
|
117
|
-
position: [150, -10]
|
|
118
|
-
scale: [1, 1]
|
|
119
|
-
rotation: 0
|
|
120
|
-
children: []
|
|
121
|
-
- id: close-button
|
|
122
|
-
type: Button2D
|
|
123
|
-
name: Close Button
|
|
124
|
-
properties:
|
|
125
|
-
width: 280
|
|
126
|
-
height: 88
|
|
127
|
-
label: "CLOSE"
|
|
128
|
-
labelFontSize: 36
|
|
129
|
-
labelColor: "#1a1a2e"
|
|
130
|
-
backgroundColor: "#ffcf33"
|
|
131
|
-
hoverColor: "#ffe066"
|
|
132
|
-
pressedColor: "#e6b800"
|
|
133
|
-
buttonAction: close-settings
|
|
134
|
-
transform:
|
|
135
|
-
position: [0, -180]
|
|
136
|
-
scale: [1, 1]
|
|
137
|
-
rotation: 0
|
|
138
|
-
children: []
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GameFlow — drives the game scene (main.pix3scene).
|
|
3
|
-
*
|
|
4
|
-
* Attach to the game scene root. Wires the MENU button to transition back to the
|
|
5
|
-
* menu scene via a fade. Extend this with your win/lose flow — call
|
|
6
|
-
* `this.scene.changeScene('res://scenes/menu.pix3scene')` on game over
|
|
7
|
-
* to return to the menu, or transition to a results scene of your own.
|
|
8
|
-
*
|
|
9
|
-
* Two conventions in here are worth keeping as you build on this template:
|
|
10
|
-
*
|
|
11
|
-
* 1. **Intent-first handlers.** Every reaction to the UI lives in a named public
|
|
12
|
-
* method marked `Intent:`, registered as a **command** (`scene.commands`)
|
|
13
|
-
* that the signal handler dispatches. Tools (and you, from the console) then
|
|
14
|
-
* trigger the behaviour with `scene.commands.dispatch('return-to-menu')`
|
|
15
|
-
* instead of faking a click, and every raised intent is journalled with the
|
|
16
|
-
* frame it happened on.
|
|
17
|
-
* 2. **A debug provider.** `registerGameDebug` publishes a JSON snapshot of the
|
|
18
|
-
* game's state so agents and dev tooling can read what the game is doing
|
|
19
|
-
* instead of guessing from pixels. Add YOUR state to `snapshot()` — score,
|
|
20
|
-
* lives, level, whatever the game owns — and keep every value JSON-safe.
|
|
21
|
-
*/
|
|
22
|
-
import { Script, registerGameDebug, type PropertySchema } from '@pix3/runtime';
|
|
23
|
-
|
|
24
|
-
/** Coarse state of the game scene; extend with your own phases (`won`, `lost`, …). */
|
|
25
|
-
type GamePhase = 'idle' | 'playing' | 'leaving';
|
|
26
|
-
|
|
27
|
-
export class GameFlow extends Script {
|
|
28
|
-
private phase: GamePhase = 'idle';
|
|
29
|
-
private menuButtonReady = false;
|
|
30
|
-
private disposeDebug: (() => void) | null = null;
|
|
31
|
-
private disposeCommands: (() => void)[] = [];
|
|
32
|
-
|
|
33
|
-
constructor(id: string, type: string) {
|
|
34
|
-
super(id, type);
|
|
35
|
-
this.config = {
|
|
36
|
-
// res:// path of the scene the MENU button transitions to.
|
|
37
|
-
menuScene: 'res://scenes/menu.pix3scene',
|
|
38
|
-
// Node id/name of the button that returns to the menu.
|
|
39
|
-
menuButton: 'menu-button',
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static getPropertySchema(): PropertySchema {
|
|
44
|
-
const stringProp = (name: string, label: string) => ({
|
|
45
|
-
name,
|
|
46
|
-
type: 'string' as const,
|
|
47
|
-
ui: { label, group: 'Game' },
|
|
48
|
-
getValue: (s: unknown) => (s as GameFlow).config[name],
|
|
49
|
-
setValue: (s: unknown, v: unknown) => {
|
|
50
|
-
(s as GameFlow).config[name] = typeof v === 'string' ? v : '';
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
nodeType: 'GameFlow',
|
|
56
|
-
properties: [stringProp('menuScene', 'Menu Scene'), stringProp('menuButton', 'Menu Button')],
|
|
57
|
-
groups: { Game: { label: 'Game Flow', expanded: true } },
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
onStart(): void {
|
|
62
|
-
this.phase = 'playing';
|
|
63
|
-
const commands = this.scene?.commands;
|
|
64
|
-
this.disposeCommands = [
|
|
65
|
-
commands?.register('return-to-menu', () => this.returnToMenu(), {
|
|
66
|
-
description: 'Leave the game and transition back to the menu scene.',
|
|
67
|
-
}),
|
|
68
|
-
].filter((dispose): dispose is () => void => dispose !== undefined);
|
|
69
|
-
|
|
70
|
-
this.disposeDebug = registerGameDebug({
|
|
71
|
-
name: 'minigame-2d',
|
|
72
|
-
version: 1,
|
|
73
|
-
// The registry IS the action list — never a second, hand-kept copy.
|
|
74
|
-
actions: () => this.scene?.commands.list().map(command => command.name) ?? [],
|
|
75
|
-
// What "the start" means for this scene, so a test can put the game back
|
|
76
|
-
// without restarting it. All this template owns is `phase`, and `playing`
|
|
77
|
-
// is the value `onStart` sets — a run that had already dispatched
|
|
78
|
-
// `return-to-menu` would otherwise be stuck in `leaving`. Reset YOUR state
|
|
79
|
-
// here as you add it (score, level, spawned things), and seed your RNG from
|
|
80
|
-
// `seed` if you want two runs to be comparable and not merely to start
|
|
81
|
-
// from the same values.
|
|
82
|
-
reset: () => {
|
|
83
|
-
this.phase = 'playing';
|
|
84
|
-
},
|
|
85
|
-
// Everything returned here must be JSON-serialisable — plain values only,
|
|
86
|
-
// never live nodes or Three.js objects. Add your own state as you build.
|
|
87
|
-
snapshot: () => ({
|
|
88
|
-
phase: this.phase,
|
|
89
|
-
menuScene: String(this.config.menuScene ?? ''),
|
|
90
|
-
menuButtonReady: this.menuButtonReady,
|
|
91
|
-
}),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const query = String(this.config.menuButton ?? '');
|
|
95
|
-
if (!query) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const button = this.findNode(query);
|
|
99
|
-
if (button) {
|
|
100
|
-
this.menuButtonReady = true;
|
|
101
|
-
// The button raises the intent through the registry, so a single tap in a
|
|
102
|
-
// test proves this binding and every later scenario can skip the tap.
|
|
103
|
-
button.connect('pressed', this, () => {
|
|
104
|
-
this.scene?.commands.dispatch('return-to-menu');
|
|
105
|
-
});
|
|
106
|
-
} else {
|
|
107
|
-
console.warn(`[GameFlow] Button "${query}" not found.`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
onDetach(): void {
|
|
112
|
-
this.disposeDebug?.();
|
|
113
|
-
this.disposeDebug = null;
|
|
114
|
-
for (const dispose of this.disposeCommands) {
|
|
115
|
-
dispose();
|
|
116
|
-
}
|
|
117
|
-
this.disposeCommands = [];
|
|
118
|
-
this.phase = 'idle';
|
|
119
|
-
this.menuButtonReady = false;
|
|
120
|
-
super.onDetach();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Intent: leave the game and transition back to the menu scene (also on game over). */
|
|
124
|
-
returnToMenu(): void {
|
|
125
|
-
const menuScene = String(this.config.menuScene ?? '');
|
|
126
|
-
if (!menuScene) {
|
|
127
|
-
console.warn('[GameFlow] No menu scene configured.');
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
this.phase = 'leaving';
|
|
131
|
-
void this.scene?.changeScene(menuScene, { transition: 'fade', durationSec: 0.3 });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MenuFlow — drives the menu scene.
|
|
3
|
-
*
|
|
4
|
-
* Attach to the menu scene root. PLAY transitions to the game scene via a fade
|
|
5
|
-
* (`this.scene.changeScene`), SETTINGS shows the settings-window prefab instance.
|
|
6
|
-
* The game lives in its own scene (main.pix3scene) so it can be opened and played
|
|
7
|
-
* on its own; this menu is the entry point wired into the full build flow.
|
|
8
|
-
*
|
|
9
|
-
* Handlers here are **intent-first**: each button's behaviour lives in a named
|
|
10
|
-
* public method marked `Intent:`, and the intent is registered as a **command**
|
|
11
|
-
* (`scene.commands`) that the button's signal handler dispatches. Three things
|
|
12
|
-
* follow, all for free: a test drives the menu with
|
|
13
|
-
* `scene.commands.dispatch('start-game')` instead of a tap; every raised intent
|
|
14
|
-
* lands in `scene.commands.log` with the frame it happened on; and one real tap
|
|
15
|
-
* is enough to prove the button is still wired to the intent it claims.
|
|
16
|
-
*
|
|
17
|
-
* It also registers a debug provider, exactly like `GameFlow` does for the game
|
|
18
|
-
* scene. `registerGameDebug` is global and last-wins, and only the running
|
|
19
|
-
* scene's flow script registers one — so without this, every tool reading the
|
|
20
|
-
* game while the menu is up would get nothing (or stale game-scene state).
|
|
21
|
-
*/
|
|
22
|
-
import {
|
|
23
|
-
Checkbox2D,
|
|
24
|
-
NodeBase,
|
|
25
|
-
Script,
|
|
26
|
-
registerGameDebug,
|
|
27
|
-
type PropertySchema,
|
|
28
|
-
} from '@pix3/runtime';
|
|
29
|
-
|
|
30
|
-
export class MenuFlow extends Script {
|
|
31
|
-
private playButtonReady = false;
|
|
32
|
-
private settingsButtonReady = false;
|
|
33
|
-
private disposeDebug: (() => void) | null = null;
|
|
34
|
-
private disposeCommands: (() => void)[] = [];
|
|
35
|
-
|
|
36
|
-
constructor(id: string, type: string) {
|
|
37
|
-
super(id, type);
|
|
38
|
-
this.config = {
|
|
39
|
-
// res:// path of the scene PLAY transitions to.
|
|
40
|
-
gameScene: 'res://scenes/main.pix3scene',
|
|
41
|
-
// Node id/name of the settings-window prefab instance to toggle.
|
|
42
|
-
settingsNode: 'settings-window',
|
|
43
|
-
playButton: 'play-button',
|
|
44
|
-
settingsButton: 'settings-button',
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
static getPropertySchema(): PropertySchema {
|
|
49
|
-
const stringProp = (name: string, label: string) => ({
|
|
50
|
-
name,
|
|
51
|
-
type: 'string' as const,
|
|
52
|
-
ui: { label, group: 'Menu' },
|
|
53
|
-
getValue: (s: unknown) => (s as MenuFlow).config[name],
|
|
54
|
-
setValue: (s: unknown, v: unknown) => {
|
|
55
|
-
(s as MenuFlow).config[name] = typeof v === 'string' ? v : '';
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
nodeType: 'MenuFlow',
|
|
61
|
-
properties: [
|
|
62
|
-
stringProp('gameScene', 'Game Scene'),
|
|
63
|
-
stringProp('settingsNode', 'Settings Window'),
|
|
64
|
-
stringProp('playButton', 'Play Button'),
|
|
65
|
-
stringProp('settingsButton', 'Settings Button'),
|
|
66
|
-
],
|
|
67
|
-
groups: { Menu: { label: 'Menu Flow', expanded: true } },
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
onStart(): void {
|
|
72
|
-
// Register the intents first, then point the buttons at them: a button
|
|
73
|
-
// dispatches, it does not call the method, so the command journal is the
|
|
74
|
-
// whole truth about what the player asked for.
|
|
75
|
-
const commands = this.scene?.commands;
|
|
76
|
-
this.disposeCommands = [
|
|
77
|
-
commands?.register('start-game', () => this.startGame(), {
|
|
78
|
-
description: 'Leave the menu and transition into the game scene.',
|
|
79
|
-
}),
|
|
80
|
-
commands?.register(
|
|
81
|
-
'open-settings',
|
|
82
|
-
() => {
|
|
83
|
-
this.openSettings();
|
|
84
|
-
// Returning a reverse step is all it takes to make an intent
|
|
85
|
-
// undoable: `scene.commands.undo()` closes the window again.
|
|
86
|
-
return { undo: () => this.closeSettings() };
|
|
87
|
-
},
|
|
88
|
-
{ description: 'Show the settings window over the menu.', undoable: true }
|
|
89
|
-
),
|
|
90
|
-
commands?.register('close-settings', () => this.closeSettings(), {
|
|
91
|
-
description: 'Hide the settings window.',
|
|
92
|
-
}),
|
|
93
|
-
].filter((dispose): dispose is () => void => dispose !== undefined);
|
|
94
|
-
|
|
95
|
-
this.playButtonReady = this.connectButton(String(this.config.playButton ?? ''), () => {
|
|
96
|
-
this.scene?.commands.dispatch('start-game');
|
|
97
|
-
});
|
|
98
|
-
this.settingsButtonReady = this.connectButton(String(this.config.settingsButton ?? ''), () => {
|
|
99
|
-
this.scene?.commands.dispatch('open-settings');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
this.disposeDebug = registerGameDebug({
|
|
103
|
-
name: 'minigame-2d',
|
|
104
|
-
version: 1,
|
|
105
|
-
// The registry IS the action list — never a second, hand-kept copy.
|
|
106
|
-
actions: () => this.scene?.commands.list().map(command => command.name) ?? [],
|
|
107
|
-
// What "the start" means for the menu: the fresh menu has the settings
|
|
108
|
-
// window closed, and an earlier `open-settings` is the one piece of state a
|
|
109
|
-
// later run would inherit. Deliberately NOT reset here: the checkboxes
|
|
110
|
-
// inside the window, which are player settings — a reset that silently
|
|
111
|
-
// reverted someone's audio choice would be a worse lie than not resetting.
|
|
112
|
-
// `seed` is ignored; the menu has no randomness to seed.
|
|
113
|
-
reset: () => this.closeSettings(),
|
|
114
|
-
// Same `name` as GameFlow — one game, one identity; `scene` says which
|
|
115
|
-
// flow answered. Everything here must stay JSON-serialisable.
|
|
116
|
-
snapshot: () => ({
|
|
117
|
-
scene: 'menu',
|
|
118
|
-
gameScene: String(this.config.gameScene ?? ''),
|
|
119
|
-
playButtonReady: this.playButtonReady,
|
|
120
|
-
settingsButtonReady: this.settingsButtonReady,
|
|
121
|
-
settingsVisible: this.isSettingsVisible(),
|
|
122
|
-
// Checkbox state inside the settings window, keyed by node id
|
|
123
|
-
// (`music-toggle` / `sfx-toggle` in the shipped prefab).
|
|
124
|
-
settingsToggles: this.readSettingsToggles(),
|
|
125
|
-
}),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
onDetach(): void {
|
|
130
|
-
// Safe in any order: the disposer clears the global only while it still
|
|
131
|
-
// holds *this* provider, so the game scene's registration is never wiped.
|
|
132
|
-
this.disposeDebug?.();
|
|
133
|
-
this.disposeDebug = null;
|
|
134
|
-
// The scene's registry is cleared on stop anyway; this covers the other
|
|
135
|
-
// case — this script being detached while the scene keeps running.
|
|
136
|
-
for (const dispose of this.disposeCommands) {
|
|
137
|
-
dispose();
|
|
138
|
-
}
|
|
139
|
-
this.disposeCommands = [];
|
|
140
|
-
this.playButtonReady = false;
|
|
141
|
-
this.settingsButtonReady = false;
|
|
142
|
-
super.onDetach();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** Intent: leave the menu and transition into the game scene. */
|
|
146
|
-
startGame(): void {
|
|
147
|
-
const gameScene = String(this.config.gameScene ?? '');
|
|
148
|
-
if (!gameScene) {
|
|
149
|
-
console.warn('[MenuFlow] No game scene configured.');
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
void this.scene?.changeScene(gameScene, { transition: 'fade', durationSec: 0.3 });
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Intent: show the settings window over the menu. */
|
|
156
|
-
openSettings(): void {
|
|
157
|
-
this.setNodeVisible(String(this.config.settingsNode ?? ''), true);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** Intent: hide the settings window (its own CLOSE button does this too). */
|
|
161
|
-
closeSettings(): void {
|
|
162
|
-
this.setNodeVisible(String(this.config.settingsNode ?? ''), false);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** Returns whether the button was found and wired (reported in the snapshot). */
|
|
166
|
-
private connectButton(query: string, handler: () => void): boolean {
|
|
167
|
-
if (!query) {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
const button = this.findNode(query);
|
|
171
|
-
if (!button) {
|
|
172
|
-
console.warn(`[MenuFlow] Button "${query}" not found.`);
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
button.connect('pressed', this, handler);
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private isSettingsVisible(): boolean {
|
|
180
|
-
const node = this.findNode(String(this.config.settingsNode ?? ''));
|
|
181
|
-
return node?.visible === true;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Every Checkbox2D under the settings window, by node id → checked. */
|
|
185
|
-
private readSettingsToggles(): Record<string, boolean> {
|
|
186
|
-
const toggles: Record<string, boolean> = {};
|
|
187
|
-
const root = this.findNode(String(this.config.settingsNode ?? ''));
|
|
188
|
-
if (!root) {
|
|
189
|
-
return toggles;
|
|
190
|
-
}
|
|
191
|
-
const visit = (node: NodeBase): void => {
|
|
192
|
-
if (node instanceof Checkbox2D) {
|
|
193
|
-
toggles[node.nodeId] = node.checked;
|
|
194
|
-
}
|
|
195
|
-
for (const child of node.children) {
|
|
196
|
-
// `children` also holds plain Three.js objects (visuals), not just nodes.
|
|
197
|
-
if (child instanceof NodeBase) {
|
|
198
|
-
visit(child);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
visit(root);
|
|
203
|
-
return toggles;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
private setNodeVisible(query: string, visible: boolean): void {
|
|
207
|
-
if (!query) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
const node = this.findNode(query);
|
|
211
|
-
if (node) {
|
|
212
|
-
node.visible = visible;
|
|
213
|
-
} else {
|
|
214
|
-
console.warn(`[MenuFlow] Node "${query}" not found.`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SettingsWindow — behavior for the settings-window prefab.
|
|
3
|
-
*
|
|
4
|
-
* Attach to the prefab root. The Music / SFX checkboxes mute or restore the
|
|
5
|
-
* engine audio buses (`scene.audio.setBusVolume`), CLOSE hides the window.
|
|
6
|
-
* Node lookups are relative to the prefab subtree so multiple instances work.
|
|
7
|
-
*
|
|
8
|
-
* Handlers are **intent-first**: each behaviour lives in a named public method
|
|
9
|
-
* marked `Intent:`, and the signal handlers below do nothing but call one. The
|
|
10
|
-
* two switches are also registered as commands — `settings.toggle-music` /
|
|
11
|
-
* `settings.toggle-sfx`, namespaced so this prefab's intents never collide with
|
|
12
|
-
* the menu's `open-settings` / `close-settings` — so a test flips sound without
|
|
13
|
-
* hunting for a checkbox on screen.
|
|
14
|
-
*
|
|
15
|
-
* Note on the checkboxes: they are event-driven, not polled. `Checkbox2D` splits
|
|
16
|
-
* its signals the way Godot does — `click` is the POINTER signal ("the box was
|
|
17
|
-
* clicked"), `toggled` is the STATE signal ("the box is now checked/unchecked"),
|
|
18
|
-
* emitted after the flip with the new value as its payload. Anything that
|
|
19
|
-
* applies the state connects to `toggled`, so the handler can simply read
|
|
20
|
-
* `checked` (or take the payload) and get the value the user just chose.
|
|
21
|
-
*/
|
|
22
|
-
import { Script, Checkbox2D, type NodeBase, type PropertySchema } from '@pix3/runtime';
|
|
23
|
-
|
|
24
|
-
/** One live signal connection, kept so `onDetach` can take it back down. */
|
|
25
|
-
interface SignalBinding {
|
|
26
|
-
node: NodeBase;
|
|
27
|
-
signal: string;
|
|
28
|
-
handler: () => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class SettingsWindow extends Script {
|
|
32
|
-
private musicToggle: Checkbox2D | null = null;
|
|
33
|
-
private sfxToggle: Checkbox2D | null = null;
|
|
34
|
-
private readonly bindings: SignalBinding[] = [];
|
|
35
|
-
private disposeCommands: (() => void)[] = [];
|
|
36
|
-
|
|
37
|
-
constructor(id: string, type: string) {
|
|
38
|
-
super(id, type);
|
|
39
|
-
this.config = {
|
|
40
|
-
musicToggle: 'music-toggle',
|
|
41
|
-
sfxToggle: 'sfx-toggle',
|
|
42
|
-
closeButton: 'close-button',
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static getPropertySchema(): PropertySchema {
|
|
47
|
-
return {
|
|
48
|
-
nodeType: 'SettingsWindow',
|
|
49
|
-
properties: [
|
|
50
|
-
{
|
|
51
|
-
name: 'musicToggle',
|
|
52
|
-
type: 'string',
|
|
53
|
-
ui: { label: 'Music Checkbox', group: 'Settings' },
|
|
54
|
-
getValue: s => (s as SettingsWindow).config.musicToggle,
|
|
55
|
-
setValue: (s, v) => {
|
|
56
|
-
(s as SettingsWindow).config.musicToggle = typeof v === 'string' ? v : '';
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: 'sfxToggle',
|
|
61
|
-
type: 'string',
|
|
62
|
-
ui: { label: 'SFX Checkbox', group: 'Settings' },
|
|
63
|
-
getValue: s => (s as SettingsWindow).config.sfxToggle,
|
|
64
|
-
setValue: (s, v) => {
|
|
65
|
-
(s as SettingsWindow).config.sfxToggle = typeof v === 'string' ? v : '';
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: 'closeButton',
|
|
70
|
-
type: 'string',
|
|
71
|
-
ui: { label: 'Close Button', group: 'Settings' },
|
|
72
|
-
getValue: s => (s as SettingsWindow).config.closeButton,
|
|
73
|
-
setValue: (s, v) => {
|
|
74
|
-
(s as SettingsWindow).config.closeButton = typeof v === 'string' ? v : '';
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
groups: { Settings: { label: 'Settings Window', expanded: true } },
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
onStart(): void {
|
|
83
|
-
this.musicToggle = this.findInSubtree(String(this.config.musicToggle ?? ''));
|
|
84
|
-
this.sfxToggle = this.findInSubtree(String(this.config.sfxToggle ?? ''));
|
|
85
|
-
|
|
86
|
-
// Match the buses to the boxes as authored, before anyone clicks anything:
|
|
87
|
-
// without the old per-frame read, nothing else would ever apply the initial state.
|
|
88
|
-
if (this.musicToggle) {
|
|
89
|
-
this.setMusicEnabled(this.musicToggle.checked);
|
|
90
|
-
}
|
|
91
|
-
if (this.sfxToggle) {
|
|
92
|
-
this.setSfxEnabled(this.sfxToggle.checked);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.connectToggle(this.musicToggle, enabled => this.setMusicEnabled(enabled));
|
|
96
|
-
this.connectToggle(this.sfxToggle, enabled => this.setSfxEnabled(enabled));
|
|
97
|
-
|
|
98
|
-
// The commands flip the BOX, not the bus: `toggled` then applies the bus, so
|
|
99
|
-
// a dispatched intent leaves exactly the state a tap would (checkmark, bus,
|
|
100
|
-
// snapshot). Applying the bus here instead would drift the two apart, and
|
|
101
|
-
// dispatching from the `toggled` handler would make the command dispatch
|
|
102
|
-
// itself — the registry caps that recursion, but the loop is the bug.
|
|
103
|
-
const commands = this.scene?.commands;
|
|
104
|
-
this.disposeCommands = [
|
|
105
|
-
commands?.register('settings.toggle-music', () => this.musicToggle?.toggle(), {
|
|
106
|
-
description: 'Flip the music checkbox, muting or restoring the music bus.',
|
|
107
|
-
}),
|
|
108
|
-
commands?.register('settings.toggle-sfx', () => this.sfxToggle?.toggle(), {
|
|
109
|
-
description: 'Flip the SFX checkbox, muting or restoring the sound-effects bus.',
|
|
110
|
-
}),
|
|
111
|
-
].filter((dispose): dispose is () => void => dispose !== undefined);
|
|
112
|
-
|
|
113
|
-
const closeButton = this.findInSubtree<NodeBase>(String(this.config.closeButton ?? ''));
|
|
114
|
-
if (closeButton) {
|
|
115
|
-
this.bind(closeButton, 'pressed', () => this.closeWindow());
|
|
116
|
-
} else {
|
|
117
|
-
console.warn('[SettingsWindow] Close button not found.');
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
onDetach(): void {
|
|
122
|
-
for (const binding of this.bindings) {
|
|
123
|
-
binding.node.disconnect(binding.signal, this, binding.handler);
|
|
124
|
-
}
|
|
125
|
-
this.bindings.length = 0;
|
|
126
|
-
// A settings window can be freed while its scene keeps running, and a
|
|
127
|
-
// command pointing at a detached prefab is exactly the dead intent the
|
|
128
|
-
// registry's scene-scoped lifetime is meant to prevent.
|
|
129
|
-
for (const dispose of this.disposeCommands) {
|
|
130
|
-
dispose();
|
|
131
|
-
}
|
|
132
|
-
this.disposeCommands = [];
|
|
133
|
-
this.musicToggle = null;
|
|
134
|
-
this.sfxToggle = null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Intent: hide the settings window. */
|
|
138
|
-
closeWindow(): void {
|
|
139
|
-
if (this.node) {
|
|
140
|
-
this.node.visible = false;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** Intent: mute or restore the music bus. */
|
|
145
|
-
setMusicEnabled(enabled: boolean): void {
|
|
146
|
-
if (!this.scene) {
|
|
147
|
-
return; // No audio yet — the next click applies it.
|
|
148
|
-
}
|
|
149
|
-
this.scene.audio.setBusVolume('music', enabled ? 1 : 0);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Intent: mute or restore the sound-effects bus. */
|
|
153
|
-
setSfxEnabled(enabled: boolean): void {
|
|
154
|
-
if (!this.scene) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
this.scene.audio.setBusVolume('sfx', enabled ? 1 : 0);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Call `apply` with the checkbox's new state. `toggled` fires once the flip is
|
|
162
|
-
* applied, so `checked` is the value the user just chose.
|
|
163
|
-
*/
|
|
164
|
-
private connectToggle(toggle: Checkbox2D | null, apply: (enabled: boolean) => void): void {
|
|
165
|
-
if (!toggle) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
this.bind(toggle, 'toggled', () => apply(toggle.checked));
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Connect a signal and remember it, so `onDetach` leaves no listener behind. */
|
|
172
|
-
private bind(node: NodeBase, signal: string, handler: () => void): void {
|
|
173
|
-
node.connect(signal, this, handler);
|
|
174
|
-
this.bindings.push({ node, signal, handler });
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** Search this prefab's subtree by node id or name (instance-safe). */
|
|
178
|
-
private findInSubtree<T extends NodeBase = NodeBase>(query: string): T | null {
|
|
179
|
-
if (!query || !this.node) {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
const visit = (node: NodeBase): NodeBase | null => {
|
|
183
|
-
if (node.nodeId === query || node.name === query) {
|
|
184
|
-
return node;
|
|
185
|
-
}
|
|
186
|
-
for (const child of node.children) {
|
|
187
|
-
if (child instanceof Object && 'nodeId' in child) {
|
|
188
|
-
const found = visit(child as NodeBase);
|
|
189
|
-
if (found) {
|
|
190
|
-
return found;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
195
|
-
};
|
|
196
|
-
return visit(this.node) as T | null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
id: minigame-2d
|
|
2
|
-
title: Minigame 2D
|
|
3
|
-
description: Separate menu + game scenes with a scene transition, and a reusable settings-window prefab (music/SFX toggles wired to audio buses).
|
|
4
|
-
projectType: 2d
|
|
5
|
-
targetPlatform: universal
|
|
6
|
-
viewport: { width: 1920, height: 1080 }
|
|
7
|
-
# The build/full-run entry scene (Project Settings → Default Export Scene Path).
|
|
8
|
-
# The menu boots first; PLAY transitions to main.pix3scene (the game).
|
|
9
|
-
entryScene: scenes/menu.pix3scene
|
|
10
|
-
order: 50
|