incanto 0.4.2 → 0.5.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/dist/2d.d.ts +74 -6
- package/dist/2d.js +4 -4
- package/dist/3d.d.ts +174 -16
- package/dist/3d.js +4 -4
- package/dist/{audio-player-DqUR3XFs.d.ts → audio-player-DkBqRTs4.d.ts} +1 -1
- package/dist/{behavior-BAQq7HGM.d.ts → behavior-CWhW3oa6.d.ts} +54 -0
- package/dist/{create-game-CZHROKcT.js → create-game-BFESHv1X.js} +37 -25
- package/dist/{create-game-CMS7DiPi.js → create-game-D8UvwXme.js} +231 -8
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-DP2WPYom.js → duplicate-BEvGBtb_.js} +1 -1
- package/dist/{gameplay-Ccruc3Wd.js → gameplay-CDFgSG6z.js} +293 -24
- package/dist/gameplay.d.ts +113 -2
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +139 -5
- package/dist/index.js +59 -6
- package/dist/{loader-CGs_G-r0.js → loader-B4OEXDZ8.js} +60 -0
- package/dist/{loader-Mo0KghCv.d.ts → loader-D2u0fVW2.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +1 -1
- package/dist/{particle-sim-DYuSUxvK.js → particle-sim-CFkILGwh.js} +84 -3
- package/dist/{particle-sim-CbN4YUuH.d.ts → particle-sim-CwJ5rI_P.d.ts} +3 -0
- package/dist/{physics-2d-KuMWPTf6.js → physics-2d-KXn1N1jF.js} +95 -10
- package/dist/{physics-3d-CXOBOUKG.js → physics-3d-C58oQzTX.js} +121 -43
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-B0gq63VW.js → register-C35HDZpm.js} +2 -2
- package/dist/{register-DPEV9_9t.js → register-D71C4rDC.js} +76 -5
- package/dist/{register-BuUV1_KB.js → register-Dl_ixIJe.js} +275 -2
- package/dist/{register-02aSywx2.js → register-DvPHJdVj.js} +390 -59
- package/dist/test.d.ts +2 -2
- package/dist/test.js +10 -10
- package/editor/assets/{agent8-DUVZGcuO.js → agent8-DruKhR22.js} +1 -1
- package/editor/assets/index-D2qufqUo.js +7417 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +847 -133
- package/skills/incanto-3d-models.md +24 -1
- package/skills/incanto-building-3d-games.md +35 -3
- package/skills/incanto-gameplay-behaviors.md +60 -0
- package/skills/incanto-hud.md +58 -0
- package/skills/incanto-node-reference.md +113 -0
- package/skills/incanto-physics-and-input.md +47 -0
- package/editor/assets/index-DsM2Yp9F.js +0 -7365
|
@@ -53,7 +53,30 @@ scales so the bounding box stands that many units tall — composes with the nod
|
|
|
53
53
|
reactive at runtime), `animation` (see below), `tint` (hex; `""`=off — multiplies into
|
|
54
54
|
every material to RESKIN one shared GLB into many variants, e.g. base human → green
|
|
55
55
|
zombie; clones materials per instance so other instances are untouched),
|
|
56
|
-
`
|
|
56
|
+
`metalness`/`roughness` (0..1 overrides applied to EVERY material; `-1`=keep authored —
|
|
57
|
+
low-poly kits usually ship fully rough, so `metalness: 0.6, roughness: 0.25` turns flat
|
|
58
|
+
paint into a glinting clear-coat under the scene's sky IBL; per-instance clones like
|
|
59
|
+
tint), `castShadow`/`receiveShadow`, plus the usual Node3D transform.
|
|
60
|
+
|
|
61
|
+
## Animating named sub-nodes (wheels, turrets, doors) — `poseNode`
|
|
62
|
+
|
|
63
|
+
Many GLBs ship articulated parts as NAMED nodes (`bunx incanto-model <file>` lists them —
|
|
64
|
+
e.g. the Kenney vehicles have `wheel-front-left` … `wheel-back-right`). From a behavior,
|
|
65
|
+
rotate one per frame with `poseNode(name, [rxDeg, ryDeg, rzDeg])` (local Euler XYZ):
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// spin the wheels by travel + steer the fronts (Kenney car GLB)
|
|
69
|
+
const model = car.getNodesByName('CarModel')[0] as ModelInstance3D;
|
|
70
|
+
this.spin += this.speed * dt * 60; // degrees of roll
|
|
71
|
+
for (const w of ['wheel-back-left', 'wheel-back-right'])
|
|
72
|
+
model.poseNode(w, [this.spin, 0, 0]);
|
|
73
|
+
for (const w of ['wheel-front-left', 'wheel-front-right'])
|
|
74
|
+
model.poseNode(w, [this.spin, steerDeg, 0]);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Poses are queued if the model hasn't finished loading, re-applied every sync, and
|
|
78
|
+
unknown names are ignored (no throw). This is intent-level articulation — for full
|
|
79
|
+
skeletal animation use `animation` clips instead.
|
|
57
80
|
|
|
58
81
|
**`targetHeight` + skinned rigs (the implausible-scale warning).** Mixamo-style humanoid
|
|
59
82
|
rigs hide their true size behind a ~0.01 armature scale + bone-driven vertex scaling, so
|
|
@@ -62,9 +62,34 @@ All 3D nodes extend `Node3D` and therefore have the transform props:
|
|
|
62
62
|
|---|---|---|
|
|
63
63
|
| `mesh` | `"box"` | `box \| sphere \| capsule \| plane \| cylinder \| gem` — `gem` is a faceted crystal (icosahedron); with `material.flatShading: true` + low `roughness` it reads as a sparkling jewel (spin/tumble it for the sparkle) |
|
|
64
64
|
| `size` | `[1,1,1]` | box: extents · sphere/gem: radius = x · capsule: radius = x, height = y · plane: x·z ground (laid FLAT on XZ) · cylinder: radius = x, height = y |
|
|
65
|
-
| `material` | `{}` | `{color: '#hex', metalness: 0..1, roughness: 0..1, opacity: 0..1, wireframe, flatShading, depthTest, depthWrite, emissive, emissiveIntensity, map, normalMap, repeat: [u,v]}` — the object IS the material state: omitted keys reset to defaults (`#ffffff`, 0, 1, 1, false, false, depthTest/Write true). `opacity < 1` turns on transparency; `flatShading` gives faceted per-face glints (the gem sparkle). `depthTest: false` (+ a high `renderOrder`) makes a flat `plane`/`cylinder` an ALWAYS-ON-TOP ground decal — AoE telegraphs, selection/spawn rings — that never z-fights with or is hidden by bumpy terrain (`depthWrite: false` keeps it from occluding later effects). `map`/`normalMap` are texture URLs (lazy-loaded, headless-safe; map samples sRGB, normalMap linear), tiled `repeat` times across each face — box/plane UVs span 0..1 per face, so for worldspace density use `repeat: [length/tile, height/tile]` (e.g. a 6×2.5 m brick wall at one tile per 2 m → `[3, 1.25]`). `color` tints the map (near-white keeps the texture's own color). NB: under ACES a very bright `emissiveIntensity` blows out to white — keep it modest (~1–2) + a saturated color for a vivid GLOWING look. Unknown keys, malformed `repeat`, or `repeat` without a map hard-fail at load |
|
|
65
|
+
| `material` | `{}` | `{color: '#hex', metalness: 0..1, roughness: 0..1, opacity: 0..1, clearcoat: 0..1, clearcoatRoughness: 0..1, envMapIntensity: >=0, wireframe, flatShading, depthTest, depthWrite, emissive, emissiveIntensity, map, normalMap, repeat: [u,v]}` — the object IS the material state: omitted keys reset to defaults (`#ffffff`, 0, 1, 1, false, false, depthTest/Write true). `opacity < 1` turns on transparency; `flatShading` gives faceted per-face glints (the gem sparkle). `depthTest: false` (+ a high `renderOrder`) makes a flat `plane`/`cylinder` an ALWAYS-ON-TOP ground decal — AoE telegraphs, selection/spawn rings — that never z-fights with or is hidden by bumpy terrain (`depthWrite: false` keeps it from occluding later effects). `map`/`normalMap` are texture URLs (lazy-loaded, headless-safe; map samples sRGB, normalMap linear), tiled `repeat` times across each face — box/plane UVs span 0..1 per face, so for worldspace density use `repeat: [length/tile, height/tile]` (e.g. a 6×2.5 m brick wall at one tile per 2 m → `[3, 1.25]`). `color` tints the map (near-white keeps the texture's own color). CAR PAINT: `clearcoat: 1` adds a glossy lacquer layer with its own reflection over a metallic base (`clearcoatRoughness` ~0.05-0.1 = mirror lacquer) — THE premium vehicle-paint look; `envMapIntensity` scales how strongly the surface mirrors the scene's sky/HDRI (glass windows want ~2, matte surfaces <1; needs a `sky`/`hdri` environment to reflect — at night with no sky there is nothing to mirror). NB: under ACES a very bright `emissiveIntensity` blows out to white — keep it modest (~1–2) + a saturated color for a vivid GLOWING look. Unknown keys, malformed `repeat`, or `repeat` without a map hard-fail at load |
|
|
66
66
|
| `castShadow` / `receiveShadow` | `false` | |
|
|
67
67
|
|
|
68
|
+
### `LoftMesh3D` (smooth swept hulls — vehicle bodies, canopies)
|
|
69
|
+
|
|
70
|
+
A SMOOTH curved surface swept through cross-sections — what box/capsule assemblies can
|
|
71
|
+
never give you. Superellipse sections are Catmull-Rom-interpolated along Z with per-vertex
|
|
72
|
+
normals, so highlights and sky reflections SWEEP across it like sheet metal. Use it for car
|
|
73
|
+
bodies, glass canopies, boat hulls, fuselages, submarines.
|
|
74
|
+
|
|
75
|
+
| Prop | Default | Notes |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `sections` | `[]` | ≥2 stations, ascending `z`: `{z, width, height, y, corner?}` — `width`/`height` are the full cross-section extents, `y` is the section CENTRE height, `corner` 0 = boxy slab … 1 = full ellipse (default 0.7; the lower half always stays flatter — vehicle floors are flat). Malformed sections hard-fail at load |
|
|
78
|
+
| `slices` | `24` | vertices around each ring (8..64) |
|
|
79
|
+
| `smooth` | `4` | interpolated rings per section gap (1..16) |
|
|
80
|
+
| `material` | `{}` | same object as MeshInstance3D **minus textures** (`map`/`normalMap`/`repeat` hard-fail — a loft has no UVs) |
|
|
81
|
+
| `castShadow` / `receiveShadow` | `false` | |
|
|
82
|
+
|
|
83
|
+
Supercar recipe (hand-tuned against McLaren F1 proportions — see `examples/racing-3d`):
|
|
84
|
+
one hull loft (low rounded nose ~`corner 0.9` → wide fender-crown stations at the axles →
|
|
85
|
+
coke-bottle waist pinch → tall rear haunches → squared kamm tail `corner ~0.25`), plus a
|
|
86
|
+
second small loft as the GLASS bubble canopy (`metalness 1, roughness 0.03,
|
|
87
|
+
envMapIntensity ~1.7`), plus a dark interior loft inside it, with rectilinear
|
|
88
|
+
`MeshInstance3D` details (splitter, wing, light bars, mirrors) on top. Paint that stays
|
|
89
|
+
saturated in sunlight: `metalness ~0.45, roughness ~0.35, clearcoat 1,
|
|
90
|
+
clearcoatRoughness 0.06, envMapIntensity ~0.9` — high metalness washes the base colour
|
|
91
|
+
out into the sky reflection.
|
|
92
|
+
|
|
68
93
|
### `Sprite3D` / `AnimatedSprite3D` (2D billboard sprites — the 2.5D look)
|
|
69
94
|
A 2D image drawn as a camera-facing quad inside the 3D world (Octopath / Don't
|
|
70
95
|
Starve / classic MapleStory in a 3D scene). Unlit — the art shows as-is. The
|
|
@@ -75,7 +100,7 @@ billboard spin. `Sprite3D`:
|
|
|
75
100
|
|---|---|---|
|
|
76
101
|
| `texture` | `""` | image URL (sampled sRGB, tinted by `tint`); empty = hidden |
|
|
77
102
|
| `size` | `[1,1]` | quad `[width, height]` in world METERS (not pixels) |
|
|
78
|
-
| `billboard` | `"y"` | `"y"` upright —
|
|
103
|
+
| `billboard` | `"y"` | `"y"` upright — screen-aligned yaw: every sprite shares the camera heading, so off-center sprites never ROLL under a pitched camera (characters, props) · `"full"` always faces the camera (items, FX) · `"none"` fixed to the node's own rotation |
|
|
79
104
|
| `anchor` | `[0.5,0]` | which point of the sprite sits at the node origin (0..1). `[0.5,0]` = bottom-center → the FEET stand on the ground; `[0.5,0.5]` = center |
|
|
80
105
|
| `tint` | `"#ffffff"` | multiplies the texture (near-white keeps original colors) |
|
|
81
106
|
| `opacity` | `1` | `< 1` turns on alpha blending |
|
|
@@ -117,7 +142,7 @@ number is a canvas-rendered texture on a child `Sprite3D`.)
|
|
|
117
142
|
|
|
118
143
|
| Prop | Default | Notes |
|
|
119
144
|
|---|---|---|
|
|
120
|
-
| `mode` | `"screen"` | `"screen"` copies the camera's orientation wholesale (screen-aligned) — a child rectangle projects as an **upright rectangle anywhere on screen** · `"y"` stays vertical
|
|
145
|
+
| `mode` | `"screen"` | `"screen"` copies the camera's orientation wholesale (screen-aligned) — a child rectangle projects as an **upright rectangle anywhere on screen** · `"y"` stays vertical with a screen-aligned yaw — no off-center roll (standing content) · `"none"` billboarding off — the authored rotation applies again |
|
|
121
146
|
|
|
122
147
|
- Use `"screen"` for flat UI shapes (bars, plates). A yaw-only look-at — the
|
|
123
148
|
tempting hand-rolled version — visibly TILTS off-center shapes when the camera
|
|
@@ -251,6 +276,13 @@ demo into a commercial-looking outdoor scene:
|
|
|
251
276
|
one extra fullscreen pass per frame — keep `Water3D` at `quality:"simple"`
|
|
252
277
|
(zero re-renders) when pairing with clouds to hold 60fps. Absent = zero cost.
|
|
253
278
|
Ideal for flight/sky scenes; see `examples/dogfight-3d`.
|
|
279
|
+
- `bloom`: a glow post pass — bright pixels bleed light, so emissive
|
|
280
|
+
materials (tail-lights, neon, lava, muzzle flashes) and sun glints actually
|
|
281
|
+
GLOW. `{ threshold?, strength? }`: `threshold` 0..1 post-tonemap brightness
|
|
282
|
+
above which pixels bloom (default 0.75 — keeps it to lights/glints; lower =
|
|
283
|
+
dreamier), `strength` how strongly the blurred glow is added back (default
|
|
284
|
+
0.85; ~1.2 = neon night). One extra half-res blur pass per frame — cheap.
|
|
285
|
+
Caustics/clouds composites take precedence (pipelines don't stack yet).
|
|
254
286
|
- `shadows`: `true` | `false` | `{ "mapSize": 1024|2048, "radius": n }`.
|
|
255
287
|
Truthy makes the main DirectionalLight cast (PCF soft, terrain + trees +
|
|
256
288
|
meshes cast/receive, mesh grass RECEIVES only). One static ortho box (±75 m
|
|
@@ -705,3 +705,63 @@ signal into a built-in's method through a `connection`).
|
|
|
705
705
|
Generated, always-current prop/signal tables for every behavior live in
|
|
706
706
|
`incanto-node-reference.md` (the "Gameplay behaviors" section).
|
|
707
707
|
```
|
|
708
|
+
|
|
709
|
+
## Game feel ("juice")
|
|
710
|
+
|
|
711
|
+
```ts
|
|
712
|
+
import { CameraShake, Cooldown, hitStop, screenFlash } from 'incanto/gameplay';
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
- **Cooldown** — fire-rate gate, replaces hand-rolled clock math:
|
|
716
|
+
`private gun = new Cooldown(0.2);` then per update `this.gun.tick(dt);
|
|
717
|
+
if (firing && this.gun.tryUse()) shoot();`. `progress` (0..1) drives a
|
|
718
|
+
UiBar reload indicator; `reset()` for reload pickups.
|
|
719
|
+
- **Camera shake** — `FollowCamera` has it built in:
|
|
720
|
+
`(cam.behavior as FollowCamera).shake(8)` (2D pixels; use ~0.2 in 3D
|
|
721
|
+
meters). For cameras WITHOUT a follow script attach the standalone
|
|
722
|
+
`CameraShake` behavior — it composes with any other position writer and
|
|
723
|
+
returns the camera exactly to base.
|
|
724
|
+
- **screenFlash(color?, opacity?, seconds?)** — full-screen damage/pickup
|
|
725
|
+
flash (DOM, headless no-op).
|
|
726
|
+
- **hitStop(engine, seconds?)** — freezes `engine.timeScale` for REAL
|
|
727
|
+
seconds then restores; stacked calls extend. Sells melee impacts.
|
|
728
|
+
- **engine.timeScale** — 1 realtime, 0.5 slow motion, 0 pause: scales
|
|
729
|
+
variable AND fixed updates together (physics, timers, behaviors).
|
|
730
|
+
|
|
731
|
+
## Game flow (win / lose / restart)
|
|
732
|
+
|
|
733
|
+
Attach `GameFlow` to the scene root (`"script": { "name": "GameFlow" }`),
|
|
734
|
+
declare a `restart` input action, and put a `%Banner` UiBanner in your HUD:
|
|
735
|
+
|
|
736
|
+
```ts
|
|
737
|
+
const flow = this.node.getRoot().behavior as GameFlow;
|
|
738
|
+
flow.gameOver('YOU DIED'); // freezes engine.timeScale, sticky banner, waits for restart
|
|
739
|
+
flow.win('AREA CLEAR');
|
|
740
|
+
flow.pause(); flow.resume();
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Terminal states ignore further transitions; the restart action (or
|
|
744
|
+
`flow.restart()`) reloads the scene from its source JSON — fresh nodes,
|
|
745
|
+
physics, input. Listen to the `flowChanged(state)` signal for custom UI.
|
|
746
|
+
`restartScene(engine)` is exported standalone.
|
|
747
|
+
|
|
748
|
+
## Boot loading overlay
|
|
749
|
+
|
|
750
|
+
```ts
|
|
751
|
+
import { preloadSceneAssets } from 'incanto';
|
|
752
|
+
await preloadSceneAssets(sceneJson.assets); // black screen + bar + %, auto-removes
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
Replaces the ~125-line hand-rolled loading screen; `createLoadingOverlay()`
|
|
756
|
+
gives you the raw overlay when you preload something else.
|
|
757
|
+
|
|
758
|
+
## Persistence (high scores, unlocks, settings)
|
|
759
|
+
|
|
760
|
+
```ts
|
|
761
|
+
import { createSaveStore } from 'incanto';
|
|
762
|
+
const save = createSaveStore('my-game'); // namespaced localStorage
|
|
763
|
+
save.set('highScore', Math.max(score, save.get('highScore', 0)));
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
JSON round-tripped; headless/private-mode falls back to in-memory (never
|
|
767
|
+
throws). `clear()` wipes only your namespace.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: incanto-hud
|
|
3
|
+
description: Screen-space HUD widgets (HudLayer + UiText / UiBar / UiBanner) — health bars, score text, wave banners declared in scene JSON, no hand-rolled DOM or CSS. Use for any in-game UI overlay in 2D or 3D games.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# HUD widgets
|
|
7
|
+
|
|
8
|
+
Declare your HUD in the scene JSON — no `index.html` markup, no CSS, no
|
|
9
|
+
`document.querySelector` in behaviors:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"name": "HUD", "type": "HudLayer",
|
|
14
|
+
"children": [
|
|
15
|
+
{ "name": "Health", "type": "UiBar",
|
|
16
|
+
"props": { "anchor": "topLeft", "value": 100, "max": 100, "label": "HP" } },
|
|
17
|
+
{ "name": "Score", "type": "UiText",
|
|
18
|
+
"props": { "anchor": "topRight", "text": "Score: 0", "size": 20 } },
|
|
19
|
+
{ "name": "Banner", "type": "UiBanner" }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`HudLayer` is a fixed, pointer-transparent DOM overlay above the canvas — it
|
|
25
|
+
works identically over the 2D and 3D renderers and is a silent no-op in
|
|
26
|
+
headless tests (core nodes, no three.js). Widgets pick one of 9 anchors:
|
|
27
|
+
`topLeft top topRight left center right bottomLeft bottom bottomRight`.
|
|
28
|
+
Widgets stacked on the same anchor form a column.
|
|
29
|
+
|
|
30
|
+
## Driving widgets from behaviors
|
|
31
|
+
|
|
32
|
+
Plain node access — same as everything else:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import type { UiBanner, UiBar, UiText } from 'incanto';
|
|
36
|
+
|
|
37
|
+
const hp = this.node.getNode('%Health') as UiBar;
|
|
38
|
+
hp.value = health.current; // fill animates; turns red below 30%
|
|
39
|
+
|
|
40
|
+
(this.node.getNode('%Score') as UiText).text = `Score: ${score}`;
|
|
41
|
+
|
|
42
|
+
const banner = this.node.getNode('%Banner') as UiBanner;
|
|
43
|
+
banner.show('WAVE 2', { color: '#f87171', seconds: 2 }); // queued, fades
|
|
44
|
+
banner.show('YOU DIED', { color: '#ef4444', seconds: 0 }); // sticky until next show()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Widget reference
|
|
48
|
+
|
|
49
|
+
- **UiText** — `text`, `size` (px), `color`, `shadow` (readability outline).
|
|
50
|
+
- **UiBar** — `value`/`max` (ratio clamped 0..1), `width`/`height`, `color`,
|
|
51
|
+
`lowColor` + `lowThreshold` (default 0.3), `background`, `label` caption.
|
|
52
|
+
- **UiBanner** — `show(text, {color, seconds})` queues center-screen
|
|
53
|
+
announcements with fade in/out; `seconds: 0` = sticky; `clear()` empties
|
|
54
|
+
the queue; signal `bannerShown(text)`. Props: `size` (font px), `seconds`
|
|
55
|
+
(default duration).
|
|
56
|
+
|
|
57
|
+
Pair with the `Health` / `ScoreKeeper` gameplay behaviors: listen to their
|
|
58
|
+
signals and write the widget props — that is the whole HUD wiring.
|
|
@@ -304,6 +304,47 @@ Signals: `movementStateChanged(state)`
|
|
|
304
304
|
| `drape` | `false` | boolean |
|
|
305
305
|
| `terrain` | `""` | string |
|
|
306
306
|
|
|
307
|
+
## `HudLayer` — `incanto`
|
|
308
|
+
|
|
309
|
+
| Prop | Default | Kind |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `zIndex` | `100` | number |
|
|
312
|
+
| `visible` | `true` | boolean |
|
|
313
|
+
|
|
314
|
+
## `Joint2D` — `incanto/2d`
|
|
315
|
+
|
|
316
|
+
| Prop | Default | Kind |
|
|
317
|
+
|---|---|---|
|
|
318
|
+
| `position` | `[0,0]` | array |
|
|
319
|
+
| `rotation` | `0` | number |
|
|
320
|
+
| `scale` | `[1,1]` | array |
|
|
321
|
+
| `renderOrder` | `0` | number |
|
|
322
|
+
| `visible` | `true` | boolean |
|
|
323
|
+
| `type` | `"revolute"` | one of: `fixed` `revolute` `rope` `spring` |
|
|
324
|
+
| `target` | `""` | string |
|
|
325
|
+
| `anchor` | `[0,0]` | array |
|
|
326
|
+
| `targetAnchor` | `[0,0]` | array |
|
|
327
|
+
| `length` | `0` | number |
|
|
328
|
+
| `stiffness` | `50` | number |
|
|
329
|
+
| `damping` | `5` | number |
|
|
330
|
+
|
|
331
|
+
## `Joint3D` — `incanto/3d`
|
|
332
|
+
|
|
333
|
+
| Prop | Default | Kind |
|
|
334
|
+
|---|---|---|
|
|
335
|
+
| `position` | `[0,0,0]` | array |
|
|
336
|
+
| `rotation` | `[0,0,0]` | array |
|
|
337
|
+
| `scale` | `[1,1,1]` | array |
|
|
338
|
+
| `visible` | `true` | boolean |
|
|
339
|
+
| `renderOrder` | `0` | number |
|
|
340
|
+
| `type` | `"spherical"` | one of: `fixed` `spherical` `rope` `spring` |
|
|
341
|
+
| `target` | `""` | string |
|
|
342
|
+
| `anchor` | `[0,0,0]` | array |
|
|
343
|
+
| `targetAnchor` | `[0,0,0]` | array |
|
|
344
|
+
| `length` | `0` | number |
|
|
345
|
+
| `stiffness` | `50` | number |
|
|
346
|
+
| `damping` | `5` | number |
|
|
347
|
+
|
|
307
348
|
## `Label` — `incanto/2d`
|
|
308
349
|
|
|
309
350
|
| Prop | Default | Kind |
|
|
@@ -319,6 +360,22 @@ Signals: `movementStateChanged(state)`
|
|
|
319
360
|
| `font` | `"monospace"` | string |
|
|
320
361
|
| `align` | `"left"` | string |
|
|
321
362
|
|
|
363
|
+
## `LoftMesh3D` — `incanto/3d`
|
|
364
|
+
|
|
365
|
+
| Prop | Default | Kind |
|
|
366
|
+
|---|---|---|
|
|
367
|
+
| `position` | `[0,0,0]` | array |
|
|
368
|
+
| `rotation` | `[0,0,0]` | array |
|
|
369
|
+
| `scale` | `[1,1,1]` | array |
|
|
370
|
+
| `visible` | `true` | boolean |
|
|
371
|
+
| `renderOrder` | `0` | number |
|
|
372
|
+
| `sections` | `[]` | array |
|
|
373
|
+
| `slices` | `24` | number |
|
|
374
|
+
| `smooth` | `4` | number |
|
|
375
|
+
| `material` | `{}` | object |
|
|
376
|
+
| `castShadow` | `false` | boolean |
|
|
377
|
+
| `receiveShadow` | `false` | boolean |
|
|
378
|
+
|
|
322
379
|
## `MeshInstance3D` — `incanto/3d`
|
|
323
380
|
|
|
324
381
|
| Prop | Default | Kind |
|
|
@@ -350,6 +407,8 @@ Signals: `movementStateChanged(state)`
|
|
|
350
407
|
| `animationLoop` | `true` | boolean |
|
|
351
408
|
| `receiveShadow` | `false` | boolean |
|
|
352
409
|
| `tint` | `""` | string |
|
|
410
|
+
| `metalness` | `-1` | number |
|
|
411
|
+
| `roughness` | `-1` | number |
|
|
353
412
|
|
|
354
413
|
Signals: `animationFinished(name)`
|
|
355
414
|
|
|
@@ -628,6 +687,44 @@ Signals: `timeout`
|
|
|
628
687
|
|---|---|---|
|
|
629
688
|
| `anchor` | `"top-left"` | one of: `top-left` `top` `top-right` `left` `center` `right` `bottom-left` `bottom` `bottom-right` |
|
|
630
689
|
|
|
690
|
+
## `UiBanner` — `incanto`
|
|
691
|
+
|
|
692
|
+
| Prop | Default | Kind |
|
|
693
|
+
|---|---|---|
|
|
694
|
+
| `anchor` | `"center"` | one of: `topLeft` `top` `topRight` `left` `center` `right` `bottomLeft` `bottom` `bottomRight` |
|
|
695
|
+
| `visible` | `true` | boolean |
|
|
696
|
+
| `size` | `42` | number |
|
|
697
|
+
| `seconds` | `2` | number |
|
|
698
|
+
|
|
699
|
+
Signals: `bannerShown`
|
|
700
|
+
|
|
701
|
+
## `UiBar` — `incanto`
|
|
702
|
+
|
|
703
|
+
| Prop | Default | Kind |
|
|
704
|
+
|---|---|---|
|
|
705
|
+
| `anchor` | `"topLeft"` | one of: `topLeft` `top` `topRight` `left` `center` `right` `bottomLeft` `bottom` `bottomRight` |
|
|
706
|
+
| `visible` | `true` | boolean |
|
|
707
|
+
| `value` | `100` | number |
|
|
708
|
+
| `max` | `100` | number |
|
|
709
|
+
| `width` | `180` | number |
|
|
710
|
+
| `height` | `14` | number |
|
|
711
|
+
| `color` | `"#4ade80"` | string |
|
|
712
|
+
| `lowColor` | `"#ef4444"` | string |
|
|
713
|
+
| `lowThreshold` | `0.3` | number |
|
|
714
|
+
| `background` | `"rgba(0,0,0,0.5)"` | string |
|
|
715
|
+
| `label` | `""` | string |
|
|
716
|
+
|
|
717
|
+
## `UiText` — `incanto`
|
|
718
|
+
|
|
719
|
+
| Prop | Default | Kind |
|
|
720
|
+
|---|---|---|
|
|
721
|
+
| `anchor` | `"topLeft"` | one of: `topLeft` `top` `topRight` `left` `center` `right` `bottomLeft` `bottom` `bottomRight` |
|
|
722
|
+
| `visible` | `true` | boolean |
|
|
723
|
+
| `text` | `""` | string |
|
|
724
|
+
| `size` | `16` | number |
|
|
725
|
+
| `color` | `"#ffffff"` | string |
|
|
726
|
+
| `shadow` | `true` | boolean |
|
|
727
|
+
|
|
631
728
|
## `VoxelGrid3D` — `incanto/3d`
|
|
632
729
|
|
|
633
730
|
| Prop | Default | Kind |
|
|
@@ -678,6 +775,12 @@ Ready-made `Behavior`s auto-registered by `createGame2D`/`createGame3D` (opt out
|
|
|
678
775
|
with `gameplay: false`). Attach via `"script": {"name": "...", "props": {...}}` and
|
|
679
776
|
wire their signals through scene `connections`. Full guide: `incanto-gameplay-behaviors.md`.
|
|
680
777
|
|
|
778
|
+
### `CameraShake`
|
|
779
|
+
|
|
780
|
+
| Prop | Default | Kind |
|
|
781
|
+
|---|---|---|
|
|
782
|
+
| `falloff` | `0.28` | number |
|
|
783
|
+
|
|
681
784
|
### `Chase`
|
|
682
785
|
|
|
683
786
|
| Prop | Default | Kind |
|
|
@@ -718,6 +821,16 @@ Signals: `dealtDamage(amount, target)`
|
|
|
718
821
|
| `smoothing` | `0` | number |
|
|
719
822
|
| `deadzone` | `0` | number |
|
|
720
823
|
|
|
824
|
+
### `GameFlow`
|
|
825
|
+
|
|
826
|
+
| Prop | Default | Kind |
|
|
827
|
+
|---|---|---|
|
|
828
|
+
| `restartAction` | `"restart"` | string |
|
|
829
|
+
| `freezeOnEnd` | `true` | boolean |
|
|
830
|
+
| `bannerPath` | `"%Banner"` | string |
|
|
831
|
+
|
|
832
|
+
Signals: `flowChanged`
|
|
833
|
+
|
|
721
834
|
### `Health`
|
|
722
835
|
|
|
723
836
|
| Prop | Default | Kind |
|
|
@@ -133,6 +133,53 @@ Injected state combines with key state (vectors clamped to unit length).
|
|
|
133
133
|
- Reference: [examples/2d-phaser-sprite-character-gravity](https://github.com/rareboe/Incanto/tree/main/examples/2d-phaser-sprite-character-gravity) — gravity, jump, attack lockout, custom
|
|
134
134
|
`Player` node type. Verified in Chromium end-to-end.
|
|
135
135
|
|
|
136
|
+
## Joints (Joint2D / Joint3D)
|
|
137
|
+
|
|
138
|
+
Link two bodies: put the joint node as a CHILD of body A, point `target` at
|
|
139
|
+
body B (any node path — `%Name` is typical):
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{ "name": "Ball", "type": "RigidBody2D",
|
|
143
|
+
"props": { "collider": { "shape": "circle", "radius": 10 } },
|
|
144
|
+
"children": [
|
|
145
|
+
{ "name": "Rope", "type": "Joint2D",
|
|
146
|
+
"props": { "type": "rope", "target": "%Anchor", "length": 100 } }
|
|
147
|
+
]}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Types — 2D: `fixed` (weld) · `revolute` (pin/hinge) · `rope` (max distance)
|
|
151
|
+
· `spring` (pull toward `length` with `stiffness`/`damping`). 3D swaps
|
|
152
|
+
`revolute` for `spherical` (ball joint). `length: 0` measures the body
|
|
153
|
+
distance at creation. `anchor`/`targetAnchor` are local offsets (px / m).
|
|
154
|
+
Chains work (pendulums, bridges: each link a body + joint to the previous).
|
|
155
|
+
|
|
156
|
+
## Raycasts
|
|
157
|
+
|
|
158
|
+
Both runtimes expose the same query (2D in PIXELS y-down, 3D in meters):
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const hit = physics.castRay(origin, dir, maxLen, excludeBody?, { staticOnly?: true });
|
|
162
|
+
// → { distance, normal, node } | null (sensors never block rays)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Exclude the shooter's own body when casting from inside it.
|
|
166
|
+
|
|
167
|
+
## Gamepad
|
|
168
|
+
|
|
169
|
+
`engine.input.attachGamepad(engine)` polls the first connected pad every
|
|
170
|
+
frame (headless no-op). Buttons are codes `Pad0`..`Pad16` in the SAME space
|
|
171
|
+
as keys — declare them in actions: `"jump": { "keys": ["Space", "Pad0"] }`
|
|
172
|
+
(standard mapping: Pad0=A/×, Pad1=B/○, Pad9=Start). Sticks:
|
|
173
|
+
`input.padAxes(0)` / `padAxes(1)` → deadzoned `{x, y}`. Unplugging releases
|
|
174
|
+
everything (no stuck inputs).
|
|
175
|
+
|
|
176
|
+
## Changing colliders at runtime
|
|
177
|
+
|
|
178
|
+
REPLACE the object — `body.collider = { shape: 'rect', size: [w, 60] }` —
|
|
179
|
+
and physics rebuilds the body that step. In-place mutation of the existing
|
|
180
|
+
collider object (`body.collider.size[0] = w`) is NOT watched (the per-step
|
|
181
|
+
change scan was removed for performance).
|
|
182
|
+
|
|
136
183
|
## Debug drawing
|
|
137
184
|
|
|
138
185
|
`physics.debugDraw = true` (the instance `enablePhysics2D/3D` returns) renders
|