@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,1331 @@
|
|
|
1
|
+
<!-- Pix3 agent kit 1.6.0 — generated from docs/node-types-reference.md. Do not edit: pix3 kit --update regenerates it. -->
|
|
2
|
+
# Pix3 node reference
|
|
3
|
+
|
|
4
|
+
Every node type the loader knows, with its properties. **Grep `### <NodeName>`** for one node
|
|
5
|
+
instead of reading this file. Keys are YAML keys under `properties:`; `position` / `rotation` /
|
|
6
|
+
`scale` go in the `transform:` block, `flow.*` in the `flow:` block, and the anchor keys in
|
|
7
|
+
`layout:` (see `pix3-scene-format`). Where a row says "not saved", the key does nothing in a file.
|
|
8
|
+
Source paths quoted here (`src/…`, `packages/…`) are files of the Pix3 engine repository, not of this project.
|
|
9
|
+
|
|
10
|
+
## Quick reference
|
|
11
|
+
|
|
12
|
+
| Node Type | Key Properties |
|
|
13
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
14
|
+
| NodeBase | id, name, type, visible, locked |
|
|
15
|
+
| Node2D | position (Vector2), rotation, scale (Vector2), opacity, blendMode, zIndex |
|
|
16
|
+
| Node3D | position (Vector3), rotation (Euler), scale (Vector3) |
|
|
17
|
+
| Sprite2D | texture, width, height, anchor, aspectRatioLocked |
|
|
18
|
+
| ColorRect2D | width, height, color (the only untextured 2D fill) |
|
|
19
|
+
| TiledSprite2D | texture, width, height, patchMode, sliceBorderLeft/Right/Top/Bottom, drawCenter (nine-slice) |
|
|
20
|
+
| AnimatedSprite2D | animationResourcePath, currentClip, isPlaying, freeOnFinish, width, height, sizeMode |
|
|
21
|
+
| Group2D | width, height (+ flow\* / anchor layout — the 2D container) |
|
|
22
|
+
| ScrollContainer2D | width, height, scrollY, dragScrollEnabled, inertiaDamping, showScrollbar |
|
|
23
|
+
| SpineSkeleton2D | skeletonPath, atlasPath, animation, loop, skin, timeScale, defaultMix (optional Spine runtime) |
|
|
24
|
+
| Camera2D | priority, zoom, offset, followTargetId, limitsEnabled, shakeAmplitude |
|
|
25
|
+
| CanvasLayer2D | width, height (fixed HUD overlay; renders after post) |
|
|
26
|
+
| Camera3D | projection, fov, near, far |
|
|
27
|
+
| VirtualCamera3D | priority, followTargetId, lookAtTargetId, blendDuration, fov |
|
|
28
|
+
| GeometryMesh | geometry, size, material, castShadow, receiveShadow |
|
|
29
|
+
| MeshInstance | src |
|
|
30
|
+
| Sprite3D | texture, width, height (world units), billboard, billboardRoll, opacity |
|
|
31
|
+
| AnimatedSprite3D | width, height, fps, playing, loop, freeOnFinish, billboard (default true) |
|
|
32
|
+
| InstancedMesh3D | maxInstances, enablePerInstanceColor, material.type/color (bulk write\*/flush from scripts) |
|
|
33
|
+
| DirectionalLightNode | color, intensity, castShadow |
|
|
34
|
+
| AmbientLightNode | color, intensity (flat fill, no shadows) |
|
|
35
|
+
| HemisphereLightNode | skyColor, groundColor, intensity |
|
|
36
|
+
| PointLightNode | color, intensity, distance, decay |
|
|
37
|
+
| SpotLightNode | color, intensity, distance, angle, penumbra |
|
|
38
|
+
| Button2D | width, height, backgroundColor, buttonAction, textureNormal/Hover/Pressed/Disabled, sliceBorder\* |
|
|
39
|
+
| Slider2D | width, minValue, maxValue, value, textureTrack/Fill/Thumb, sliceBorder\* |
|
|
40
|
+
| Joystick2D | enabled, radius, floating, axisHorizontal, axisVertical |
|
|
41
|
+
| Checkbox2D | size, checked, textureBox, textureBoxChecked, textureMark |
|
|
42
|
+
| Bar2D | width, value, minValue, maxValue, barColor, backBackgroundColor, textureTrough/Fill, sliceBorder\* |
|
|
43
|
+
| InventorySlot2D | width, height, quantity, showQuantity, backdropColor, selectionColor |
|
|
44
|
+
| AudioPlayer | audioTrack, autoplay, loop, volume, bus, pitchVariation, volumeVariation |
|
|
45
|
+
| PostProcess | affect2D, bloom*, vignette*, chromaticAberration*, aoMode, ssao*, lut\* |
|
|
46
|
+
| Group | NodeBase only — an organisational container |
|
|
47
|
+
|
|
48
|
+
## Choosing the right node
|
|
49
|
+
|
|
50
|
+
#### For 2D Projects:
|
|
51
|
+
|
|
52
|
+
1. **Start with a Group2D** as your scene root (the game viewport size comes from project settings; use anchor layout for responsiveness)
|
|
53
|
+
2. Add **Sprite2D** for images and graphics
|
|
54
|
+
3. Before the art exists, build with **ColorRect2D** — it is the only solid-colour 2D primitive, and
|
|
55
|
+
a game made of coloured rects is playable a day before a game waiting on sprites
|
|
56
|
+
4. Use **TiledSprite2D** in `nine-slice` mode for panels, windows and bars — stretching a bordered
|
|
57
|
+
texture with Sprite2D smears its corners
|
|
58
|
+
5. Use **Button2D**, **Slider2D**, **Joystick2D** for UI controls, **ScrollContainer2D** for lists
|
|
59
|
+
6. Use **Node2D** as bare transforms and **Group2D** wherever children must lay out against a size
|
|
60
|
+
|
|
61
|
+
#### For 3D Projects:
|
|
62
|
+
|
|
63
|
+
1. Add a **Camera3D** to define your viewpoint
|
|
64
|
+
2. Use **GeometryMesh** for simple shapes
|
|
65
|
+
3. Use **MeshInstance** for imported 3D models
|
|
66
|
+
4. Add **DirectionalLightNode** for overall lighting
|
|
67
|
+
5. Add **PointLightNode** or **SpotLightNode** for localized lighting
|
|
68
|
+
6. Add **AmbientLightNode** or **HemisphereLightNode** as fill so unlit faces are not black
|
|
69
|
+
7. For cinematics / dynamic framing, add **VirtualCamera3D** nodes and a **Camera Brain** (`core:CameraBrain`) on the Camera3D to blend between them by priority
|
|
70
|
+
8. For bloom / vignette / colour grading, add one **PostProcess** node anywhere in the tree
|
|
71
|
+
|
|
72
|
+
## 2D Nodes
|
|
73
|
+
|
|
74
|
+
All 2D nodes operate in screen space and are rendered using an orthographic camera. They use a left-handed coordinate system where X increases to the right and Y increases upward.
|
|
75
|
+
|
|
76
|
+
### Node2D
|
|
77
|
+
|
|
78
|
+
The base class for all 2D scene nodes. Use this for simple grouping or as a container for other 2D elements.
|
|
79
|
+
|
|
80
|
+
**Type String:** `Node2D`
|
|
81
|
+
|
|
82
|
+
**Properties:**
|
|
83
|
+
|
|
84
|
+
| Property | Type | Default | Description |
|
|
85
|
+
| --------------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `position` | Vector2 | (0, 0) | X and Y coordinates |
|
|
87
|
+
| `rotation` | number | 0 | Rotation in degrees |
|
|
88
|
+
| `scale` | Vector2 | (1, 1) | X and Y scale factors |
|
|
89
|
+
| `opacity` | number | 1 | Local opacity multiplier, inherited by child 2D nodes |
|
|
90
|
+
| `blendMode` | enum | normal | How the node's own visuals combine with the backdrop: `normal`, `additive`, `multiply`, `subtract`. NOT inherited by children |
|
|
91
|
+
| `zIndex` | number | 0 | Draw-order override, `-4096..4096` (integer). Higher draws on top |
|
|
92
|
+
| `zAsRelative` | boolean | true | Add `zIndex` to the parent's effective z instead of treating it as absolute |
|
|
93
|
+
| `flow.enabled` | boolean | false | Stack this container's children in tree order instead of leaving them where they were authored |
|
|
94
|
+
| `flow.direction` | enum | vertical | `vertical` (a column) or `horizontal` (a row) |
|
|
95
|
+
| `flow.gap` | number | 0 | Space between two children, px |
|
|
96
|
+
| `flow.paddingX` / `flow.paddingY` | number | 0 | Inset from the container's edges |
|
|
97
|
+
| `flow.align` | enum | start | Cross-axis placement of each child: `start`, `center`, `end`. Inspector label: **Cross Axis** |
|
|
98
|
+
| `flow.autoSize` | boolean | false | Grow the container along the flow axis so the last child fits |
|
|
99
|
+
|
|
100
|
+
**Usage Notes:**
|
|
101
|
+
|
|
102
|
+
- **Flow vs. anchors.** An anchor (`layout`) pins ONE node to its parent's edges; a
|
|
103
|
+
flow is the container deciding where each child begins. With `flow.enabled` the
|
|
104
|
+
container owns the main axis and each child's own anchor still owns the cross
|
|
105
|
+
one — a settings row can pin its toggle to the right edge while the column
|
|
106
|
+
decides how far down the row sits. This is why no `Layout2D` node exists.
|
|
107
|
+
- **Vocabulary and how the inspector shows it.** Three different things used to be
|
|
108
|
+
called "align", so the words are now fixed: **Anchors** is the persistent rule
|
|
109
|
+
placing this node in its parent (`layout*` properties), **Flow** is the
|
|
110
|
+
persistent rule placing this node's children, **Cross Axis** is the flow's
|
|
111
|
+
perpendicular placement (`flow.align`), and **Align** / **Distribute** are the
|
|
112
|
+
one-shot operations on a selection (the viewport strip and `Node ▸ Align ▸`).
|
|
113
|
+
Because Anchors and Flow are orthogonal — a panel can stretch to its parent
|
|
114
|
+
*and* stack its children — the inspector shows them as two sub-blocks of one
|
|
115
|
+
**Layout** section, each with its own switch, rather than as one mode selector.
|
|
116
|
+
A child of a flow container sees only its *driven* Position axis disabled:
|
|
117
|
+
the main axis always, the cross axis only when its own Anchors are off.
|
|
118
|
+
- Cannot have children by default (set `isContainer = true` to enable)
|
|
119
|
+
- Transforms affect all children in local space
|
|
120
|
+
- Rotation is in degrees; positive is counter-clockwise (Y points up), and scripts see radians in `rotation.z`
|
|
121
|
+
- **Draw order:** by default the 2D pass paints in scene-tree DFS order (a later/deeper node draws
|
|
122
|
+
on top — Godot-like). `zIndex` lifts a node out of that order without moving it in the tree:
|
|
123
|
+
nodes are bucketed by _effective_ z first, and tree order only breaks ties inside a bucket. With
|
|
124
|
+
`zAsRelative` (the default) a subtree keeps its internal layering wherever it is reparented; set
|
|
125
|
+
it to `false` for an "always on top" overlay that must not inherit an ancestor's offset. Both
|
|
126
|
+
fields serialize only when non-default, so scenes that never touch z-order are unchanged.
|
|
127
|
+
- **Blend mode:** `blendMode` applies to the materials the node itself owns (a sprite's quad, a
|
|
128
|
+
control's skin + label) and is _not_ inherited the way `opacity` is — set it on each node that
|
|
129
|
+
should glow, not on a wrapping `Group2D`. `additive` is the glow/VFX mode; `multiply` and
|
|
130
|
+
`subtract` darken, and because a transparent PNG's cutout pixels are usually black they will
|
|
131
|
+
darken through the cutout too. A non-normal mode forces `material.transparent` on (three.js
|
|
132
|
+
disables blending for opaque materials) and opts the node's meshes out of the 2D quad batcher,
|
|
133
|
+
so use it for the handful of nodes that need it rather than across a whole scene. `screen` is
|
|
134
|
+
intentionally not offered: it needs `CustomBlending`, and no factor pair reproduces it without
|
|
135
|
+
either ignoring `opacity` or darkening the backdrop as the node fades. Spine skeletons are
|
|
136
|
+
unaffected — their blend modes come per-slot from the spine runtime.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### ColorRect2D
|
|
141
|
+
|
|
142
|
+
A solid-colour rectangle — the engine's **only** untextured 2D fill primitive, and the placeholder
|
|
143
|
+
both agent skills tell you to reach for when art does not exist yet. Blocking gameplay on art is the
|
|
144
|
+
mistake it exists to prevent: build the whole game out of coloured rects, then swap them for
|
|
145
|
+
`Sprite2D`/`TiledSprite2D` once the art lands.
|
|
146
|
+
|
|
147
|
+
**Type String:** `ColorRect2D`
|
|
148
|
+
|
|
149
|
+
**Properties** (plus everything on Node2D):
|
|
150
|
+
|
|
151
|
+
| Property | Type | Default | Description |
|
|
152
|
+
| -------- | ------ | ------- | ------------------------------- |
|
|
153
|
+
| `width` | number | 100 | Width in design pixels |
|
|
154
|
+
| `height` | number | 100 | Height in design pixels |
|
|
155
|
+
| `color` | color | #ffffff | Fill colour (authored sRGB hex) |
|
|
156
|
+
|
|
157
|
+
**Usage Notes:**
|
|
158
|
+
|
|
159
|
+
- Centred on its own origin, like every 2D node — `position` is the rect's centre, not a corner.
|
|
160
|
+
- `opacity` comes from `Node2D` and multiplies the fill; there is no separate alpha property.
|
|
161
|
+
- No texture, no nine-slice: for a panel with a border use `TiledSprite2D` in `nine-slice` mode.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### Sprite2D
|
|
166
|
+
|
|
167
|
+
A 2D image display node. Renders a textured quad that always faces the camera.
|
|
168
|
+
|
|
169
|
+
**Type String:** `Sprite2D`
|
|
170
|
+
|
|
171
|
+
**Properties:**
|
|
172
|
+
|
|
173
|
+
| Property | Type | Default | Description |
|
|
174
|
+
| ------------------- | ------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
175
|
+
| `texture` | texture | null | Texture resource (`{ type: texture, url: res://… }`); a legacy `texturePath` string is still accepted on load |
|
|
176
|
+
| `textureKey` | string | "" | Localization sprite key; when it resolves, the localized path wins over `texture` |
|
|
177
|
+
| `anchor` | vector2 | [0.5, 0.5] | Normalized pivot the image is positioned around |
|
|
178
|
+
| `width` | number | texture width | Display width in pixels; when unset it takes the texture's natural width on load (64 placeholder until then) |
|
|
179
|
+
| `height` | number | texture height | Display height in pixels; when unset it takes the texture's natural height on load (64 placeholder until then) |
|
|
180
|
+
| `aspectRatioLocked` | boolean | false | Keep width/height at the texture's aspect ratio while resizing |
|
|
181
|
+
| `color` | color | #ffffff | Constructor-only placeholder colour shown before the texture loads; reset to white on load, not in the schema or saved |
|
|
182
|
+
|
|
183
|
+
**Usage Notes:**
|
|
184
|
+
|
|
185
|
+
- Supports PNG, JPG, WebP textures
|
|
186
|
+
- Aspect ratio is controlled by width/height properties
|
|
187
|
+
- Texture is scaled to fit the specified dimensions
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### TiledSprite2D
|
|
192
|
+
|
|
193
|
+
A texture mapped onto a rectangle of arbitrary size by one of five fill algorithms — the node for
|
|
194
|
+
**UI panels, windows, bars and repeating backgrounds**, where `Sprite2D`'s plain stretch would smear
|
|
195
|
+
the corners. This is the engine's nine-slice; UI Kit Forge emits its slice borders directly.
|
|
196
|
+
|
|
197
|
+
**Type String:** `TiledSprite2D`
|
|
198
|
+
|
|
199
|
+
**Properties** (plus everything on Node2D):
|
|
200
|
+
|
|
201
|
+
| Property | Type | Default | Description |
|
|
202
|
+
| ---------------------------------- | ------- | ---------- | ------------------------------------------------------------------------------- |
|
|
203
|
+
| `texture` | texture | null | `{ type: 'texture', url: 'res://…' }` (a bare `texturePath` string is read too) |
|
|
204
|
+
| `width` | number | 128 | Rect width in design pixels (0 is valid — a collapsed rect) |
|
|
205
|
+
| `height` | number | 128 | Rect height in design pixels |
|
|
206
|
+
| `patchMode` | enum | `stretch` | `stretch` \| `tile` \| `nine-slice` \| `three-slice-h` \| `three-slice-v` |
|
|
207
|
+
| `sliceBorderLeft/Right/Top/Bottom` | number | 0 | Border insets in **source-texture pixels** (Godot's `patch_margin_*`) |
|
|
208
|
+
| `drawCenter` | boolean | true | Draw the middle patch; false leaves a hollow frame |
|
|
209
|
+
| `axisStretchHorizontal` | enum | `stretch` | `stretch` \| `tile` — how the stretchable regions fill horizontally |
|
|
210
|
+
| `axisStretchVertical` | enum | `stretch` | Same, vertically |
|
|
211
|
+
| `tileScale` | vector2 | (1, 1) | Repeat scale in `tile` mode |
|
|
212
|
+
| `tileOffset` | vector2 | (0, 0) | Repeat phase shift |
|
|
213
|
+
| `anchor` | vector2 | (0.5, 0.5) | Normalized pivot: (0,0) bottom-left, (1,1) top-right |
|
|
214
|
+
|
|
215
|
+
**Usage Notes:**
|
|
216
|
+
|
|
217
|
+
- The four `sliceBorder*` scalars are separate schema properties, not a nested object — set them
|
|
218
|
+
individually from a script or a tool call.
|
|
219
|
+
- A 64×64 frame with a 16 px border covers a window of any size: corners stay crisp, edges scale.
|
|
220
|
+
- `three-slice-h` is the right mode for a horizontal bar (left cap · middle · right cap); only the
|
|
221
|
+
left/right borders matter.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### AnimatedSprite2D
|
|
226
|
+
|
|
227
|
+
Flipbook sprite animation driven by a `.pix3anim` resource — a set of named clips over a
|
|
228
|
+
spritesheet or a list of frame images. Authored in the editor's Sprite Editor. For **skeletal**
|
|
229
|
+
animation use SpineSkeleton2D instead; for a property/timeline animation over any
|
|
230
|
+
node, use the `core:AnimationPlayer` behaviour.
|
|
231
|
+
|
|
232
|
+
**Type String:** `AnimatedSprite2D`
|
|
233
|
+
|
|
234
|
+
**Properties** (plus everything on Node2D):
|
|
235
|
+
|
|
236
|
+
| Property | Type | Default | Description |
|
|
237
|
+
| ----------------------- | ------- | ---------- | -------------------------------------------------------------------------- |
|
|
238
|
+
| `animationResourcePath` | string | "" | `res://…/foo.pix3anim` |
|
|
239
|
+
| `currentClip` | string | "" | Clip name from that resource (empty = first clip) |
|
|
240
|
+
| `isPlaying` | boolean | true | Play on start |
|
|
241
|
+
| `currentFrame` | number | 0 | Frame index within the active clip |
|
|
242
|
+
| `freeOnFinish` | boolean | false | Remove the node when a non-looping clip ends (one-shot VFX) |
|
|
243
|
+
| `width` | number | 64 | Display width in pixels |
|
|
244
|
+
| `height` | number | 64 | Display height in pixels |
|
|
245
|
+
| `sizeMode` | enum | `stretch` | `stretch` (fit width/height) \| `native` (use each frame's own pixel size) |
|
|
246
|
+
| `anchor` | vector2 | (0.5, 0.5) | Normalized pivot |
|
|
247
|
+
| `color` | color | #ffffff | Tint |
|
|
248
|
+
|
|
249
|
+
**Usage Notes:**
|
|
250
|
+
|
|
251
|
+
- Frame timing lives in the clip (per-frame duration multipliers), not on the node — there is no
|
|
252
|
+
`fps` property here, unlike AnimatedSprite3D.
|
|
253
|
+
- `freeOnFinish` + a one-shot clip is the whole "spawn an explosion" pattern: no script needed.
|
|
254
|
+
- `sizeMode: native` is what you want for a trimmed spritesheet whose frames differ in size.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
### Group2D
|
|
259
|
+
|
|
260
|
+
A sized 2D container. Unlike `Node2D` (a bare transform), it has a `width`/`height` rectangle, which
|
|
261
|
+
is what anchor layout and flow layout resolve their children against — so this is the node to use as
|
|
262
|
+
a **scene root** and as any panel that lays out its children.
|
|
263
|
+
|
|
264
|
+
**Type String:** `Group2D`
|
|
265
|
+
|
|
266
|
+
**Properties** (plus everything on Node2D, including the full `flow*` and anchor-layout set):
|
|
267
|
+
|
|
268
|
+
| Property | Type | Default | Description |
|
|
269
|
+
| -------- | ------ | ------- | --------------------------------- |
|
|
270
|
+
| `width` | number | 100 | Container width in design pixels |
|
|
271
|
+
| `height` | number | 100 | Container height in design pixels |
|
|
272
|
+
|
|
273
|
+
**Usage Notes:**
|
|
274
|
+
|
|
275
|
+
- It draws nothing. The editor outlines it as an adornment; a build shows only its children.
|
|
276
|
+
- With `flowEnabled` it becomes a row/column stack (`flowDirection`, `flowGap`, `flowPaddingX/Y`,
|
|
277
|
+
`flowAlign`, `flowAutoSize`) — the engine's layout container, no script required.
|
|
278
|
+
- Node order inside it is paint order, as everywhere in 2D.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### SpineSkeleton2D
|
|
283
|
+
|
|
284
|
+
A Spine skeletal-animation node. Renders a skeleton exported from the
|
|
285
|
+
[Spine editor](https://esotericsoftware.com/) in the 2D layer, with animations
|
|
286
|
+
selectable in the Inspector and drivable from scripts.
|
|
287
|
+
|
|
288
|
+
**Type String:** `SpineSkeleton2D`
|
|
289
|
+
|
|
290
|
+
**Requires the optional Spine runtime.** `@esotericsoftware/spine-threejs` (`~4.3`)
|
|
291
|
+
is an _optional_ peer dependency: pix3 declares the module contract and the host
|
|
292
|
+
registers a loader for it (`setSpineModuleLoader`), so projects that never use
|
|
293
|
+
Spine neither install nor download it. The editor and the exported player register
|
|
294
|
+
it automatically; a consumer project that places a `SpineSkeleton2D` adds:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import { setSpineModuleLoader } from '@pix3/runtime';
|
|
298
|
+
|
|
299
|
+
setSpineModuleLoader(() => import('@esotericsoftware/spine-threejs'));
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Using the official Spine Runtimes requires a Spine Editor license (Spine Runtimes
|
|
303
|
+
License). The skeleton export must come from a Spine version matching the installed
|
|
304
|
+
runtime's minor (4.3 export ⇄ 4.3 runtime); a mismatch surfaces as a load error
|
|
305
|
+
naming both files.
|
|
306
|
+
|
|
307
|
+
**Properties:**
|
|
308
|
+
|
|
309
|
+
| Property | Type | Default | Description |
|
|
310
|
+
| ----------------- | --------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
311
|
+
| `skeletonPath` | string | null | Skeleton export, `.json` or `.skel` (res://) |
|
|
312
|
+
| `atlasPath` | string | null | Atlas export, `.atlas` (res://) |
|
|
313
|
+
| `texture` | texture ref | null | Optional page-image override; single-page atlases only |
|
|
314
|
+
| `animation` | string / select | '' | Animation to play; a dropdown of real names once loaded |
|
|
315
|
+
| `loop` | boolean | true | Loop the authored animation |
|
|
316
|
+
| `isPlaying` | boolean | true | Advance time in play mode |
|
|
317
|
+
| `skin` | string / select | '' | Skin to apply; empty = the skeleton's default |
|
|
318
|
+
| `timeScale` | number | 1 | Playback speed multiplier |
|
|
319
|
+
| `defaultMix` | number | 0 | Crossfade duration between animations, seconds |
|
|
320
|
+
| `color` | color | #ffffff | Tint (applied through spine's skeleton color) |
|
|
321
|
+
| `twoColorTint` | boolean | false | Enable tint-black rendering (dark-tint exports) |
|
|
322
|
+
| `freeOnFinish` | boolean | false | `queueFree()` when a non-looping animation ends |
|
|
323
|
+
| `previewInEditor` | boolean | false | Animate in the editor viewport. **Off by default** — a placed skeleton holds its first frame; the Inspector's Play/Reset buttons drive it |
|
|
324
|
+
|
|
325
|
+
**Script API:**
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
const hero = scene.getNode<SpineSkeleton2D>('Hero');
|
|
329
|
+
|
|
330
|
+
hero.play('run', { loop: true, mixDuration: 0.2 });
|
|
331
|
+
hero.queue('idle', { loop: true, delay: 0.5 }); // after the current entry
|
|
332
|
+
hero.stop({ mixDuration: 0.25 }); // mix back to the setup pose
|
|
333
|
+
hero.pause();
|
|
334
|
+
hero.resume();
|
|
335
|
+
hero.setSkin('blue');
|
|
336
|
+
hero.setMix('run', 'idle', 0.3); // per-pair crossfade
|
|
337
|
+
hero.setTimeScale(1.5);
|
|
338
|
+
|
|
339
|
+
hero.getAnimationNames(); // ['idle', 'run', …]
|
|
340
|
+
hero.getSkinNames();
|
|
341
|
+
hero.getCurrentAnimation(); // animation on track 0, or null
|
|
342
|
+
hero.getSetupBounds(); // setup-pose AABB, or null before load
|
|
343
|
+
hero.isLoaded;
|
|
344
|
+
|
|
345
|
+
hero.resetToFirstFrame(); // rewind the current animation (pose only)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
In the Inspector the **Animation** group shows the animation and skin as dropdowns
|
|
349
|
+
of the loaded skeleton's real names, plus an **Editor Preview** row: `Play`/`Pause`
|
|
350
|
+
toggles `previewInEditor` (an ordinary undoable edit) and `Reset` rewinds to the
|
|
351
|
+
first frame. Reset is transient pose-only state — it never enters undo history and
|
|
352
|
+
never dirties the scene, matching how the animation timeline's scrub preview
|
|
353
|
+
behaves.
|
|
354
|
+
|
|
355
|
+
**Signals:**
|
|
356
|
+
|
|
357
|
+
| Signal | Arguments | When |
|
|
358
|
+
| -------------------- | -------------------------------------------- | ---------------------------------------------------------- |
|
|
359
|
+
| `animation-started` | `(name, trackIndex)` | A track entry became current |
|
|
360
|
+
| `animation-finished` | `(name, trackIndex)` | A non-looping animation ended (also flips `isPlaying` off) |
|
|
361
|
+
| `animation-looped` | `(name, trackIndex)` | A looping animation completed a loop |
|
|
362
|
+
| `spine-event` | `(name, { int, float, string }, trackIndex)` | A keyed animation event fired |
|
|
363
|
+
|
|
364
|
+
**Export:** the HTML / zip playable export bundles the Spine runtime _statically_
|
|
365
|
+
into `index.html`, but only when a scene actually places a `SpineSkeleton2D` — a
|
|
366
|
+
dynamic import would become a chunk that a single-file export can never fetch.
|
|
367
|
+
Skeleton, atlas and the atlas' page images ship with it; projects without a
|
|
368
|
+
skeleton are unaffected in size. The generated npm project gets the dependency
|
|
369
|
+
added to its `package.json` on the same condition.
|
|
370
|
+
|
|
371
|
+
**Usage Notes:**
|
|
372
|
+
|
|
373
|
+
- Sizing comes from the node transform (`scale`), not a width/height pair — the
|
|
374
|
+
skeleton is authored in its own pixel units. The parse-time skeleton scale stays
|
|
375
|
+
at 1 so all instances share one cached `SkeletonData`.
|
|
376
|
+
- The skeleton data and atlas page textures are shared across every node (and the
|
|
377
|
+
editor viewport proxy) that references the same files; each node owns only its
|
|
378
|
+
own `Skeleton`/`AnimationState`.
|
|
379
|
+
- Atlas pages are loaded as standalone textures and are excluded from the
|
|
380
|
+
pre-launch texture atlas — their UVs come from the `.atlas` file. For the same
|
|
381
|
+
reason a Spine skeleton does not join the 2D quad batcher.
|
|
382
|
+
- Shader effects (`core:adjust`, …) are not supported: spine creates its batch
|
|
383
|
+
materials dynamically. Use `color` / `twoColorTint` for tinting.
|
|
384
|
+
- CPU cost is per skeleton per frame (geometry is rebuilt on the CPU). Budget for
|
|
385
|
+
units-to-dozens of visible skeletons, not hundreds.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
### Button2D
|
|
390
|
+
|
|
391
|
+
An interactive button control for 2D user interfaces. Responds to pointer clicks and provides visual feedback.
|
|
392
|
+
|
|
393
|
+
**Type String:** `Button2D`
|
|
394
|
+
|
|
395
|
+
**Properties:**
|
|
396
|
+
|
|
397
|
+
| Property | Type | Default | Description |
|
|
398
|
+
| ------------------- | ------- | -------- | ---------------------------------------------------------------- |
|
|
399
|
+
| `width` | number | 100 | Button width in pixels |
|
|
400
|
+
| `height` | number | 40 | Button height in pixels |
|
|
401
|
+
| `backgroundColor` | color | #4a4a4a | Default background |
|
|
402
|
+
| `hoverColor` | color | #5a5a5a | Background on hover |
|
|
403
|
+
| `pressedColor` | color | #3a3a3a | Background when pressed |
|
|
404
|
+
| `buttonAction` | string | "Submit" | Action identifier |
|
|
405
|
+
| `textureNormal` | texture | null | Skin sprite for the idle state |
|
|
406
|
+
| `textureHover` | texture | null | Skin sprite on hover; falls back to `textureNormal` |
|
|
407
|
+
| `texturePressed` | texture | null | Skin sprite while pressed; falls back to `textureNormal` |
|
|
408
|
+
| `textureDisabled` | texture | null | Skin sprite while disabled; falls back to `textureNormal` |
|
|
409
|
+
| `sliceBorderLeft` | number | 0 | Left 9-slice inset of the state skins, in source px; 0 = stretch |
|
|
410
|
+
| `sliceBorderRight` | number | 0 | Right 9-slice inset, in source px |
|
|
411
|
+
| `sliceBorderTop` | number | 0 | Top 9-slice inset, in source px |
|
|
412
|
+
| `sliceBorderBottom` | number | 0 | Bottom 9-slice inset, in source px |
|
|
413
|
+
|
|
414
|
+
**Usage Notes:**
|
|
415
|
+
|
|
416
|
+
- Emits button press events when clicked
|
|
417
|
+
- Visual states: default, hover, pressed
|
|
418
|
+
- Use `buttonAction` to identify button function in scripts
|
|
419
|
+
- A state sprite replaces the flat colour for that state (the material tint goes white); with no
|
|
420
|
+
sprites at all the button keeps its colour behaviour
|
|
421
|
+
- **Nine-slice**: set the four `sliceBorder*` insets (source-texture pixels, Godot's `patch_margin_*`) to stop the skin being smeared. The corners keep their pixel size while the edges and centre stretch, so one 64x64 sprite fits any size. All-zero (the default) is the plain stretch, unchanged.
|
|
422
|
+
- The four insets apply to **every** state sprite, and each state's patch is cut against _its own_
|
|
423
|
+
natural size — a hover skin authored at 2x still gets the same physical corners
|
|
424
|
+
- A sliced skin opts out of the 2D quad batcher (the batcher extracts four _unit_ corners, which a
|
|
425
|
+
baked patch is not); an unsliced button still batches
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
### Label2D
|
|
430
|
+
|
|
431
|
+
A multiline text label for 2D UI. Wraps text to a fixed box, aligns it in both axes, and can reveal it with a typewriter effect.
|
|
432
|
+
|
|
433
|
+
**Type String:** `Label2D`
|
|
434
|
+
|
|
435
|
+
**Properties:**
|
|
436
|
+
|
|
437
|
+
| Property | Type | Default | Description |
|
|
438
|
+
| ------------------------------------------- | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
439
|
+
| `label` | string | "" | Text to display; `\n` breaks lines |
|
|
440
|
+
| `labelFontFamily` | string | Arial | Font family. A family the project ships (`ProjectManifest.fonts`) is registered before the first frame; anything else falls back to a system face |
|
|
441
|
+
| `labelFontSize` | number | 16 | Font size in pixels |
|
|
442
|
+
| `labelFontWeight` | number \| string | normal | CSS weight (400/700/900…) — a display face and its Cyrillic supplier usually differ |
|
|
443
|
+
| `labelColor` | color | #ffffff | Text color |
|
|
444
|
+
| `labelOutlineWidth` | number | 0 | Alias of `outlineWidth` below (the shared `UIControl2D` caption outline); one outline, two spellings |
|
|
445
|
+
| `labelOutlineColor` | color | #000000 | Alias of `outlineColor` below |
|
|
446
|
+
| `labelShadowColor` | color \| null | null | Drop-shadow colour; empty = no shadow |
|
|
447
|
+
| `labelShadowOffsetX` / `labelShadowOffsetY` | number | 0 | Drop-shadow offset in px |
|
|
448
|
+
| `labelLetterSpacing` | number | 0 | Extra px between glyphs |
|
|
449
|
+
| `labelAlign` | enum | center | Horizontal alignment: `left`, `center`, `right` |
|
|
450
|
+
| `labelVAlign` | enum | middle | Vertical alignment: `top`, `middle`, `bottom` |
|
|
451
|
+
| `width` | number | 0 | Fixed box width; text word-wraps to it. 0 = auto-size (no wrap) |
|
|
452
|
+
| `height` | number | 0 | Fixed box height for vertical alignment. 0 = auto-size to the lines |
|
|
453
|
+
| `typewriterSpeed` | number | 0 | Characters per second for the typewriter reveal; 0 = off |
|
|
454
|
+
| `glowColor` | color | #ffffff | Glow colour; inert while `glowStrength` is 0 |
|
|
455
|
+
| `glowStrength` | number | 0 | Neon glow around the glyphs (0–4); 0 = off |
|
|
456
|
+
| `outlineColor` | color | #000000 | Outline colour; inert while `outlineWidth` is 0 |
|
|
457
|
+
| `outlineWidth` | number | 0 | Contrast outline half-width in px; 0 = off |
|
|
458
|
+
|
|
459
|
+
**Usage Notes:**
|
|
460
|
+
|
|
461
|
+
- The box is centered on the node position (like other UI controls); alignment places the text inside that box.
|
|
462
|
+
- Set `width` manually to get word wrap — there is no auto-grow layout yet.
|
|
463
|
+
- **Glow/outline are canvas-drawn** (`ctx.shadowBlur` passes + a `strokeText` underlay), not post-processing — which is exactly why HUD text can glow: a `CanvasLayer2D` subtree is drawn _after_ the post-processing composer and can never bloom. Both are off by default, so existing scenes are unchanged.
|
|
464
|
+
- Turning either on grows the label's canvas (and the mesh showing it) by a bleed on each side so the blur/stroke isn't clipped; the tap target stays the authored box.
|
|
465
|
+
- `glowStrength` 1 is a subtle halo, 2–3 reads as neon (each whole step adds an additive shadow pass, capped at 3); the blur scales with `labelFontSize`, so a HUD label and a title glow proportionally.
|
|
466
|
+
- Scripts: `setText(text)` replaces the text and restarts the typewriter; `skipTypewriter()` completes it instantly; `restartTypewriter()` replays it; `isTyping` reports progress; the node emits `'typewriter-complete'` when the reveal finishes.
|
|
467
|
+
- The typewriter runs in play mode only (it advances in `tick`); the editor viewport always shows the full text.
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
### Slider2D
|
|
472
|
+
|
|
473
|
+
A horizontal slider control for selecting numeric values. Useful for volume controls, brightness settings, or any continuous value input.
|
|
474
|
+
|
|
475
|
+
**Type String:** `Slider2D`
|
|
476
|
+
|
|
477
|
+
**Properties:**
|
|
478
|
+
|
|
479
|
+
| Property | Type | Default | Description |
|
|
480
|
+
| ---------------------- | ------- | -------- | ---------------------------------------------------------- |
|
|
481
|
+
| `width` | number | 200 | Slider width in pixels |
|
|
482
|
+
| `height` | number | 20 | Slider height in pixels |
|
|
483
|
+
| `handleSize` | number | 20 | Handle knob size |
|
|
484
|
+
| `trackBackgroundColor` | color | #333333 | Empty track color |
|
|
485
|
+
| `trackFilledColor` | color | #4a9eff | Filled track color |
|
|
486
|
+
| `handleColor` | color | #ffffff | Handle color |
|
|
487
|
+
| `minValue` | number | 0 | Minimum value |
|
|
488
|
+
| `maxValue` | number | 100 | Maximum value |
|
|
489
|
+
| `value` | number | 50 | Current value |
|
|
490
|
+
| `axisName` | string | "Slider" | Identifier for axis |
|
|
491
|
+
| `textureTrack` | texture | null | Sprite for the track background |
|
|
492
|
+
| `textureFill` | texture | null | Sprite for the filled portion left of the handle |
|
|
493
|
+
| `textureThumb` | texture | null | Sprite for the handle; never nine-sliced |
|
|
494
|
+
| `sliceBorderLeft` | number | 0 | Left 9-slice inset of the track/fill sprites, in source px |
|
|
495
|
+
| `sliceBorderRight` | number | 0 | Right 9-slice inset, in source px |
|
|
496
|
+
| `sliceBorderTop` | number | 0 | Top 9-slice inset, in source px |
|
|
497
|
+
| `sliceBorderBottom` | number | 0 | Bottom 9-slice inset, in source px |
|
|
498
|
+
|
|
499
|
+
**Usage Notes:**
|
|
500
|
+
|
|
501
|
+
- Drag the handle to change values
|
|
502
|
+
- A slot's sprite replaces its flat colour (tint goes white); unset slots keep the colour
|
|
503
|
+
- **Nine-slice**: set the four `sliceBorder*` insets (source-texture pixels, Godot's `patch_margin_*`) to stop the skin being smeared. The corners keep their pixel size while the edges and centre stretch, so one 64x64 sprite fits any size. All-zero (the default) is the plain stretch, unchanged.
|
|
504
|
+
- The border applies to the track and the fill, never the thumb — a knob is drawn at its authored
|
|
505
|
+
size, so there is nothing to slice. The fill is **re-cut** as the value moves, so its caps keep
|
|
506
|
+
their pixel size instead of squashing
|
|
507
|
+
- Value is clamped between min and max
|
|
508
|
+
- Emits the pointer lifecycle signals (`pointerdown`/`pressed`/`pointerup`/`released`/`click`) but **no** state signal: the value is written during the drag, so by the time `released`/`click` fire, `value` is already the value the gesture produced. Read `slider.value` there, or track the live value through `input.getAxis(axisName)`.
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
### Joystick2D
|
|
513
|
+
|
|
514
|
+
A virtual analog stick control for touch or mouse input. Commonly used for character movement or camera control in games.
|
|
515
|
+
|
|
516
|
+
**Type String:** `Joystick2D`
|
|
517
|
+
|
|
518
|
+
**Properties:**
|
|
519
|
+
|
|
520
|
+
| Property | Type | Default | Description |
|
|
521
|
+
| ---------------- | ------- | ---------- | --------------------------------------------------------------------------- |
|
|
522
|
+
| `enabled` | boolean | true | When false the stick accepts no input and drops any drag in progress |
|
|
523
|
+
| `radius` | number | 50 | Base radius; also the handle's travel limit |
|
|
524
|
+
| `floating` | boolean | false | Hidden until a touch summons it under the finger, then fades out on release |
|
|
525
|
+
| `axisHorizontal` | string | Horizontal | Virtual axis written with the X deflection (-1..1) |
|
|
526
|
+
| `axisVertical` | string | Vertical | Virtual axis written with the Y deflection (-1..1) |
|
|
527
|
+
|
|
528
|
+
**Usage Notes:**
|
|
529
|
+
|
|
530
|
+
- Writes normalized X/Y values (-1 to 1) into the two named axes; centred is (0, 0)
|
|
531
|
+
- Captures the finger that started the drag and follows only that one, so a second thumb can press
|
|
532
|
+
buttons (or drive a second stick) without disturbing it. A `pointercancel` — a finger dragged off
|
|
533
|
+
the edge of the screen — returns the axes to zero, it never leaves them pushed
|
|
534
|
+
- A floating stick starts on a **new** touch that is not over UI, asked per finger: a button held by
|
|
535
|
+
another thumb no longer blocks it
|
|
536
|
+
- Ideal for mobile/touch interfaces
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
### Checkbox2D
|
|
541
|
+
|
|
542
|
+
A toggle checkbox control for boolean settings.
|
|
543
|
+
|
|
544
|
+
**Type String:** `Checkbox2D`
|
|
545
|
+
|
|
546
|
+
**Properties:**
|
|
547
|
+
|
|
548
|
+
| Property | Type | Default | Description |
|
|
549
|
+
| ------------------- | ------- | -------- | --------------------------------------------------- |
|
|
550
|
+
| `size` | number | 30 | Side of the square box, in pixels |
|
|
551
|
+
| `checked` | boolean | false | Checked state |
|
|
552
|
+
| `uncheckedColor` | color | #ffffff | Box fill colour while unchecked |
|
|
553
|
+
| `checkedColor` | color | #4a9eff | Box fill colour while checked |
|
|
554
|
+
| `checkmarkColor` | color | #ffffff | Colour of the fallback tick (ignored with a sprite) |
|
|
555
|
+
| `checkmarkAction` | string | Checkbox | Virtual input action pulsed / latched on toggle |
|
|
556
|
+
| `textureBox` | texture | null | Sprite for the box; also the checked fallback |
|
|
557
|
+
| `textureBoxChecked` | texture | null | Optional sprite for the box while checked |
|
|
558
|
+
| `textureMark` | texture | null | Sprite drawn over the box while checked (the tick) |
|
|
559
|
+
|
|
560
|
+
**Signals:**
|
|
561
|
+
|
|
562
|
+
| Signal | Arguments | When |
|
|
563
|
+
| ------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------- |
|
|
564
|
+
| `pointerdown` / `pressed` | — | A press landed inside the box |
|
|
565
|
+
| `pointerup` / `released` | — | The press ended |
|
|
566
|
+
| `click` | — | Released inside the box — a completed _gesture_, emitted **before** the box flips |
|
|
567
|
+
| `toggled` | `(checked)` | The checked state **changed**, emitted **after** the flip, the repaint and the virtual action are in place |
|
|
568
|
+
|
|
569
|
+
`click` and `toggled` are deliberately separate, as in Godot (`pressed` vs `toggled`):
|
|
570
|
+
`click` says "the user clicked me" and runs while `checked` still holds its
|
|
571
|
+
pre-click value, so a handler that applies `checked` from `click` applies the
|
|
572
|
+
_previous_ state (inverted behaviour). Connect anything that _acts on the state_
|
|
573
|
+
to `toggled` and read the payload or the node:
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
musicToggle.connect('toggled', this, checked => scene.audio.setBusVolume('music', checked ? 1 : 0));
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
`toggled` fires for every spelling of the change — a tap, the `toggle` /
|
|
580
|
+
`setChecked` interactions, `checkbox.checked = x` from a script, an Inspector
|
|
581
|
+
edit — and never fires when the assigned value equals the current one.
|
|
582
|
+
|
|
583
|
+
**Usage Notes:**
|
|
584
|
+
|
|
585
|
+
- Toggle between checked/unchecked states
|
|
586
|
+
- The hit area spans the box plus its label, so clicking the text toggles too
|
|
587
|
+
- `checkmarkAction` pulses a virtual button for one frame and latches an axis (1/0) with the state
|
|
588
|
+
- A sprite in a slot replaces the corresponding flat colour (the tint goes white). `textureBoxChecked`
|
|
589
|
+
is optional: without it the checked box keeps drawing `textureBox`
|
|
590
|
+
- The mark exists **only while checked**, sprite or not. With `textureMark` it is a box-sized,
|
|
591
|
+
unrotated quad; without one it stays the historical tilted colour bar
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### Bar2D
|
|
596
|
+
|
|
597
|
+
A progress bar or health bar display.
|
|
598
|
+
|
|
599
|
+
**Type String:** `Bar2D`
|
|
600
|
+
|
|
601
|
+
**Properties:**
|
|
602
|
+
|
|
603
|
+
| Property | Type | Default | Description |
|
|
604
|
+
| --------------------- | ------- | ------- | ------------------------------------------------------------------------------------ |
|
|
605
|
+
| `width` | number | 150 | Bar width in pixels |
|
|
606
|
+
| `height` | number | 20 | Bar height in pixels |
|
|
607
|
+
| `value` | number | 100 | Current fill value, clamped to `minValue`..`maxValue` |
|
|
608
|
+
| `minValue` | number | 0 | Value at which the bar is empty |
|
|
609
|
+
| `maxValue` | number | 100 | Value at which the bar is full |
|
|
610
|
+
| `backBackgroundColor` | color | #333333 | Trough (background) colour |
|
|
611
|
+
| `barColor` | color | #ff4444 | Fill colour |
|
|
612
|
+
| `showBorder` | boolean | true | Draw a frame around the bar |
|
|
613
|
+
| `borderColor` | color | #000000 | Frame colour |
|
|
614
|
+
| `borderWidth` | number | 2 | Frame thickness in pixels (not in the Inspector schema; authored/saved in YAML only) |
|
|
615
|
+
| `textureTrough` | texture | null | Sprite for the empty trough behind the fill |
|
|
616
|
+
| `textureFill` | texture | null | Sprite for the filled portion |
|
|
617
|
+
| `sliceBorderLeft` | number | 0 | Left 9-slice inset of the trough/fill sprites, in source px |
|
|
618
|
+
| `sliceBorderRight` | number | 0 | Right 9-slice inset, in source px |
|
|
619
|
+
| `sliceBorderTop` | number | 0 | Top 9-slice inset, in source px |
|
|
620
|
+
| `sliceBorderBottom` | number | 0 | Bottom 9-slice inset, in source px |
|
|
621
|
+
|
|
622
|
+
**Usage Notes:**
|
|
623
|
+
|
|
624
|
+
- Fill fraction = (value - minValue) / (maxValue - minValue)
|
|
625
|
+
- Useful for health bars, mana bars, loading progress
|
|
626
|
+
- Can be oriented horizontally
|
|
627
|
+
- A slot's sprite replaces its flat colour (tint goes white); unset slots keep the colour
|
|
628
|
+
- **Nine-slice**: set the four `sliceBorder*` insets (source-texture pixels, Godot's `patch_margin_*`) to stop the skin being smeared. The corners keep their pixel size while the edges and centre stretch, so one 64x64 sprite fits any size. All-zero (the default) is the plain stretch, unchanged.
|
|
629
|
+
- The fill is **re-cut** at the new width every time `value` changes, so a sliced fill keeps its
|
|
630
|
+
end caps at full pixel size all the way down to an empty bar instead of squashing them
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
### InventorySlot2D
|
|
635
|
+
|
|
636
|
+
A specialized slot control for inventory systems. Supports drag-and-drop for item management.
|
|
637
|
+
|
|
638
|
+
**Type String:** `InventorySlot2D`
|
|
639
|
+
|
|
640
|
+
**Properties:**
|
|
641
|
+
|
|
642
|
+
| Property | Type | Default | Description |
|
|
643
|
+
| ------------------ | ------- | -------------- | ------------------------------------------------- |
|
|
644
|
+
| `width` | number | 60 | Slot size (20..200) |
|
|
645
|
+
| `height` | number | 60 | Slot size (20..200) |
|
|
646
|
+
| `backdropColor` | color | #555555 | Slot background |
|
|
647
|
+
| `borderColor` | color | #888888 | Border colour |
|
|
648
|
+
| `borderWidth` | number | 2 | Border thickness, px |
|
|
649
|
+
| `quantity` | number | 0 | Stack count shown in the corner (≥ 0) |
|
|
650
|
+
| `showQuantity` | boolean | true | Draw the count |
|
|
651
|
+
| `quantityFontSize` | number | 12 | Count text size, px |
|
|
652
|
+
| `selectionColor` | color | #ffff00 | Highlight colour while selected |
|
|
653
|
+
| `selectedAction` | string | `SlotSelected` | Virtual button pressed while the slot is selected |
|
|
654
|
+
|
|
655
|
+
**Signals:**
|
|
656
|
+
|
|
657
|
+
| Signal | Arguments | When |
|
|
658
|
+
| ---------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------- |
|
|
659
|
+
| `pointerdown` / `pressed` / `pointerup` / `released` | — | Pointer lifecycle, same as every UI control |
|
|
660
|
+
| `click` | — | Released inside the slot, emitted **before** the selection flips |
|
|
661
|
+
| `toggled` | `(selected)` | The selection **changed**, emitted **after** the highlight and the virtual action are in place |
|
|
662
|
+
|
|
663
|
+
Same split as `Checkbox2D`: read `selected` from a `toggled` listener, not from a
|
|
664
|
+
`click` one (which still sees the pre-click value). A slot inside a
|
|
665
|
+
`ScrollContainer2D` refuses the click entirely while the list is being flicked,
|
|
666
|
+
so neither signal fires for a scroll drag.
|
|
667
|
+
|
|
668
|
+
**Usage Notes:**
|
|
669
|
+
|
|
670
|
+
- Can hold one item at a time
|
|
671
|
+
- Visual indicator for item count
|
|
672
|
+
- Supports drag operations
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
### ScrollContainer2D
|
|
677
|
+
|
|
678
|
+
A vertically scrolling viewport that clips its children, with drag, wheel and inertia, plus an
|
|
679
|
+
optional themeable scrollbar. Use it for inventories, leaderboards, long settings lists.
|
|
680
|
+
|
|
681
|
+
**Type String:** `ScrollContainer2D`
|
|
682
|
+
|
|
683
|
+
**Properties** (plus everything on Node2D):
|
|
684
|
+
|
|
685
|
+
| Property | Type | Default | Description |
|
|
686
|
+
| ----------------------- | ------- | ------- | ---------------------------------------------------------------------------------------- |
|
|
687
|
+
| `width` | number | 100 | Viewport width |
|
|
688
|
+
| `height` | number | 100 | Viewport height (the clipped window) |
|
|
689
|
+
| `scrollY` | number | 0 | Current scroll offset, clamped to content |
|
|
690
|
+
| `dragScrollEnabled` | boolean | true | Drag the content to scroll |
|
|
691
|
+
| `wheelScrollEnabled` | boolean | true | Mouse wheel / trackpad |
|
|
692
|
+
| `inertiaEnabled` | boolean | true | Fling continues after release |
|
|
693
|
+
| `wheelSensitivity` | number | 1 | Wheel multiplier |
|
|
694
|
+
| `dragThreshold` | number | 6 | Pixels of movement before a press becomes a drag (below it, the press goes to the child) |
|
|
695
|
+
| `inertiaDamping` | number | 14 | Higher stops the fling sooner |
|
|
696
|
+
| `showScrollbar` | boolean | true | Draw the scrollbar |
|
|
697
|
+
| `scrollbarWidth` | number | 8 | Bar width (min 2) |
|
|
698
|
+
| `scrollbarMinHeight` | number | 24 | Shortest the thumb may get (min 8) |
|
|
699
|
+
| `scrollbarInset` | number | 8 | Inset from the right edge |
|
|
700
|
+
| `scrollbarColor` | color | #f5f7ff | Thumb tint |
|
|
701
|
+
| `scrollbarTrackColor` | color | #ffffff | Track tint |
|
|
702
|
+
| `scrollbarThumbTexture` | texture | null | Optional thumb skin |
|
|
703
|
+
| `scrollbarTrackTexture` | texture | null | Optional track skin |
|
|
704
|
+
|
|
705
|
+
**Usage Notes:**
|
|
706
|
+
|
|
707
|
+
- Script API: `scrollBy(delta)`, `scrollTo(offset)`, `fling(velocity)`, and the `scrollY` accessor.
|
|
708
|
+
- The scrollbar meshes carry the 2D overlay flag, so they float above the container's _children_
|
|
709
|
+
rather than being painted under them.
|
|
710
|
+
- Vertical only. A horizontal carousel is a `Group2D` with a flow row and your own drag script.
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
### Camera2D
|
|
715
|
+
|
|
716
|
+
A 2D game camera (Godot-style). Like `VirtualCamera3D` it does **not** render — it _describes_ how the shared 2D orthographic pass is framed. Each frame the runtime picks the highest-priority visible `Camera2D` and applies its pan (`position` + `offset`), `zoom`, clamped `limits`, and shake to the 2D camera. With no `Camera2D` in the scene the 2D pass keeps its default identity framing, so existing 2D scenes / playable ads are unaffected. Every knob is a flat schema property, so the keyframe timeline animates `position`, `offset`, `zoom`, `priority`, etc. with no animation code.
|
|
717
|
+
|
|
718
|
+
**Type String:** `Camera2D`
|
|
719
|
+
|
|
720
|
+
**Properties:**
|
|
721
|
+
|
|
722
|
+
| Property | Type | Default | Description |
|
|
723
|
+
| ---------------- | ------- | --------- | ----------------------------------------------------------------------- |
|
|
724
|
+
| `priority` | number | 10 | Highest-priority visible Camera2D drives the 2D view (animatable) |
|
|
725
|
+
| `zoom` | number | 1 | >1 magnifies (zooms in), <1 zooms out |
|
|
726
|
+
| `offset` | vector2 | 0,0 | Framing offset added to position (never written by follow / shake) |
|
|
727
|
+
| `followTargetId` | node | — | Node whose position this camera follows (empty = authored position) |
|
|
728
|
+
| `followOffset` | vector2 | 0,0 | Offset from the follow target |
|
|
729
|
+
| `followDamping` | number | 8 | Higher = snappier follow (0 = instant) |
|
|
730
|
+
| `deadzone` | vector2 | 0,0 | World half-extents the target may move within before the camera follows |
|
|
731
|
+
| `limitsEnabled` | boolean | false | Clamp the visible view inside an axis-aligned world box |
|
|
732
|
+
| `limitsCenter` | vector2 | 0,0 | Limits box center |
|
|
733
|
+
| `limitsSize` | vector2 | 1000,1000 | Limits box size |
|
|
734
|
+
| `shakeAmplitude` | number | 8 | Peak shake displacement in world units |
|
|
735
|
+
| `shakeFrequency` | number | 24 | Shake oscillation speed |
|
|
736
|
+
| `shakeDuration` | number | 0.35 | Shake duration in seconds |
|
|
737
|
+
| `shakeDecay` | number | 1.5 | Falloff power (0 = steady, 1 = linear, >1 = punchy tail) |
|
|
738
|
+
|
|
739
|
+
**Usage Notes:**
|
|
740
|
+
|
|
741
|
+
- `position` is the camera center (follow damps it toward the target); `offset` is a separate framing bias that follow and shake never touch.
|
|
742
|
+
- Limits clamp the view **center** zoom-aware, so the view edge never crosses the box; a box smaller than the view pins the center to `limitsCenter`.
|
|
743
|
+
- Shake is additive at apply time (never mutates `position`) and, being tick-driven, respects `Time.scale` — a hitstop freezes it, slow-mo stretches it. Trigger it from a script with `scene.juice.shake('camera2d')` (or `scene.juice.shake('camera')` in a pure-2D scene).
|
|
744
|
+
- v1: screen-anchored HUD shares this camera and pans / zooms with the world. Use a `CanvasLayer2D` to pin a HUD.
|
|
745
|
+
|
|
746
|
+
---
|
|
747
|
+
|
|
748
|
+
### CanvasLayer2D
|
|
749
|
+
|
|
750
|
+
A Godot-style `CanvasLayer` — a clean UI overlay band. Its subtree renders on a separate layer through an always-identity camera **after** the post-processing composer, so it is (a) a **fixed HUD** that ignores any `Camera2D` pan/zoom, and (b) **never post-processed** — bloom/vignette/chromatic-aberration leave it crisp (e.g. a restart dialog over a blurred game-over scene). Extends `Group2D` (width/height container); no extra authored properties in v1.
|
|
751
|
+
|
|
752
|
+
**Type String:** `CanvasLayer2D`
|
|
753
|
+
|
|
754
|
+
**Properties:** inherits `Group2D` (`width`, `height`) + `Node2D` transform/anchor/opacity.
|
|
755
|
+
|
|
756
|
+
**Usage Notes:**
|
|
757
|
+
|
|
758
|
+
- Content under a CanvasLayer2D is pinned in design-space coordinates regardless of the active Camera2D, and its pointer hit-tests stay correct while the world camera pans.
|
|
759
|
+
- Multiple CanvasLayer2D nodes stack by scene-tree order (like ordinary 2D draw order).
|
|
760
|
+
- Unlike Godot, inheritance is **not** broken: an ancestor's transform, opacity, and visibility still flow into the overlay subtree — only the render camera differs. Author at the scene root for a fully independent layer.
|
|
761
|
+
- Runtime overlay behavior is play-mode only; in the editor it renders as a normal Group2D container.
|
|
762
|
+
|
|
763
|
+
## 3D Nodes
|
|
764
|
+
|
|
765
|
+
All 3D nodes operate in world space using a perspective camera by default. They use a right-handed coordinate system where X is right, Y is up, and Z is toward the viewer.
|
|
766
|
+
|
|
767
|
+
### Node3D
|
|
768
|
+
|
|
769
|
+
The base class for all 3D scene nodes. Use this for simple grouping or as a container for other 3D elements.
|
|
770
|
+
|
|
771
|
+
**Type String:** `Node3D`
|
|
772
|
+
|
|
773
|
+
**Properties:**
|
|
774
|
+
|
|
775
|
+
| Property | Type | Default | Description |
|
|
776
|
+
| ---------- | ------- | --------- | --------------------------- |
|
|
777
|
+
| `position` | Vector3 | (0, 0, 0) | X, Y, Z coordinates |
|
|
778
|
+
| `rotation` | Euler | (0, 0, 0) | Pitch, Yaw, Roll in degrees |
|
|
779
|
+
| `scale` | Vector3 | (1, 1, 1) | X, Y, Z scale factors |
|
|
780
|
+
|
|
781
|
+
**Rotation Order:** XYZ (Pitch → Yaw → Roll)
|
|
782
|
+
|
|
783
|
+
**Usage Notes:**
|
|
784
|
+
|
|
785
|
+
- Rotation values are in degrees, stored as radians internally
|
|
786
|
+
- Default scale (1, 1, 1) means no scaling
|
|
787
|
+
- Children inherit all transforms
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
### Camera3D
|
|
792
|
+
|
|
793
|
+
A camera node that defines the viewpoint for rendering. The scene can have multiple cameras, but only one is active at a time.
|
|
794
|
+
|
|
795
|
+
**Type String:** `Camera3D`
|
|
796
|
+
|
|
797
|
+
**Properties:**
|
|
798
|
+
|
|
799
|
+
| Property | Type | Default | Description |
|
|
800
|
+
| ------------ | ------ | ----------- | ----------------------- |
|
|
801
|
+
| `projection` | enum | perspective | Projection type |
|
|
802
|
+
| `fov` | number | 60 | Field of view (degrees) |
|
|
803
|
+
| `near` | number | 0.1 | Near clipping plane |
|
|
804
|
+
| `far` | number | 1000 | Far clipping plane |
|
|
805
|
+
|
|
806
|
+
**Projection Types:**
|
|
807
|
+
|
|
808
|
+
| Type | Description |
|
|
809
|
+
| -------------- | -------------------------------- |
|
|
810
|
+
| `perspective` | Perspective projection (default) |
|
|
811
|
+
| `orthographic` | Orthographic projection |
|
|
812
|
+
|
|
813
|
+
**Usage Notes:**
|
|
814
|
+
|
|
815
|
+
- Perspective: Objects get smaller with distance
|
|
816
|
+
- Orthographic: No perspective distortion
|
|
817
|
+
- Default looks down the negative Z axis
|
|
818
|
+
- Use `setTargetPosition()` to point camera at a target
|
|
819
|
+
|
|
820
|
+
---
|
|
821
|
+
|
|
822
|
+
### VirtualCamera3D
|
|
823
|
+
|
|
824
|
+
A lightweight "virtual camera" (Cinemachine-lite). It does **not** render — it only describes a desired framing. Attach a **Camera Brain** (`core:CameraBrain`) component to a real `Camera3D`; each frame the brain picks the highest-priority visible virtual camera and blends the render camera toward it. Because every knob is a schema property, the keyframe timeline can animate `priority`, `fov`, `position`, etc. — switching cameras is "raise this one's priority above that one" — with no animation code.
|
|
825
|
+
|
|
826
|
+
**Type String:** `VirtualCamera3D`
|
|
827
|
+
|
|
828
|
+
**Properties:**
|
|
829
|
+
|
|
830
|
+
| Property | Type | Default | Description |
|
|
831
|
+
| ------------------ | ------- | ---------- | ------------------------------------------------------------------------- |
|
|
832
|
+
| `priority` | number | 10 | Highest-priority live virtual camera wins (animatable) |
|
|
833
|
+
| `fov` | number | 60 | Field of view applied to a perspective render camera |
|
|
834
|
+
| `orthographicSize` | number | 5 | Size applied to an orthographic render camera |
|
|
835
|
+
| `followTargetId` | node | — | Node whose position this camera follows (empty = authored position) |
|
|
836
|
+
| `followOffset` | vector3 | 0,0,0 | World-space offset from the follow target |
|
|
837
|
+
| `followDamping` | number | 8 | Higher = snappier follow (0 = instant) |
|
|
838
|
+
| `deadzone` | vector3 | 0,0,0 | World half-extents the target may move within before the camera follows |
|
|
839
|
+
| `lookAtTargetId` | node | — | Node this camera orients toward (empty = authored rotation) |
|
|
840
|
+
| `lookAtWeight` | number | 1 | 0 = keep authored rotation, 1 = fully track the target |
|
|
841
|
+
| `rotationDamping` | number | 8 | Higher = snappier aim (0 = instant) |
|
|
842
|
+
| `confinerEnabled` | boolean | false | Clamp the camera position inside an axis-aligned box |
|
|
843
|
+
| `confinerCenter` | vector3 | 0,0,0 | Confiner box center |
|
|
844
|
+
| `confinerSize` | vector3 | 10,10,10 | Confiner box size |
|
|
845
|
+
| `blendDuration` | number | 1 | Seconds to blend the render camera toward this one when it becomes active |
|
|
846
|
+
| `blendEasing` | enum | cubicInOut | Easing curve used for the blend |
|
|
847
|
+
|
|
848
|
+
**Usage Notes:**
|
|
849
|
+
|
|
850
|
+
- Requires a `Camera3D` carrying the `core:CameraBrain` component — that camera is the only one that renders.
|
|
851
|
+
- Standby cameras are still solved every frame, so a camera is already framed when it is cut to.
|
|
852
|
+
- With no follow target, position is left to authored / keyframed values (dolly by keyframes). Same for rotation with no look-at target.
|
|
853
|
+
- Setting a Camera Brain's **Blend On Switch** off makes cuts instantaneous.
|
|
854
|
+
- Scripts can force the _next_ activation blend with `CameraBrainBehavior.overrideNextBlend(durationSec, easing?)` — a one-shot override that wins even when Blend On Switch is off. The Cutscene Director (`scene.cutscene.playCinematic`) uses it to smooth the cut into and out of a cinematic virtual camera. See the runtime spec §6.13.
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
### GeometryMesh
|
|
859
|
+
|
|
860
|
+
A 3D mesh node with a built-in primitive geometry and a PBR material. The shape
|
|
861
|
+
is inspector-switchable and animatable via the `geometry`/`size` schema
|
|
862
|
+
properties.
|
|
863
|
+
|
|
864
|
+
**Type String:** `GeometryMesh`
|
|
865
|
+
|
|
866
|
+
**Properties:**
|
|
867
|
+
|
|
868
|
+
| Property | Type | Default | Description |
|
|
869
|
+
| ------------------------- | ------- | --------- | ----------------------------------------------------------------------- |
|
|
870
|
+
| `geometry` | enum | "box" | Primitive shape (see below) |
|
|
871
|
+
| `size` | Vector3 | (1, 1, 1) | Interpreted per shape (see below) |
|
|
872
|
+
| `material.color` | color | #4e8df5 | Surface color |
|
|
873
|
+
| `material.roughness` | number | 0.35 | Surface roughness (0-1) |
|
|
874
|
+
| `material.metalness` | number | 0.25 | Metallic appearance (0-1) |
|
|
875
|
+
| `material.map` | texture | — | Albedo (diffuse) texture (res://); required for UV Scroll to be visible |
|
|
876
|
+
| `material.aoMap` | texture | — | Baked ambient-occlusion map (set by the AO baker) |
|
|
877
|
+
| `material.aoMapIntensity` | number | 1 | Strength of the baked AO map (0 = off) |
|
|
878
|
+
| `castShadow` | boolean | true | Whether the mesh casts shadows |
|
|
879
|
+
| `receiveShadow` | boolean | true | Whether the mesh receives shadows cast by others |
|
|
880
|
+
|
|
881
|
+
**Shader Effects (attached list):**
|
|
882
|
+
|
|
883
|
+
Shader effects are **added from a picker** (Inspector → **Effects** → **Add**),
|
|
884
|
+
Unity/Godot-style — not fixed checkboxes. Each attached effect shows an **enable
|
|
885
|
+
toggle**, its params, and a **Remove** control; the built-in effects come from a
|
|
886
|
+
registry (`core:*`) and can be extended with `user:*` effects later. One instance
|
|
887
|
+
of each type per mesh (v1). Effects are injected into the standard PBR material
|
|
888
|
+
via `onBeforeCompile` and `#ifdef`-gated on `material.defines`, so a disabled
|
|
889
|
+
effect costs zero GPU and lighting/shadows/albedo/AO maps still apply.
|
|
890
|
+
|
|
891
|
+
Attached effects contribute their params to the node's schema **per instance**
|
|
892
|
+
(as `fx.<effect>.<param>`), so each param — and each effect's `enabled` flag — is
|
|
893
|
+
individually **keyframe-animatable** from the timeline. Animate the numeric
|
|
894
|
+
params rather than the enable toggles (a toggle flip recompiles the shader; cheap
|
|
895
|
+
after the first compile, which three caches per variant).
|
|
896
|
+
|
|
897
|
+
| Effect (`type`) | Params | What it does |
|
|
898
|
+
| ---------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
899
|
+
| Dissolve (`core:dissolve`) | `amount` (0-1), `scale`, `edgeWidth`, `edgeColor` | Noise-thresholded `discard` with an emissive glowing edge; drive `amount` 0→1 to dissolve away |
|
|
900
|
+
| Rim Light (`core:rim`) | `color`, `intensity` (0-5), `power` | Fresnel-based emissive rim, brightest at grazing angles |
|
|
901
|
+
| UV Scroll (`core:uv-scroll`) | `speed` (uv/s) | Scrolls the albedo map. **Play-mode only** (accumulated per tick); static in the edit viewport. Needs `material.map` |
|
|
902
|
+
| Flash Tint (`core:flash`) | `color`, `amount` (0-1) | Blends the final lit color toward a flat color; a hit/damage flash |
|
|
903
|
+
|
|
904
|
+
Serialized under `material.effects` as an ordered array of `{ type, enabled,
|
|
905
|
+
params }` (only non-default params are written).
|
|
906
|
+
|
|
907
|
+
**Geometry Types & `size` semantics:**
|
|
908
|
+
|
|
909
|
+
`size` is a single `[x, y, z]` vector reinterpreted per shape, so one editable
|
|
910
|
+
field works for every primitive:
|
|
911
|
+
|
|
912
|
+
| Type | `size` meaning |
|
|
913
|
+
| ---------- | ---------------------------------------------------- |
|
|
914
|
+
| `box` | Full extents: width `x`, height `y`, depth `z` |
|
|
915
|
+
| `sphere` | Diameter `x` (y, z ignored) |
|
|
916
|
+
| `plane` | A horizontal floor `x` by `z` (lies in the XZ plane) |
|
|
917
|
+
| `cylinder` | Diameter `x`, height `y` |
|
|
918
|
+
| `cone` | Base diameter `x`, height `y` |
|
|
919
|
+
| `torus` | Outer diameter `x`; tube thickness scales with `y` |
|
|
920
|
+
|
|
921
|
+
**Usage Notes:**
|
|
922
|
+
|
|
923
|
+
- Changing `geometry` or `size` in the inspector rebuilds the mesh live.
|
|
924
|
+
- Material uses PBR (Physically Based Rendering).
|
|
925
|
+
- Roughness: 0 = glossy, 1 = matte. Metalness: 0 = non-metal, 1 = metal.
|
|
926
|
+
- Material color/roughness/metalness edits persist through save and play mode
|
|
927
|
+
(the live material is serialized, not a stale authored snapshot).
|
|
928
|
+
- Node opacity does **not** currently fade a GeometryMesh (its material isn't
|
|
929
|
+
registered for opacity blending).
|
|
930
|
+
- Effects are attached from a registry-backed picker (see above), not fixed
|
|
931
|
+
checkboxes; their params surface per-instance as `fx.<effect>.<param>` schema
|
|
932
|
+
props so they remain keyframe-animatable.
|
|
933
|
+
- A dissolving mesh still casts an intact shadow (the depth/shadow pass has no
|
|
934
|
+
`discard`); accepted for now.
|
|
935
|
+
- `castShadow`/`receiveShadow` (Inspector → **Rendering**) are authored on the
|
|
936
|
+
node and mirrored onto the child render mesh; both default to **true**, which is
|
|
937
|
+
what this node hardcoded before they were exposed, so existing scenes are
|
|
938
|
+
unchanged. Turn `receiveShadow` off for a mesh that should stay evenly lit
|
|
939
|
+
(unlit-looking floors, UI-ish props) without touching the light itself.
|
|
940
|
+
|
|
941
|
+
---
|
|
942
|
+
|
|
943
|
+
### MeshInstance
|
|
944
|
+
|
|
945
|
+
A node that loads and displays external 3D models in GLB or GLTF format.
|
|
946
|
+
|
|
947
|
+
**Type String:** `MeshInstance`
|
|
948
|
+
|
|
949
|
+
**Properties:**
|
|
950
|
+
|
|
951
|
+
| Property | Type | Default | Description |
|
|
952
|
+
| -------- | ------ | ------- | --------------------------- |
|
|
953
|
+
| `src` | string | null | Path to model file (res://) |
|
|
954
|
+
|
|
955
|
+
**Usage Notes:**
|
|
956
|
+
|
|
957
|
+
- Supports GLB (binary) and GLTF formats
|
|
958
|
+
- Path uses `res://` protocol for project resources
|
|
959
|
+
- Can contain multiple meshes, materials, and animations
|
|
960
|
+
- Animations can be played via script components
|
|
961
|
+
|
|
962
|
+
---
|
|
963
|
+
|
|
964
|
+
### InstancedMesh3D
|
|
965
|
+
|
|
966
|
+
A `THREE.InstancedMesh` wrapper for rendering many copies of one geometry/material in a single draw call — for large ECS-driven simulations (crowds, particles, tiles). Populate it in bulk from a script/system, not the inspector.
|
|
967
|
+
|
|
968
|
+
**Type String:** `InstancedMesh3D`
|
|
969
|
+
|
|
970
|
+
**Node-level (serialized) properties:**
|
|
971
|
+
|
|
972
|
+
| Property | Type | Default | Description |
|
|
973
|
+
| ------------------------------------------- | ------- | ----------- | --------------------------------------------------------------------------------------------------------------- |
|
|
974
|
+
| `maxInstances` | number | — | Buffer capacity (instance count ceiling); read-only in inspector |
|
|
975
|
+
| `enablePerInstanceColor` | boolean | false | Allocate a per-instance color buffer |
|
|
976
|
+
| `castShadow` | boolean | — | Forwarded to the underlying mesh |
|
|
977
|
+
| `receiveShadow` | boolean | — | Forwarded to the underlying mesh |
|
|
978
|
+
| `frustumCulled` | boolean | — | Forwarded to the underlying mesh |
|
|
979
|
+
| `visibleInstanceCount` | number | — | How many instances currently draw (read-only display; not saved) |
|
|
980
|
+
| `material.type` | enum | `standard` | Material family — `standard` (PBR) / `lambert` (mobile default) / `basic` (unlit); inspector: **Material Type** |
|
|
981
|
+
| `material.color` | color | `#ffffff` | Colour every instance shares; a per-instance colour multiplies it |
|
|
982
|
+
| `material.roughness` / `material.metalness` | number | 0.35 / 0.25 | `standard` only; dropped from the file for the other families |
|
|
983
|
+
|
|
984
|
+
**Bulk API (call from a script/system, then `flush()`):**
|
|
985
|
+
|
|
986
|
+
- `writeMatrices(data, options?)` — write raw N×16 `Float32Array` instance matrices.
|
|
987
|
+
- `writeTransforms(data, options?)` — write position/rotation/scale transforms (composed to matrices for you).
|
|
988
|
+
- `writeColors(data, options?)` — write per-instance colors (requires `enablePerInstanceColor`).
|
|
989
|
+
- `flush()` — upload dirty buffers to the GPU. `SceneRunner` calls `flush()` on every `InstancedMesh3D` before each frame; call it yourself if you mutate outside the runner loop.
|
|
990
|
+
- `setGeometry(geometry)` / `setMaterial(material)`; `getInstanceMatrixBuffer()` / `getInstanceColorBuffer()` for direct access.
|
|
991
|
+
|
|
992
|
+
**Usage Notes:**
|
|
993
|
+
|
|
994
|
+
- **Instance buffers are NOT serialized** — only the node-level config above is saved. Repopulate buffers at runtime.
|
|
995
|
+
- The `material` block IS serialized, and is what makes an instanced mesh authorable: before it existed the loader built no material at all, so a scene-authored instanced mesh always rendered with a shared white PBR default — unreachable from the inspector and past the project's mobile material policy. A `material` handed to the constructor in code still wins over the authored block.
|
|
996
|
+
- Raycasts against an instanced mesh return the hit `instanceId`.
|
|
997
|
+
- Backed by `ECSService` for project-managed ECS worlds; runtime lives in `packages/pix3-runtime/src/nodes/3D/InstancedMesh3D.ts` + `core/ECSService.ts`.
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
### AmbientLightNode
|
|
1002
|
+
|
|
1003
|
+
Uniform light from every direction with no falloff and no shadows — the cheapest possible fill.
|
|
1004
|
+
Raises the floor of a scene so unlit faces are not pure black.
|
|
1005
|
+
|
|
1006
|
+
**Type String:** `AmbientLightNode`
|
|
1007
|
+
|
|
1008
|
+
**Properties** (plus everything on Node3D):
|
|
1009
|
+
|
|
1010
|
+
| Property | Type | Default | Description |
|
|
1011
|
+
| ----------- | ------ | ------- | ------------ |
|
|
1012
|
+
| `color` | color | #ffffff | Light colour |
|
|
1013
|
+
| `intensity` | number | 0.5 | Brightness |
|
|
1014
|
+
|
|
1015
|
+
**Usage Notes:**
|
|
1016
|
+
|
|
1017
|
+
- Position and rotation are inert: ambient light has no direction and no origin.
|
|
1018
|
+
- Flattens everything it touches. Use it under a `DirectionalLightNode`, not instead of one; for a
|
|
1019
|
+
fill with some sense of sky and ground, prefer HemisphereLightNode.
|
|
1020
|
+
- Authored colours convert exactly once — never call `convertSRGBToLinear` on the hex you set.
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
### HemisphereLightNode
|
|
1025
|
+
|
|
1026
|
+
A two-colour gradient fill: sky colour from above, ground colour from below, interpolated by each
|
|
1027
|
+
surface's normal. An outdoor scene's fill light, and a much better default than flat ambient.
|
|
1028
|
+
|
|
1029
|
+
**Type String:** `HemisphereLightNode`
|
|
1030
|
+
|
|
1031
|
+
**Properties** (plus everything on Node3D):
|
|
1032
|
+
|
|
1033
|
+
| Property | Type | Default | Description |
|
|
1034
|
+
| ------------- | ------ | ------- | -------------------------- |
|
|
1035
|
+
| `skyColor` | color | #ffffff | Colour arriving from above |
|
|
1036
|
+
| `groundColor` | color | #444444 | Colour bounced from below |
|
|
1037
|
+
| `intensity` | number | 0.5 | Brightness |
|
|
1038
|
+
|
|
1039
|
+
**Usage Notes:**
|
|
1040
|
+
|
|
1041
|
+
- No shadows and no falloff, like ambient — it is a fill, not a key light.
|
|
1042
|
+
- A warm `skyColor` with a cool `groundColor` (or the reverse) reads as outdoor bounce for free.
|
|
1043
|
+
|
|
1044
|
+
---
|
|
1045
|
+
|
|
1046
|
+
### DirectionalLightNode
|
|
1047
|
+
|
|
1048
|
+
A light source that emits parallel rays in a single direction, like the sun. Illuminates all objects from the same angle.
|
|
1049
|
+
|
|
1050
|
+
**Type String:** `DirectionalLight`
|
|
1051
|
+
|
|
1052
|
+
**Properties:**
|
|
1053
|
+
|
|
1054
|
+
| Property | Type | Default | Description |
|
|
1055
|
+
| ------------ | ------- | ------- | --------------------- |
|
|
1056
|
+
| `color` | color | #ffffff | Light color |
|
|
1057
|
+
| `intensity` | number | 1.0 | Light brightness |
|
|
1058
|
+
| `castShadow` | boolean | true | Enable shadow casting |
|
|
1059
|
+
|
|
1060
|
+
**Usage Notes:**
|
|
1061
|
+
|
|
1062
|
+
- Light direction is determined by node rotation
|
|
1063
|
+
- Good for outdoor lighting and sun simulation
|
|
1064
|
+
- Constant illumination regardless of distance
|
|
1065
|
+
- Shadow map size is auto-calculated
|
|
1066
|
+
|
|
1067
|
+
---
|
|
1068
|
+
|
|
1069
|
+
### PointLightNode
|
|
1070
|
+
|
|
1071
|
+
A light source that emits rays in all directions from a single point. Like a light bulb.
|
|
1072
|
+
|
|
1073
|
+
**Type String:** `PointLight`
|
|
1074
|
+
|
|
1075
|
+
**Properties:**
|
|
1076
|
+
|
|
1077
|
+
| Property | Type | Default | Description |
|
|
1078
|
+
| ------------ | ------- | ------- | ---------------------------- |
|
|
1079
|
+
| `color` | color | #ffffff | Light color |
|
|
1080
|
+
| `intensity` | number | 1.0 | Light brightness |
|
|
1081
|
+
| `distance` | number | 0 | Maximum range (0 = infinite) |
|
|
1082
|
+
| `decay` | number | 2 | Falloff rate |
|
|
1083
|
+
| `castShadow` | boolean | true | Enable shadow casting |
|
|
1084
|
+
|
|
1085
|
+
**Usage Notes:**
|
|
1086
|
+
|
|
1087
|
+
- Intensity decreases with distance (inverse square law)
|
|
1088
|
+
- Use `distance` to limit effective range
|
|
1089
|
+
- Decay of 2 is physically accurate
|
|
1090
|
+
- Good for lamps, candles, torches
|
|
1091
|
+
|
|
1092
|
+
---
|
|
1093
|
+
|
|
1094
|
+
### SpotLightNode
|
|
1095
|
+
|
|
1096
|
+
A light source that emits a cone of light in a specific direction. Like a flashlight or spotlight.
|
|
1097
|
+
|
|
1098
|
+
**Type String:** `SpotLight`
|
|
1099
|
+
|
|
1100
|
+
**Properties:**
|
|
1101
|
+
|
|
1102
|
+
| Property | Type | Default | Description |
|
|
1103
|
+
| ------------ | ------- | ------- | ---------------------------- |
|
|
1104
|
+
| `color` | color | #ffffff | Light color |
|
|
1105
|
+
| `intensity` | number | 1.0 | Light brightness |
|
|
1106
|
+
| `distance` | number | 0 | Maximum range (0 = infinite) |
|
|
1107
|
+
| `angle` | number | 60 | Cone angle (degrees) |
|
|
1108
|
+
| `penumbra` | number | 0 | Edge softness (0-1) |
|
|
1109
|
+
| `decay` | number | 2 | Falloff rate |
|
|
1110
|
+
| `castShadow` | boolean | true | Enable shadow casting |
|
|
1111
|
+
|
|
1112
|
+
**Usage Notes:**
|
|
1113
|
+
|
|
1114
|
+
- Penumbra creates soft edge transitions
|
|
1115
|
+
- Angle controls the cone width
|
|
1116
|
+
- Good for stage lights, flashlights, focused lighting
|
|
1117
|
+
- Target direction is determined by node rotation
|
|
1118
|
+
|
|
1119
|
+
---
|
|
1120
|
+
|
|
1121
|
+
### Sprite3D
|
|
1122
|
+
|
|
1123
|
+
A textured quad in the 3D world — optionally billboarded so it always faces the camera. For
|
|
1124
|
+
impostors, floating markers, particles you place by hand, and 2.5D characters.
|
|
1125
|
+
|
|
1126
|
+
**Type String:** `Sprite3D`
|
|
1127
|
+
|
|
1128
|
+
**Properties** (plus everything on Node3D):
|
|
1129
|
+
|
|
1130
|
+
| Property | Type | Default | Description |
|
|
1131
|
+
| -------------------- | ------- | ------- | ---------------------------------------------------------------------- |
|
|
1132
|
+
| `texture` | texture | null | `{ type: 'texture', url: 'res://…' }` (`texturePath` string also read) |
|
|
1133
|
+
| `width` | number | 1 | Quad width in **world units**, not pixels |
|
|
1134
|
+
| `height` | number | 1 | Quad height in world units |
|
|
1135
|
+
| `billboard` | boolean | false | Face the camera every frame |
|
|
1136
|
+
| `billboardRoll` | number | 0 | Roll, in degrees, applied after billboarding |
|
|
1137
|
+
| `opacity` | number | 1 | Alpha |
|
|
1138
|
+
| `textureAspectRatio` | number | null | Captured from the texture on load (not saved) |
|
|
1139
|
+
| `aspectRatioLocked` | boolean | false | Keep width/height at the texture's aspect (inspector-only, not saved) |
|
|
1140
|
+
|
|
1141
|
+
**Usage Notes:**
|
|
1142
|
+
|
|
1143
|
+
- Sizes are world units — a 1×1 sprite is one metre wide, not 1 px. This is the single most common
|
|
1144
|
+
surprise when porting a 2D idea into 3D.
|
|
1145
|
+
- Unlike 2D nodes, this one keeps mipmaps: it is a 3D texture and is minified by distance.
|
|
1146
|
+
|
|
1147
|
+
---
|
|
1148
|
+
|
|
1149
|
+
### AnimatedSprite3D
|
|
1150
|
+
|
|
1151
|
+
A billboarded flipbook in the 3D world: same spritesheet idea as
|
|
1152
|
+
AnimatedSprite2D, placed in world space. Unlike the 2D node it carries its own
|
|
1153
|
+
frame rate.
|
|
1154
|
+
|
|
1155
|
+
**Type String:** `AnimatedSprite3D`
|
|
1156
|
+
|
|
1157
|
+
**Properties** (plus everything on Node3D):
|
|
1158
|
+
|
|
1159
|
+
| Property | Type | Default | Description |
|
|
1160
|
+
| -------------- | ------- | -------- | ------------------------------------------------------------ |
|
|
1161
|
+
| `width` | number | 1 | Quad width in world units |
|
|
1162
|
+
| `height` | number | 1 | Quad height in world units |
|
|
1163
|
+
| `fps` | number | 10 | Frames per second |
|
|
1164
|
+
| `playing` | boolean | true | Play on start |
|
|
1165
|
+
| `loop` | boolean | true | Repeat when the clip ends |
|
|
1166
|
+
| `freeOnFinish` | boolean | false | Remove the node when a non-looping clip ends |
|
|
1167
|
+
| `currentFrame` | number | 0 | Frame index (playback state, not saved) |
|
|
1168
|
+
| `billboard` | boolean | **true** | Face the camera (note: the opposite default from `Sprite3D`) |
|
|
1169
|
+
| `color` | color | #ffffff | Tint |
|
|
1170
|
+
| `opacity` | number | 1 | Alpha |
|
|
1171
|
+
|
|
1172
|
+
**Usage Notes:**
|
|
1173
|
+
|
|
1174
|
+
- `billboard` defaults to true here and false on `Sprite3D` — an explosion wants to face you, a
|
|
1175
|
+
poster on a wall does not.
|
|
1176
|
+
- `loop: false` + `freeOnFinish: true` is the fire-and-forget 3D hit effect.
|
|
1177
|
+
|
|
1178
|
+
---
|
|
1179
|
+
|
|
1180
|
+
### Particles3D
|
|
1181
|
+
|
|
1182
|
+
A CPU-simulated particle emitter rendered as a single `InstancedMesh`. Supports
|
|
1183
|
+
billboarded planes, spheres or cubes, per-particle color/alpha/size ramps, an
|
|
1184
|
+
emitter shape (point/sphere/box), optional ribbon **trails**, and **sub-emitters**
|
|
1185
|
+
that burst a second emitter on particle death.
|
|
1186
|
+
|
|
1187
|
+
**Type String:** `Particles3D`
|
|
1188
|
+
|
|
1189
|
+
**Key Properties:**
|
|
1190
|
+
|
|
1191
|
+
| Property | Type | Default | Description |
|
|
1192
|
+
| --------------------------- | ------- | ----------- | ----------------------------------------------------------------------------------------------------- |
|
|
1193
|
+
| `emissionRate` | number | 24 | Particles spawned per second |
|
|
1194
|
+
| `maxParticles` | number | 512 | Simulation pool size (also the instance cap) |
|
|
1195
|
+
| `lifetime` | number | 2 | Base particle lifetime (s), jittered ±15% |
|
|
1196
|
+
| `speed` / `speedSpread` | number | 2 / 0.5 | Initial speed and its random spread |
|
|
1197
|
+
| `gravity` | vector3 | (0,0,0) | Constant acceleration (sim-space vector) |
|
|
1198
|
+
| `startColor`/`endColor` | color | white/amber | Color ramp over life |
|
|
1199
|
+
| `startAlpha`/`endAlpha` | number | 1 / 0 | Alpha ramp over life |
|
|
1200
|
+
| `simulationSpace` | enum | `local` | `local` = particles follow the emitter; `world` = particles are emitted into world space and stay put |
|
|
1201
|
+
| `trailEnabled` | boolean | false | Draw a camera-facing ribbon behind each particle |
|
|
1202
|
+
| `trailLifetime` | number | 0.3 | How long (s) a trail sample survives |
|
|
1203
|
+
| `trailWidth` | number | 0.05 | Ribbon width at the head |
|
|
1204
|
+
| `trailSegments` | number | 16 | Ribbon resolution (clamped 2–64) |
|
|
1205
|
+
| `trailFade` | number | 1 | Alpha falloff along the ribbon (0 = solid, 1 = fade to transparent) |
|
|
1206
|
+
| `subEmitterId` | node | — | Another `Particles3D` fired as a burst at each particle death |
|
|
1207
|
+
| `subEmitterBurstCount` | number | 8 | Particles spawned per death (0–128) |
|
|
1208
|
+
| `subEmitterInheritVelocity` | number | 0 | Fraction of the dead particle's velocity passed to the burst (0–1) |
|
|
1209
|
+
|
|
1210
|
+
**Simulation space (behavior change):** `simulationSpace` was persisted since it
|
|
1211
|
+
shipped but was previously **ignored** — every emitter simulated in `local` space
|
|
1212
|
+
regardless of the value. It now works: in `world` mode already-spawned particles
|
|
1213
|
+
keep their world position when the emitter moves (trails, exhaust, muzzle smoke),
|
|
1214
|
+
implemented by neutralizing the emitter's ancestor transform each frame
|
|
1215
|
+
(`renderRoot.matrix = matrixWorld⁻¹`). Any externally-authored scene that set
|
|
1216
|
+
`simulationSpace: world` will change from the old (buggy) local-follow to true
|
|
1217
|
+
world-space; there is no migration — the field finally does what its label says.
|
|
1218
|
+
|
|
1219
|
+
**Usage Notes:**
|
|
1220
|
+
|
|
1221
|
+
- Trails are best with `simulationSpace: world`; in `local` mode on a moving
|
|
1222
|
+
emitter the whole ribbon rides with the node.
|
|
1223
|
+
- Trails allocate `maxParticles × trailSegments` samples — keep `maxParticles`
|
|
1224
|
+
moderate when trails are enabled (buffers exist only while `trailEnabled`).
|
|
1225
|
+
- The sub-emitter target is a normal `Particles3D`, typically authored with
|
|
1226
|
+
`emissionRate: 0` so it only fires from bursts. Bursts are deferred to after the
|
|
1227
|
+
simulation loop, so self-reference and any tick order are safe (≤1 frame latency).
|
|
1228
|
+
- Trail material is additive and untextured in v1.
|
|
1229
|
+
- All new fields are flat scalars, so they are keyframe-animatable from the timeline.
|
|
1230
|
+
|
|
1231
|
+
## Post-processing
|
|
1232
|
+
|
|
1233
|
+
### PostProcess
|
|
1234
|
+
|
|
1235
|
+
The scene's post-processing stack: bloom, vignette, chromatic aberration, ambient occlusion and a
|
|
1236
|
+
LUT colour grade, as one node. The runner picks the **first active** `PostProcess` node in the tree
|
|
1237
|
+
and builds a composer from it; with no such node the plain two-pass path runs and costs nothing.
|
|
1238
|
+
|
|
1239
|
+
**Type String:** `PostProcess`
|
|
1240
|
+
|
|
1241
|
+
**Properties:**
|
|
1242
|
+
|
|
1243
|
+
| Property | Type | Default | Description |
|
|
1244
|
+
| ---------------------------- | ------- | --------- | ---------------------------------------------------------------------------- |
|
|
1245
|
+
| `affect2D` | boolean | true | Also run the effects over the 2D pass |
|
|
1246
|
+
| `bloomEnabled` | boolean | true | Bloom on |
|
|
1247
|
+
| `bloomIntensity` | number | 1 | Bloom strength |
|
|
1248
|
+
| `bloomThreshold` | number | 0.9 | Luminance above which pixels bloom |
|
|
1249
|
+
| `bloomSmoothing` | number | 0.025 | Threshold knee |
|
|
1250
|
+
| `bloomRadius` | number | 0.85 | Blur radius |
|
|
1251
|
+
| `vignetteEnabled` | boolean | false | Vignette on |
|
|
1252
|
+
| `vignetteOffset` | number | 0.35 | Where the darkening starts |
|
|
1253
|
+
| `vignetteDarkness` | number | 0.5 | How dark the edges go |
|
|
1254
|
+
| `chromaticAberrationEnabled` | boolean | false | Chromatic aberration on |
|
|
1255
|
+
| `chromaticAberrationOffset` | number | 0.002 | Channel separation |
|
|
1256
|
+
| `aoMode` | enum | `inherit` | Ambient occlusion: `inherit` (project setting) \| `off` \| `ssao` \| `baked` |
|
|
1257
|
+
| `ssaoIntensity` | number | 2.5 | SSAO strength |
|
|
1258
|
+
| `ssaoRadius` | number | 0.25 | SSAO sample radius, world units |
|
|
1259
|
+
| `lutEnabled` | boolean | false | Colour grading on |
|
|
1260
|
+
| `lutSrc` | string | "" | `res://…` path to the LUT image |
|
|
1261
|
+
| `lutIntensity` | number | 1 | Grade blend amount |
|
|
1262
|
+
|
|
1263
|
+
**Usage Notes:**
|
|
1264
|
+
|
|
1265
|
+
- It has no transform: the properties sit flat in `properties`, and position/rotation/scale do not
|
|
1266
|
+
apply.
|
|
1267
|
+
- The whole stack is one lazily-created composer. Every effect off = no composer at all.
|
|
1268
|
+
- In a single-file playable export, `postprocessing` is pulled in through a generated
|
|
1269
|
+
`virtual:runtime-*` module — using this node is fine for an export, it just adds weight.
|
|
1270
|
+
|
|
1271
|
+
## Audio
|
|
1272
|
+
|
|
1273
|
+
### AudioPlayer
|
|
1274
|
+
|
|
1275
|
+
A node that plays an audio clip through the runtime mixer. Attach it anywhere in
|
|
1276
|
+
the scene; drive it via `autoplay`, from a script (`node.play()`), or from an
|
|
1277
|
+
`AnimationPlayer` audio track.
|
|
1278
|
+
|
|
1279
|
+
**Type String:** `AudioPlayer`
|
|
1280
|
+
|
|
1281
|
+
**Key Properties:**
|
|
1282
|
+
|
|
1283
|
+
| Property | Type | Default | Description |
|
|
1284
|
+
| ----------------- | ------- | ------- | ------------------------------------------------------ |
|
|
1285
|
+
| `audioTrack` | string | — | Asset URL (`res://…`, `data:audio/…`, or absolute URL) |
|
|
1286
|
+
| `autoplay` | boolean | false | Play automatically on the first tick |
|
|
1287
|
+
| `loop` | boolean | false | Loop the clip |
|
|
1288
|
+
| `volume` | number | 1 | Per-clip volume (0–1), before the bus gain |
|
|
1289
|
+
| `bus` | enum | `sfx` | Mixer bus: `master`, `music`, or `sfx` |
|
|
1290
|
+
| `pitchVariation` | number | 0 | Random ± playback-rate spread per play (0–1) |
|
|
1291
|
+
| `volumeVariation` | number | 0 | Random ± volume spread per play (0–1) |
|
|
1292
|
+
|
|
1293
|
+
`bus`, `pitchVariation` and `volumeVariation` are also available on the
|
|
1294
|
+
`core:PlaySound` behavior (`PlaySoundBehavior`), with identical semantics.
|
|
1295
|
+
|
|
1296
|
+
### Buses, snapshots & `scene.audio`
|
|
1297
|
+
|
|
1298
|
+
The runtime mixer routes every playback through three fixed buses:
|
|
1299
|
+
|
|
1300
|
+
```
|
|
1301
|
+
sound → sfx ┐
|
|
1302
|
+
music ┼→ master → output
|
|
1303
|
+
```
|
|
1304
|
+
|
|
1305
|
+
Each bus has a volume and a permanently-wired (transparent) lowpass filter, so
|
|
1306
|
+
mixing and snapshot transitions are click-free `AudioParam` ramps. Scripts reach
|
|
1307
|
+
the mixer through `this.scene.audio`:
|
|
1308
|
+
|
|
1309
|
+
```ts
|
|
1310
|
+
// One-shot playback (loads + caches via the AssetLoader)
|
|
1311
|
+
this.scene.audio.play('res://sfx/hit.ogg', { bus: 'sfx', pitchVariation: 0.1 });
|
|
1312
|
+
|
|
1313
|
+
// Mixer volume (e.g. from a settings menu)
|
|
1314
|
+
this.scene.audio.setBusVolume('music', 0.5);
|
|
1315
|
+
|
|
1316
|
+
// Snapshots — named per-bus lowpass + volume-scale states
|
|
1317
|
+
this.scene.audio.registerSnapshot({ name: 'underwater', lowpassHz: { master: 500 } });
|
|
1318
|
+
this.scene.audio.applySnapshot('underwater');
|
|
1319
|
+
this.scene.audio.resetSnapshot(); // back to 'default'
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
**Built-in snapshots:** `'default'` (fully open) and `'muffled'`
|
|
1323
|
+
(`master` lowpass 700 Hz, volume ×0.85). A snapshot's volume scale composes _on
|
|
1324
|
+
top of_ the user's bus volume, so entering/leaving a snapshot never forgets the
|
|
1325
|
+
authored mix.
|
|
1326
|
+
|
|
1327
|
+
**Slow-motion auto-muffle:** while `scene.time` is in slow motion the mixer
|
|
1328
|
+
automatically blends to `'muffled'` and back on return to normal speed. This is
|
|
1329
|
+
driven by the slow-mo base scale, **not** the frozen scale — a `hitstop(…)`
|
|
1330
|
+
freeze does **not** muffle audio (otherwise every micro-freeze would pump the
|
|
1331
|
+
filter).
|