@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,4 +1,4 @@
|
|
|
1
|
-
<!-- Pix3 agent kit
|
|
1
|
+
<!-- Pix3 agent kit 2.0.0-alpha.0 — generated from docs/pix3-specification.md ("Scene File Format"). Do not edit: pix3 kit --update regenerates it. -->
|
|
2
2
|
# `.pix3scene` — the engine specification
|
|
3
3
|
|
|
4
4
|
Excerpts of the engine's specification, as shipped with this CLI. `SKILL.md` beside this file
|
|
@@ -11,14 +11,14 @@ layout (`res://` is always relative to the project root). Source paths quoted he
|
|
|
11
11
|
|
|
12
12
|
- Declarative: The file describes the composition and structure of the scene, not the process of its creation.
|
|
13
13
|
- Asset Referencing: Assets (models, textures) are not embedded in the file but are referenced via relative paths with a res:// prefix (path from the project root).
|
|
14
|
-
- Flat Asset Layout: a game project keeps one folder per asset type **at its root** — `scenes/`, `sprites/` (images/textures), `models/`, `audio/`, `fonts/`, `spine/` (skeleton + atlas + pages together), `scripts/`, `locales/` — with free subdivision inside (`sprites/ui/…`). There is no `assets/` wrapper folder;
|
|
15
|
-
- SVG textures: an `.svg` works as a `Sprite2D` / `Button2D` texture only when its root `<svg>` carries `xmlns="http://www.w3.org/2000/svg"` and px `width`/`height` (a viewBox alone has no intrinsic size), it is self-contained, and the Blob it is decoded from is typed `image/svg+xml` —
|
|
14
|
+
- Flat Asset Layout: a game project keeps one folder per asset type **at its root** — `scenes/`, `sprites/` (images/textures), `models/`, `audio/`, `fonts/`, `spine/` (skeleton + atlas + pages together), `scripts/`, `locales/` — with free subdivision inside (`sprites/ui/…`). There is no `assets/` wrapper folder; `pix3 new` creates this layout.
|
|
15
|
+
- SVG textures: an `.svg` works as a `Sprite2D` / `Button2D` texture only when its root `<svg>` carries `xmlns="http://www.w3.org/2000/svg"` and px `width`/`height` (a viewBox alone has no intrinsic size), it is self-contained, and the Blob it is decoded from is typed `image/svg+xml` — the editor types the bytes it reads by extension (`packages/editor-core/src/services/project/file-content-type.ts`) and a build embeds every asset with its type (`packages/vite-plugin/src/files/content-type.ts`). `pix3 validate` checks referenced SVGs (`E_SVG_INVALID`, `E_SVG_NO_SIZE`, `W_SVG_VIEWBOX_ONLY`, `W_SVG_EXTERNAL_REF`).
|
|
16
16
|
- Composition: Complex scenes are assembled from simpler ones by instantiating other scene files.
|
|
17
17
|
- Unambiguous Structure: An explicit children key is used to denote the list of child nodes, which separates the hierarchy from the properties of the node itself.
|
|
18
18
|
- Unique Identification: Every node must have an id field. The value is a short, cryptographically secure unique identifier (similar to Nano ID) to provide a balance between file readability and the absolute reliability of references.
|
|
19
19
|
- Versioned Schema: Each file includes a `version` field; migrations are maintained in the SceneManager and run automatically on load.
|
|
20
20
|
- Conflict Resolution: Instance overrides always win over parent definitions. Duplicate IDs trigger validation errors during import.
|
|
21
|
-
- Forgiving Vocabulary: a `type:` the loader does not know does **not** fail the load — the node is built as a bare `NodeBase` placeholder that does nothing, so a scene written by a newer version (or with a typo) stays openable and editable. Because such a node is otherwise indistinguishable from a working one, it is reported on three surfaces: the renderability lint (`inert-nodes
|
|
21
|
+
- Forgiving Vocabulary: a `type:` the loader does not know does **not** fail the load — the node is built as a bare `NodeBase` placeholder that does nothing, so a scene written by a newer version (or with a typo) stays openable and editable. Because such a node is otherwise indistinguishable from a working one, it is reported on three surfaces: the renderability lint (`inert-nodes`, a warning when play starts and in `pix3_game_run` observations), the loader diagnostic (`describeUnknownNodeType`, which suggests the nearest known name), and the Scene Tree row, which carries a warn badge and states the reason in its tooltip. `isInertNode` (`packages/runtime/src/core/renderability-lint.ts`) is the one predicate behind all three; `pix3 validate` reports the same node as `E_UNKNOWN_NODE_TYPE`.
|
|
22
22
|
|
|
23
23
|
## Skinned 2D UI controls
|
|
24
24
|
|
|
@@ -57,13 +57,9 @@ The four `sliceBorderLeft/Right/Top/Bottom` scalars are the same nine-slice inse
|
|
|
57
57
|
fill; the slider thumb is drawn at its authored size and is never sliced. A fill
|
|
58
58
|
that shrinks with `value` is **re-cut** at the new width, so its end caps keep
|
|
59
59
|
their pixel size. All-zero (the default) is the plain stretch. A sliced skin opts
|
|
60
|
-
out of the 2D quad batcher, because the batcher extracts four
|
|
60
|
+
out of the 2D quad batcher, because the batcher extracts four _unit_ corners, and
|
|
61
61
|
is excluded from the pre-launch texture atlas for the same reason.
|
|
62
62
|
|
|
63
|
-
Sprites for every one of these slots — and the matching insets — can be generated
|
|
64
|
-
and applied without leaving the editor; the files and their manifest are specified
|
|
65
|
-
in **§6.23 UI Kit Assets**.
|
|
66
|
-
|
|
67
63
|
```yaml
|
|
68
64
|
- id: 'q7Bd-1mZ_kTn0-Vx4pA'
|
|
69
65
|
type: 'Button2D'
|
|
@@ -114,7 +110,7 @@ in **§6.23 UI Kit Assets**.
|
|
|
114
110
|
A `SpineSkeleton2D` references its Spine export by two `res://` paths — the
|
|
115
111
|
skeleton (`.json` or `.skel`) and the atlas (`.atlas`). The atlas' page images are
|
|
116
112
|
NOT listed: they are named inside the atlas text and resolved relative to it (the
|
|
117
|
-
|
|
113
|
+
build's asset scan, `packages/vite-plugin/src/build/scan.ts`, parses `.atlas` files for the same reason). `texture` is an
|
|
118
114
|
optional single-page override.
|
|
119
115
|
|
|
120
116
|
```yaml
|
|
@@ -151,14 +147,30 @@ Groups provide runtime categorization for nodes (for example, `enemies`, `ui`, `
|
|
|
151
147
|
- `removeFromGroup(group)`
|
|
152
148
|
- `isInGroup(group)`
|
|
153
149
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
`SceneManager` provides group-based queries and invocation:
|
|
150
|
+
A node's groups are its own `groups` set (names trimmed); it is the only membership record —
|
|
151
|
+
nothing indexes it, so a change is seen by the next query.
|
|
157
152
|
|
|
158
|
-
|
|
159
|
-
- `callGroup(group, method, ...args)` - calls matching component methods across grouped nodes.
|
|
153
|
+
#### 6.19.2 Scene API
|
|
160
154
|
|
|
161
|
-
|
|
155
|
+
A script queries and calls groups through `this.scene` (`SceneService`), during play and in the
|
|
156
|
+
exported player alike:
|
|
157
|
+
|
|
158
|
+
- `getNodesInGroup(group)` - the running scene's nodes in `group`, in tree order (depth-first,
|
|
159
|
+
a parent before its children, roots in order); empty outside a running game. Read at call
|
|
160
|
+
time from the nodes' `groups`, so `addToGroup` / `removeFromGroup`, a spawned prefab, a freed
|
|
161
|
+
node and `changeScene` all show in the next call. Like `findNode`, it walks the running scene
|
|
162
|
+
only — not the autoloads' hidden root, and not the editor's authored graph (play runs on a
|
|
163
|
+
clone).
|
|
164
|
+
- `callGroup(group, method, ...args)` - Godot's `call_group`: calls `method(...args)` on every
|
|
165
|
+
component of every node in the group that has a function of that name, and returns how many
|
|
166
|
+
calls it made. The node list is taken before the first call. A component without the method
|
|
167
|
+
is skipped; when the group has nodes but no component has the method, one warning names the
|
|
168
|
+
group and the method (no throw). An empty group calls nothing, silently.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
for (const enemy of this.scene.getNodesInGroup('enemies')) enemy.visible = false;
|
|
172
|
+
this.scene.callGroup('enemies', 'onAlarm', 3);
|
|
173
|
+
```
|
|
162
174
|
|
|
163
175
|
#### 6.19.3 Serialization
|
|
164
176
|
|
|
@@ -3,7 +3,7 @@ name: pix3-scripts
|
|
|
3
3
|
description: How to write a Pix3 game script (scripts/*.ts, `export class X extends Script`, attached as `user:X`) — lifecycle, the required getPropertySchema, config, node lookup, signals, transforms, spawning, input, juice/tweens/audio, 2D physics, scene changes, the built-in core:* components and their config keys (reference.md), and the traps that compile clean and break at runtime. Use BEFORE writing or editing any file in scripts/ or attaching a core: component.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- Pix3 agent kit
|
|
6
|
+
<!-- Pix3 agent kit 2.0.0-alpha.0 -->
|
|
7
7
|
|
|
8
8
|
# Writing a script
|
|
9
9
|
|
|
@@ -13,7 +13,8 @@ it. Import only from `@pix3/runtime`, `three`, and other files under the scripts
|
|
|
13
13
|
against the runtime's declarations (`.pix3/types/`, or your `node_modules` when the project
|
|
14
14
|
has its own `tsconfig.json`); your editor sees the same types through the root `tsconfig.json`.
|
|
15
15
|
|
|
16
|
-
`reference.md` beside this file has the
|
|
16
|
+
`reference.md` beside this file has the rest of the API (wheel and multi-touch input, the full 2D
|
|
17
|
+
physics surface, `registerGameDebug`, cutscenes, multiplayer), the capability catalog and
|
|
17
18
|
**every `core:` component with its config keys and defaults** — reach for a built-in before
|
|
18
19
|
writing a script for the same effect.
|
|
19
20
|
|
|
@@ -130,7 +131,8 @@ before children's.
|
|
|
130
131
|
`node.position = …` throws at runtime.
|
|
131
132
|
- 2D space: design pixels, origin centre, X right, **Y up**. YAML rotation is degrees;
|
|
132
133
|
`rotation.z` is radians.
|
|
133
|
-
- `visible`, `name`, `id
|
|
134
|
+
- `visible`, `name`, `nodeId` (the scene id; `id` is three.js's number), `children`,
|
|
135
|
+
`parentNode`, `findById(nodeId)`, `findByName(name)`,
|
|
134
136
|
`adoptChild(child)`, `queueFree()` (safe inside `onUpdate`), `getComponent(Class)`,
|
|
135
137
|
`addComponent(c)`, `removeComponent(c)`.
|
|
136
138
|
- 2D node props as fields: `width`, `height`, `opacity`, `zIndex`, `blendMode`. Assigning
|
|
@@ -155,16 +157,17 @@ before children's.
|
|
|
155
157
|
`node.disconnect('name', this, handler)`. Every handler connected with `this` as the target
|
|
156
158
|
is **auto-disconnected on detach, like own-node signals** — including ones on another node
|
|
157
159
|
(`gameRoot.connect('score', this, this.onScore)`). Window listeners, store subscriptions and
|
|
158
|
-
timers are still yours to clean up in `onDetach` (
|
|
160
|
+
timers are still yours to clean up in `onDetach` ("Hosting an existing game", below). Keep the
|
|
159
161
|
handler in a field (no `.bind(this)`) so an early manual `disconnect` gets the same function.
|
|
160
162
|
- UI controls (`Button2D`, `Slider2D`, `Checkbox2D`, `Joystick2D`, …) emit `pressed` (touch
|
|
161
163
|
went down inside), `released`, `click` (down and up inside — a completed tap),
|
|
162
164
|
`pointerdown`, `pointerup`; `Checkbox2D` also `toggled`; `Label2D` emits
|
|
163
|
-
`typewriter-complete`.
|
|
164
|
-
cancelled by sliding off — use it for menus and anything costly
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
`typewriter-complete`. Wire game buttons to `pressed` (instant); `click` can be
|
|
166
|
+
cancelled by sliding off — use it for menus and anything costly (purchases):
|
|
167
|
+
`button.connect('click', this, this.onBuy)`.
|
|
168
|
+
- Scripts talk through signals on a shared node such as `game-root` (`score-changed`,
|
|
169
|
+
`lives-changed`, `game-won`, `game-lost`, …). Listen to the ones that exist; add new signal
|
|
170
|
+
names for new mechanics; never rename existing ones.
|
|
168
171
|
|
|
169
172
|
## `this.scene`
|
|
170
173
|
|
|
@@ -175,7 +178,8 @@ before children's.
|
|
|
175
178
|
- Spawn: `const n = await scene.instantiate('res://scenes/prefabs/x.pix3scene', { parent, instanceId })`
|
|
176
179
|
(`parent` = node | query; prefab = a scene file with one root). Despawn: `n.queueFree()`.
|
|
177
180
|
- Scene change: `await scene.changeScene('res://scenes/menu.pix3scene', { transition: 'fade' | 'none', durationSec: 0.3, onLoaded })`
|
|
178
|
-
(`durationSec` is each of fade-out and fade-in).
|
|
181
|
+
(`durationSec` is each of fade-out and fade-in). State that must survive it is an autoload:
|
|
182
|
+
`scene.getAutoload(GameState)` (recipe: `pix3-scene-format/project-files.md` → "An autoload").
|
|
179
183
|
- Time: `scene.time.hitstop(ms)` — no options; only on a contact START, never per frame.
|
|
180
184
|
`scene.time.slowMotion(scale, { durationMs, blendMs })` — both real-time ms; no `durationMs`
|
|
181
185
|
= until `scene.time.reset()`.
|
|
@@ -201,18 +205,18 @@ before children's.
|
|
|
201
205
|
Sound without the editor: `pix3 sfx coin` writes `audio/coin.wav` (presets `coin`, `jump`,
|
|
202
206
|
`hit`, `explosion`, `powerup`, `click`, or words: `pix3 sfx "short high coin pickup" --out audio/pickup.wav`;
|
|
203
207
|
`--seed <n>` for a variation) — offline, no key; `.wav` plays everywhere `.ogg`/`.mp3` do.
|
|
204
|
-
Sound cannot be proven audible from the
|
|
205
|
-
a real user gesture, and
|
|
208
|
+
Sound cannot be proven audible from the bridge: the browser only starts Web Audio after
|
|
209
|
+
a real user gesture, and a synthetic click from chrome-devtools-mcp is not one. Report a sound as
|
|
206
210
|
"code path verified, audibility not" and ask the human to listen.
|
|
207
211
|
- Overlap queries without physics response: `scene.collision2d.overlapPoint(x, y, group?)`,
|
|
208
212
|
`overlapCircle(x, y, r, group?)`, `overlapRect(cx, cy, w, h, group?)`, `raycast(…)` over
|
|
209
|
-
nodes carrying `core:Hitbox2D` (
|
|
213
|
+
nodes carrying `core:Hitbox2D` (tappers, collectors, dodgers).
|
|
210
214
|
- 2D physics with response: `core:PhysicsBody2D` + `core:Collider2D` on the same node;
|
|
211
215
|
`scene.physics2d.getBody(node)` → `setVelocity`, `applyImpulse`, `teleport`. Never
|
|
212
|
-
hand-write a 2D solver, never import rapier for 2D. (
|
|
213
|
-
|
|
216
|
+
hand-write a 2D solver, never import rapier for 2D. (A game that already runs its own
|
|
217
|
+
solver keeps it — do not add engine physics on top of it.)
|
|
214
218
|
- Intents: `scene.commands.register('name', handler, { description })`, `dispatch(name)`.
|
|
215
|
-
|
|
219
|
+
Register one per game and publish state with `registerGameDebug` from one rules script —
|
|
216
220
|
when you add a mechanic, add its field to that snapshot instead of registering a second one.
|
|
217
221
|
|
|
218
222
|
## `this.input`
|
|
@@ -223,10 +227,10 @@ before children's.
|
|
|
223
227
|
`scene.getPointer2DWorldPosition()` for world), `input.isPointerOverUI(pointerId)`,
|
|
224
228
|
`input.getButton(name)`, `input.getAxis(name)`.
|
|
225
229
|
- Keys held: `input.getButton('Key_ArrowLeft')`, `getButton('Key_D')` — `Key_` + the
|
|
226
|
-
`KeyboardEvent.code` (
|
|
230
|
+
`KeyboardEvent.code` (the form player controllers use). In `input.keyEvents`
|
|
227
231
|
compare `event.code` (`'KeyW'`, `'ArrowUp'`, `'Space'`) — case-sensitive.
|
|
228
232
|
|
|
229
|
-
## Hosting an existing game
|
|
233
|
+
## Hosting an existing game
|
|
230
234
|
|
|
231
235
|
- **Use the game's own channel.** Before wiring a script, find how the game talks:
|
|
232
236
|
`grep -rn "CustomEvent\|dispatchEvent\|addEventListener\|subscribe(\|EventTarget\|emit(" src scripts`.
|
|
@@ -259,23 +263,29 @@ before children's.
|
|
|
259
263
|
(or on any node the game positions itself) is overwritten every frame and never shows. Use the
|
|
260
264
|
game's own shake (`grep -rn "shake" src scripts`) when it has one; the same goes for its own
|
|
261
265
|
sound and tween helpers.
|
|
262
|
-
- The project's own `AGENTS.md` wins on process (planning first, where config goes); this kit
|
|
263
|
-
wins on Pix3 facts (YAML, runtime API, `pix3 check`).
|
|
264
266
|
|
|
265
267
|
## Traps (compile clean, break at runtime)
|
|
266
268
|
|
|
267
269
|
- Assigning `position` / `rotation` / `scale` throws. Mutate them.
|
|
268
270
|
- A component that throws in `onStart` / `onUpdate` is **auto-disabled** and the game keeps
|
|
269
271
|
running — the thing just freezes. Guard lookups (`if (!node) { console.warn(...); return; }`).
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
`pix3_errors` right after starting play (or `pix3 smoke`'s `E_SMOKE_SCRIPT`) shows the throw —
|
|
273
|
+
read it before judging anything else.
|
|
272
274
|
- `as any` on `this.node` hides exactly the error above, and from `pix3 check`. Use
|
|
273
275
|
`instanceof` narrowing.
|
|
274
276
|
- Hitstop every frame of an overlap freezes the game — edge-trigger it.
|
|
275
277
|
- A hidden `UIControl2D` takes no input; `onUpdate` still runs on hidden nodes.
|
|
276
278
|
- `getComponent('user:X')` is wrong — pass the class.
|
|
277
|
-
-
|
|
278
|
-
|
|
279
|
+
- `event.key` depends on the layout and on Shift — match `event.code`.
|
|
280
|
+
- **Moves the wrong way** (sideways, backwards) is a math bug, and flipping signs does not
|
|
281
|
+
converge. With world +Y up, `rotation.z` turns the node's local +Y (its nose) to
|
|
282
|
+
`(-sin θ, cos θ)`: forward is `vx = -Math.sin(rot) * speed`, `vy = Math.cos(rot) * speed`, and
|
|
283
|
+
aiming the nose along `(dx, dy)` is `rotation.z = Math.atan2(-dx, dy)`.
|
|
284
|
+
- **A button does nothing**: something must `connect` to its signal (`pressed` / `click`) on the
|
|
285
|
+
node id the scene really has — check the id in `pix3 tree`, then that the script carrying the
|
|
286
|
+
handler is attached and `enabled`.
|
|
287
|
+
- Ending a run belongs to the one script that owns the rules: call it; do not show the result
|
|
288
|
+
overlay from a second place.
|
|
279
289
|
|
|
280
290
|
## Known gaps
|
|
281
291
|
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
<!-- Pix3 agent kit
|
|
1
|
+
<!-- Pix3 agent kit 2.0.0-alpha.0 — generated from docs/nodes-and-systems.md, packages/cli/kit-includes/engine-api-map.md and the runtime's component registry. Do not edit: pix3 kit --update regenerates it. -->
|
|
2
2
|
# Script API reference
|
|
3
3
|
|
|
4
|
-
Generated from the engine's own documentation and code.
|
|
5
|
-
|
|
6
|
-
`
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
`node_modules/@pix3/runtime` when the project has its own `tsconfig.json`). Source paths quoted here (`src/…`, `packages/…`) are files of the Pix3 engine repository, not of this project.
|
|
4
|
+
Generated from the engine's own documentation and code; `SKILL.md` beside it is the practical
|
|
5
|
+
part. Grep a heading instead of reading it whole. The runtime's declarations are in
|
|
6
|
+
`.pix3/types/@pix3/runtime/` (or `node_modules/@pix3/runtime` when the project has its own
|
|
7
|
+
`tsconfig.json`). Source paths quoted here (`src/…`, `packages/…`) are files of the Pix3 engine
|
|
8
|
+
repository, not of this project.
|
|
10
9
|
|
|
11
10
|
## The surface a `Script` sees
|
|
12
11
|
|
|
13
12
|
Inside any `Script` subclass:
|
|
14
13
|
|
|
15
|
-
- `this.node` — the owning `NodeBase` (transform, `visible`, `getComponent`, `addComponent`, `connect`/`emit`, `findById`/`findByName`/`findByPath`, `children`, `parentNode`). `getComponent<T>(type: new (...args) => T): T | null` takes the component **class**, not a string ID — `node.getComponent(CarController)`, importing the class by relative path (`./CarController`). There is no string-based lookup (`getComponent('user:CarController')` fails); `user:*` IDs are for
|
|
16
|
-
- `this.scene` — the `SceneService` (all of §4's `scene.*` APIs, plus `getActiveCamera()`, `getActiveCamera2D()`, `findNode(query)`, `getRootNodes()`, `getViewportInfo()`/`onViewportChanged()`/`isPortrait()`, `raycastViewport(nx,ny)`, `getAudioService`/`getAssetLoader`/`getResourceManager`/`getECSService`, plus `network` and `netNodes` for multiplayer,
|
|
14
|
+
- `this.node` — the owning `NodeBase` (transform, `visible`, `getComponent`, `addComponent`, `connect`/`emit`, `findById`/`findByName`/`findByPath`, `children`, `parentNode`). `getComponent<T>(type: new (...args) => T): T | null` takes the component **class**, not a string ID — `node.getComponent(CarController)`, importing the class by relative path (`./CarController`). There is no string-based lookup (`getComponent('user:CarController')` fails); `user:*` IDs are for scene YAML only. To fetch by hand: `node.components.find(c => c instanceof CarController)`.
|
|
15
|
+
- `this.scene` — the `SceneService` (all of §4's `scene.*` APIs, plus `getActiveCamera()`, `getActiveCamera2D()`, `findNode(query)`, `getRootNodes()`, `getNodesInGroup(group)` / `callGroup(group, method, ...args)` for node groups (spec §6.19), `getViewportInfo()`/`onViewportChanged()`/`isPortrait()`, `raycastViewport(nx,ny)`, `getAudioService`/`getAssetLoader`/`getResourceManager`/`getECSService`, plus `network` and `netNodes` for multiplayer, `commands` for named game intents, and `getAutoload(nameOrClass)` for the project's autoload singletons, §6.17 of the spec). May be `undefined` in some editor previews — guard it.
|
|
17
16
|
- `this.input` — the `InputService` (§4 Input).
|
|
18
17
|
- `this.findNode(query)` — resolve another node by id / name / slash-path, or `null` if absent (`get_node_or_null`).
|
|
19
|
-
- `this.getNode(query)` — same lookup but **throws** if the node is missing (`get_node`)
|
|
18
|
+
- `this.getNode(query)` — same lookup but **throws** if the node is missing (`get_node`); `getNode<T>(query)` types the result.
|
|
20
19
|
|
|
21
20
|
**Lifecycle:** `onAttach(node)` → `onStart()` (first frame) → `onUpdate(dt)` (every
|
|
22
21
|
frame, `dt` is scaled game time) → `onDetach()`. Define `static getPropertySchema()`
|
|
@@ -51,154 +50,82 @@ you never clone textures yourself. Author the region once and drive it from
|
|
|
51
50
|
throwing `tickEditorPreview` disables the component and surfaces the error like a
|
|
52
51
|
play-mode hook, so the editor keeps running.
|
|
53
52
|
|
|
54
|
-
## Engine API map
|
|
55
|
-
|
|
56
|
-
### A `Script`
|
|
57
|
-
|
|
58
|
-
- Lifecycle: `onAttach(node)` → `onStart()` (first frame) → `onUpdate(dt)` (scaled seconds;
|
|
59
|
-
frozen by hitstop) → `onDetach()`. Defaults in the constructor's `this.config = {…}`;
|
|
60
|
-
`static getPropertySchema()` exposes them to the inspector / `set_component_property`.
|
|
61
|
-
- `this.node` (owner `NodeBase`), `this.scene` (`SceneService`, guard for `undefined` in
|
|
62
|
-
editor previews), `this.input` (`InputService`), `this.findNode(q)` (id / name / path →
|
|
63
|
-
node or null), `this.getNode(q)` (throws when missing).
|
|
64
|
-
- Reach another script: `import { Other } from './Other'; this.node.getComponent(Other)` —
|
|
65
|
-
the class, never the `user:Other` string.
|
|
53
|
+
## Engine API map — beyond `SKILL.md`
|
|
66
54
|
|
|
67
55
|
### Nodes
|
|
68
56
|
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
- Signals: `node.emit(name, ...args)`, `node.connect(name, target, fn)`,
|
|
77
|
-
`node.disconnect(name, target, fn)`. A script's handlers (`target === this`) are
|
|
78
|
-
auto-disconnected on detach — on its own node and on any other node it connected to.
|
|
79
|
-
Window listeners, store subscriptions and timers are still yours to clean up.
|
|
80
|
-
**UI controls emit:** every `UIControl2D` (`Button2D`, `Slider2D`, `Checkbox2D`, `Joystick2D`,
|
|
81
|
-
`InventorySlot2D`, `ScrollContainer2D`) → `'pressed'`, `'released'`, `'click'` (a completed
|
|
82
|
-
tap — wire buttons to THIS), `'pointerdown'`, `'pointerup'`; `Checkbox2D` / `InventorySlot2D`
|
|
83
|
-
also `'toggled'`; `Label2D` → `'typewriter-complete'`. Read a control's value from its
|
|
84
|
-
properties (`Slider2D.value`, `Checkbox2D.checked`) inside the handler.
|
|
85
|
-
- **Node2D** adds design-pixel space (origin centre, X right, **Y up**): `width`, `height`,
|
|
86
|
-
`opacity` (0..1), `zIndex` (higher draws on top; ties = tree order, later sibling on top),
|
|
87
|
-
`blendMode` (`normal | additive | multiply | subtract` — `additive` for glow/VFX), anchored
|
|
88
|
-
`layout`. Construct at runtime with the same props the scene YAML uses:
|
|
89
|
-
`new ColorRect2D({ id, width, height, color })`, `new Sprite2D({ id, texturePath:
|
|
90
|
-
'res://sprites/ph-circle.png', width, height })`, `new Label2D({ id, label, labelFontSize,
|
|
91
|
-
labelColor })`, `new Group2D({ id, width, height })`, then `parent.adoptChild(node)`.
|
|
57
|
+
- `queueFree()` is safe inside `onUpdate`; `dispose()` only outside the tick.
|
|
58
|
+
- Every `UIControl2D` (`Button2D`, `Slider2D`, `Checkbox2D`, `Joystick2D`, `InventorySlot2D`,
|
|
59
|
+
`ScrollContainer2D`) emits `'pressed'`, `'released'`, `'click'`, `'pointerdown'`,
|
|
60
|
+
`'pointerup'`; `Checkbox2D` / `InventorySlot2D` also `'toggled'`. Read a control's value from
|
|
61
|
+
its properties (`Slider2D.value`, `Checkbox2D.checked`) inside the handler.
|
|
62
|
+
- Constructed at runtime with the scene YAML's property names, a `Sprite2D` too:
|
|
63
|
+
`new Sprite2D({ id, texturePath: 'res://sprites/ph-circle.png', width, height })`.
|
|
92
64
|
- 2D node types: `ColorRect2D`, `Sprite2D`, `TiledSprite2D`, `AnimatedSprite2D`, `Group2D`,
|
|
93
|
-
`Label2D`
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
post-fx, never blooms), `SpineSkeleton2D`, `Layout2D`.
|
|
65
|
+
`Label2D`, `Button2D`, `Slider2D`, `Checkbox2D`, `Bar2D`, `Joystick2D`, `InventorySlot2D`,
|
|
66
|
+
`ScrollContainer2D`, `Camera2D`, `CanvasLayer2D` (HUD layer, drawn after post-fx, never
|
|
67
|
+
blooms), `SpineSkeleton2D`.
|
|
97
68
|
- 3D node types: `Node3D`, `Camera3D`, `VirtualCamera3D`, `GeometryMesh`, `MeshInstance`,
|
|
98
69
|
`InstancedMesh3D`, `Sprite3D`, `AnimatedSprite3D`, `Particles3D`, lights (`Ambient`,
|
|
99
70
|
`Hemisphere`, `Directional`, `Point`, `Spot`), `PostProcess` (bloom / vignette /
|
|
100
71
|
chromatic aberration; `affect2D: true` for 2D scenes), `AudioPlayer`.
|
|
101
|
-
- Shader effects on a 2D node: `effects: [{ type: 'core:tint', params: { color, amount } }]`
|
|
102
|
-
(also `core:adjust`, `core:grayscale`) — the way a near-white placeholder takes a colour.
|
|
103
72
|
|
|
104
73
|
### `this.scene`
|
|
105
74
|
|
|
106
|
-
-
|
|
107
|
-
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
-
|
|
114
|
-
transition: 'fade' | 'none', durationSec: 0.3 })`; `scene.fadeToBlack(sec, onDone?)`,
|
|
75
|
+
- Cameras: `getActiveCamera()`, `getActiveCamera2D()`. `getPointer2DWorldPosition(pointerId)`
|
|
76
|
+
is null once that finger is up (Godot's `get_global_mouse_position()` for the primary one).
|
|
77
|
+
- Autoloads by name too: `scene.getAutoload<GameState>('GameState')`.
|
|
78
|
+
- Groups (a node's `groups: [enemies]` in the scene, `node.addToGroup('enemies')` at run time):
|
|
79
|
+
`scene.getNodesInGroup('enemies')` lists the running scene's members in tree order;
|
|
80
|
+
`scene.callGroup('enemies', 'onAlarm', 3)` calls that method on every component that has it
|
|
81
|
+
(returns the count, warns once when none has it).
|
|
82
|
+
- Real-time overlays that survive hitstop: `scene.fadeToBlack(sec, onDone?)`,
|
|
115
83
|
`scene.fadeFromBlack(sec, onDone?)`, `scene.switchCameraWithFade(cameraId, outSec, inSec,
|
|
116
|
-
onDone?)
|
|
117
|
-
|
|
118
|
-
-
|
|
119
|
-
`
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
`
|
|
130
|
-
|
|
131
|
-
`
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`tap | score | bounce | explosion | powerup | win | lose | laser | tick`;
|
|
145
|
-
`await play('res://sfx/hit.ogg', { bus: 'sfx', pitchVariation: 0.1, volumeVariation: 0.1 })`
|
|
146
|
-
for an authored clip; `setBusVolume(bus, v, fadeSec)`, `applySnapshot(name)`, `stopAll()`.
|
|
147
|
-
- **2D physics (`scene.physics2d`)** — the built-in rigid-body solver; never hand-write one,
|
|
148
|
-
never import rapier for 2D. Author Unity-style: `core:PhysicsBody2D` (`static | kinematic |
|
|
149
|
-
dynamic`, `mass`, `restitution`, `friction`, `linearDamping`, `fixedRotation`, `bullet`,
|
|
150
|
-
`emitContacts`) **and** `core:Collider2D` (`rect | circle | polygon | capsule`, `isSensor`,
|
|
151
|
-
`group`) on the same node; a `core:Collider2D` alone is static geometry. Gravity is
|
|
152
|
-
negative Y (`core:PhysicsWorld2D` on the root or `scene.physics2d.setGravity(0, -1800)`;
|
|
153
|
-
top-down = `(0, 0)`). From a script: `const body = scene.physics2d.getBody(node)` →
|
|
154
|
-
`setVelocity(vx, vy)`, `applyImpulse(ix, iy)`, `applyForce`, `teleport(x, y, rot?)`, `wake()`,
|
|
155
|
-
`velocityX/Y`, `isSleeping`; queries `raycast(...)`, `overlapCircle(...)`,
|
|
156
|
-
`overlapRect(...)`, `moveAndSlide(...)`; signals on the node: `body-entered` /
|
|
157
|
-
`body-exited` (sensors), `contact-started` / `contact-ended` (with `emitContacts`).
|
|
158
|
-
Joints: `core:RevoluteJoint2D`.
|
|
159
|
-
- 2D overlap-only queries (no response): `scene.collision2d.overlapPoint(x, y, group?)`,
|
|
160
|
-
`overlapCircle(x, y, r, group?)`, `overlapRect(cx, cy, w, h, group?)`, `raycast(x1, y1, x2,
|
|
161
|
-
y2, group?)` over nodes carrying `core:Hitbox2D`.
|
|
162
|
-
- Game intents: `scene.commands.register(name, handler, { description })`,
|
|
163
|
-
`dispatch(name, args)`, `list()`, `log` — how tests and the menu drive the game without
|
|
164
|
-
clicking.
|
|
165
|
-
- Debug surface for `game_observe` / `game_run`: `import { registerGameDebug } from
|
|
166
|
-
'@pix3/runtime'`; in `onStart`: `this.disposeDebug = registerGameDebug({ name, version: 1,
|
|
167
|
-
snapshot: () => ({ …JSON-safe state… }), inspect: (query, args) => …, action: (name, args)
|
|
168
|
-
=> … })`, call the returned disposer in `onDetach`. Global and last-wins — only the running
|
|
169
|
-
scene's flow/rules script registers one; recipes already do (extend theirs, do not add a
|
|
170
|
-
second).
|
|
171
|
-
- Cutscenes: `scene.cutscene.playCinematic(id, {…})`; camera blends via `core:CameraBrain`
|
|
172
|
-
+ `VirtualCamera3D`.
|
|
84
|
+
onDone?)`, `scene.flash({ color, intensity, durationSec })`.
|
|
85
|
+
- Time: `scene.time.setScale`, `reset`, `scale`, `isFrozen` beside `hitstop` / `slowMotion`.
|
|
86
|
+
- Juice presets as components: `core:BurstOnSignal`, `core:SfxOnSignal`, `core:Shake`,
|
|
87
|
+
`core:PunchScale`, `core:PopIn`, `core:Fade`.
|
|
88
|
+
- Tweens: `finished` is a `Promise<'completed' | 'cancelled'>`; `props` may also be `scale` as a
|
|
89
|
+
number (uniform) or `{x, y}`, or any numeric dotted path; on a plain object every key is a
|
|
90
|
+
dotted path. `crossFade` is the in-scene counterpart of `changeScene`'s fade (state and panel
|
|
91
|
+
switches). A trail is freed with its node.
|
|
92
|
+
- Audio: `setBusVolume(bus, v, fadeSec)`, `applySnapshot(name)`, `stopAll()`.
|
|
93
|
+
- **2D physics (`scene.physics2d`)**: `core:PhysicsBody2D` (`static | kinematic | dynamic`,
|
|
94
|
+
`mass`, `restitution`, `friction`, `linearDamping`, `fixedRotation`, `bullet`, `emitContacts`)
|
|
95
|
+
**and** `core:Collider2D` (`rect | circle | polygon | capsule`, `isSensor`, `group`) on the
|
|
96
|
+
same node; a `core:Collider2D` alone is static geometry. Gravity is negative Y
|
|
97
|
+
(`core:PhysicsWorld2D` on the root or `scene.physics2d.setGravity(0, -1800)`; top-down =
|
|
98
|
+
`(0, 0)`). A body: `applyForce`, `wake()`, `velocityX/Y`, `isSleeping` beside `setVelocity`,
|
|
99
|
+
`applyImpulse`, `teleport(x, y, rot?)`; queries `raycast(...)`, `overlapCircle(...)`,
|
|
100
|
+
`overlapRect(...)`, `moveAndSlide(...)`; signals on the node: `body-entered` / `body-exited`
|
|
101
|
+
(sensors), `contact-started` / `contact-ended` (with `emitContacts`). Joints:
|
|
102
|
+
`core:RevoluteJoint2D`.
|
|
103
|
+
- Game intents: `scene.commands` also has `list()` and `log` — how tests and the menu drive the
|
|
104
|
+
game without clicking.
|
|
105
|
+
- The debug surface `pix3_game_run`'s `gameState` predicates read: `import { registerGameDebug }
|
|
106
|
+
from '@pix3/runtime'`; in `onStart`: `this.disposeDebug = registerGameDebug({ name, version: 1,
|
|
107
|
+
snapshot: () => ({ …JSON-safe state… }), inspect: (query, args) => …, action: (name, args) =>
|
|
108
|
+
… })`, call the returned disposer in `onDetach`. Global and last-wins — only the running
|
|
109
|
+
scene's flow/rules script registers one (extend it, do not add a second).
|
|
110
|
+
- Cutscenes: `scene.cutscene.playCinematic(id, {…})`; camera blends via `core:CameraBrain` +
|
|
111
|
+
`VirtualCamera3D`.
|
|
173
112
|
- Multiplayer: `scene.network`, `scene.netNodes`, `core:NetworkedNode`,
|
|
174
113
|
`core:ReplicatedTransform` (only when the brief says multiplayer).
|
|
175
114
|
|
|
176
115
|
### `this.input`
|
|
177
116
|
|
|
178
|
-
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
`
|
|
186
|
-
|
|
187
|
-
(`Action_Primary`, arrows / WASD axes), `lock()` / `unlock()`.
|
|
188
|
-
- Keyboard: compare `event.code` (`'KeyW'`, `'ArrowUp'`), case-sensitive.
|
|
189
|
-
- A tap-to-place mechanic is: on a `'down'` event, `scene.getPointer2DWorldPosition(ev.pointerId)`,
|
|
117
|
+
- Wheel: `input.wheelDelta` (plain scroll wheel only); `input.wheelZoomDelta` (Ctrl/⌘ + wheel
|
|
118
|
+
and trackpad pinch — use it for camera zoom, it is excluded from `wheelDelta`);
|
|
119
|
+
`input.wheelModifiers` (`{ ctrl, meta, shift, alt }`). The canvas already `preventDefault()`s
|
|
120
|
+
every wheel event, so ctrl-wheel never zooms the page.
|
|
121
|
+
- Fingers: `input.getActivePointers()`, `input.getPointer(id)`, `input.pointerDownCount`. Gate
|
|
122
|
+
gestures on `input.isPointerOverUI(pointerId)`, not on `isHoveringUI`.
|
|
123
|
+
- Actions: `getButton('Action_Primary')`, the arrows / WASD axes of `getAxis(name)`;
|
|
124
|
+
`lock()` / `unlock()`.
|
|
125
|
+
- A tap-to-place mechanic: on a `'down'` event, `scene.getPointer2DWorldPosition(ev.pointerId)`,
|
|
190
126
|
convert to the board's local space (`board.worldToLocal` or subtract the board's world
|
|
191
127
|
position), place the node, `scene.juice.burst({ x, y })`, `scene.audio.sfx('tap')`.
|
|
192
128
|
|
|
193
|
-
### Traps
|
|
194
|
-
|
|
195
|
-
- Assigning `position` / `rotation` / `scale` throws — mutate them.
|
|
196
|
-
- A component that throws in `onStart` / `onUpdate` is auto-disabled and the game keeps
|
|
197
|
-
running — `read_errors` after `play_start` is the only way to see it.
|
|
198
|
-
- `this.node as any` disables exactly the check that would have caught the above.
|
|
199
|
-
- Hitstop every frame while an overlap lasts freezes the contact forever — edge-trigger it.
|
|
200
|
-
- A hidden `UIControl2D` takes no input; `tick` still runs on hidden nodes.
|
|
201
|
-
|
|
202
129
|
## Capability catalog — nodes
|
|
203
130
|
|
|
204
131
|
Add via the editor **Create** menu / `Create*Command`, or author in `.pix3scene`
|
|
@@ -241,7 +168,7 @@ tables: node-types-reference.md.
|
|
|
241
168
|
}
|
|
242
169
|
```
|
|
243
170
|
|
|
244
|
-
**Character with variants/states (weapon skins, outfits)** — one `.pix3anim` whose clips are named `<variant>.<state>` (`sword.idle`, `sword.attack`, `bow.idle`, …; a clip without the separator is a variant-less state such as `die`), an `AnimatedSprite2D` as the prefab root (`sizeMode: native` — every frame carries `sourceSize`; the frame `anchor` at the feet, so the node's position is where the character stands) and **`core:CharacterVisual2D`** on that same node (`variant`, `state`, `separator`, optional `spriteNodeId`; empty = the host itself, else its first `AnimatedSprite2D` child). Keep the sprite as the root: selecting the character then selects the drawn quad, and the viewport frames the visible art and marks the pivot at the node position — a `Group2D` wrapper frames a centred box that the frame anchor pushes off the art. Game code says `character.playState('attack', { restart: true })` and `character.setVariant('bow')` (keeps the state, restarts its clip) and never spells a clip name; both return `false` for a pair the resource lacks, no hidden fallback. A finished one-shot holds its last frame and the root emits **`state-finished`** `(state, variant)` — the component adds no movement, physics, AI or automatic transitions. `getVariants()` / `getStates(variant?)` read the vocabulary off the clip names. This is Unity's Sprite Library categories/labels + Sprite Resolver, not an AnimationTree.
|
|
171
|
+
**Character with variants/states (weapon skins, outfits)** — one `.pix3anim` whose clips are named `<variant>.<state>` (`sword.idle`, `sword.attack`, `bow.idle`, …; a clip without the separator is a variant-less state such as `die`), an `AnimatedSprite2D` as the prefab root (`sizeMode: native` — every frame carries `sourceSize`; the frame `anchor` at the feet, so the node's position is where the character stands) and **`core:CharacterVisual2D`** on that same node (`variant`, `state`, `separator`, optional `spriteNodeId`; empty = the host itself, else its first `AnimatedSprite2D` child). Keep the sprite as the root: selecting the character then selects the drawn quad, and the viewport frames the visible art and marks the pivot at the node position — a `Group2D` wrapper frames a centred box that the frame anchor pushes off the art. Game code says `character.playState('attack', { restart: true })` and `character.setVariant('bow')` (keeps the state, restarts its clip) and never spells a clip name; both return `false` for a pair the resource lacks, no hidden fallback. A finished one-shot holds its last frame and the root emits **`state-finished`** `(state, variant)` — the component adds no movement, physics, AI or automatic transitions. `getVariants()` / `getStates(variant?)` read the vocabulary off the clip names. This is Unity's Sprite Library categories/labels + Sprite Resolver, not an AnimationTree. **`pix3 character-compile <spec.yaml>`** (`packages/cli/src/character/`) emits exactly this shape from frame PNGs named in a small spec (`clips: [{ variant, state, frames | sequence, fps?, loop? }]`) — a managed sprite folder (`sprites/<slug>/<slug>.pix3anim` + `<variant>_<state>_<nnnn>.png`, the frames copied in) plus `scenes/prefabs/<Name>.pix3scene`; pass `anchor` (y from the top) at the feet row so the node's position is where the character stands — the default is the canvas centre. It never overwrites a file with other content without `--force`, and `--dry-run` shows the plan.
|
|
245
172
|
|
|
246
173
|
**Spritesheet mode** instead: set top-level `texturePath` and give each frame a UV rect `offset:{x,y}` + `repeat:{x,y}` (these default to 0 → sample nothing, so they're required here). Frames may carry `durationMultiplier` and `events:[{signal,args}]` (fired on play-driven frame entry). Node wiring: `type: AnimatedSprite2D`, properties `animationResourcePath`, `currentClip`, `isPlaying`, `freeOnFinish` (one-shot self-destruct), `width`/`height`, `anchor`, `sizeMode`. First spawn of a runtime-instantiated clip warms its texture cache; if the first play must be pixel-perfect, spawn one invisible warm-up at level start. Authoring GUI: the editor's **Sprite Editor** produces the same file — one shell (canvas + clips rail + frame timeline) that edits both a bare image and a `.pix3anim`; selecting a frame binds the canvas to that frame's texture, and crop / rotate / flip / background-removal / generation write straight back into the frame.
|
|
247
174
|
|
|
@@ -302,7 +229,7 @@ cost is per skeleton per frame, so budget dozens, not hundreds. Full property ta
|
|
|
302
229
|
Attach in the inspector or in YAML `components:`. These are the pre-built,
|
|
303
230
|
designer-facing behaviors — prefer them over writing a script for the same
|
|
304
231
|
effect. Registered in
|
|
305
|
-
packages/
|
|
232
|
+
packages/runtime/src/behaviors/register-behaviors.ts.
|
|
306
233
|
|
|
307
234
|
| Component id | Does |
|
|
308
235
|
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -341,7 +268,7 @@ so a keyframe **event track** or a script `emit()` can fire them.
|
|
|
341
268
|
(3D-only), `core:uv-scroll`, `core:flash`, `core:adjust`
|
|
342
269
|
(brightness/contrast/saturation), `core:grayscale`, `core:tint`. Params are
|
|
343
270
|
keyframe-animatable. See
|
|
344
|
-
packages/
|
|
271
|
+
packages/runtime/src/shader-effects/.
|
|
345
272
|
|
|
346
273
|
## `core:` components — every config key
|
|
347
274
|
|