@pix3/cli 1.6.3 → 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,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pix3-scene-format
|
|
3
|
-
description: The .pix3scene YAML format — file header, node shape (id/type/name/properties/children/components), component shape, 2D transform and layout blocks, res:// references, textures, prefab instances and overrides, scenes/ui overlays and the visible/initiallyVisible split
|
|
3
|
+
description: The .pix3scene YAML format — file header, node shape (id/type/name/properties/children/components), component shape, 2D transform and layout blocks, res:// references, textures, prefab instances and overrides, scenes/ui overlays and the visible/initiallyVisible split; `.pix3anim` flipbooks (and `pix3 character-compile`) in `pix3anim.md` beside it; creating, renaming, moving, deleting files, extracting a branch into a prefab, locales, `pix3project.yaml` and build options in `project-files.md` beside it. Use BEFORE creating or editing any .pix3scene file (scene, prefab or overlay) in this project.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- Pix3 agent kit
|
|
6
|
+
<!-- Pix3 agent kit 2.0.0-alpha.0 -->
|
|
7
7
|
# `.pix3scene` format
|
|
8
8
|
|
|
9
9
|
Scenes, prefabs and overlays are the same file type: YAML with a `.pix3scene` extension.
|
|
@@ -14,19 +14,16 @@ The engine's own statement of the format (principles, skinned UI controls, Spine
|
|
|
14
14
|
|
|
15
15
|
## Before you write
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- If your tools support it, write to a temporary file and rename it over the target. Not
|
|
21
|
-
required for correctness — the editor waits for the file to stop changing — but cleaner.
|
|
22
|
-
- Then `pix3 check` (see `pix3-verify`).
|
|
17
|
+
Sync, re-read the file, change only the lines you mean to change, then `pix3 check` (`AGENTS.md`
|
|
18
|
+
rules 2 and 4). If your tools support it, write to a temporary file and rename it over the
|
|
19
|
+
target — not required (the editor waits for the file to stop changing), but cleaner.
|
|
23
20
|
|
|
24
21
|
## File
|
|
25
22
|
|
|
26
23
|
```yaml
|
|
27
24
|
version: 1.0.0 # the only format version; copy it
|
|
28
25
|
metadata: # optional, free-form
|
|
29
|
-
author: Pix3
|
|
26
|
+
author: Pix3
|
|
30
27
|
description: Tapper gameplay — falling objects
|
|
31
28
|
root: # list of root nodes; at least one
|
|
32
29
|
- id: game-root
|
|
@@ -64,7 +61,7 @@ root: # list of root nodes; at least one
|
|
|
64
61
|
Rules:
|
|
65
62
|
|
|
66
63
|
- **`id`**: short, readable, kebab-case, unique (`combo-label`, `spawner-bonus`). Two nodes
|
|
67
|
-
with the same id fail the load. Never rename an id
|
|
64
|
+
with the same id fail the load. Never rename an id other scripts use — scripts
|
|
68
65
|
find nodes by id; rename `name` instead.
|
|
69
66
|
- **`type`**: must be a real node type. An unknown or misspelled type does **not** fail the
|
|
70
67
|
load — the node becomes an inert placeholder that draws and does nothing.
|
|
@@ -83,8 +80,8 @@ transform:
|
|
|
83
80
|
rotation: 0 # DEGREES in YAML; positive = counter-clockwise (Y is up)
|
|
84
81
|
```
|
|
85
82
|
|
|
86
|
-
2D space: origin at the centre of the screen/parent, **X right, Y up**.
|
|
87
|
-
1080 x 1920
|
|
83
|
+
2D space: origin at the centre of the screen/parent, **X right, Y up**. A portrait game uses e.g. a
|
|
84
|
+
1080 x 1920 design (top edge y = 960, bottom y = -960); other projects read
|
|
88
85
|
`viewportBaseSize` / `projectType` in `pix3project.yaml` — a `3d` project may have loose
|
|
89
86
|
top-level 2D nodes (a HUD) and no 2D root at all, so position new HUD relative to the HUD
|
|
90
87
|
nodes that are already there. Scripts see the rotation in
|
|
@@ -102,17 +99,28 @@ layout:
|
|
|
102
99
|
enabled: true
|
|
103
100
|
horizontalAlign: left # left | center | right | stretch
|
|
104
101
|
verticalAlign: top # top | center | bottom | stretch
|
|
102
|
+
left: 40 # margins, px from the parent's edge to the node's same edge:
|
|
103
|
+
top: 30 # `left` keeps `left`, `right` keeps `right`, `stretch` keeps both
|
|
105
104
|
```
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
An anchored axis is placed by its margin, and keeps it when the parent resizes (a different
|
|
107
|
+
screen aspect, a container resized in the editor). On that axis the `transform.position`
|
|
108
|
+
component is ignored — write `0` — and under `stretch` the node's `width` / `height` is derived
|
|
109
|
+
from the two margins, so leave it out (a `Checkbox2D`'s `size` / `Joystick2D`'s `radius` too: its
|
|
110
|
+
side is the stretched span). A centred axis has no margin: `position` places it, as
|
|
111
|
+
for any node. A file with the anchors but **no margins** still loads: the margins are read off
|
|
112
|
+
the rect, and the editor rewrites the node in the margin form the first time it saves the file.
|
|
113
|
+
`pix3 tree <scene>` prints the margins and the rect they give at the design size —
|
|
114
|
+
`pos=(-400,900) size=200x60 layout=left/top(left=40,top=30)` — so to move such a node, change
|
|
115
|
+
its margin, not its `position`.
|
|
116
|
+
HUD widgets anchor to screen edges; full-screen backgrounds and roots use `stretch` on both
|
|
117
|
+
axes (a root's reference is `viewportBaseSize`, so a root keeps its full rect, no margins).
|
|
111
118
|
|
|
112
119
|
**A new HUD node must not overlap the HUD that is already there.** Before you place it, read
|
|
113
120
|
the `position`, `width`/`height` and anchoring of its siblings and pick a free spot (a combo
|
|
114
|
-
label placed "top-right" once landed under the game's Shop button). With the
|
|
115
|
-
confirm it with
|
|
121
|
+
label placed "top-right" once landed under the game's Shop button). With the editor open,
|
|
122
|
+
confirm it with `pix3_screenshot` + `take_screenshot` while the game runs (`pix3-editor`);
|
|
123
|
+
without it, ask the human to look.
|
|
116
124
|
|
|
117
125
|
Flow (stack children in a row/column) is a separate block:
|
|
118
126
|
`flow: { enabled: true, direction: vertical, gap: 16, paddingX: 0, paddingY: 0, align: start, autoSize: false }`.
|
|
@@ -126,7 +134,7 @@ are ignored — `pix3 validate` says so); on an **instance** node they are the o
|
|
|
126
134
|
- **Colour**: a quoted hex string, `color: "#141a2e"`. Always quote — an unquoted `#` starts
|
|
127
135
|
a YAML comment and the value becomes empty.
|
|
128
136
|
- **Asset path**: `res://` + path from the project root (the folder with `pix3project.yaml`),
|
|
129
|
-
whatever the layout: `res://sprites/ph-target.png` in a
|
|
137
|
+
whatever the layout: `res://sprites/ph-target.png` in a new project, `res://src/assets/textures/x.png`
|
|
130
138
|
in a project that keeps assets under `src/`. Follow the folders the project already has; a
|
|
131
139
|
new project from `pix3 new` uses one folder per type at the root (`sprites/`, `audio/`,
|
|
132
140
|
`fonts/`, `models/`, `spine/`, `scripts/`, `scenes/`).
|
|
@@ -134,6 +142,9 @@ are ignored — `pix3 validate` says so); on an **instance** node they are the o
|
|
|
134
142
|
`textureNormal`, `textureFill`, …). The saver always writes this form; a bare
|
|
135
143
|
`texturePath: 'res://…'` string is only read for compatibility.
|
|
136
144
|
- **Strings** with `:` `#` `{` or leading spaces: quote them (`label: "SCORE 0"`).
|
|
145
|
+
- **Flipbook**: an `AnimatedSprite2D` points at a `.pix3anim` (`animationResourcePath`) — a JSON
|
|
146
|
+
file of named clips; its fields and defaults are in `pix3anim.md` beside this file. A character
|
|
147
|
+
with variants and states is compiled, not hand-written: `pix3 character-compile`.
|
|
137
148
|
|
|
138
149
|
### Shader effects (`Sprite2D`, `AnimatedSprite2D`, `Button2D` only)
|
|
139
150
|
|
|
@@ -143,7 +154,7 @@ effects:
|
|
|
143
154
|
params: { color: "#7ee787", amount: 1 }
|
|
144
155
|
```
|
|
145
156
|
|
|
146
|
-
`core:tint` multiplies a near-white placeholder PNG to the colour — this is how
|
|
157
|
+
`core:tint` multiplies a near-white placeholder PNG to the colour — this is how
|
|
147
158
|
placeholders get their palette. Also `core:adjust`, `core:grayscale`, `core:flash`,
|
|
148
159
|
`core:uv-scroll`, `core:dissolve`. Other 2D nodes do not read `effects` (a `ColorRect2D` takes
|
|
149
160
|
its colour from `color`). On a `GeometryMesh` the stack lives at `material.effects`.
|
|
@@ -181,9 +192,10 @@ its colour from `color`). On a `GeometryMesh` the stack lives at `material.effec
|
|
|
181
192
|
stays the instance's own id). `findByName('Result Overlay')` finds the instance above, not
|
|
182
193
|
the name written in `result.pix3scene`; omit `name` to keep the prefab's.
|
|
183
194
|
- Instance `properties` are applied to the prefab's root through its **schema names**:
|
|
184
|
-
`visible`, `opacity`, `width`, `layoutEnabled`, `horizontalAlign`,
|
|
185
|
-
|
|
186
|
-
|
|
195
|
+
`visible`, `opacity`, `width`, `layoutEnabled`, `horizontalAlign`, the margins `layoutLeft` /
|
|
196
|
+
`layoutRight` / `layoutTop` / `layoutBottom`, … plus a `transform` block (`position`,
|
|
197
|
+
`rotation`, `scale`). The `layout:` / `flow:` blocks of a plain node are not read here — write
|
|
198
|
+
`layoutEnabled: true` + `horizontalAlign: right` + `layoutRight: 20` instead.
|
|
187
199
|
- Do **not** add `components` or `children` to an instance node — they are ignored (the
|
|
188
200
|
content comes from the prefab). To change what is inside, edit the prefab file itself, or
|
|
189
201
|
override one inner node's properties:
|
|
@@ -199,9 +211,11 @@ its colour from `color`). On a `GeometryMesh` the stack lives at `material.effec
|
|
|
199
211
|
them by id (`retry-button` inside `scenes/ui/result.pix3scene` is found as `retry-button`).
|
|
200
212
|
- A prefab carries fixed defaults only (e.g. its `core:Hitbox2D` group). Tunables live on
|
|
201
213
|
plain nodes in the host scene.
|
|
202
|
-
- Where prefabs live: `scenes/prefabs/`
|
|
214
|
+
- Where prefabs live: `scenes/prefabs/` or a top-level `prefabs/` — the export
|
|
203
215
|
treats both (and `scenes/ui/`) as prefabs, never as a boot scene.
|
|
204
216
|
- Spawned at runtime with `await this.scene.instantiate('res://scenes/prefabs/x.pix3scene', { parent })`.
|
|
217
|
+
- Turning a branch of a scene into a prefab (the editor has no "Save as Prefab"):
|
|
218
|
+
`project-files.md` → "Extract a branch into a prefab".
|
|
205
219
|
|
|
206
220
|
## Full-screen UI lives in `scenes/ui/`
|
|
207
221
|
|
|
@@ -215,23 +229,6 @@ scene with `properties: { visible: false }`. Two different flags, both needed:
|
|
|
215
229
|
|
|
216
230
|
Never put a full-screen dimmer/panel inline in `main.pix3scene`.
|
|
217
231
|
|
|
218
|
-
## Recipe tool names → file edits
|
|
219
|
-
|
|
220
|
-
`design/recipe.md` was written for the in-editor agent. Without its tools:
|
|
221
|
-
|
|
222
|
-
| recipe.md says | you do |
|
|
223
|
-
| --- | --- |
|
|
224
|
-
| tunable with `component: "user:X"` | edit `config.<property>` of the component with `type: user:X` on the node with that `id` |
|
|
225
|
-
| tunable without `component` | edit `properties.<property>` on the node with that `id` (in the `transform` / `layout` / `flow` block where it belongs) |
|
|
226
|
-
| `set_property` | edit `properties` in the YAML |
|
|
227
|
-
| `set_component_property` | edit the component's `config` |
|
|
228
|
-
| `create_node` / `add_component` | add the node / component entry to the YAML |
|
|
229
|
-
| `fs_write` (+ `overwrite: true`) | write the file |
|
|
230
|
-
| `play_start`, `game_input`, `game_observe`, `game_run` | the same tools over the live channel when it is connected (`pix3-verify`); otherwise `pix3 check`, then `pix3 smoke <the scene you changed>`, then ask the human to press Play and tell them what to look for |
|
|
231
|
-
|
|
232
|
-
Values out of a tunable's `min`/`max` load as written (`pix3 validate` warns); the script's
|
|
233
|
-
schema `setValue` may clamp them.
|
|
234
|
-
|
|
235
232
|
## Known gaps
|
|
236
233
|
|
|
237
234
|
- `metadata.description` is free text; nothing reads it but humans.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<!-- Pix3 agent kit 2.0.0-alpha.0 — the .pix3anim format. Generated by pix3 kit; pix3 kit --update replaces it unless you edited it. -->
|
|
2
|
+
# `.pix3anim` — flipbook animation resources
|
|
3
|
+
|
|
4
|
+
A `.pix3anim` is **JSON**: a set of named clips, each a list of frames. `AnimatedSprite2D`
|
|
5
|
+
plays one: `animationResourcePath: res://…/foo.pix3anim`, `currentClip`, `isPlaying`,
|
|
6
|
+
`sizeMode`, `freeOnFinish` (the node's properties are in `pix3-nodes`; `AnimatedSprite3D` takes
|
|
7
|
+
its own `frames` list instead). The loader
|
|
8
|
+
reads the resource and every frame texture it names, also for a node that arrives through a
|
|
9
|
+
prefab or `scene.instantiate`. **Every field may be omitted**: the loader fills in the default
|
|
10
|
+
in the tables below, so hand-write only what you mean.
|
|
11
|
+
|
|
12
|
+
Keep a flipbook in its own folder, the frames beside it — `sprites/<name>/<name>.pix3anim` +
|
|
13
|
+
`sprites/<name>/<clip>_<nnnn>.png` (the editor's Sprite Editor writes and reads that layout and
|
|
14
|
+
shows the folder as one item). Nothing enforces it: any `res://` path works.
|
|
15
|
+
|
|
16
|
+
## Create one and bind it
|
|
17
|
+
|
|
18
|
+
The editor does not create a `.pix3anim` (its inspector binds an existing one); you write it:
|
|
19
|
+
|
|
20
|
+
1. Frames into one folder: `sprites/<name>/<clip>_0001.png`, `<clip>_0002.png`, … (or SVGs —
|
|
21
|
+
by the rules of AGENTS.md rule 5; `pix3 validate` checks the frames a `.pix3anim` names like
|
|
22
|
+
a scene's sprites: `E_SVG_*`, reported on the `.pix3anim`).
|
|
23
|
+
2. `sprites/<name>/<name>.pix3anim`: `version`, `texturePath: ""` and one clip per animation,
|
|
24
|
+
each frame naming its image — the example under "Two modes" below; leave out every field
|
|
25
|
+
whose default (the tables) is what you want.
|
|
26
|
+
3. Bind it on the `AnimatedSprite2D` node:
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
- id: coin-spin
|
|
30
|
+
type: AnimatedSprite2D
|
|
31
|
+
name: Coin Spin
|
|
32
|
+
properties:
|
|
33
|
+
width: 96
|
|
34
|
+
height: 96
|
|
35
|
+
animationResourcePath: res://sprites/coin/coin.pix3anim
|
|
36
|
+
currentClip: spin
|
|
37
|
+
isPlaying: true
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A script switches clips with `sprite.play('spin')`.
|
|
41
|
+
4. `pix3 check`: a wrong `animationResourcePath` is `E_MISSING_RESOURCE`, a frame path that
|
|
42
|
+
points nowhere `E_MISSING_FRAME`, a file that is not JSON `E_ANIM_JSON`; then
|
|
43
|
+
`pix3 smoke <the scene>`.
|
|
44
|
+
|
|
45
|
+
## Two modes
|
|
46
|
+
|
|
47
|
+
- **Sequence** (one image per frame — the usual case): every frame sets `texturePath`; the
|
|
48
|
+
top-level `texturePath` stays `""`.
|
|
49
|
+
- **Spritesheet**: the top-level `texturePath` is the sheet; each frame leaves its own
|
|
50
|
+
`texturePath` empty and sets the UV rect `offset` + `repeat` (fractions of the sheet). Both
|
|
51
|
+
default to `{ "x": 0, "y": 0 }`, which samples nothing — required in this mode.
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"version": "1.0.0",
|
|
56
|
+
"texturePath": "",
|
|
57
|
+
"clips": [
|
|
58
|
+
{
|
|
59
|
+
"name": "burst",
|
|
60
|
+
"fps": 30,
|
|
61
|
+
"loop": false,
|
|
62
|
+
"frames": [
|
|
63
|
+
{ "texturePath": "res://sprites/burst/burst_0001.png" },
|
|
64
|
+
{ "texturePath": "res://sprites/burst/burst_0002.png", "durationMultiplier": 2 }
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## The fields
|
|
72
|
+
|
|
73
|
+
Types are the runtime's (`AnimationResource.ts`); "Omitted →" is what the loader fills in. A
|
|
74
|
+
field marked `?` is optional in the file format itself.
|
|
75
|
+
|
|
76
|
+
### `AnimationResource`
|
|
77
|
+
|
|
78
|
+
The file.
|
|
79
|
+
|
|
80
|
+
| Field | Type | Omitted → | Notes |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| `version` | `string` | `"1.0.0"` | The only version; copy it |
|
|
83
|
+
| `texturePath` | `string` | `""` | Spritesheet mode: `res://` of the sheet. Sequence mode: `""` |
|
|
84
|
+
| `clips` | `AnimationClip[]` | `[]` | Clip names must be unique — scripts address clips by name |
|
|
85
|
+
|
|
86
|
+
### `AnimationClip`
|
|
87
|
+
|
|
88
|
+
| Field | Type | Omitted → | Notes |
|
|
89
|
+
| --- | --- | --- | --- |
|
|
90
|
+
| `name` | `string` | `clip-<n>` (1-based position) | What `currentClip` and `sprite.play(name)` use |
|
|
91
|
+
| `frames` | `AnimationFrame[]` | `[]` | In play order |
|
|
92
|
+
| `fps` | `number` | `12` | Frames per second; positive |
|
|
93
|
+
| `loop` | `boolean` | `true` | `false`: holds the last frame, `isPlaying` turns false, the node emits `animation-finished` (`freeOnFinish: true` then removes it) |
|
|
94
|
+
| `playbackMode` | `AnimationPlaybackMode` | `"normal"` | `"normal"` or `"ping-pong"` |
|
|
95
|
+
|
|
96
|
+
### `AnimationFrame`
|
|
97
|
+
|
|
98
|
+
| Field | Type | Omitted → | Notes |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
| `textureIndex` | `number` | `0` | Legacy; leave it 0 |
|
|
101
|
+
| `offset` | `AnimationVector2` | `{"x":0,"y":0}` | Spritesheet mode: UV origin of the frame in the sheet |
|
|
102
|
+
| `repeat` | `AnimationVector2` | `{"x":0,"y":0}` | Spritesheet mode: UV size of the frame in the sheet |
|
|
103
|
+
| `durationMultiplier` | `number` | `1` | This frame lasts `durationMultiplier / fps` seconds |
|
|
104
|
+
| `anchor` | `AnimationVector2` | `{"x":0.5,"y":0.5}` | The point of the frame (0–1, **y from the top**) that lands on the node's position — the feet for a character |
|
|
105
|
+
| `texturePath` | `string` | `""` | Sequence mode: `res://` of this frame's image |
|
|
106
|
+
| `boundingBox` | `AnimationBoundingBox` | `{"x":0,"y":0,"width":0,"height":0}` | Frame rect in source pixels |
|
|
107
|
+
| `collisionPolygon` | `AnimationPolygonPoint[]` | `[]` | Optional outline in frame space |
|
|
108
|
+
| `events?` | `AnimationFrameEvent[]` | `[]` | Signals the node emits when play enters this frame |
|
|
109
|
+
| `sourceSize?` | `AnimationSize` | the `boundingBox` size | Pixel size of the frame image; `sizeMode: native` lays the node out from it (`0×0` = unknown → stretch) |
|
|
110
|
+
| `points?` | `AnimationFramePoint[]` | `[]` | Named sockets (a muzzle, a hand) that move across frames; `core:PointAttachment` parks a child on one |
|
|
111
|
+
|
|
112
|
+
### `AnimationVector2`
|
|
113
|
+
|
|
114
|
+
| Field | Type | Omitted → | Notes |
|
|
115
|
+
| --- | --- | --- | --- |
|
|
116
|
+
| `x` | `number` | — | |
|
|
117
|
+
| `y` | `number` | — | |
|
|
118
|
+
|
|
119
|
+
### `AnimationBoundingBox`
|
|
120
|
+
|
|
121
|
+
| Field | Type | Omitted → | Notes |
|
|
122
|
+
| --- | --- | --- | --- |
|
|
123
|
+
| `x` | `number` | — | From the frame's left edge, pixels |
|
|
124
|
+
| `y` | `number` | — | From the frame's top edge, pixels |
|
|
125
|
+
| `width` | `number` | — | |
|
|
126
|
+
| `height` | `number` | — | |
|
|
127
|
+
|
|
128
|
+
### `AnimationPolygonPoint`
|
|
129
|
+
|
|
130
|
+
| Field | Type | Omitted → | Notes |
|
|
131
|
+
| --- | --- | --- | --- |
|
|
132
|
+
| `x` | `number` | — | |
|
|
133
|
+
| `y` | `number` | — | |
|
|
134
|
+
|
|
135
|
+
### `AnimationFrameEvent`
|
|
136
|
+
|
|
137
|
+
| Field | Type | Omitted → | Notes |
|
|
138
|
+
| --- | --- | --- | --- |
|
|
139
|
+
| `signal` | `string` | — | Emitted on the node; an empty signal drops the event |
|
|
140
|
+
| `args` | `string` | — | Raw text, parsed when it fires (same convention as the keyframe event track) |
|
|
141
|
+
|
|
142
|
+
### `AnimationSize`
|
|
143
|
+
|
|
144
|
+
| Field | Type | Omitted → | Notes |
|
|
145
|
+
| --- | --- | --- | --- |
|
|
146
|
+
| `width` | `number` | — | |
|
|
147
|
+
| `height` | `number` | — | |
|
|
148
|
+
|
|
149
|
+
### `AnimationFramePoint`
|
|
150
|
+
|
|
151
|
+
| Field | Type | Omitted → | Notes |
|
|
152
|
+
| --- | --- | --- | --- |
|
|
153
|
+
| `name` | `string` | — | Unique within the frame (the first wins) |
|
|
154
|
+
| `x` | `number` | — | 0–1 of the frame width |
|
|
155
|
+
| `y` | `number` | — | 0–1 of the frame height, from the top |
|
|
156
|
+
| `angle?` | `number` | — | Degrees, clockwise, 0 = pointing right |
|
|
157
|
+
|
|
158
|
+
`AnimationPlaybackMode` is `"normal" | "ping-pong"`.
|
|
159
|
+
|
|
160
|
+
## A character with variants and states
|
|
161
|
+
|
|
162
|
+
One `.pix3anim` whose clips are named `<variant>.<state>` (`sword.idle`, `sword.attack`,
|
|
163
|
+
`bow.idle`; a clip without the dot is a variant-less state such as `die`), an `AnimatedSprite2D`
|
|
164
|
+
prefab root with `sizeMode: native`, and `core:CharacterVisual2D` on it (`variant`, `state`).
|
|
165
|
+
Game code calls `playState('attack', { restart: true })` and `setVariant('bow')` and never spells
|
|
166
|
+
a clip name. Do not write it by hand — compile it from the frame images:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pix3 character-compile art/goblin.yaml --dry-run # the plan
|
|
170
|
+
pix3 character-compile art/goblin.yaml # sprites/goblin/…, scenes/prefabs/Goblin.pix3scene
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
# art/goblin.yaml — frame paths are relative to this file; frames must be PNG
|
|
175
|
+
name: Goblin
|
|
176
|
+
anchor: { x: 0.5, y: 0.9 } # the feet
|
|
177
|
+
clips:
|
|
178
|
+
- { variant: sword, state: idle, fps: 10, frames: [sword/idle_1.png, sword/idle_2.png] }
|
|
179
|
+
- { variant: sword, state: attack, fps: 15, sequence: sword/attack } # sword/attack_<n>.png
|
|
180
|
+
- { state: die, sequence: die/die }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
It refuses to overwrite a file with other content (`--force` to replace) and prints the defaults
|
|
184
|
+
it chose (fps 12, one-shot `attack`/`die`/`hit`) as warnings. Then instance the prefab
|
|
185
|
+
(`instance: res://scenes/prefabs/Goblin.pix3scene`) and run `pix3 check`.
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
<!-- Pix3 agent kit 2.0.0-alpha.0 — project files the editor leaves to you. Generated by pix3 kit; pix3 kit --update replaces it unless you edited it. -->
|
|
2
|
+
# Project files the editor leaves to you
|
|
3
|
+
|
|
4
|
+
The Pix3 editor edits a project that is already open: nodes, properties, what is inside a
|
|
5
|
+
prefab, the clips of a `.pix3anim`, the entries of a locale table. It **never creates, renames,
|
|
6
|
+
moves or deletes a file, never edits `pix3project.yaml` and never builds**. When the human asks
|
|
7
|
+
for one of those, you do it — by writing files and running the CLI. The editor follows the disk
|
|
8
|
+
on its own. After every recipe: `pix3 check`, then (editor open) `pix3_sync` with `expect` =
|
|
9
|
+
the sha256 of every file you wrote, or (no editor) `pix3 smoke <the scene>`.
|
|
10
|
+
|
|
11
|
+
| The human asks for | Recipe |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| a new scene, a copy of a scene ("Save As") | [New scene](#new-scene) |
|
|
14
|
+
| a folder | create it; the asset panel shows it |
|
|
15
|
+
| a script | `export class X extends Script` in `scripts/X.ts` — `pix3-scripts` |
|
|
16
|
+
| an asset renamed, moved or deleted | [Rename, move, delete](#rename-move-delete) |
|
|
17
|
+
| a branch of a scene turned into a prefab ("Save Branch as Prefab") | [Extract a branch into a prefab](#extract-a-branch-into-a-prefab) |
|
|
18
|
+
| a flipbook for an `AnimatedSprite2D` | `pix3anim.md` → "Create one and bind it" |
|
|
19
|
+
| a language added or removed | [Add or remove a locale](#add-or-remove-a-locale) |
|
|
20
|
+
| project settings (resolution, entry scene, quality, fonts …) | [`pix3project.yaml`](#pix3projectyaml) |
|
|
21
|
+
| a build, an export | `npm run build` → `dist/` — [Build options](#build-options) |
|
|
22
|
+
| an image | write an SVG (AGENTS.md rule 5), or the human generates one in the editor's Generate panel and saves it into the project |
|
|
23
|
+
|
|
24
|
+
## New scene
|
|
25
|
+
|
|
26
|
+
1. Copy the entry scene (`defaultExportScenePath` in `pix3project.yaml`) to
|
|
27
|
+
`scenes/<name>.pix3scene` — it carries the root sized from `viewportBaseSize`. Keep the root
|
|
28
|
+
and what every screen has (a background), drop the root's `components` and the children that
|
|
29
|
+
are the other scene's game, and update `metadata.description` and the header comment. Ids
|
|
30
|
+
only have to be unique **within** a scene, so a copy keeps them.
|
|
31
|
+
2. A scene under `scenes/` that is not under `scenes/prefabs/`, `prefabs/` or `scenes/ui/` is a
|
|
32
|
+
playable scene: reach it with `await this.scene.changeScene('res://scenes/<name>.pix3scene')`.
|
|
33
|
+
To make the game boot into it, set `defaultExportScenePath: scenes/<name>.pix3scene` — and
|
|
34
|
+
give it a way on (a menu's button script calls `changeScene` to the game).
|
|
35
|
+
3. Under `scenes/prefabs/`, `prefabs/` or `scenes/ui/` the file is a prefab or an overlay: it is
|
|
36
|
+
instanced, never booted.
|
|
37
|
+
|
|
38
|
+
## Rename, move, delete
|
|
39
|
+
|
|
40
|
+
Everything refers to files by path, and nothing rewrites those paths for you.
|
|
41
|
+
|
|
42
|
+
1. Find every reference first, in every text file of the project (not `node_modules/`,
|
|
43
|
+
`.pix3/`, `dist/`, `.git/`, and not the kit's own docs in `.claude/`):
|
|
44
|
+
`grep -rn "sprites/coin.png" --exclude-dir={node_modules,.pix3,dist,.git,.claude} .` — for
|
|
45
|
+
a folder, search it with its trailing slash (`sprites/coin/`), which `sprites/coins/` does
|
|
46
|
+
not match.
|
|
47
|
+
— scenes and prefabs (`res://…`), `.pix3anim` frame paths, `locales/*.json` `sprites`,
|
|
48
|
+
scripts, and `pix3project.yaml` (which writes paths **without** `res://`:
|
|
49
|
+
`defaultExportScenePath`, `fonts[].path`). Search the folder name too: a path a script builds
|
|
50
|
+
at runtime (`` `res://levels/${n}.pix3scene` ``) has no literal to find.
|
|
51
|
+
2. Move the file (`git mv` when git tracks it, else `mv`) and rewrite every reference; a folder
|
|
52
|
+
move rewrites the prefix. A flipbook folder keeps its `<name>/<name>.pix3anim` pairing:
|
|
53
|
+
renaming `sprites/coin/` to `sprites/coins/` renames `coin.pix3anim` to `coins.pix3anim` too.
|
|
54
|
+
To delete, first remove or replace the references, then the file.
|
|
55
|
+
3. `pix3 check` names a reference that still points nowhere: a scene's (`E_MISSING_RESOURCE`,
|
|
56
|
+
`E_MISSING_PREFAB`), a `.pix3anim` frame (`E_MISSING_FRAME`) and a locale table's `sprites`
|
|
57
|
+
image (`E_MISSING_LOCALE_SPRITE`); `pix3 smoke` on a scene reports a missing file only when
|
|
58
|
+
it loads, as the **warning** `W_SMOKE_MISSING_RESOURCE` (exit 0).
|
|
59
|
+
|
|
60
|
+
A script's file name is free to change: `type: user:Mover` names the exported class. Renaming
|
|
61
|
+
the class means rewriting every `type: user:Mover` and every `import`.
|
|
62
|
+
|
|
63
|
+
## Extract a branch into a prefab
|
|
64
|
+
|
|
65
|
+
The branch: a node of the host scene with everything under it, e.g. in `scenes/main.pix3scene`
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
- id: coin-hud
|
|
69
|
+
type: Group2D
|
|
70
|
+
name: Coin HUD
|
|
71
|
+
properties:
|
|
72
|
+
width: 260
|
|
73
|
+
height: 80
|
|
74
|
+
transform: { position: [0, 0], scale: [1, 1], rotation: 0 }
|
|
75
|
+
layout: { enabled: true, horizontalAlign: left, verticalAlign: top, left: 60, top: 40 }
|
|
76
|
+
children:
|
|
77
|
+
- id: coin-icon
|
|
78
|
+
type: ColorRect2D
|
|
79
|
+
name: Coin Icon
|
|
80
|
+
properties: { width: 64, height: 64, color: "#f5c542", transform: { position: [-80, 0] } }
|
|
81
|
+
- id: coin-count
|
|
82
|
+
type: Label2D
|
|
83
|
+
name: Coin Count
|
|
84
|
+
properties: { label: "0", labelFontSize: 48, transform: { position: [40, 0] } }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
1. Sync and re-read the host scene (AGENTS.md rule 2).
|
|
88
|
+
2. Write `scenes/prefabs/CoinHud.pix3scene`: `version: 1.0.0` and a `root:` with **exactly one**
|
|
89
|
+
node — the branch root, cut with everything under it (`properties`, `components`, `groups`,
|
|
90
|
+
`children`) and indented back to the top level.
|
|
91
|
+
3. The prefab root keeps its size but not its placement: set its `transform` to
|
|
92
|
+
`{ position: [0, 0], scale: [1, 1], rotation: 0 }` and drop its `layout:` block. Where it
|
|
93
|
+
sits is the instance's business (step 5).
|
|
94
|
+
4. Ids. Keep the branch's ids — the prefab root's `id` as it was, the inner ones unique in the
|
|
95
|
+
file. In the host scene the instance node's id becomes the root's id, and every inner node
|
|
96
|
+
keeps its prefab id **when that id is free in the host scene**; otherwise the loader renames
|
|
97
|
+
it `<id>-1`, `<id>-2`, … — which is what happens to the second instance of the same prefab.
|
|
98
|
+
So a lookup by id (`findNode('coin-count')`) finds the first instance's node only; to reach
|
|
99
|
+
an inner node of each instance, go through the instance — a second copy is one more instance
|
|
100
|
+
node with an id and name you choose (`coin-hud-2`, "Coin HUD 2"), its inner `coin-count`
|
|
101
|
+
becomes `coin-count-1`: `this.findNode('coin-hud-2')?.findByName('Coin Count')` (names never
|
|
102
|
+
change; the renamed id is its `nodeId`). A `node` property of a component inside the prefab that names another
|
|
103
|
+
node of the prefab is re-pointed per instance by the loader.
|
|
104
|
+
5. Replace the branch in the host scene, at the same place in `children` (tree order is paint
|
|
105
|
+
order), with one instance node: the branch root's `id` (scripts that find it keep finding
|
|
106
|
+
it) and `name`, `instance: res://scenes/prefabs/CoinHud.pix3scene`, and `properties` with the
|
|
107
|
+
root's `transform` and its anchoring in the **flat** names an instance reads —
|
|
108
|
+
`layoutEnabled`, `horizontalAlign`, `verticalAlign` and the margins `layoutLeft` /
|
|
109
|
+
`layoutRight` / `layoutTop` / `layoutBottom` (scene-format "Prefab instance"). No `type`,
|
|
110
|
+
no `components`, no `children`:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
- id: coin-hud
|
|
114
|
+
name: Coin HUD
|
|
115
|
+
instance: res://scenes/prefabs/CoinHud.pix3scene
|
|
116
|
+
properties:
|
|
117
|
+
transform: { position: [0, 0] }
|
|
118
|
+
layoutEnabled: true
|
|
119
|
+
horizontalAlign: left
|
|
120
|
+
verticalAlign: top
|
|
121
|
+
layoutLeft: 60
|
|
122
|
+
layoutTop: 40
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
6. A value that differed per copy goes into the instance: on the root as above, on an inner
|
|
126
|
+
node in `overrides.byLocalId` (`coin-count: { properties: { label: "10" } }`, keyed by the
|
|
127
|
+
prefab's ids). These are node **properties** and the values a copy starts with: a script
|
|
128
|
+
inside the prefab that sets the same property in `onStart` wins, and a component's `config`
|
|
129
|
+
cannot differ per copy (no component overrides) — make the script read the node property,
|
|
130
|
+
or keep the tunable on a plain node of the host scene. Components start parent first, so a
|
|
131
|
+
host script's `onStart` sees an inner node before the prefab's own scripts touched it. A
|
|
132
|
+
label with a `labelKey` shows that key's text, whatever `label` says: per copy, override the
|
|
133
|
+
`labelKey` (one key per copy) instead.
|
|
134
|
+
7. `pix3 check` — `E_PREFAB_ROOT` (not one root), `E_DUPLICATE_ID`, `E_MISSING_PREFAB`,
|
|
135
|
+
`E_UNKNOWN_OVERRIDE_TARGET` name the slips — then sync with `expect` of both files and look
|
|
136
|
+
at it running.
|
|
137
|
+
|
|
138
|
+
## Add or remove a locale
|
|
139
|
+
|
|
140
|
+
A locale is a table `locales/<id>.json` at the project root — `strings` (key → text, `{name}`
|
|
141
|
+
placeholders) and `sprites` (key → `res://` image for art with baked-in text). Nodes and
|
|
142
|
+
scripts name **keys** (`labelKey` on a UI control, `textureKey` on a `Sprite2D`,
|
|
143
|
+
`this.scene.localization.tr('menu.play')`), never a locale.
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"$meta": { "locale": "de", "name": "Deutsch" },
|
|
148
|
+
"strings": { "menu.play": "Spielen" },
|
|
149
|
+
"sprites": {}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Add one:
|
|
154
|
+
|
|
155
|
+
0. No table yet: write the default one first — `locales/en.json` with the keys the game uses
|
|
156
|
+
(`labelKey` on the nodes; keep `label` as well, it is what shows while there is no table) —
|
|
157
|
+
and declare it alone (`locales: [en]`, step 2).
|
|
158
|
+
1. Copy the default locale's table to `locales/<id>.json`, set `$meta.locale` and `$meta.name`
|
|
159
|
+
(`"direction": "rtl"` for a right-to-left language) and translate the values. Keep every key;
|
|
160
|
+
one you cannot translate yet stays `""` and falls back to the fallback locale, then to the key.
|
|
161
|
+
2. Declare it in `pix3project.yaml`, creating the block if there is none:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
localization:
|
|
165
|
+
defaultLocale: en
|
|
166
|
+
locales: [en, de]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Without a block every `locales/*.json` is a locale (`en` the default when there is one);
|
|
170
|
+
with it, only the tables it names count — the build ships those and no other.
|
|
171
|
+
|
|
172
|
+
Remove one:
|
|
173
|
+
|
|
174
|
+
1. Never the `defaultLocale` or the `fallbackLocale` — point those at another locale first.
|
|
175
|
+
2. Drop the id from `localization.locales`, delete `locales/<id>.json`, and grep the scripts for
|
|
176
|
+
the id (`setLocale('de')`). An image only that table's `sprites` used is now unreferenced
|
|
177
|
+
(`pix3 check` lists those under `sprites/` as `W_UNUSED_ASSET`).
|
|
178
|
+
|
|
179
|
+
`pix3 check` checks the tables: a declared locale without its file (`E_LOCALE_MISSING` for the
|
|
180
|
+
default or fallback locale, `W_LOCALE_MISSING` for another), a table that is not JSON
|
|
181
|
+
(`E_`/`W_LOCALE_JSON`), a value that is not a string (`E_`/`W_LOCALE_VALUE` — keys are flat,
|
|
182
|
+
`"menu.play"`, never nested), a `sprites` image that does not exist (`E_MISSING_LOCALE_SPRITE`),
|
|
183
|
+
a `labelKey` the default locale has no text for (`E_LOCALE_KEY`: the node would show the key
|
|
184
|
+
itself), a key a script passes as a literal — `tr('menu.play')`, `setTextKey('…')`,
|
|
185
|
+
`trPlural('…')` — with no text (`E_LOCALE_SCRIPT_KEY`), and a `textureKey` /
|
|
186
|
+
`stateTextureKeys` / `trSprite('…')` key with no `sprites` entry (`E_`/`W_LOCALE_SPRITE_KEY`).
|
|
187
|
+
A key the script builds at run time (`` tr(`shop.${id}`) ``, `tr(key)`) is not checked — read
|
|
188
|
+
those back yourself (or look at the running game).
|
|
189
|
+
|
|
190
|
+
In a script, `this.scene.localization`: `tr(key, { name: value })` (fills `{name}`),
|
|
191
|
+
`trPlural(key, count)`, `trSprite(key)`, `has(key)`, `locale`, `locales`, `setLocale(id)`,
|
|
192
|
+
`onChange(fn)`; a node shows `tr(node.labelKey)` when it has a key.
|
|
193
|
+
|
|
194
|
+
## `pix3project.yaml`
|
|
195
|
+
|
|
196
|
+
The manifest. Change a key only when the human asks for that setting (or a recipe above says
|
|
197
|
+
so), keep every key you do not know, and copy the style of the file.
|
|
198
|
+
|
|
199
|
+
| Key | What |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| `version` | `1.0.0`; leave it |
|
|
202
|
+
| `defaultExportScenePath` | the scene the game boots into (no `res://`) |
|
|
203
|
+
| `viewportBaseSize` | `{ width, height }` — the design resolution 2D layout is authored against |
|
|
204
|
+
| `projectType` | `2d` or `3d` |
|
|
205
|
+
| `targetPlatform` | `mobile`, `desktop` or `universal` |
|
|
206
|
+
| `textureFiltering` | `linear` (smooth) or `nearest` (pixel art) for 2D textures |
|
|
207
|
+
| `ambientOcclusion` | `off`, `baked`, `realtime` or `adaptive` — the project default |
|
|
208
|
+
| `quality` | `{ antialias, shadows, maxPixelRatio }` |
|
|
209
|
+
| `localization` | `{ defaultLocale, fallbackLocale?, locales }` — above |
|
|
210
|
+
| `fonts` | `[{ family, path, weight, style, unicodeRange? }]` — font files registered before the first frame; `path` without `res://` |
|
|
211
|
+
| `autoloads` | `[{ singleton, scriptPath, enabled }]` — game-wide script singletons that survive `changeScene` (recipe below); `[]` when there are none |
|
|
212
|
+
| `metadata` | the editor's and `pix3 kit`'s bookkeeping (`projectId`, `agentKit`) — do not edit |
|
|
213
|
+
|
|
214
|
+
### An autoload (state or a service for the whole game)
|
|
215
|
+
|
|
216
|
+
State that must outlive a scene change (score across levels, settings, a music player) is an
|
|
217
|
+
autoload: one script instance per game session, built before the first scene starts, kept
|
|
218
|
+
across every `changeScene`, the same in the editor's play, `npm run dev` and the build.
|
|
219
|
+
|
|
220
|
+
1. `scripts/GameState.ts`: `export class GameState extends Script { … }` — the class is named
|
|
221
|
+
like the file (that is how the autoload finds it).
|
|
222
|
+
2. In `pix3project.yaml`:
|
|
223
|
+
```yaml
|
|
224
|
+
autoloads:
|
|
225
|
+
- singleton: GameState
|
|
226
|
+
scriptPath: scripts/GameState.ts
|
|
227
|
+
```
|
|
228
|
+
3. Any script: `const state = this.scene.getAutoload(GameState)` (import the class) or
|
|
229
|
+
`this.scene.getAutoload<GameState>('GameState')`; null when it is not declared.
|
|
230
|
+
|
|
231
|
+
It runs `onStart`/`onUpdate` like a scene script, before the scene's every frame, and has no
|
|
232
|
+
node in any scene (it draws nothing; give the visuals to a scene). Commands it registers with
|
|
233
|
+
`scene.commands` belong to the scene and go with it. `pix3 check` reports an autoload that does
|
|
234
|
+
not resolve (`E_AUTOLOAD`).
|
|
235
|
+
|
|
236
|
+
## Build options
|
|
237
|
+
|
|
238
|
+
`npm run build` writes `dist/index.html` (one self-contained file) and
|
|
239
|
+
`dist/<name>.report.json` (what it weighs and why). How it builds is `pix3()` in
|
|
240
|
+
`vite.config.ts`, not the manifest: `pix3({ build: 'zip' })` (a zip to host instead of one
|
|
241
|
+
file), `pix3({ compress: true })` (gzip inside the page, about two thirds smaller),
|
|
242
|
+
`pix3({ entryScene: 'scenes/level-1.pix3scene' })` (boot another scene than
|
|
243
|
+
`defaultExportScenePath`), `pix3({ strip: { keep: [...] } })` (when the build says a library
|
|
244
|
+
needs runtime classes it cannot see).
|