@pix3/cli 1.6.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 -0
- package/README.md +754 -0
- package/dist/index.js +494 -0
- package/dist/runtime-types.json +1 -0
- package/dist/smoke/prebuilt/build-7MIXIMSZ.mjs +948 -0
- package/dist/smoke/prebuilt/chunk-J2SJVMDO.mjs +2 -0
- package/dist/smoke/prebuilt/chunk-OJXQXIIQ.mjs +239 -0
- package/dist/smoke/prebuilt/chunk-P2CBRXPJ.mjs +4050 -0
- package/dist/smoke/prebuilt/runtime.mjs +2 -0
- package/dist/smoke/prebuilt/smoke-worker.mjs +3 -0
- package/dist/smoke/prebuilt/three.mjs +2 -0
- package/dist/smoke/prebuilt/tree-defaults.mjs +2 -0
- package/dist/validate/prebuilt/build-AUGKXER3.js +948 -0
- package/dist/validate/prebuilt/bundle-main.js +24 -0
- package/dist/validate/prebuilt/chunk-6ACT6FNV.js +4050 -0
- package/dist/validate/prebuilt/chunk-BS2OZM7U.js +239 -0
- package/dist/validate/prebuilt/runtime.js +2 -0
- package/dist/validate/prebuilt/three.js +2 -0
- package/kit/files/.claude/skills/pix3-nodes/SKILL.md +210 -0
- package/kit/files/.claude/skills/pix3-nodes/reference.md +1331 -0
- package/kit/files/.claude/skills/pix3-scene-format/SKILL.md +235 -0
- package/kit/files/.claude/skills/pix3-scene-format/reference.md +176 -0
- package/kit/files/.claude/skills/pix3-scripts/SKILL.md +281 -0
- package/kit/files/.claude/skills/pix3-scripts/reference.md +638 -0
- package/kit/files/.claude/skills/pix3-verify/SKILL.md +386 -0
- package/kit/files/.claude/skills/pix3-verify/reference.md +21 -0
- package/kit/files/AGENTS.md +203 -0
- package/kit/files/CLAUDE.md +1 -0
- package/kit/kit.json +24 -0
- package/package.json +54 -0
- package/templates/empty-2d/files/README.md +20 -0
- package/templates/empty-2d/files/scenes/main.pix3scene +68 -0
- package/templates/empty-2d/files/sprites/pix3-logo.png +0 -0
- package/templates/empty-2d/template.yaml +7 -0
- package/templates/empty-3d/files/README.md +20 -0
- package/templates/empty-3d/files/scenes/main.pix3scene +108 -0
- package/templates/empty-3d/files/sprites/pix3-logo.png +0 -0
- package/templates/empty-3d/template.yaml +7 -0
- package/templates/idea-blank/files/README.md +22 -0
- package/templates/idea-blank/files/scenes/main.pix3scene +37 -0
- package/templates/idea-blank/template.yaml +10 -0
- package/templates/minigame-2d/files/README.md +44 -0
- package/templates/minigame-2d/files/design/tests/reachability.json +1 -0
- package/templates/minigame-2d/files/design/tests/routines/mute-music.json +44 -0
- package/templates/minigame-2d/files/scenes/main.pix3scene +84 -0
- package/templates/minigame-2d/files/scenes/menu.pix3scene +118 -0
- package/templates/minigame-2d/files/scenes/ui/settings-window.pix3scene +138 -0
- package/templates/minigame-2d/files/scripts/GameFlow.ts +133 -0
- package/templates/minigame-2d/files/scripts/MenuFlow.ts +217 -0
- package/templates/minigame-2d/files/scripts/SettingsWindow.ts +198 -0
- package/templates/minigame-2d/template.yaml +10 -0
- package/templates/playable-2d/files/README.md +30 -0
- package/templates/playable-2d/files/design/recipe.md +83 -0
- package/templates/playable-2d/files/design/tests/reachability.json +4 -0
- package/templates/playable-2d/files/design/tests/routines/intro-to-cta.json +32 -0
- package/templates/playable-2d/files/scenes/main.pix3scene +111 -0
- package/templates/playable-2d/files/scenes/ui/end-screen.pix3scene +91 -0
- package/templates/playable-2d/files/scenes/ui/intro.pix3scene +65 -0
- package/templates/playable-2d/files/scripts/CtaButton.ts +55 -0
- package/templates/playable-2d/files/scripts/GameFlow.ts +256 -0
- package/templates/playable-2d/files/sprites/pix3-logo.png +0 -0
- package/templates/playable-2d/template.yaml +9 -0
- package/templates/playable-3d/files/README.md +32 -0
- package/templates/playable-3d/files/design/recipe.md +127 -0
- package/templates/playable-3d/files/design/tests/reachability.json +4 -0
- package/templates/playable-3d/files/design/tests/routines/intro-to-cta.json +32 -0
- package/templates/playable-3d/files/scenes/main.pix3scene +230 -0
- package/templates/playable-3d/files/scenes/ui/end-screen.pix3scene +91 -0
- package/templates/playable-3d/files/scenes/ui/intro.pix3scene +65 -0
- package/templates/playable-3d/files/scripts/CtaButton.ts +55 -0
- package/templates/playable-3d/files/scripts/GameFlow.ts +309 -0
- package/templates/playable-3d/files/scripts/GameRules.ts +290 -0
- package/templates/playable-3d/files/scripts/ScoreHud.ts +113 -0
- package/templates/playable-3d/template.yaml +9 -0
- package/templates/recipe-arena-2d/files/README.md +48 -0
- package/templates/recipe-arena-2d/files/design/recipe.md +127 -0
- package/templates/recipe-arena-2d/files/design/tests/reachability.json +4 -0
- package/templates/recipe-arena-2d/files/design/tests/routines/terminal-retry.json +23 -0
- package/templates/recipe-arena-2d/files/scenes/main.pix3scene +292 -0
- package/templates/recipe-arena-2d/files/scenes/menu.pix3scene +100 -0
- package/templates/recipe-arena-2d/files/scenes/prefabs/hazard.pix3scene +42 -0
- package/templates/recipe-arena-2d/files/scenes/prefabs/pickup.pix3scene +45 -0
- package/templates/recipe-arena-2d/files/scenes/ui/result.pix3scene +85 -0
- package/templates/recipe-arena-2d/files/scripts/GameRules.ts +368 -0
- package/templates/recipe-arena-2d/files/scripts/MenuFlow.ts +106 -0
- package/templates/recipe-arena-2d/files/scripts/PlayerController.ts +151 -0
- package/templates/recipe-arena-2d/files/scripts/ScoreHud.ts +94 -0
- package/templates/recipe-arena-2d/files/scripts/Spawner.ts +214 -0
- package/templates/recipe-arena-2d/files/scripts/TouchRules.ts +174 -0
- 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 +10 -0
- package/templates/recipe-blank-2d/files/README.md +61 -0
- package/templates/recipe-blank-2d/files/design/recipe.md +148 -0
- package/templates/recipe-blank-2d/files/design/tests/reachability.json +4 -0
- package/templates/recipe-blank-2d/files/design/tests/routines/restart.json +27 -0
- package/templates/recipe-blank-2d/files/design/tests/routines/terminal-retry.json +23 -0
- package/templates/recipe-blank-2d/files/scenes/main.pix3scene +224 -0
- package/templates/recipe-blank-2d/files/scenes/ui/result.pix3scene +85 -0
- package/templates/recipe-blank-2d/files/scripts/GameRules.ts +350 -0
- package/templates/recipe-blank-2d/files/scripts/ScoreHud.ts +110 -0
- 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 +10 -0
- package/templates/recipe-bouncer-2d/files/README.md +81 -0
- package/templates/recipe-bouncer-2d/files/design/recipe.md +146 -0
- package/templates/recipe-bouncer-2d/files/design/tests/reachability.json +4 -0
- package/templates/recipe-bouncer-2d/files/design/tests/routines/terminal-retry.json +23 -0
- package/templates/recipe-bouncer-2d/files/scenes/main.pix3scene +615 -0
- package/templates/recipe-bouncer-2d/files/scenes/menu.pix3scene +191 -0
- package/templates/recipe-bouncer-2d/files/scenes/ui/result.pix3scene +87 -0
- package/templates/recipe-bouncer-2d/files/scripts/BallBody.ts +298 -0
- package/templates/recipe-bouncer-2d/files/scripts/GameRules.ts +377 -0
- package/templates/recipe-bouncer-2d/files/scripts/MenuFlow.ts +109 -0
- package/templates/recipe-bouncer-2d/files/scripts/PaddleController.ts +112 -0
- package/templates/recipe-bouncer-2d/files/scripts/ScoreHud.ts +94 -0
- package/templates/recipe-bouncer-2d/files/scripts/TouchRules.ts +263 -0
- package/templates/recipe-bouncer-2d/files/scripts/ball-collision.ts +307 -0
- 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 +10 -0
- package/templates/recipe-grid-3d/files/README.md +22 -0
- package/templates/recipe-grid-3d/files/design/recipe.md +110 -0
- package/templates/recipe-grid-3d/files/scenes/main.pix3scene +169 -0
- package/templates/recipe-grid-3d/files/scenes/menu.pix3scene +100 -0
- package/templates/recipe-grid-3d/files/scenes/ui/result.pix3scene +105 -0
- package/templates/recipe-grid-3d/files/scripts/GridBoard.ts +319 -0
- package/templates/recipe-grid-3d/files/scripts/GridRules.ts +278 -0
- package/templates/recipe-grid-3d/files/scripts/MenuFlow.ts +106 -0
- package/templates/recipe-grid-3d/files/scripts/ScoreHud.ts +94 -0
- package/templates/recipe-grid-3d/template.yaml +10 -0
- package/templates/recipe-tapper-2d/files/README.md +39 -0
- package/templates/recipe-tapper-2d/files/design/recipe.md +132 -0
- package/templates/recipe-tapper-2d/files/design/tests/reachability.json +4 -0
- package/templates/recipe-tapper-2d/files/design/tests/routines/terminal-retry.json +23 -0
- package/templates/recipe-tapper-2d/files/scenes/main.pix3scene +269 -0
- package/templates/recipe-tapper-2d/files/scenes/menu.pix3scene +100 -0
- package/templates/recipe-tapper-2d/files/scenes/prefabs/hazard.pix3scene +42 -0
- package/templates/recipe-tapper-2d/files/scenes/prefabs/target.pix3scene +45 -0
- package/templates/recipe-tapper-2d/files/scenes/ui/result.pix3scene +85 -0
- package/templates/recipe-tapper-2d/files/scripts/GameRules.ts +368 -0
- package/templates/recipe-tapper-2d/files/scripts/MenuFlow.ts +106 -0
- package/templates/recipe-tapper-2d/files/scripts/ScoreHud.ts +94 -0
- package/templates/recipe-tapper-2d/files/scripts/Spawner.ts +214 -0
- package/templates/recipe-tapper-2d/files/scripts/TouchRules.ts +192 -0
- 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 +10 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
<!-- Pix3 agent kit 1.6.0 — generated from docs/nodes-and-systems.md, src/services/agent/agent-skills/engine-api-map.md and the runtime's component registry. Do not edit: pix3 kit --update regenerates it. -->
|
|
2
|
+
# Script API reference
|
|
3
|
+
|
|
4
|
+
Generated from the engine's own documentation and code. Grep a heading instead of reading it
|
|
5
|
+
whole. Tool names the in-editor agent uses appear in places: `add_component` /
|
|
6
|
+
`set_component_property` = edit the scene YAML's `components:`; `list_component_types` = the
|
|
7
|
+
"`core:` components" section at the end of this file; `engine_search` / `engine_read` do not
|
|
8
|
+
exist for you — the runtime's declarations are in `.pix3/types/@pix3/runtime/` (or in
|
|
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.
|
|
10
|
+
|
|
11
|
+
## The surface a `Script` sees
|
|
12
|
+
|
|
13
|
+
Inside any `Script` subclass:
|
|
14
|
+
|
|
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 `add_component`/scene YAML only. To fetch by hand: `node.components.find(c => c instanceof CarController)`.
|
|
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, and `commands` for named game intents). May be `undefined` in some editor previews — guard it.
|
|
17
|
+
- `this.input` — the `InputService` (§4 Input).
|
|
18
|
+
- `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`). In the in-editor code editor the argument autocompletes to the node names/paths of the open scenes and the return type is the exact node type (`this.getNode('Hero')` → `Sprite2D`), à la Godot's `$Node` / WPF `x:Name`. Any other string resolves to `NodeBase`, so a script reused in a scene that lacks the name still type-checks — the names are hints, never constraints. (Typed names come from the editor augmenting `SceneNodeNames`; it's empty in exported games, where only `getNode<T>(query)` applies.)
|
|
20
|
+
|
|
21
|
+
**Lifecycle:** `onAttach(node)` → `onStart()` (first frame) → `onUpdate(dt)` (every
|
|
22
|
+
frame, `dt` is scaled game time) → `onDetach()`. Define `static getPropertySchema()`
|
|
23
|
+
to expose inspector-editable params (see §6). `this.config` holds params.
|
|
24
|
+
|
|
25
|
+
> **Ordering gotcha:** a node's components tick _before_ its children. Don't arm
|
|
26
|
+
> cross-node state in `onStart` that a child component's `onStart` will reset the
|
|
27
|
+
> same frame (e.g. a child camera's `CameraBrain`). Trigger such calls from a
|
|
28
|
+
> gameplay event or after a frame.
|
|
29
|
+
|
|
30
|
+
> **Real vs scaled time:** `onUpdate(dt)` and keyframe clips run on _scaled_
|
|
31
|
+
> `dt` (frozen by hitstop). Anything that must ignore hitstop/slow-mo (screen
|
|
32
|
+
> chrome, timers) uses `performance.now()` — mirror how `flash()`/letterbox work.
|
|
33
|
+
|
|
34
|
+
**Editor preview (draw the node your way without play mode):** implement
|
|
35
|
+
`tickEditorPreview(dt, ctx)` — the editor calls it on every non-play frame it
|
|
36
|
+
paints, for each enabled component. The viewport still renders on demand:
|
|
37
|
+
implementing the hook does not keep it painting — only `ctx.requestRender()`
|
|
38
|
+
called _during_ the tick asks for the next frame (called later, e.g. when an
|
|
39
|
+
asset finishes loading, it is a one-off repaint). The status bar shows **Live**
|
|
40
|
+
and names the script while one keeps the loop hot. Use `ctx.setAppearanceOverride({ textureRegion?, tint?,
|
|
41
|
+
visible? })` to change how _this component's node_ draws in the editor viewport;
|
|
42
|
+
it is immediate-mode (stop pushing → the proxy reverts) and never mutates or
|
|
43
|
+
serializes the node. `ctx.assetLoader` / `ctx.requestRender()` are also provided;
|
|
44
|
+
call `requestRender()` for continuous animation. For UV cropping specifically,
|
|
45
|
+
`Sprite2D.setTextureRegion({ x, y, width, height } | null)` shows a normalized
|
|
46
|
+
sub-rect of the texture (e.g. one digit of an odometer strip) — a transient,
|
|
47
|
+
non-serialized crop. It is per-sprite even when several sprites reuse the same
|
|
48
|
+
cached texture (the runtime crops a private clone that shares the GPU image), so
|
|
49
|
+
you never clone textures yourself. Author the region once and drive it from
|
|
50
|
+
**both** `onUpdate` (play) and `tickEditorPreview` (edit) so the two modes match. A
|
|
51
|
+
throwing `tickEditorPreview` disables the component and surfaces the error like a
|
|
52
|
+
play-mode hook, so the editor keeps running.
|
|
53
|
+
|
|
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.
|
|
66
|
+
|
|
67
|
+
### Nodes
|
|
68
|
+
|
|
69
|
+
- Transform is three.js and **read-only by reference**: `node.position.set(x, y, 0)`,
|
|
70
|
+
`node.position.x += dx`, `node.rotation.z = radians`, `node.scale.set(s, s, 1)`. Never
|
|
71
|
+
assign `node.position = …`.
|
|
72
|
+
- `visible`, `name`, `id`, `children`, `parentNode`, `findById(id)`, `findByName(name)`,
|
|
73
|
+
`adoptChild(child)` (runtime parenting), `queueFree()` (safe inside `onUpdate`; use
|
|
74
|
+
`dispose()` only outside the tick), `getComponent(Class)`, `addComponent(c)`,
|
|
75
|
+
`removeComponent(c)`.
|
|
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)`.
|
|
92
|
+
- 2D node types: `ColorRect2D`, `Sprite2D`, `TiledSprite2D`, `AnimatedSprite2D`, `Group2D`,
|
|
93
|
+
`Label2D` (`label`, `labelFontSize`, `labelColor`, `glowColor`/`glowStrength`,
|
|
94
|
+
`outlineColor`/`outlineWidth`), `Button2D`, `Slider2D`, `Checkbox2D`, `Bar2D`, `Joystick2D`,
|
|
95
|
+
`InventorySlot2D`, `ScrollContainer2D`, `Camera2D`, `CanvasLayer2D` (HUD layer, drawn after
|
|
96
|
+
post-fx, never blooms), `SpineSkeleton2D`, `Layout2D`.
|
|
97
|
+
- 3D node types: `Node3D`, `Camera3D`, `VirtualCamera3D`, `GeometryMesh`, `MeshInstance`,
|
|
98
|
+
`InstancedMesh3D`, `Sprite3D`, `AnimatedSprite3D`, `Particles3D`, lights (`Ambient`,
|
|
99
|
+
`Hemisphere`, `Directional`, `Point`, `Spot`), `PostProcess` (bloom / vignette /
|
|
100
|
+
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
|
+
|
|
104
|
+
### `this.scene`
|
|
105
|
+
|
|
106
|
+
- Lookup: `findNode(q)`, `findNodeById`, `findNodeByName`, `findNodeByPath`, `getRootNodes()`,
|
|
107
|
+
`getActiveCamera()`, `getActiveCamera2D()`, `getViewportInfo()`, `isPortrait()`.
|
|
108
|
+
- **Pointer in 2D world space:** `scene.getPointer2DWorldPosition()` → `Vector2 | null`
|
|
109
|
+
(design pixels, Y up; the primary finger), `getPointer2DWorldPosition(pointerId)` for one
|
|
110
|
+
finger (null once it is up). Godot's `get_global_mouse_position()`.
|
|
111
|
+
- Spawning: `await scene.instantiate('res://prefabs/x.pix3scene', { parent: 'container' })`
|
|
112
|
+
→ the new node (prefab = a `.pix3scene` with one root). Despawn with `node.queueFree()`.
|
|
113
|
+
- Scenes & transitions: `await scene.changeScene('res://scenes/menu.pix3scene', {
|
|
114
|
+
transition: 'fade' | 'none', durationSec: 0.3 })`; `scene.fadeToBlack(sec, onDone?)`,
|
|
115
|
+
`scene.fadeFromBlack(sec, onDone?)`, `scene.switchCameraWithFade(cameraId, outSec, inSec,
|
|
116
|
+
onDone?)`; `scene.flash({ color, intensity, durationSec })`. Real-time overlays — they
|
|
117
|
+
survive hitstop.
|
|
118
|
+
- Time: `scene.time.hitstop(ms)` (edge-triggered — on a contact START, never per frame),
|
|
119
|
+
`scene.time.slowMotion(scale, { durationMs, blendMs })`, `setScale`, `reset`, `scale`,
|
|
120
|
+
`isFrozen`.
|
|
121
|
+
- **Juice (`scene.juice`)** — one-liners, call them WITH the mechanic: `shake(target, {
|
|
122
|
+
amplitude: 8, frequency: 24, duration: 0.35, decay: 1.5 })`, `punchScale(target, { amount: 0.3,
|
|
123
|
+
duration: 0.35, vibrato: 3 })`, `popIn(target, { from: 0, duration: 0.4, easing: 'backOut' })`
|
|
124
|
+
(these three take `duration`, not `durationSec`), `flash({ color: '#ffffff', intensity: 1,
|
|
125
|
+
durationSec: 0.2 })`, `burst(anchor, { count: 14,
|
|
126
|
+
speed: 260, spread: 2π, direction, lifeSec: 0.5, color | colors: [], sizePx: 10, gravityY:
|
|
127
|
+
-600, fadeOut: true, additive: true, zIndex })`, `floatText('+25', { at, color,
|
|
128
|
+
fontSizePx: 28, driftPx: 60, durationSec: 0.8, glow })`. `target` = node | node query |
|
|
129
|
+
`'camera'` / `'camera2d'`; `anchor` = node | query | `{ x, y }` 2D world point. Presets as
|
|
130
|
+
components: `core:BurstOnSignal`, `core:SfxOnSignal`, `core:Shake`, `core:PunchScale`,
|
|
131
|
+
`core:PopIn`, `core:Fade`.
|
|
132
|
+
- **Tweens (`scene.tween`)** — Godot-style, on scaled time: `scene.tween.to(target, props, {
|
|
133
|
+
durationSec: 0.3, ease: 'cubicOut', delaySec, yoyo, repeat (-1 = forever), onUpdate,
|
|
134
|
+
onComplete })` → `{ cancel(), finished: Promise<'completed' | 'cancelled'>, isRunning }`.
|
|
135
|
+
`props` for a node: `x`, `y`, `position: {x, y}`, `scale` (number = uniform, or `{x, y}`),
|
|
136
|
+
`rotation` (radians), `opacity`, `width`, `height`, or any numeric dotted path; for a plain
|
|
137
|
+
object every key is a dotted path. `fadeIn(node, sec)`, `fadeOut(node, sec, { hide: true })`,
|
|
138
|
+
`crossFade(from, to, sec)` (state/panel switches — the in-scene counterpart of
|
|
139
|
+
`changeScene`'s fade), `killAll(target?)`. Use these instead of hand-lerping in `onUpdate`.
|
|
140
|
+
- **Trail (`scene.juice.trail`)**: `scene.juice.trail(node, { lifeSec: 0.35, widthPx: 14,
|
|
141
|
+
color | colors: [], additive: true, zIndex, maxPoints: 48 })` → a fading ribbon that follows
|
|
142
|
+
the node (a ball, a projectile); `trail.stop()` lets it fade out; freeing the node frees it.
|
|
143
|
+
- **Audio (`scene.audio`)**: `sfx(preset, { volume, pitch })` — procedural, no asset; presets
|
|
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`.
|
|
173
|
+
- Multiplayer: `scene.network`, `scene.netNodes`, `core:NetworkedNode`,
|
|
174
|
+
`core:ReplicatedTransform` (only when the brief says multiplayer).
|
|
175
|
+
|
|
176
|
+
### `this.input`
|
|
177
|
+
|
|
178
|
+
- Per frame: `input.pointerEvents` → `{ type: 'down' | 'move' | 'up' | 'cancel', pointerId,
|
|
179
|
+
x, y }[]` (`cancel` is never a completed tap); `input.keyEvents`; `input.wheelDelta`.
|
|
180
|
+
- Polled: `input.isPointerDown` (any finger), `input.pointerPosition` (primary finger,
|
|
181
|
+
canvas space — convert with `scene.getPointer2DWorldPosition()`), `input.getActivePointers()`,
|
|
182
|
+
`input.getPointer(id)`, `input.pointerDownCount`, `input.isPointerOverUI(pointerId)` (gate
|
|
183
|
+
gestures on THIS, not on `isHoveringUI`), `input.getButton(name)` / `getAxis(name)`
|
|
184
|
+
(`Action_Primary`, arrows / WASD axes), `lock()` / `unlock()`.
|
|
185
|
+
- Keyboard: compare `event.code` (`'KeyW'`, `'ArrowUp'`), case-sensitive.
|
|
186
|
+
- A tap-to-place mechanic is: on a `'down'` event, `scene.getPointer2DWorldPosition(ev.pointerId)`,
|
|
187
|
+
convert to the board's local space (`board.worldToLocal` or subtract the board's world
|
|
188
|
+
position), place the node, `scene.juice.burst({ x, y })`, `scene.audio.sfx('tap')`.
|
|
189
|
+
|
|
190
|
+
### Traps
|
|
191
|
+
|
|
192
|
+
- Assigning `position` / `rotation` / `scale` throws — mutate them.
|
|
193
|
+
- A component that throws in `onStart` / `onUpdate` is auto-disabled and the game keeps
|
|
194
|
+
running — `read_errors` after `play_start` is the only way to see it.
|
|
195
|
+
- `this.node as any` disables exactly the check that would have caught the above.
|
|
196
|
+
- Hitstop every frame while an overlap lasts freezes the contact forever — edge-trigger it.
|
|
197
|
+
- A hidden `UIControl2D` takes no input; `tick` still runs on hidden nodes.
|
|
198
|
+
|
|
199
|
+
## Capability catalog — nodes
|
|
200
|
+
|
|
201
|
+
Add via the editor **Create** menu / `Create*Command`, or author in `.pix3scene`
|
|
202
|
+
YAML, or (from a script) construct + `parent.adoptChild(child)`. Full property
|
|
203
|
+
tables: node-types-reference.md.
|
|
204
|
+
|
|
205
|
+
**Structure / base**
|
|
206
|
+
|
|
207
|
+
- `Node2D`, `Node3D` — transform containers (2D uses anchors/layout; 3D is a
|
|
208
|
+
Three.js `Object3D`). `Group2D` groups 2D content.
|
|
209
|
+
|
|
210
|
+
**2D content & UI** (orthographic overlay pass; draw order = tree order)
|
|
211
|
+
|
|
212
|
+
- `Sprite2D`, `AnimatedSprite2D`, `TiledSprite2D`, `ColorRect2D` — images / frames / 9-slice-ish tiling / solid rects.
|
|
213
|
+
- `SpineSkeleton2D` — a Spine skeleton (`.json`/`.skel` + `.atlas`). Skeletal rigs, mesh deformation and animation mixing, i.e. what a flipbook cannot do; see the recipe below.
|
|
214
|
+
- UI controls: `Button2D`, `Label2D`, `Slider2D`, `Joystick2D`, `Checkbox2D`, `Bar2D`, `ScrollContainer2D`, `InventorySlot2D`.
|
|
215
|
+
**Skins:** these controls are colour-driven by default and take sprites through texture slots — `Button2D` `textureNormal/Hover/Pressed/Disabled`, `Checkbox2D` `textureBox` / `textureBoxChecked` / `textureMark`, `Slider2D` `textureTrack` / `textureFill` / `textureThumb`, `Bar2D` `textureTrough` / `textureFill`, plus `ScrollContainer2D`'s thumb/track. A set slot replaces that flat colour; an unset one keeps it. `Button2D`, `Slider2D` (track+fill) and `Bar2D` (trough+fill) also take the four `sliceBorder*` nine-slice insets `TiledSprite2D` uses, so one 64x64 skin fits any size instead of smearing — and a fill that shrinks with `value` is re-cut, not squashed. A sliced skin opts out of the 2D quad batcher.
|
|
216
|
+
`Label2D` is multiline: a fixed `width` word-wraps, `labelAlign`/`labelVAlign` align inside the box, and `typewriterSpeed` + `setText()`/`skipTypewriter()`/`'typewriter-complete'` give a per-character reveal.
|
|
217
|
+
- `Camera2D` — pan/zoom/limits/shake for the 2D pass. `CanvasLayer2D` — fixed HUD layer, unaffected by Camera2D.
|
|
218
|
+
- `AnimatedSprite2D` (and `AnimatedSprite3D`) play a flipbook from a **`.pix3anim`** resource — see the recipe below. A non-looping clip emits **`animation-finished`** (clip name as arg) when it stops on the last frame. For self-freeing one-shot VFX set **`freeOnFinish: true`** on the node (destroys itself when the clip ends — no component); use `core:FreeOnSignal` only when the trigger is some _other_ signal.
|
|
219
|
+
|
|
220
|
+
**Flipbook animation (`.pix3anim`)** — hand-author it; the file is plain JSON and `SceneLoader` auto-loads the resource + every frame texture (also when the node arrives via `scene.instantiate` of a prefab). Every omitted field is defaulted on load (fps 12, loop true, `playbackMode` normal, anchor 0.5/0.5, `durationMultiplier` 1). Save it next to the frames, point the node at it. **Sequence mode** (one image per frame — the common case, e.g. an impact flash):
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"version": "1.0.0",
|
|
225
|
+
"texturePath": "",
|
|
226
|
+
"clips": [
|
|
227
|
+
{
|
|
228
|
+
"name": "burst",
|
|
229
|
+
"fps": 30,
|
|
230
|
+
"loop": false,
|
|
231
|
+
"frames": [
|
|
232
|
+
{ "texturePath": "res://.../fireb0001.png" },
|
|
233
|
+
{ "texturePath": "res://.../fireb0002.png" }
|
|
234
|
+
/* … one entry per frame … */
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**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.
|
|
242
|
+
|
|
243
|
+
**Frame presentation — `sizeMode`, per-frame `anchor`, `sourceSize`.** Two anchors are in play and they mean different things. The **node** `anchor` is a global pivot in the node's `width × height` box (y up, same as `Sprite2D.anchor`). Each **frame's** `anchor` is that frame's own origin inside its — possibly tightly cropped — raster, normalized with **y measured from the top** (image convention, like `boundingBox`). They compose: the quad is placed so the frame anchor lands on the node's position, then shifted by the node pivot. That is what makes cropping pay off — crop a frame tighter, move its anchor to the old visual centre, and the animation is pixel-identical while the PNG (and the atlas) shrinks. `sizeMode` decides how a frame fills the box: `'stretch'` (default, and what every pre-existing scene assumes) scales every frame to exactly `width × height`; `'native'` renders each frame at its own `sourceSize` scaled by one per-clip factor derived from the clip's FIRST frame, so mixed-size frames keep their relative proportions and resizing the node scales the whole animation uniformly (the editor sets `'native'` on newly created nodes). `sourceSize` is an optional per-frame `{width,height}` the editor stamps whenever a frame is added, imported or sliced, so native layout never waits on a texture load; a frame with no known size falls back to stretch, so legacy files keep working. The math lives in one shared module (`core/animated-sprite-layout.ts`) because the editor viewport draws SEPARATE proxy meshes — both apply the same resolver.
|
|
244
|
+
|
|
245
|
+
**Named frame points (sockets).** `AnimationFrame.points?: [{name, x, y, angle?}]` — points that live in frame space (normalized, y from the top; `angle` in degrees, 0 = right) and move _and rotate_ across frames: a muzzle on a barrel, a hand socket an item follows through a walk cycle. Read them from scripts — `sprite.getFramePoint('muzzle')` returns node-local `{x, y, angle}` usable directly as a child position, `getFramePointWorld('muzzle')` adds the node's world transform and accumulated Z rotation, `getClipPointNames()` lists the active clip's points; all return `null`/`[]` when the current frame doesn't define the point. Frame `events` compose naturally: an emitting frame fires `muzzle-flash`, the handler reads `getFramePoint('muzzle')`. For the "item in hand" case attach **`core:PointAttachment`** to the child (`point`, `applyRotation`, `offsetX`/`offsetY`, optional `spriteNodeId`); it parks the node on the named point every tick and leaves it alone on frames that don't define it. Authoring: the Sprite Editor's **points** canvas tool (drag the dot, drag the direction handle for the angle; the previous frame's points ghost behind as a mini onion-skin).
|
|
246
|
+
|
|
247
|
+
**Spine skeletal animation (`SpineSkeleton2D`)** — for rigs authored in the Spine
|
|
248
|
+
editor, when a flipbook (`.pix3anim`) is not enough: bone hierarchies, mesh
|
|
249
|
+
deformation, skins, and crossfaded animation mixing. Point the node at its
|
|
250
|
+
`skeletonPath` (`.json`/`.skel`) and `atlasPath` (`.atlas`); the page images come
|
|
251
|
+
from the atlas text and are resolved next to it. Once the asset loads, the
|
|
252
|
+
Inspector's `animation`/`skin` fields become dropdowns of the skeleton's real
|
|
253
|
+
names. From a script:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const hero = scene.getNode<SpineSkeleton2D>('Hero');
|
|
257
|
+
hero.play('run', { loop: true, mixDuration: 0.2 });
|
|
258
|
+
hero.queue('idle', { loop: true });
|
|
259
|
+
hero.setSkin('blue');
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Editor playback is **opt-in**: a placed skeleton holds its first frame until you
|
|
263
|
+
press Play on the Inspector's Editor Preview row (`previewInEditor`), and Reset
|
|
264
|
+
rewinds to that frame without touching the authored state.
|
|
265
|
+
|
|
266
|
+
Signals: `animation-started`, `animation-finished` (non-looping end — pair with
|
|
267
|
+
`freeOnFinish: true` for one-shot VFX), `animation-looped`, and `spine-event` for
|
|
268
|
+
keyed animation events. Sizing is the node transform, not width/height. Spine is an
|
|
269
|
+
**optional** dependency (`@esotericsoftware/spine-threejs` `~4.3`, Spine Runtimes
|
|
270
|
+
License, lazily imported): the editor and the exported player register it
|
|
271
|
+
automatically, a consumer project calls
|
|
272
|
+
`setSpineModuleLoader(() => import('@esotericsoftware/spine-threejs'))` once.
|
|
273
|
+
Not batched/atlased and no shader-effect support (spine owns its materials); CPU
|
|
274
|
+
cost is per skeleton per frame, so budget dozens, not hundreds. Full property table:
|
|
275
|
+
`docs/node-types-reference.md` → `### SpineSkeleton2D`.
|
|
276
|
+
|
|
277
|
+
**3D content**
|
|
278
|
+
|
|
279
|
+
- `GeometryMesh` — primitive/standard-material mesh; supports **shader effects** (§4) and baked/realtime AO.
|
|
280
|
+
- `MeshInstance` — a loaded model (glTF). `InstancedMesh3D` — GPU-instanced copies for crowds.
|
|
281
|
+
- `Sprite3D`, `AnimatedSprite3D` — billboarded sprites in 3D.
|
|
282
|
+
- `Particles3D` — GPU-ish particle system with trails + sub-emitters + world/local sim.
|
|
283
|
+
|
|
284
|
+
**Cameras & lights**
|
|
285
|
+
|
|
286
|
+
- `Camera3D` — the single render camera (attach `core:CameraBrain` for blending).
|
|
287
|
+
- `VirtualCamera3D` — non-rendering "virtual camera" rigs selected by priority (§4 Camera system).
|
|
288
|
+
- `DirectionalLightNode`, `PointLightNode`, `SpotLightNode`, `AmbientLightNode`, `HemisphereLightNode`.
|
|
289
|
+
|
|
290
|
+
**Other**
|
|
291
|
+
|
|
292
|
+
- `AudioPlayer` — a scene-graph audio source (§4 Audio).
|
|
293
|
+
- `PostProcess` — enables the post-processing pipeline (§4 Post-processing).
|
|
294
|
+
|
|
295
|
+
## Capability catalog — `core:*` behaviours
|
|
296
|
+
|
|
297
|
+
Attach in the inspector or in YAML `components:`. These are the pre-built,
|
|
298
|
+
designer-facing behaviors — prefer them over writing a script for the same
|
|
299
|
+
effect. Registered in
|
|
300
|
+
packages/pix3-runtime/src/behaviors/register-behaviors.ts.
|
|
301
|
+
|
|
302
|
+
| Component id | Does |
|
|
303
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
304
|
+
| `core:Rotate` | Continuous rotation of a 3D node |
|
|
305
|
+
| `core:SimpleMove` | Simple test movement |
|
|
306
|
+
| `core:Sine` | Oscillate a node along an axis |
|
|
307
|
+
| `core:Follow` | Smoothly follow a target node's position/rotation |
|
|
308
|
+
| `core:PinToNode` | Pin a 2D UI node to a 3D target (screen projection) |
|
|
309
|
+
| `core:Fade` | Fade a 2D node's opacity in/out (optional auto-destroy) |
|
|
310
|
+
| `core:RadialProgress` | Circular progress mask on a Sprite2D |
|
|
311
|
+
| `core:AnimationPlayer` | Play keyframe clips on this node + descendants (§4) |
|
|
312
|
+
| `core:PointAttachment` | Keep this node on a named frame point of a parent `AnimatedSprite2D` (hand socket, muzzle) every tick, optionally copying the point's angle |
|
|
313
|
+
| `core:PlaySound` | Play a sound when a node signal fires |
|
|
314
|
+
| `core:SfxOnSignal` | Play a **procedural** (asset-free) sound preset when a node signal fires — see §4 "Procedural SFX" |
|
|
315
|
+
| `core:BurstOnSignal` | Spawn a one-shot 2D particle burst at this node when a signal fires (juice) |
|
|
316
|
+
| `core:FreeOnSignal` | `queueFree` this node when a signal fires on it (e.g. `animation-finished`), after an optional delay — one-shot VFX lifecycle |
|
|
317
|
+
| `core:Shake` | Additive positional shake (juice) |
|
|
318
|
+
| `core:PunchScale` | Squash-and-stretch scale punch (juice) |
|
|
319
|
+
| `core:PopIn` | Spawn pop-in scale with overshoot (juice) |
|
|
320
|
+
| `core:CameraBrain` | Blend the render camera between virtual cameras (§4) |
|
|
321
|
+
| `core:Hitbox2D` | Queryable 2D collision shape (rect/circle/polygon, group tag) — see §4 "2D collision" |
|
|
322
|
+
| `core:PhysicsBody2D` | Rigid body simulated by `scene.physics2d` (static/kinematic/dynamic) — see §4 "2D physics" |
|
|
323
|
+
| `core:Collider2D` | Physics shape (rect/circle/polygon, rotation-aware, may be concave); a sensor with no body is an Area2D |
|
|
324
|
+
| `core:PhysicsWorld2D` | Sets the 2D world gravity; attach to the scene root |
|
|
325
|
+
| `core:RevoluteJoint2D` | Hinge with optional angle limits and a motor — flippers, swinging doors, ragdoll links |
|
|
326
|
+
| `core:NetworkedNode` | Bind this node to a replicated entity — spawn one for the local player, adopt a peer's — see §4 "Multiplayer replication" |
|
|
327
|
+
| `core:ReplicatedTransform` | Replicate position/rotation: owner publishes quantized, peers interpolate on a timed buffer |
|
|
328
|
+
|
|
329
|
+
Most juice behaviors have a `triggerEvent` (a signal name) and/or `playOnStart`,
|
|
330
|
+
so a keyframe **event track** or a script `emit()` can fire them.
|
|
331
|
+
|
|
332
|
+
**Shader effects** (added via the inspector "Add Effect" picker or
|
|
333
|
+
`node.attachEffect(id)`) attach to `GeometryMesh` (3D) and to `Sprite2D` /
|
|
334
|
+
`AnimatedSprite2D` / `Button2D` skin (2D): `core:dissolve`, `core:rim`
|
|
335
|
+
(3D-only), `core:uv-scroll`, `core:flash`, `core:adjust`
|
|
336
|
+
(brightness/contrast/saturation), `core:grayscale`, `core:tint`. Params are
|
|
337
|
+
keyframe-animatable. See
|
|
338
|
+
packages/pix3-runtime/src/shader-effects/.
|
|
339
|
+
|
|
340
|
+
## `core:` components — every config key
|
|
341
|
+
|
|
342
|
+
Read from the runtime's component registry for this CLI's version: the keys a `config:` block
|
|
343
|
+
may carry, their type, the default a fresh component has, and the inspector's range/options.
|
|
344
|
+
|
|
345
|
+
### `core:AnimationPlayer`
|
|
346
|
+
|
|
347
|
+
Plays keyframe animation clips on this node and its descendants
|
|
348
|
+
|
|
349
|
+
| Key | Type | Default | Notes |
|
|
350
|
+
| --- | --- | --- | --- |
|
|
351
|
+
| `autoplay` | string | `""` | Clip name to play automatically on start (empty = none) |
|
|
352
|
+
| `speed` | number | `1` | ≥ 0; Playback speed multiplier |
|
|
353
|
+
|
|
354
|
+
### `core:BurstOnSignal`
|
|
355
|
+
|
|
356
|
+
Spawn a one-shot 2D particle burst at this node when a signal fires (juice)
|
|
357
|
+
|
|
358
|
+
| Key | Type | Default | Notes |
|
|
359
|
+
| --- | --- | --- | --- |
|
|
360
|
+
| `signal` | string | `"hit"` | Node signal that fires the burst (e.g. hit, damaged, pointerdown) |
|
|
361
|
+
| `count` | number | `14` | 1..512; Particles per burst |
|
|
362
|
+
| `color` | color | `""` | Particle colour; empty = white |
|
|
363
|
+
| `speed` | number | `260` | ≥ 0; Initial particle speed |
|
|
364
|
+
| `sizePx` | number | `10` | ≥ 0.5 |
|
|
365
|
+
| `lifeSec` | number | `0.5` | ≥ 0.05 |
|
|
366
|
+
| `gravityY` | number | `-600` | Vertical acceleration; negative falls, 0 = weightless |
|
|
367
|
+
|
|
368
|
+
### `core:CameraBrain`
|
|
369
|
+
|
|
370
|
+
Blends the render camera between virtual cameras by priority (Cinemachine-lite)
|
|
371
|
+
|
|
372
|
+
| Key | Type | Default | Notes |
|
|
373
|
+
| --- | --- | --- | --- |
|
|
374
|
+
| `blendEnabled` | boolean | `true` | Ease between cameras on activation. Off = hard cut. |
|
|
375
|
+
|
|
376
|
+
### `core:Collider2D`
|
|
377
|
+
|
|
378
|
+
Physics shape (rect/circle/polygon, rotation-aware, may be concave). With no Physics Body above it this is static world geometry; as a sensor it reports body-entered / body-exited.
|
|
379
|
+
|
|
380
|
+
| Key | Type | Default | Notes |
|
|
381
|
+
| --- | --- | --- | --- |
|
|
382
|
+
| `shape` | select | `"rect"` | rect \| circle \| polygon \| capsule; All of them follow the node rotation and scale. A capsule is upright along local Y and sized by height + radius (Godot’s convention). A polygon may be concave; it is split into convex parts for you. |
|
|
383
|
+
| `width` | number | `64` | Rect width in design pixels. |
|
|
384
|
+
| `height` | number | `64` | Rect height, or a capsule’s TOTAL height including both caps. |
|
|
385
|
+
| `radius` | number | `32` | Circle radius, or a capsule’s cap radius (its half-width). |
|
|
386
|
+
| `offsetX` | number | `0` | Shape offset from the node origin. |
|
|
387
|
+
| `offsetY` | number | `0` | Shape offset from the node origin. |
|
|
388
|
+
| `polygonSource` | select | `"manual"` | manual \| frame; manual: the vertices below. frame: the collision polygon of the animation frame showing right now. |
|
|
389
|
+
| `points` | object | `[]` | Vertices in node-local pixels (y up). Edit them in the viewport. |
|
|
390
|
+
| `friction` | number | `0.4` | Coulomb friction. Combined between two colliders as the geometric mean. |
|
|
391
|
+
| `restitution` | number | `0.2` | Bounciness, 0 to 1. Combined as the maximum of the two. Slow contacts never bounce. |
|
|
392
|
+
| `density` | number | `1` | Mass per unit area, used when the body derives its own mass. |
|
|
393
|
+
| `sensor` | boolean | `false` | Detect overlaps and report body-entered / body-exited without pushing anything (Godot Area2D). |
|
|
394
|
+
| `group` | string | `"default"` | Tag used to filter physics queries (same convention as Hitbox2D). |
|
|
395
|
+
|
|
396
|
+
### `core:Fade`
|
|
397
|
+
|
|
398
|
+
Fades 2D node opacity in and out with optional auto-destroy
|
|
399
|
+
|
|
400
|
+
| Key | Type | Default | Notes |
|
|
401
|
+
| --- | --- | --- | --- |
|
|
402
|
+
| `FadeInTime` | number | `0.5` | ≥ 0 |
|
|
403
|
+
| `FadeOutTime` | number | `0.5` | ≥ 0 |
|
|
404
|
+
| `FadeInDelay` | number | `0` | ≥ 0 |
|
|
405
|
+
| `FadeOutDelay` | number | `0` | ≥ 0 |
|
|
406
|
+
| `DestroyOnFadeOut` | boolean | `false` | |
|
|
407
|
+
| `FadeOnStart` | boolean | `false` | |
|
|
408
|
+
|
|
409
|
+
### `core:Follow`
|
|
410
|
+
|
|
411
|
+
Smoothly follows a target node position and/or rotation
|
|
412
|
+
|
|
413
|
+
| Key | Type | Default | Notes |
|
|
414
|
+
| --- | --- | --- | --- |
|
|
415
|
+
| `targetNodeId` | node | `""` | Node to follow |
|
|
416
|
+
| `positionSmoothing` | number | `8` | ≥ 0; Higher values follow position faster |
|
|
417
|
+
| `followPositionX` | boolean | `true` | |
|
|
418
|
+
| `followPositionY` | boolean | `true` | |
|
|
419
|
+
| `followPositionZ` | boolean | `true` | |
|
|
420
|
+
| `rotationSmoothing` | number | `8` | ≥ 0; Higher values follow rotation faster |
|
|
421
|
+
| `followRotationX` | boolean | `false` | |
|
|
422
|
+
| `followRotationY` | boolean | `true` | |
|
|
423
|
+
| `followRotationZ` | boolean | `false` | |
|
|
424
|
+
|
|
425
|
+
### `core:FreeOnSignal`
|
|
426
|
+
|
|
427
|
+
Frees this node when a signal fires on it (e.g. animation-finished) — one-shot VFX lifecycle
|
|
428
|
+
|
|
429
|
+
| Key | Type | Default | Notes |
|
|
430
|
+
| --- | --- | --- | --- |
|
|
431
|
+
| `signal` | string | `"animation-finished"` | Node signal that triggers the free (e.g. animation-finished) |
|
|
432
|
+
| `delay` | number | `0` | ≥ 0; Seconds to wait after the signal before freeing |
|
|
433
|
+
|
|
434
|
+
### `core:Hitbox2D`
|
|
435
|
+
|
|
436
|
+
Queryable 2D collision shape (rect/circle) with group filtering — hit-test via scene.collision2d
|
|
437
|
+
|
|
438
|
+
| Key | Type | Default | Notes |
|
|
439
|
+
| --- | --- | --- | --- |
|
|
440
|
+
| `shape` | select | `"rect"` | rect \| circle \| polygon; Rect and circle are axis-aligned (rotation ignored, scale honored); polygon is rotation-aware and may be concave |
|
|
441
|
+
| `width` | number | `64` | |
|
|
442
|
+
| `height` | number | `64` | |
|
|
443
|
+
| `radius` | number | `32` | |
|
|
444
|
+
| `offsetX` | number | `0` | |
|
|
445
|
+
| `offsetY` | number | `0` | |
|
|
446
|
+
| `polygonSource` | select | `"manual"` | manual \| frame; manual: the vertices below. frame: the collision polygon of the animation frame currently showing on this AnimatedSprite2D. |
|
|
447
|
+
| `points` | object | `[]` | Vertices in node-local pixels (y up). Edit them in the viewport. |
|
|
448
|
+
| `group` | string | `"default"` | Collision group tag used to filter queries (Godot-style group) |
|
|
449
|
+
| `debugDraw` | boolean | `false` | Render the shape outline in play mode |
|
|
450
|
+
|
|
451
|
+
### `core:NetworkedNode`
|
|
452
|
+
|
|
453
|
+
Binds this node to a replicated entity — spawns one for the local player, or adopts the entity a peer spawned
|
|
454
|
+
|
|
455
|
+
| Key | Type | Default | Notes |
|
|
456
|
+
| --- | --- | --- | --- |
|
|
457
|
+
| `prefabPath` | string | `""` | This node's own prefab (res:// path). Indexes the build's netKindTable to the wire kind, and is what every other client instantiates for this entity. |
|
|
458
|
+
| `ownership` | select | `"owned"` | owned \| shared \| transferable; What happens when the owner leaves: owned = despawned, shared = reassigned to the new host, transferable = reassignable to anyone. |
|
|
459
|
+
| `spawnOnStart` | boolean | `true` | Request an entity as soon as this node starts. Turn it off for a node a script spawns explicitly, or for a prefab that only ever represents a peer. |
|
|
460
|
+
| `despawnOnDetach` | boolean | `true` | Despawn the entity when this node goes away (scene change, queueFree). Off leaves a ghost in the room until its owner disconnects. |
|
|
461
|
+
| `appFlags` | number | `0` | 0..31; Game-defined flag bits 3–7 (0–31), replicated verbatim. |
|
|
462
|
+
|
|
463
|
+
### `core:PhysicsBody2D`
|
|
464
|
+
|
|
465
|
+
Rigid body simulated by scene.physics2d — static, kinematic or dynamic. Shape comes from sibling or descendant Collider 2D components.
|
|
466
|
+
|
|
467
|
+
| Key | Type | Default | Notes |
|
|
468
|
+
| --- | --- | --- | --- |
|
|
469
|
+
| `bodyType` | select | `"dynamic"` | static \| kinematic \| dynamic; static: never moves. kinematic: moved by scripts/animation, pushes others, is not pushed. dynamic: simulated. |
|
|
470
|
+
| `gravityScale` | number | `1` | Multiplier on world gravity. 0 makes the body float; negative makes it rise. |
|
|
471
|
+
| `mass` | number | `0` | Explicit mass. 0 derives it from the collider area times its density. |
|
|
472
|
+
| `linearDamping` | number | `0` | Per-second velocity decay. |
|
|
473
|
+
| `angularDamping` | number | `0.05` | Per-second spin decay. |
|
|
474
|
+
| `fixedRotation` | boolean | `false` | Lock the rotation — the usual choice for a character or a UI prop. |
|
|
475
|
+
| `bullet` | boolean | `false` | Sweep this body against static geometry so a fast circle cannot tunnel through a wall. |
|
|
476
|
+
| `canSleep` | boolean | `true` | Let the body stop simulating once it has been at rest for half a second. |
|
|
477
|
+
| `emitContacts` | boolean | `false` | Fire contact-started / contact-ended on this node. Off by default: most games never read them. |
|
|
478
|
+
|
|
479
|
+
### `core:PhysicsWorld2D`
|
|
480
|
+
|
|
481
|
+
Sets the 2D world gravity. Attach to the scene root.
|
|
482
|
+
|
|
483
|
+
| Key | Type | Default | Notes |
|
|
484
|
+
| --- | --- | --- | --- |
|
|
485
|
+
| `gravityX` | number | `0` | Sideways gravity in px/s^2. 0 for most games. |
|
|
486
|
+
| `gravityY` | number | `-1960` | Vertical gravity in px/s^2. Y is up, so falling is NEGATIVE. |
|
|
487
|
+
|
|
488
|
+
### `core:PinToNode`
|
|
489
|
+
|
|
490
|
+
Pins a 2D UI node to a 3D target node
|
|
491
|
+
|
|
492
|
+
| Key | Type | Default | Notes |
|
|
493
|
+
| --- | --- | --- | --- |
|
|
494
|
+
| `targetNodeId` | node | `""` | |
|
|
495
|
+
| `yOffset` | number | `2` | |
|
|
496
|
+
|
|
497
|
+
### `core:PlaySound`
|
|
498
|
+
|
|
499
|
+
Plays an audio track when a node signal is emitted
|
|
500
|
+
|
|
501
|
+
| Key | Type | Default | Notes |
|
|
502
|
+
| --- | --- | --- | --- |
|
|
503
|
+
| `audioTrack` | string | `""` | Asset URL (res://..., data:audio/..., or absolute URL) |
|
|
504
|
+
| `triggerEvent` | string | `"pointerdown"` | Node signal or UI event (pointerdown, pointerup, click) |
|
|
505
|
+
| `volume` | number | `1` | 0..1 |
|
|
506
|
+
| `loop` | boolean | `false` | |
|
|
507
|
+
| `bus` | enum | `"sfx"` | master \| music \| sfx |
|
|
508
|
+
| `pitchVariation` | number | `0` | 0..1; Random ± playback-rate spread per shot |
|
|
509
|
+
| `volumeVariation` | number | `0` | 0..1; Random ± volume spread per shot |
|
|
510
|
+
|
|
511
|
+
### `core:PointAttachment`
|
|
512
|
+
|
|
513
|
+
Keeps this node on a named frame point of a parent AnimatedSprite2D (hand socket, muzzle) every tick
|
|
514
|
+
|
|
515
|
+
| Key | Type | Default | Notes |
|
|
516
|
+
| --- | --- | --- | --- |
|
|
517
|
+
| `point` | string | `""` | Named frame point to follow |
|
|
518
|
+
| `applyRotation` | boolean | `true` | Copy the point's angle into this node |
|
|
519
|
+
| `offsetX` | number | `0` | |
|
|
520
|
+
| `offsetY` | number | `0` | |
|
|
521
|
+
| `spriteNodeId` | node | `""` | AnimatedSprite2D to read points from (empty = nearest ancestor) |
|
|
522
|
+
|
|
523
|
+
### `core:PopIn`
|
|
524
|
+
|
|
525
|
+
Scales a node up from zero with an overshoot on spawn (juice)
|
|
526
|
+
|
|
527
|
+
| Key | Type | Default | Notes |
|
|
528
|
+
| --- | --- | --- | --- |
|
|
529
|
+
| `from` | number | `0` | ≥ 0; Starting fraction of the authored scale |
|
|
530
|
+
| `duration` | number | `0.4` | ≥ 0 |
|
|
531
|
+
| `easing` | select | `"backOut"` | backOut \| elasticOut \| bounceOut \| cubicOut \| quadOut \| linear |
|
|
532
|
+
| `triggerEvent` | string | `""` | Node signal that triggers the pop (empty = manual/API only) |
|
|
533
|
+
| `playOnStart` | boolean | `true` | |
|
|
534
|
+
|
|
535
|
+
### `core:PunchScale`
|
|
536
|
+
|
|
537
|
+
Squash-and-stretch scale punch on a signal or on start (juice)
|
|
538
|
+
|
|
539
|
+
| Key | Type | Default | Notes |
|
|
540
|
+
| --- | --- | --- | --- |
|
|
541
|
+
| `amount` | number | `0.3` | ≥ 0; Peak scale delta (0.3 = +30%) |
|
|
542
|
+
| `duration` | number | `0.35` | ≥ 0 |
|
|
543
|
+
| `vibrato` | number | `3` | ≥ 0; Oscillations before settling |
|
|
544
|
+
| `triggerEvent` | string | `""` | Node signal that triggers the punch (empty = manual/API only) |
|
|
545
|
+
| `playOnStart` | boolean | `false` | |
|
|
546
|
+
|
|
547
|
+
### `core:RadialProgress`
|
|
548
|
+
|
|
549
|
+
Masks a Sprite2D texture as a circular progress bar
|
|
550
|
+
|
|
551
|
+
| Key | Type | Default | Notes |
|
|
552
|
+
| --- | --- | --- | --- |
|
|
553
|
+
| `value` | number | `1` | 0..1; Target progress from 0.0 (empty) to 1.0 (full) |
|
|
554
|
+
| `animationSpeed` | number | `3` | 0..20; How fast the progress bar animates (higher = faster) |
|
|
555
|
+
| `startAngle` | number | `0` | -360..360; Starting angle in degrees. 0 = top (12 o'clock), 90 = right |
|
|
556
|
+
| `clockwise` | boolean | `true` | Fill direction: clockwise or counter-clockwise |
|
|
557
|
+
|
|
558
|
+
### `core:ReplicatedTransform`
|
|
559
|
+
|
|
560
|
+
Replicates this node's position and rotation: the owner publishes quantized values, peers render them on a timed interpolation buffer
|
|
561
|
+
|
|
562
|
+
| Key | Type | Default | Notes |
|
|
563
|
+
| --- | --- | --- | --- |
|
|
564
|
+
| `sendRateHz` | number | `20` | 1..60; How often the owner publishes. The session still only sends what actually changed (dirty detection compares quantized integers), so an idle entity costs nothing. |
|
|
565
|
+
| `replicateRotation` | boolean | `true` | Send rotation as well as position (256 steps per turn, 1.41°). |
|
|
566
|
+
| `plane` | select | `"xy"` | xy \| xz; Which axes the wire's X/Y drive: xy for 2D nodes, xz for a top-down 3D game. |
|
|
567
|
+
| `interpolationDelayMs` | number | `0` | 0..500; 0 = adaptive (two room ticks plus measured jitter). Raise it for a lossy link, lower it for a LAN — it is latency traded for smoothness. |
|
|
568
|
+
|
|
569
|
+
### `core:RevoluteJoint2D`
|
|
570
|
+
|
|
571
|
+
Hinge: pins this body to a pivot so it can only rotate about it. With no connected node the hinge is fixed to the world — the pinball flipper / swinging door case. Optional angle limits and a motor.
|
|
572
|
+
|
|
573
|
+
| Key | Type | Default | Notes |
|
|
574
|
+
| --- | --- | --- | --- |
|
|
575
|
+
| `anchorX` | number | `0` | Pivot point in this node’s local pixels. (0, 0) hinges on the node origin. |
|
|
576
|
+
| `anchorY` | number | `0` | Pivot point in this node’s local pixels. |
|
|
577
|
+
| `connectedNode` | string | `""` | Name, id or path of the body to hinge to. Empty pins the hinge to the world, which is what a flipper or a swinging door wants. |
|
|
578
|
+
| `limitEnabled` | boolean | `false` | Restrict the swing to an angle range. |
|
|
579
|
+
| `lowerAngle` | number | `-45` | Degrees, relative to the rest pose. |
|
|
580
|
+
| `upperAngle` | number | `45` | Degrees, relative to the rest pose. |
|
|
581
|
+
| `motorEnabled` | boolean | `false` | Drive the hinge towards a target spin. |
|
|
582
|
+
| `motorSpeed` | number | `0` | Target spin in degrees per second. Flip its sign to swing the other way. |
|
|
583
|
+
| `maxMotorTorque` | number | `100000` | Torque ceiling. Too low and the motor cannot lift the arm; too high and it ignores everything it hits. |
|
|
584
|
+
| `collideConnected` | boolean | `false` | Let the two hinged bodies collide. Off by default: they overlap at the pivot, and a contact there fights the joint. |
|
|
585
|
+
|
|
586
|
+
### `core:Rotate`
|
|
587
|
+
|
|
588
|
+
Rotates a 3D node continuously
|
|
589
|
+
|
|
590
|
+
| Key | Type | Default | Notes |
|
|
591
|
+
| --- | --- | --- | --- |
|
|
592
|
+
| `rotationSpeed` | number | `1` | 0..10; Speed of rotation in radians per second |
|
|
593
|
+
|
|
594
|
+
### `core:SfxOnSignal`
|
|
595
|
+
|
|
596
|
+
Play a procedural (asset-free) sound preset when a node signal fires
|
|
597
|
+
|
|
598
|
+
| Key | Type | Default | Notes |
|
|
599
|
+
| --- | --- | --- | --- |
|
|
600
|
+
| `signal` | string | `"pointerdown"` | Node signal that plays the sound (pointerdown, hit, damaged, …) |
|
|
601
|
+
| `preset` | enum | `"tap"` | tap \| score \| bounce \| explosion \| powerup \| win \| lose \| laser \| tick; Built-in synth patch played on the sfx bus |
|
|
602
|
+
| `volume` | number | `1` | 0..1 |
|
|
603
|
+
| `pitch` | number | `1` | 0.25..4; Frequency multiplier: 2 = an octave up, 0.5 = an octave down |
|
|
604
|
+
|
|
605
|
+
### `core:Shake`
|
|
606
|
+
|
|
607
|
+
Smooth positional shake, additive over other motion (juice)
|
|
608
|
+
|
|
609
|
+
| Key | Type | Default | Notes |
|
|
610
|
+
| --- | --- | --- | --- |
|
|
611
|
+
| `amplitude` | number | `8` | ≥ 0 |
|
|
612
|
+
| `frequency` | number | `24` | ≥ 0 |
|
|
613
|
+
| `duration` | number | `0.35` | ≥ 0 |
|
|
614
|
+
| `decay` | number | `1.5` | ≥ 0; Falloff power (0 = steady, 1 = linear, >1 = punchy tail) |
|
|
615
|
+
| `triggerEvent` | string | `""` | Node signal that starts the shake (empty = manual/API only) |
|
|
616
|
+
| `playOnStart` | boolean | `false` | |
|
|
617
|
+
|
|
618
|
+
### `core:SimpleMove`
|
|
619
|
+
|
|
620
|
+
Moves a 3D node in a simple pattern (for testing)
|
|
621
|
+
|
|
622
|
+
| Key | Type | Default | Notes |
|
|
623
|
+
| --- | --- | --- | --- |
|
|
624
|
+
| `speed` | number | `5` | 0..50; Movement speed units per second |
|
|
625
|
+
| `axisHorizontal` | string | `"Horizontal"` | Input axis name for horizontal movement |
|
|
626
|
+
| `axisVertical` | string | `"Vertical"` | Input axis name for vertical movement |
|
|
627
|
+
| `movementPlane` | select | `"xz"` | xz \| xy; Select whether vertical input moves on XY or XZ plane |
|
|
628
|
+
| `invertVertical` | boolean | `true` | Invert sign for vertical input axis |
|
|
629
|
+
|
|
630
|
+
### `core:Sine`
|
|
631
|
+
|
|
632
|
+
Oscillates a node along a selected axis
|
|
633
|
+
|
|
634
|
+
| Key | Type | Default | Notes |
|
|
635
|
+
| --- | --- | --- | --- |
|
|
636
|
+
| `amplitude` | number | `1` | ≥ 0; Maximum displacement from the center |
|
|
637
|
+
| `frequency` | number | `1` | ≥ 0; Oscillations per second |
|
|
638
|
+
| `axis` | select | `"y"` | x \| y \| z; The axis to oscillate along |
|