incanto 0.74.0 → 0.75.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/bin/incanto-multiplay.mjs +52 -4
- package/dist/2d.d.ts +83 -7
- package/dist/2d.js +4 -3
- package/dist/3d.d.ts +743 -167
- package/dist/3d.js +6 -6
- package/dist/{agent8-CHTT4unP.js → agent8-B-bh3n0X.js} +1 -1
- package/dist/{audio-player-BOrk4eQx.d.ts → audio-player-BRo2uvG6.d.ts} +23 -2
- package/dist/{behavior-J1cmVah0.d.ts → behavior-B_245qRy.d.ts} +15 -1
- package/dist/{create-game-BAiA-FjP.js → create-game-DVUhHs2B.js} +79 -10
- package/dist/{create-game-ssvGvP7W.js → create-game-DnwhJ5nW.js} +83 -7
- package/dist/debug.d.ts +1 -1
- package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-O8mxpgRX.d.ts} +1 -1
- package/dist/editor.js +1600 -1529
- package/dist/env.d.ts +1 -1
- package/dist/{environment-presets-BkqlWewf.js → environment-presets-BP77Dl0K.js} +2005 -60
- package/dist/{gameplay-Bxe1sMVT.js → gameplay-D-JuGura.js} +9309 -6595
- package/dist/gameplay.d.ts +573 -31
- package/dist/gameplay.js +3 -2
- package/dist/index.d.ts +150 -6
- package/dist/index.js +6 -6
- package/dist/{loader-BAeWMYfF.d.ts → loader-CbkVdXL8.d.ts} +2 -2
- package/dist/net.d.ts +2 -2
- package/dist/net.js +2 -2
- package/dist/{physics-2d-C-SfvjzH.js → physics-2d-x3bCrQf0.js} +250 -31
- package/dist/{physics-3d-rOzeg890.js → physics-3d-Bz2VPQ6D.js} +673 -51
- package/dist/{picking-BMV34Pjl.js → picking-Buita5T9.js} +2 -2
- package/dist/{save-slots-CEUGeLuB.js → pose-ByFB_J3O.js} +217 -11
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{register-CjociOtt.js → register-B768Qedj.js} +596 -2
- package/dist/{register-DRQyZKGU.js → register-DxAbTO2T.js} +191 -430
- package/dist/{replay-B6BbgGM_.js → replay-DrqQ6dZj.js} +133 -18
- package/dist/{replay-wZK5Ok5c.d.ts → replay-W5nCw_cU.d.ts} +21 -2
- package/dist/{rng-Bb-IutXB.d.ts → rng-BsXZg3D6.d.ts} +7 -0
- package/dist/{split-screen-BxZ5rBe7.js → split-screen-B-dIcQsQ.js} +10 -5
- package/dist/{split-screen-D6sx8z3Z.d.ts → split-screen-CiLJf2-C.d.ts} +3 -3
- package/dist/{sprite-animation-Bz3DHD_4.js → sprite-animation-SQa5gIu2.js} +8 -6
- package/dist/{src-CqtvYtSN.js → src-DLKVc6Hj.js} +1 -1
- package/dist/{test-CEl0y2Sw.js → test-HlVUeCSn.js} +487 -82
- package/dist/test.d.ts +90 -12
- package/dist/test.js +2 -2
- package/dist/vite.js +3 -3
- package/editor/assets/{agent8-BlzturGi.js → agent8-BXlYM9rv.js} +1 -1
- package/editor/assets/{debug-BMUi8usj.js → debug-S42XGP4Y.js} +1 -1
- package/editor/assets/index-DgrgUKno.js +11046 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +1630 -299
- package/skills/incanto-3d-character.md +358 -20
- package/skills/incanto-audio.md +24 -2
- package/skills/incanto-building-2d-games.md +21 -2
- package/skills/incanto-building-3d-games.md +38 -3
- package/skills/incanto-editor.md +17 -6
- package/skills/incanto-environment.md +227 -2
- package/skills/incanto-game-feel.md +60 -1
- package/skills/incanto-gameplay-behaviors.md +668 -20
- package/skills/incanto-hud.md +116 -0
- package/skills/incanto-multiplayer.md +141 -2
- package/skills/incanto-node-reference.md +393 -16
- package/skills/incanto-performance.md +32 -0
- package/skills/incanto-physics-and-input.md +241 -13
- package/skills/incanto-playtesting.md +2 -2
- package/skills/incanto-save-slots.md +61 -2
- package/skills/incanto-scene-json-authoring.md +13 -0
- package/skills/incanto-verifying-your-game.md +86 -3
- package/templates-app/beacon-isle-3d/PROJECT/Status.md +12 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +151 -2
- package/templates-app/molehill-2d/PROJECT/Status.md +7 -0
- package/templates-app/molehill-2d/package.json +1 -1
- package/templates-app/molehill-2d/src/game.scene.json +129 -2
- package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
- package/templates-app/platformer-2d/docs/project-2d-rules.md +3 -2
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/behaviors.ts +40 -26
- package/templates-app/platformer-2d/src/game.scene.json +144 -2
- package/templates-app/platformer-2d/verify.ts +18 -1
- package/templates-app/star-survivor/PROJECT/Status.md +7 -0
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/behaviors.ts +9 -8
- package/templates-app/star-survivor/src/game.scene.json +142 -4
- package/templates-app/star-survivor/verify.ts +13 -0
- package/templates-app/tps-3d/PROJECT/Status.md +6 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +71 -10
- package/templates-app/village-quest-3d/PROJECT/Status.md +7 -0
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/behaviors.ts +8 -16
- package/templates-app/village-quest-3d/src/grove.scene.json +121 -5
- package/templates-app/village-quest-3d/src/village.scene.json +126 -2
- package/templates-app/village-quest-3d/verify.ts +52 -2
- package/editor/assets/index-CrUCQoaB.js +0 -11046
|
@@ -64,6 +64,13 @@ did not until now.
|
|
|
64
64
|
| `maxJumps` | `1` | `2` = double jump. Extra jumps work in mid-air. |
|
|
65
65
|
| `airControl` | `0.2` | how much ground control you keep airborne (0 = committed, 1 = full). |
|
|
66
66
|
| `fallGravity` | `2.5` | gravity multiplier while falling. Higher = snappier arc. |
|
|
67
|
+
|
|
68
|
+
And one that is about the CAMERA rather than the jump:
|
|
69
|
+
|
|
70
|
+
| prop | default | what it buys |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `pitchStart` | `0` | where the camera RESTS before anyone looks around, in degrees. Negative looks down; `-14` is a good third-person default. On a PHONE it stops being a nicety: a portrait screen is very tall, so a level camera spends its top half on sky and a player with no mouse cannot fix it before they start dragging. |
|
|
73
|
+
| `mantleHeight` | `0` | the pull-up: on the way down from a jump, a ledge ahead no higher than this above the capsule's bottom is caught and pulled up onto. A jump a hand short of a roof is the most common near-miss in a platformer; try `0.9`. `0` = a jump near a wall feels exactly as it did. |
|
|
67
74
|
| `wallJumpImpulse` | `[0, 0]` | `[away, up]` m/s — airborne and pressing INTO a wall, `jump` kicks off it: away along the wall's normal, up. Try `[6.5, 7]`. A fixed arc (the jump cut never halves it) and air control stands down for 0.18 s so the stick still pointing at the wall cannot steer the kick back into it. Emits `wallJumped(normal)`. |
|
|
68
75
|
| `wallSlideSpeed` | `0` | max fall speed while pressing into a wall (`fallGravity` stands down too). Try `2`. |
|
|
69
76
|
| `platformCarry` | `true` | ride whatever you are standing on. Off = the controller steers in WORLD space, so standing still on a moving floor brakes you off it (measured: 95.9% of a moving deck's travel kept, 0.0% with this off). |
|
|
@@ -71,6 +78,15 @@ did not until now.
|
|
|
71
78
|
Every one defaults to OFF (`0` / `1`) or to the previous hard-coded constant, so
|
|
72
79
|
a scene that asks for nothing behaves exactly as before.
|
|
73
80
|
|
|
81
|
+
**Getting hit is the controller's too.** When the body's `Health` (on the body,
|
|
82
|
+
or on a child such as `Vitals`) declares `knockback` / `knockUp` /
|
|
83
|
+
`staggerSeconds`, the controller drops the stick, the jump and the dash for the
|
|
84
|
+
stagger and writes the kick as the body's velocity — `knockUp` is the hop. A
|
|
85
|
+
club with `DamageOnContact` and a player Health of `knockback 7, knockUp 2.5,
|
|
86
|
+
staggerSeconds 0.2` is a hit that knocks the player half a metre back, off the
|
|
87
|
+
ground, and takes the controls for a fifth of a second — no game code. See
|
|
88
|
+
"Hit reactions" in `incanto-gameplay-behaviors.md`.
|
|
89
|
+
|
|
74
90
|
**The controller owns the wall-jump.** A kick written from a behaviour lasted
|
|
75
91
|
three frames on a platformer built from the tarball: the jump cut halved its
|
|
76
92
|
rise the moment the tap was released (7 → 2.2 m/s) and air control steered
|
|
@@ -92,6 +108,13 @@ measures 0.32 / 1.26 (`jumpCutMultiplier`), a double jump line
|
|
|
92
108
|
(`maxJumps: 2`), and the coyote and buffer windows still measured with a double
|
|
93
109
|
jump declared. It used to give up on both the moment `maxJumps` went above 1.
|
|
94
110
|
|
|
111
|
+
**`stepHeight` steps onto a ledge at the APEX of a jump too.** A fence 1.5 m
|
|
112
|
+
high was climbed by a jump that peaks at 1.3 m: at the apex the fence top sat
|
|
113
|
+
0.2 m above the capsule's bottom, inside `stepHeight` 0.4, and the step-up
|
|
114
|
+
lifted the character over. That is what makes landing on a ledge forgiving; a
|
|
115
|
+
wall that must stop a walker is jump apex + `stepHeight` tall (`examples/steed-3d`'s
|
|
116
|
+
fence is 2 m).
|
|
117
|
+
|
|
95
118
|
**Jump reads the button EDGE.** Holding the jump key no longer re-jumps every
|
|
96
119
|
frame — that auto-hop was never intended and it made coyote time incoherent (a
|
|
97
120
|
held button would re-fire through the whole window).
|
|
@@ -126,6 +149,248 @@ at rest: 0.01 → y 0.839, 0.05 → 0.840, 0.3 → 1.008, 0.6 → 1.308 for a ca
|
|
|
126
149
|
contact height is 0.84. Leave it at the default and use `stepHeight` for ledges (the
|
|
127
150
|
docstring's own advice: a raised `floatHeight` makes the character weightless).
|
|
128
151
|
|
|
152
|
+
## Swimming (water too deep to stand in)
|
|
153
|
+
|
|
154
|
+
A `Water3D` is a surface with no bottom, and until now the controller never
|
|
155
|
+
asked it anything: a player who walked off a beach into a four-metre lagoon
|
|
156
|
+
stood on the bed with the camera underwater and the run clip playing, in every
|
|
157
|
+
3D scene with water. Now the body SWIMS:
|
|
158
|
+
|
|
159
|
+
| prop | default | what it buys |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `swimSpeed` | `1.6` | the pace a plain key gives in water (m/s; `sprint` still multiplies, by the ratio it multiplies a run). **`0` turns swimming OFF** — the body sinks to the bed, which is what a drowning pit wants and what every scene got before. |
|
|
162
|
+
| `swimDepth` | `0.35` | how far the body's origin rides BELOW the surface. The default keeps a 1.7 m character's head out of the water. |
|
|
163
|
+
| `diveAction` | `"dive"` | held: swim DOWN at `swimSpeed`. Not in the input map = no diving, silently. |
|
|
164
|
+
|
|
165
|
+
- **When.** The body swims when floating would put it HIGHER than standing:
|
|
166
|
+
the waterline (`surface − swimDepth`) above where the hover spring would
|
|
167
|
+
hold it on the bed it can see, or no bed within reach. That one comparison
|
|
168
|
+
is the whole wade-in/wade-out — walk down a beach and the water takes you
|
|
169
|
+
where the bed drops away; swim toward a shelf and the spring takes over
|
|
170
|
+
where it rises. No zones, no triggers.
|
|
171
|
+
- **What.** Gravity is off, a spring holds the origin at the waterline (a
|
|
172
|
+
swimmer rides the swell the way a `Buoyancy` raft does — the same
|
|
173
|
+
`heightAt`), horizontal control is full (water gives purchase), and the
|
|
174
|
+
state is `swim` for the `animations` map / `movementStateChanged`.
|
|
175
|
+
- **Diving.** Hold `dive` to go down; let go and you rise on your own. With
|
|
176
|
+
the head under (`controller.submerged`), holding `jump` swims UP. Air,
|
|
177
|
+
drowning and what is down there are your game's business — read
|
|
178
|
+
`controller.submerged` in a behavior and count.
|
|
179
|
+
- **Getting out.** A swimmer's step is measured from the WATER, not from the
|
|
180
|
+
foot: swim into a shelf, a dock or a rock whose top is no more than
|
|
181
|
+
`stepHeight` above the surface and the body CLIMBS out on its own — driven
|
|
182
|
+
up at 2 m/s until its bottom is level with the ledge, then onto it. (The
|
|
183
|
+
capsule hangs `swimDepth + halfHeight` under the surface, so from its bottom
|
|
184
|
+
a shelf a hand under the waterline would be a 0.6 m wall; a swimmer at that
|
|
185
|
+
shelf simply puts their hands on it. The hover spring could not do this —
|
|
186
|
+
it is sized to hold a capsule a hair off the ground and stalled with the
|
|
187
|
+
body's round bottom a third of a metre under the ledge, pressed to its
|
|
188
|
+
face.) Higher than `stepHeight` above the water it is a wall, exactly as on
|
|
189
|
+
land. `jump` at the surface hops — SIZED to clear the surface by
|
|
190
|
+
`stepHeight`, whatever `jumpVelocity` says — and the same climb takes a
|
|
191
|
+
ledge on the way down. So author a dock or a rock a swimmer should reach
|
|
192
|
+
**no more than `stepHeight` above the water**. A beach needs nothing: the
|
|
193
|
+
bed rises, the standing height passes the waterline, and you walk out.
|
|
194
|
+
- **Readbacks.** `controller.swimming`, `controller.submerged`.
|
|
195
|
+
- **Found on the way (fixed, same release):** the ground probe's side rays
|
|
196
|
+
start `radius` out from the capsule's centre, so pressed against a wall one
|
|
197
|
+
of them started INSIDE the wall and hit it at distance zero — a character
|
|
198
|
+
hugging a wall in mid-air read as standing, the ground jump came back while
|
|
199
|
+
falling, and a hop out of the water at a rock's face was cancelled the frame
|
|
200
|
+
it started. A zero-distance hit is ignored now. And `wallSlideSpeed` held a
|
|
201
|
+
third of a metre a second loose (the solver added one tick of gravity back
|
|
202
|
+
after the clamp); at the cap gravity stands down.
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{ "name": "Controller", "type": "CharacterController3D",
|
|
206
|
+
"props": { "view": "free", "swimSpeed": 1.8, "stepHeight": 0.5,
|
|
207
|
+
"animations": { "idle": "$idle", "run": "$run", "swim": "$anims/swim" } } }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Composed in `examples/lagoon-3d` (a pearl dive with an air meter); the
|
|
211
|
+
mixamorig `swim.glb` clip is on the CDN beside the locomotion set.
|
|
212
|
+
|
|
213
|
+
## Ladders
|
|
214
|
+
|
|
215
|
+
Every 3D game with a second floor has one, and until now an `Area3D`
|
|
216
|
+
against a wall was a box the player walked into. A ladder is any `Area3D` in
|
|
217
|
+
the controller's `ladderGroup` — no node type, no code:
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{ "name": "Ladder", "type": "Area3D", "groups": ["ladder"],
|
|
221
|
+
"props": { "position": [-3, 2.2, 0.35], "collider": { "shape": "box", "size": [1, 4.4, 0.7] } },
|
|
222
|
+
"children": [ …rails and rungs as MeshInstance3D… ] }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
| prop | default | what it buys |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `climbSpeed` | `1.5` | up and down the rungs, m/s, from the stick's OWN vertical (W/S — the camera gets no say). **`0` = no ladders.** |
|
|
228
|
+
| `ladderGroup` | `"ladder"` | which `Area3D`s are ladders |
|
|
229
|
+
|
|
230
|
+
- **Grabbing.** Push UP into a ladder you overlap and you are on it: gravity
|
|
231
|
+
off, held to the rungs, state `climb`. Walking PAST one never grabs it —
|
|
232
|
+
only pushing up does. Falling past one while holding DOWN grabs it too,
|
|
233
|
+
which is how you climb down from a ledge: stand at its edge over the
|
|
234
|
+
ladder, hold back, step off.
|
|
235
|
+
- **The top.** The same climb that takes a swimmer out of the water takes the
|
|
236
|
+
body onto the ledge: the ladder should lean on the FACE of the thing you
|
|
237
|
+
are climbing onto (a terrace, a wall with a platform on top, a loft's
|
|
238
|
+
edge) so the ledge is AHEAD of the climber — a ladder up through a hole in
|
|
239
|
+
a floor has its ledge to the sides, and the climb-out looks forward.
|
|
240
|
+
- **The bottom.** Down while standing at its foot steps off.
|
|
241
|
+
- **Letting go.** `jump` pushes off the rungs, away from the ladder, with a
|
|
242
|
+
moment's lockout so it is not grabbed straight back.
|
|
243
|
+
- **Animation.** The state is `climb` — map it in `animations`. The CDN's
|
|
244
|
+
mixamorig set has no climbing clip; `walk` reads as climbing well enough
|
|
245
|
+
on the rungs (`examples/tower-3d` does that).
|
|
246
|
+
- **Readback.** `controller.onLadder`.
|
|
247
|
+
|
|
248
|
+
Composed in `examples/tower-3d` ("Lantern Tower": three terraces, three
|
|
249
|
+
ladders, a sweeping beam and a lantern that wants three flasks).
|
|
250
|
+
|
|
251
|
+
## Mantling (the pull-up)
|
|
252
|
+
|
|
253
|
+
A jump at `jumpVelocity: 5` rises 1.27 m and the capsule's round bottom rides
|
|
254
|
+
a corner up to ~0.3 m more, so a 1.6 m roof is cleared and a 2 m one is a
|
|
255
|
+
wall — a hand short. `mantleHeight: 0.9` catches it: on the way down from
|
|
256
|
+
ANY jump, while the stick is held toward the ledge, a top ahead within that
|
|
257
|
+
height of the capsule's bottom is taken by the same climb that takes a
|
|
258
|
+
swimmer out of the water and a climber off a ladder (driven up at 2 m/s,
|
|
259
|
+
then onto it; state `climb` meanwhile). Above the reach it is still a wall;
|
|
260
|
+
a vertical hop beside a ledge is still a hop. Composed in
|
|
261
|
+
`examples/rooftops-3d`.
|
|
262
|
+
|
|
263
|
+
## Crouching (`crouchHeight`)
|
|
264
|
+
|
|
265
|
+
A duct to crawl through, a laser to duck under, a desk to hide behind:
|
|
266
|
+
every stealth game and shooter has a crouch, and the controller could not
|
|
267
|
+
make itself shorter. One prop turns it on:
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{ "name": "Ctl", "type": "CharacterController3D",
|
|
271
|
+
"props": { "crouchHeight": 0.3, "crouchSpeedMultiplier": 0.5 } }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
- **`crouchHeight`** (m, `0` = no crouch) is the capsule's `height` while the
|
|
275
|
+
`crouch` key is held — the standing capsule is `height` + 2 × `radius`
|
|
276
|
+
tall (1.6 m for the default rig), the crouched one `crouchHeight` +
|
|
277
|
+
2 × `radius` (0.94 m), so a duct with a metre of headroom is crawled
|
|
278
|
+
through and stops a standing character at its mouth. The body sinks by
|
|
279
|
+
half the difference in the same step, so the feet stay put and the eye
|
|
280
|
+
drops with them in first person; the Skin's authored offset is lifted by
|
|
281
|
+
the same amount so the model's feet stay on the floor, and restored on
|
|
282
|
+
standing.
|
|
283
|
+
- **Hold-to-crouch.** The key let go stands you up — if there is headroom.
|
|
284
|
+
A ray from the capsule's top asks; under a duct you stay down until you
|
|
285
|
+
are out from under it, and `crouching` reads true the whole time. A jump
|
|
286
|
+
press while the key is held is not a hop and not a stand.
|
|
287
|
+
- **`crouchSpeedMultiplier`** (0.5) is the pace, as a fraction of the standing
|
|
288
|
+
pace. `state` reads `crouch` (still) and `sneak` (moving), so the
|
|
289
|
+
`animations` map takes clips for both.
|
|
290
|
+
- Declare the action: `"crouch": { "type": "button", "keys": ["KeyC", "ControlLeft"] }`
|
|
291
|
+
(`crouchAction` renames it). A missing action is tolerated and reported
|
|
292
|
+
once, like `sprint`.
|
|
293
|
+
|
|
294
|
+
`examples/vents-3d` is the composition: ducts, a laser grid at head height,
|
|
295
|
+
a guard's sightline you crouch under.
|
|
296
|
+
|
|
297
|
+
## A death that falls — `Ragdoll3D`
|
|
298
|
+
|
|
299
|
+
Every enemy in every example died the same way: the model played a clip, or
|
|
300
|
+
vanished. Nothing crumpled, slid down a slope or hung off the rail, because a
|
|
301
|
+
ragdoll is ten bodies and nine joints built from a skeleton nobody wanted to
|
|
302
|
+
write. One node under the body, beside the Skin:
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{ "name": "Brute", "type": "CharacterBody3D", "children": [
|
|
306
|
+
{ "name": "Skin", "type": "ModelInstance3D", "props": { "model": "$base" } },
|
|
307
|
+
{ "name": "Hp", "type": "Node", "script": { "name": "Health", "props": { "max": 50 } } },
|
|
308
|
+
{ "name": "Ragdoll", "type": "Ragdoll3D", "props": { "target": "../Skin", "lifetime": 8 } }
|
|
309
|
+
] }
|
|
310
|
+
```
|
|
311
|
+
```jsonc
|
|
312
|
+
{ "signal": "died", "from": "Brute/Hp", "to": "Brute/Ragdoll", "handler": "activate" }
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- **Idle it is nothing.** `activate()` reads the skeleton at `target`
|
|
316
|
+
(Mixamo names — `Hips`, `Spine`, `Head`, `LeftArm`… with the forgiving
|
|
317
|
+
lookup `findBone` uses), builds a capsule `RigidBody3D` per limb where the
|
|
318
|
+
bones are — torso, head, upper arms, forearms, thighs, shins — joins each
|
|
319
|
+
to its parent with a spherical `Joint3D`, gives them the velocity of the
|
|
320
|
+
body they fell out of, and from then on writes the bones' world matrices
|
|
321
|
+
from the limbs every frame. The limbs go under the scene root in the
|
|
322
|
+
`ragdoll` group; `limbs()` lists them.
|
|
323
|
+
- **Turn the rest off beside it**: the body's collider (`enabled: false`) so
|
|
324
|
+
the capsule stops holding the corpse up, the controller or the chase.
|
|
325
|
+
`Health.freeOnDeath` would delete the model the ragdoll is driving — leave
|
|
326
|
+
it off and let `lifetime` free the limbs, then free the corpse yourself on
|
|
327
|
+
the `reset` signal.
|
|
328
|
+
- `mass` (70 kg, shared out by limb), `damping` (3 — how fast the flailing
|
|
329
|
+
dies), `collide` (limbs against each other; adjacent ones never). A rig
|
|
330
|
+
without hands or a `HeadTop_End` still builds — the forearm ends at the
|
|
331
|
+
wrist bone, the head is a ball.
|
|
332
|
+
- **Headless there is no skeleton**: `activate(pose)` takes bone positions
|
|
333
|
+
(`{ Hips: [x, y, z], … }`), so a harness can build one where the model would
|
|
334
|
+
be and ask that it fell, that the elbow still joins, that the limbs carried
|
|
335
|
+
the run. `active` reads true while it is up.
|
|
336
|
+
|
|
337
|
+
## Letting go — `enabled`
|
|
338
|
+
|
|
339
|
+
The player gets into a car, sits through a cutscene, opens a menu: the
|
|
340
|
+
controller has to LET GO, and `enabled: false` is that. For getting into a car or
|
|
341
|
+
onto a horse, the whole handoff — this controller off, the body parked and
|
|
342
|
+
riding along, the Skin on the saddle, the steed's controller or `Vehicle3D`
|
|
343
|
+
on, and back — is the `Mount` gameplay behaviour (`incanto-gameplay-behaviors.md`). Off, it reads no
|
|
344
|
+
input, applies no forces (no hover spring either), drives no camera and
|
|
345
|
+
sets no animation — the body under it is an ordinary `RigidBody3D` until it
|
|
346
|
+
is on again. A game parks the character out of sight beside it:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
ctl.enabled = false; // the controller lets go
|
|
350
|
+
player.enabled = false; // the body's collider is off — nothing bumps the empty seat
|
|
351
|
+
player.visible = false;
|
|
352
|
+
player.gravityScale = 0; // a collider-less dynamic body would fall through the floor
|
|
353
|
+
// …and each frame while driving: player.position = car.position
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
and on the way out puts it back where the door is, `enabled`/`visible` on,
|
|
357
|
+
`gravityScale` 1, THEN `ctl.enabled = true` — the controller re-finds the
|
|
358
|
+
current camera and drives it from the next frame (the camera pops to the
|
|
359
|
+
rig; smooth it yourself if the cut shows). `Vehicle3D.enabled` is the same
|
|
360
|
+
switch on the car's side, and `examples/errands-3d` flips both with one
|
|
361
|
+
`Interactable` press.
|
|
362
|
+
|
|
363
|
+
## Two players at one keyboard
|
|
364
|
+
|
|
365
|
+
A second player is a second ACTION SET, and a controller told to read it:
|
|
366
|
+
|
|
367
|
+
```jsonc
|
|
368
|
+
// the scene's input map declares both
|
|
369
|
+
"input": {
|
|
370
|
+
"move": { "type": "vector2", "keys": { "up": ["KeyW"], "down": ["KeyS"], "left": ["KeyA"], "right": ["KeyD"] } },
|
|
371
|
+
"jump": { "type": "button", "keys": ["Space"] },
|
|
372
|
+
"move2": { "type": "vector2", "keys": { "up": ["ArrowUp"], "down": ["ArrowDown"], "left": ["ArrowLeft"], "right": ["ArrowRight"] } },
|
|
373
|
+
"jump2": { "type": "button", "keys": ["ShiftRight"] }
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
```json
|
|
377
|
+
{ "name": "Controller", "type": "CharacterController3D",
|
|
378
|
+
"props": { "view": "quarter", "camera": "none",
|
|
379
|
+
"moveAction": "move2", "jumpAction": "jump2", "sprintAction": "sprint2" } }
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
- `moveAction` / `jumpAction` / `sprintAction` have been there since the
|
|
383
|
+
controller shipped and no example had ever set them. They work: measured on
|
|
384
|
+
`examples/coop-3d`, player one's keys move smith one 2.4 m and smith two
|
|
385
|
+
**0.00 m**.
|
|
386
|
+
- **Every player's controller needs `camera: "none"`**, and one camera gets a
|
|
387
|
+
`GroupCamera` (see `incanto-gameplay-behaviors.md`) — otherwise two
|
|
388
|
+
controllers and the group camera all write the same camera every frame.
|
|
389
|
+
- Anything else a player operates takes the same treatment: `Carry.action`,
|
|
390
|
+
`Interactable.action`, `Shoot`'s action — name the second player's own.
|
|
391
|
+
- The playtest bot drives EVERY controller it finds, each on its own action set,
|
|
392
|
+
so a two-player game is exercised rather than half-exercised.
|
|
393
|
+
|
|
129
394
|
## Views
|
|
130
395
|
|
|
131
396
|
| view | what it does | key props |
|
|
@@ -135,7 +400,21 @@ docstring's own advice: a raised `floatHeight` makes the character weightless).
|
|
|
135
400
|
| `quarter` | fixed isometric pitch 35.264°, NO mouse look | `camDistance` 40, `mouseLook: false` |
|
|
136
401
|
| `side` | camera at +z `camDistance`, lock movement to ±x | `mouseLook: false` |
|
|
137
402
|
|
|
138
|
-
The controller drives the scene's `current` Camera3D every frame (smoothed)
|
|
403
|
+
The controller drives the scene's `current` Camera3D every frame (smoothed) —
|
|
404
|
+
unless you take it off that duty:
|
|
405
|
+
|
|
406
|
+
```json
|
|
407
|
+
{ "name": "Controller", "type": "CharacterController3D",
|
|
408
|
+
"props": { "view": "quarter", "camera": "none" } }
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**`camera: "none"` leaves the camera exactly where the scene puts it.** A whole
|
|
412
|
+
shelf of games has a FIXED view — a board game, a bomb arena, an isometric
|
|
413
|
+
puzzle, a fixed-angle horror — and could not use this controller at all, because
|
|
414
|
+
it re-posed their camera every frame. Pair it with an authored `Camera3D` (and
|
|
415
|
+
a `FollowCamera` behaviour if the board scrolls); `incanto-check` no longer
|
|
416
|
+
calls that pair a fight. The default is `"drive"`, which is what every existing
|
|
417
|
+
scene already gets.
|
|
139
418
|
Wheel zooms only when `zoomMax > zoomMin`.
|
|
140
419
|
|
|
141
420
|
In `free` view the camera AIMS at the character every frame (`lookAt`), so the
|
|
@@ -160,7 +439,7 @@ enemy never yanks the camera in. `firstPerson` skips it (the camera is at the ey
|
|
|
160
439
|
|
|
161
440
|
## Animations
|
|
162
441
|
|
|
163
|
-
`controller.state` is `idle | walk | run | fastRun | airborne`; the
|
|
442
|
+
`controller.state` is `idle | walk | run | fastRun | airborne | swim | climb | crouch | sneak | dash`; the
|
|
164
443
|
`movementStateChanged` signal fires on transitions — map states to clips
|
|
165
444
|
(`$animation` assets are retargeted onto the skin's own rig — GLB and VRM alike — so a
|
|
166
445
|
clip never changes your character's proportions):
|
|
@@ -168,7 +447,8 @@ clip never changes your character's proportions):
|
|
|
168
447
|
```ts
|
|
169
448
|
controller.on('movementStateChanged', (state) => {
|
|
170
449
|
skin.animation = { idle: '$idle', run: '$run', fastRun: '$runFast',
|
|
171
|
-
airborne: '$jump', walk: '$walk'
|
|
450
|
+
airborne: '$jump', walk: '$walk', swim: '$swim',
|
|
451
|
+
climb: '$climb' }[state];
|
|
172
452
|
});
|
|
173
453
|
```
|
|
174
454
|
|
|
@@ -225,7 +505,7 @@ Map movement states straight in JSON — no behavior needed:
|
|
|
225
505
|
{ "name": "Controller", "type": "CharacterController3D",
|
|
226
506
|
"props": { "view": "free",
|
|
227
507
|
"animations": { "idle": "$idle", "walk": "$walk", "run": "$run",
|
|
228
|
-
"fastRun": "$sprint", "airborne": "$jump" } } }
|
|
508
|
+
"fastRun": "$sprint", "airborne": "$jump", "swim": "$swim" } } }
|
|
229
509
|
```
|
|
230
510
|
|
|
231
511
|
The controller writes `skin.animation` on every state change (crossfaded by
|
|
@@ -233,7 +513,7 @@ the model). The `movementStateChanged` signal still fires for extras.
|
|
|
233
513
|
|
|
234
514
|
**Where those clips come from.** Locomotion clips live at
|
|
235
515
|
`https://agent8-games.verse8.io/assets/3d/animations/mixamorig/<name>.glb`
|
|
236
|
-
(`idle-00`, `walk`, `run-medium`, `run-fast`, `jump`) and play on any mixamorig
|
|
516
|
+
(`idle-00`, `walk`, `run-medium`, `run-fast`, `jump`, `swim`) and play on any mixamorig
|
|
237
517
|
model as they are — retargeted through the humanoid map for a VRM. You do not
|
|
238
518
|
have to assemble any of this by hand: run the character URL your asset MCP gave
|
|
239
519
|
you through `bunx incanto-model <url>` and it prints the whole thing — assets,
|
|
@@ -292,6 +572,11 @@ NPCs feel alive when they track you. One node, zero code:
|
|
|
292
572
|
- `weight` (0.85) is how far the head commits; `forwardAxis` defaults to the
|
|
293
573
|
mixamo head convention (+z).
|
|
294
574
|
- Also good for chests (`bone: "Spine2"`, small weight) and turrets.
|
|
575
|
+
- **`node.engaged`** (0..1) is how far it is committed RIGHT NOW — the whole
|
|
576
|
+
observable effect of this node, and until now nothing could read it. It turns
|
|
577
|
+
a rendered bone, so it stays 0 in a headless run (no GLB, no skeleton): check
|
|
578
|
+
it in the browser, where `attachedBone` tells you the bone was found and
|
|
579
|
+
`engaged` tells you the head is following.
|
|
295
580
|
|
|
296
581
|
## Facing a direction in 3D — the +Z-FORWARD rule (read before turning ANY skin)
|
|
297
582
|
|
|
@@ -383,6 +668,12 @@ This cannot be an `auditScene` warning: a scene file has no velocity, and the bu
|
|
|
383
668
|
is a behavior writing a heading at runtime. `facingReport()` from `incanto/test`
|
|
384
669
|
is the same check for your own tests.
|
|
385
670
|
|
|
671
|
+
Like the feel numbers above it, this presses your title screen's START before
|
|
672
|
+
driving the character — a paused world moves nowhere, and `did not move —
|
|
673
|
+
nothing to compare` was the answer it gave about three shipped games whose only
|
|
674
|
+
fault was that their menu was up. When nothing on screen starts the game it says
|
|
675
|
+
THAT instead.
|
|
676
|
+
|
|
386
677
|
## Facing the mouse in a top-down view (twin-stick)
|
|
387
678
|
|
|
388
679
|
In `quarter` view the mouse does not look, and the controller turns the skin
|
|
@@ -404,29 +695,76 @@ Measured on `examples/survivor-3d`: a cursor 10 m east is yaw 90°, and a bolt
|
|
|
404
695
|
given `direction: [dx, 0, dz]` flies where the skin looks. Boot with
|
|
405
696
|
`pointer: { lockOnClick: false }` — a locked cursor is no aim at all.
|
|
406
697
|
|
|
407
|
-
## Lock-on and a dodge
|
|
698
|
+
## Lock-on (`lockOnAction`) and a dodge
|
|
408
699
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
700
|
+
Lock-on used to be a one-line recipe — the camera yaw toward the enemy, every
|
|
701
|
+
frame — and the half it never had was the one that matters: the SKIN kept
|
|
702
|
+
facing the move direction, so a locked player circling a brute showed it a
|
|
703
|
+
shoulder, and a sword hitbox hung off the skin's yaw swung sideways. The
|
|
704
|
+
controller owns it now:
|
|
413
705
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
706
|
+
| prop | default | what it does |
|
|
707
|
+
|---|---|---|
|
|
708
|
+
| `lockOnAction` | `""` | the button that toggles the lock (`""` = no lock-on) |
|
|
709
|
+
| `lockGroup` | `"enemy"` | who can be locked: the nearest live, visible member within… |
|
|
710
|
+
| `lockRange` | `12` | …this many metres; the lock lets go by itself at 1.5 × the range, or when the target dies or leaves the tree |
|
|
711
|
+
|
|
712
|
+
```json
|
|
713
|
+
{ "name": "Controller", "type": "CharacterController3D",
|
|
714
|
+
"props": { "view": "free", "lockOnAction": "lockOn", "lockRange": 14 } }
|
|
418
715
|
```
|
|
419
716
|
|
|
420
|
-
|
|
421
|
-
|
|
717
|
+
**Locked means the character FACES the target while its feet go anywhere.**
|
|
718
|
+
The camera yaw follows player → target every frame, so the stick — which is
|
|
719
|
+
camera-relative — strafes round it; the skin turns to the target (at
|
|
720
|
+
`turnSpeed`) even standing still; `state` stays `walk`/`run` while circling.
|
|
721
|
+
Anything you hang off the skin's yaw — `examples/melee-3d`'s `Edge` hitbox at
|
|
722
|
+
`sin/cos(skin yaw)` — swings at the target. Methods `lock(node)`, `unlock()`,
|
|
723
|
+
`toggleLock()`; readback `lockTarget`; signal `lockChanged(target | null)`.
|
|
724
|
+
`examples/boss-3d` locks onto the colossus with `lockGroup: "boss"`,
|
|
725
|
+
`lockRange: 40`.
|
|
726
|
+
|
|
727
|
+
`incanto-feel` knows: a controller with a `lockTarget` is reported as
|
|
728
|
+
`LOCKED ON` rather than measured against its travel — a strafe is the
|
|
729
|
+
intended reading, not a defect.
|
|
730
|
+
|
|
731
|
+
A dodge is a dash plus i-frames that start BEFORE the hit. The dash is the
|
|
732
|
+
controller's (`dashSpeed` — see below); the i-frames are `Health.protect`,
|
|
733
|
+
not `invulnerableFor` (which opens only after one), hung on the `dashed`
|
|
734
|
+
signal:
|
|
422
735
|
|
|
736
|
+
```json
|
|
737
|
+
{ "name": "Ctl", "type": "CharacterController3D",
|
|
738
|
+
"props": { "dashSpeed": 9, "dashSeconds": 0.35, "dashCooldown": 0.5 } }
|
|
739
|
+
```
|
|
423
740
|
```ts
|
|
424
|
-
(player.behavior as Health).protect(0.35);
|
|
425
|
-
body.linearVelocity = [dir[0] * 9, body.linearVelocity[1], dir[2] * 9];
|
|
741
|
+
controller.on('dashed', () => (player.behavior as Health).protect(0.35));
|
|
426
742
|
```
|
|
427
743
|
|
|
428
|
-
|
|
429
|
-
|
|
744
|
+
## A dash / dodge roll (`dashSpeed`)
|
|
745
|
+
|
|
746
|
+
Two examples carried the same twelve lines — a timer, a direction, and
|
|
747
|
+
`body.linearVelocity = dir × 9` written EVERY frame because the controller
|
|
748
|
+
brakes toward its own pace and a single impulse dies — while
|
|
749
|
+
`CharacterController2D` had `dashSpeed` since 0.33. Now the 3D controller
|
|
750
|
+
has it too:
|
|
751
|
+
|
|
752
|
+
| prop | default | what it does |
|
|
753
|
+
|---|---|---|
|
|
754
|
+
| `dashSpeed` | `0` | m/s for the roll; `0` = no dash |
|
|
755
|
+
| `dashSeconds` | `0.25` | how long the velocity is held |
|
|
756
|
+
| `dashCooldown` | `0` | seconds after a roll ends before the next can start |
|
|
757
|
+
| `dashAction` | `dash` | pressed = roll (tolerated when missing, like `sprint`) |
|
|
758
|
+
| `dashBackstep` | `false` | with no stick, roll AWAY from where the skin faces — the Souls-like backstep a lock-on game wants |
|
|
759
|
+
|
|
760
|
+
- The roll goes where the stick points (camera-relative), or with no stick
|
|
761
|
+
where the skin faces — or away from it with `dashBackstep`, which is what
|
|
762
|
+
a neutral roll means while locked on to something.
|
|
763
|
+
- `state` reads `dash` for the roll — map a clip to it — and `dashed` fires
|
|
764
|
+
once per roll. Not while crouched, on a ladder or swimming.
|
|
765
|
+
- The velocity is written last, every step of the roll, over whatever the
|
|
766
|
+
step's impulses did; gravity still runs, so a roll off a ledge falls.
|
|
767
|
+
- `examples/melee-3d` rolls with it and keeps its i-frames on `dashed`.
|
|
430
768
|
|
|
431
769
|
## First-person weapon viewmodel + the tracer-from-the-muzzle rule
|
|
432
770
|
|
package/skills/incanto-audio.md
CHANGED
|
@@ -226,8 +226,30 @@ non-WebAudio paths use, so headless gameplay is unaffected.
|
|
|
226
226
|
> (`refDistance: 100`, `maxDistance: 900` is a reasonable start); `incanto check`
|
|
227
227
|
> warns when it sees `spatial: true` in a 2D scene with the defaults left alone.
|
|
228
228
|
|
|
229
|
-
> **Headless / verify VM:**
|
|
230
|
-
>
|
|
229
|
+
> **Headless / verify VM:** no sound comes out — and the ANSWER still does. The
|
|
230
|
+
> audio record carries where each spatial sound arrived from, computed from the
|
|
231
|
+
> scene's own current camera when no renderer has fed a pose:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
const ring = engine.audio.recent().at(-1);
|
|
235
|
+
ring.pan; // −1 hard left … +1 hard right (absent when the sound is not spatial)
|
|
236
|
+
ring.gain; // 0..1 after the distance rolloff — 1 inside `refDistance`
|
|
237
|
+
ring.distance; // metres (px in 2D) from the listener when it sounded
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Thirty shipped scenes set `spatial: true` before this existed and not one of
|
|
241
|
+
them could be checked: the record said a sound FIRED and nothing about whether
|
|
242
|
+
it was audible or which side it came from. Now "walk toward the ringing" is a
|
|
243
|
+
mechanic a harness can verify — `examples/earshot-3d` is a whole game of it
|
|
244
|
+
(a bell on the left records `pan −0.98`; turn the listener around and the same
|
|
245
|
+
bell reads `+0.98`).
|
|
246
|
+
|
|
247
|
+
Two things to know when you measure it:
|
|
248
|
+
|
|
249
|
+
- **A sound that is not spatial has no `pan`/`gain`/`distance` at all** — the
|
|
250
|
+
fields are absent rather than zero, so a UI click never looks centred.
|
|
251
|
+
- **A scene with no current camera has no listener**, and the record says
|
|
252
|
+
nothing rather than inventing a sound in the middle of your head.
|
|
231
253
|
|
|
232
254
|
---
|
|
233
255
|
|
|
@@ -216,7 +216,8 @@ ALWAYS confirm in the browser (`bun run dev`) that the feet touch the surface.
|
|
|
216
216
|
| `smoothing` | `0` | 0 = snap; 0.85–0.95 = smooth chase (frame-rate independent) |
|
|
217
217
|
| `zoom` | `1` | 2 = world pixels doubled |
|
|
218
218
|
| `limits` | `[]` | `[minX,minY,maxX,maxY]` — view rect clamped inside |
|
|
219
|
-
| `current` | `false` | mark exactly one |
|
|
219
|
+
| `current` | `false` | mark exactly one — switch at runtime with `cam.makeCurrent()`, which clears the others (a wirable handler) |
|
|
220
|
+
| `blendSeconds` | `0` | seconds the view FLIES (centre and zoom, eased) from the last drawn view to this camera when it becomes current; `0` = a cut; `makeCurrent(0.5)` asks once |
|
|
220
221
|
Camera `position` is the view CENTER, and it composes with its ancestors like any
|
|
221
222
|
other node — so parenting the camera to the player (the Godot/Phaser idiom) works,
|
|
222
223
|
and so does putting it inside the container that holds the level. `follow` and the
|
|
@@ -346,6 +347,12 @@ between a readable and an unusable phone build.
|
|
|
346
347
|
|
|
347
348
|
## Game flow recipes
|
|
348
349
|
|
|
350
|
+
- **A title screen that HOLDS the world**: `GameFlow.pause()` from a behaviour's
|
|
351
|
+
`onReady`, and `resume()` when the player presses your RUN button. The freeze
|
|
352
|
+
survives the boot — the tree readies inside `loadScene` and `setScene` resets
|
|
353
|
+
the time scale for the game-over swap, so a pause asked for during the load
|
|
354
|
+
used to be erased and the game booted with `state: 'paused'` and the player
|
|
355
|
+
walking around behind the title. `examples/shell-3d` is the whole shell.
|
|
349
356
|
- **Scene transition** (level 2, title → game):
|
|
350
357
|
```ts
|
|
351
358
|
import { loadScene } from 'incanto';
|
|
@@ -545,6 +552,8 @@ behaves exactly as before.
|
|
|
545
552
|
| `dashSpeed` / `dashSeconds` / `dashAction` | `0` / `0.15` / `dash` | a straight-line dash |
|
|
546
553
|
| `wallSlideSpeed` | `0` | capped fall while against a wall |
|
|
547
554
|
| `wallJumpImpulse` | `[0, 0]` | `[away, up]` push off a wall |
|
|
555
|
+
| `crouchHeight` | `0` | the collider's height (px) while `crouch` is held — a rect's `size[1]`, a capsule's `height`; the body drops by half the difference so the feet stay put, a low gap is ducked through, and a ceiling keeps you down until you are out from under it. Hold-to-crouch: a jump press under a held key is not a hop |
|
|
556
|
+
| `crouchSpeedMultiplier` / `crouchAction` | `0.5` / `crouch` | the pace while crouched; the action (tolerated when missing) |
|
|
548
557
|
|
|
549
558
|
```json
|
|
550
559
|
{ "name": "Controller", "type": "CharacterController2D",
|
|
@@ -553,8 +562,18 @@ behaves exactly as before.
|
|
|
553
562
|
"wallSlideSpeed": 60, "wallJumpImpulse": [260, 380] } }
|
|
554
563
|
```
|
|
555
564
|
|
|
565
|
+
**Getting hit is the controller's too.** A `Health` on the body (or a child of
|
|
566
|
+
it) with `knockback` / `knockUp` / `staggerSeconds` takes the stick and the jump
|
|
567
|
+
for the stagger, and the controller writes the kick as the body's velocity — in
|
|
568
|
+
topDown as the whole velocity, in platformer as `x` with `knockUp` as the hop
|
|
569
|
+
(pass it positive; the engine flips it to `−y`). The platformer's hand-rolled
|
|
570
|
+
`KNOCK_X` / `KNOCK_UP` / `CONTROL_LOCK` are `knockback 420, knockbackSeconds
|
|
571
|
+
0.22, knockUp 360, staggerSeconds 0.22`; a controller of your own reads the
|
|
572
|
+
kick itself (`applyKnockback: false`) — see "Hit reactions" in
|
|
573
|
+
`incanto-gameplay-behaviors.md`.
|
|
574
|
+
|
|
556
575
|
**`movementStateChanged(state)`** fires on every change — `idle` · `run` ·
|
|
557
|
-
`jump` · `fall` · `wallSlide` · `dash` — so an `AnimatedSprite2D` follows the
|
|
576
|
+
`jump` · `fall` · `wallSlide` · `dash` · `crouch` · `sneak` — so an `AnimatedSprite2D` follows the
|
|
558
577
|
character from scene JSON instead of a behavior polling velocities. The 3D
|
|
559
578
|
sibling has had this signal since it shipped.
|
|
560
579
|
|
|
@@ -58,7 +58,10 @@ One call does register → load → physics → input → renderer → start. Ph
|
|
|
58
58
|
disables); `pointer: { lockOnClick: false }` for look without lock. `seed` /
|
|
59
59
|
`fixedHz` / `pixelRatio` / `adaptiveResolution` pass through (adaptive
|
|
60
60
|
resolution is ON by default: slow frames render fewer pixels and recover
|
|
61
|
-
automatically — pass `false` for screenshots and pixel-diff tests).
|
|
61
|
+
automatically — pass `false` for screenshots and pixel-diff tests). `autoStart: false`
|
|
62
|
+
hands back a game that has not run a frame — call `game.engine.start()` once
|
|
63
|
+
everything that must exist before the first one does (a `NetworkManager`; see
|
|
64
|
+
`incanto-multiplayer.md`). The manual `Engine` + `loadScene` +
|
|
62
65
|
`Renderer3D` boot still exists for full control.
|
|
63
66
|
|
|
64
67
|
Imperative per-frame logic can hang off `game.engine.updated.connect((dt) => { ... })`; for
|
|
@@ -281,9 +284,41 @@ and `Fill.position = [-(1 - frac) / 2, 0, 0.01]`. The offsets stay local to the
|
|
|
281
284
|
(rotated) bar, so the drain always hugs the bar's left edge.
|
|
282
285
|
|
|
283
286
|
### `Camera3D`
|
|
284
|
-
`fov: 60`, `near: 0.1`, `far: 1000`, `current: false`, `lookAt: ""
|
|
287
|
+
`fov: 60`, `near: 0.1`, `far: 1000`, `current: false`, `lookAt: ""`, `blendSeconds: 0`,
|
|
288
|
+
`minHorizontalFov: 0`.
|
|
285
289
|
Mark exactly ONE camera `current: true` (otherwise the first camera in tree order is used).
|
|
286
|
-
|
|
290
|
+
|
|
291
|
+
**`fov` is VERTICAL**, and the aspect comes from the canvas — so what the player
|
|
292
|
+
sees ACROSS is `2·atan(tan(fov/2)·aspect)`. A `fov: 60` camera shows 91° across a
|
|
293
|
+
16:9 window and **30° across a 390×844 phone held in portrait**.
|
|
294
|
+
`minHorizontalFov` is the floor: ask for a width in degrees and the vertical fov
|
|
295
|
+
widens until the screen carries it, while any window already wider keeps the
|
|
296
|
+
authored fov. `52` suits a game meant to be playable in one hand — and note what
|
|
297
|
+
the geometry costs: 52° across a 9:19.5 screen is a 93° vertical fov, so ask for
|
|
298
|
+
a width, not for everything.
|
|
299
|
+
|
|
300
|
+
`cam.viewFov(aspect)` answers what the camera will actually render with at a
|
|
301
|
+
given shape of screen — the authored `fov`, or the widened one. Use it in a
|
|
302
|
+
harness rather than copying the formula:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const cam = game.scene.root.getNode('Camera') as Camera3D;
|
|
306
|
+
cam.viewFov(390 / 844); // 93 — the vertical fov a portrait phone gets
|
|
307
|
+
cam.viewFov(16 / 9); // 60 — the authored one, on a desktop window
|
|
308
|
+
```
|
|
309
|
+
To SWITCH at runtime call `cam.makeCurrent()` — it clears every other camera's
|
|
310
|
+
`current`, which setting your own never did, so a cutscene camera that set
|
|
311
|
+
itself `current` changed nothing visible. It is a handler a connection can name:
|
|
312
|
+
`triggerEnter → DoorCam.makeCurrent`. Aspect ratio is automatic.
|
|
313
|
+
|
|
314
|
+
**A switch is a CUT unless the camera asks otherwise.** `blendSeconds` on the
|
|
315
|
+
camera being switched TO makes the picture FLY from the last drawn pose to that
|
|
316
|
+
camera over so many seconds of real time — position, orientation and fov, eased
|
|
317
|
+
in and out — and `makeCurrent(0.6)` asks for it once. The nodes never move:
|
|
318
|
+
picking, framing and the editor read the node; only the picture takes the long
|
|
319
|
+
way round. A second switch mid-flight starts from where the picture IS.
|
|
320
|
+
`examples/heist-3d`'s door camera and gameplay camera declare `0.6`; a camera
|
|
321
|
+
that is the FIRST frame (an intro rail) has nothing to fly from and cuts.
|
|
287
322
|
|
|
288
323
|
**Point it with `lookAt`, not with a `rotation` you worked out.**
|
|
289
324
|
|
package/skills/incanto-editor.md
CHANGED
|
@@ -134,12 +134,23 @@ Opens a local page (default `http://127.0.0.1:5179/`) with three panes:
|
|
|
134
134
|
node in the scene; the handler is a dropdown of real methods when the target
|
|
135
135
|
has no script, and free text when it does (those handlers live in your
|
|
136
136
|
TypeScript, which the editor never loads). An end that no longer resolves is
|
|
137
|
-
marked red with the reason. For what a row cannot express — `filter
|
|
138
|
-
— the scene header still has `connections` as raw JSON.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
marked red with the reason. For what a row cannot express — `filter`, `once`
|
|
138
|
+
and `args` — the scene header still has `connections` as raw JSON. A
|
|
139
|
+
connection's keys are exactly `[signal, from, to, handler, once, filter,
|
|
140
|
+
args]`, a closed set, so an invented one is a load error rather than a key
|
|
141
|
+
that quietly does nothing. **`args` survives editing but is not shown**: a row
|
|
142
|
+
whose wire carries bound arguments reads as `phase2 → HUD/Banner.show` with no
|
|
143
|
+
hint of the words it says.
|
|
144
|
+
|
|
145
|
+
**Re-pointing a target keeps your handler.** A handler the new target does not
|
|
146
|
+
have is replaced (the row would otherwise stay green here and hard-fail at
|
|
147
|
+
load), and the row remembers what it started with — so changing your mind and
|
|
148
|
+
putting the target back puts the handler back. Before that, a round trip left
|
|
149
|
+
`phase2 → HUD/Banner.show` as `HUD/Banner.childNames`: the replacement took
|
|
150
|
+
the target's first offer, and the offers included the tree plumbing every node
|
|
151
|
+
inherits. They do not any more — the node base class is read the way the
|
|
152
|
+
behavior base class always was, so `queueFree` stays and `childNames`,
|
|
153
|
+
`wiringPath` and the rest are gone.
|
|
143
154
|
**Validation now runs WITH connections**: the editor used to strip them before
|
|
144
155
|
loading, so it reported green for a scene the game hard-fails to open.
|
|
145
156
|
- **Viewport with DIRECT MANIPULATION** — a mode toolbar (top left) + the
|