incanto 0.74.0 → 0.76.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 +861 -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-ssvGvP7W.js → create-game-BMIPKrQW.js} +84 -8
- package/dist/{create-game-BAiA-FjP.js → create-game-Dai5vfVu.js} +80 -11
- package/dist/debug.d.ts +1 -1
- package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-Bzt0GzVp.d.ts} +13 -3
- package/dist/editor.js +1600 -1529
- package/dist/env.d.ts +1 -1
- package/dist/{environment-presets-BkqlWewf.js → environment-presets-e_9YCnGu.js} +2361 -104
- package/dist/{gameplay-Bxe1sMVT.js → gameplay-Bfff_beb.js} +10919 -6798
- 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-Tuf5LAyq.js} +250 -31
- package/dist/{physics-3d-rOzeg890.js → physics-3d-C5MOujfQ.js} +673 -51
- package/dist/{picking-BMV34Pjl.js → picking-DIwE48sU.js} +10 -7
- 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-DRQyZKGU.js → register-7E3P1gz6.js} +191 -430
- package/dist/{register-CjociOtt.js → register-B768Qedj.js} +596 -2
- 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-D823V07k.js} +1 -1
- package/dist/{test-CEl0y2Sw.js → test-CVbxnXlv.js} +487 -82
- package/dist/test.d.ts +90 -12
- package/dist/test.js +2 -2
- package/dist/vite.js +34 -5
- package/editor/assets/{agent8-BlzturGi.js → agent8-DA-I5qhv.js} +1 -1
- package/editor/assets/{debug-BMUi8usj.js → debug-99tEPoGm.js} +1 -1
- package/editor/assets/index-Db74LZY-.js +11579 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +1639 -280
- package/skills/incanto-3d-character.md +383 -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 +29 -8
- package/skills/incanto-environment.md +257 -14
- 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 +398 -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 +157 -3
- 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,29 @@ 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
|
+
**`slopeLimitDeg` (55) is the steepest ground the character STANDS on**, and
|
|
112
|
+
below it a character does not slide at all — measured, on a hillside, for as
|
|
113
|
+
long as you leave it there. It used to slide down everything: 0.18 m in three
|
|
114
|
+
seconds on a 6.9° slope, 2.87 m on a 27° one, always exactly downhill. The
|
|
115
|
+
cause is the hover spring measuring its clearance STRAIGHT DOWN while the
|
|
116
|
+
clearance that decides whether the capsule touches is measured perpendicular
|
|
117
|
+
to the surface — negative past ~13° — and every rig setting `friction: 0` (the
|
|
118
|
+
right setting: the character rides the spring, so floor friction only brakes
|
|
119
|
+
it) then slid on the contact. So the spring aims perpendicular now, and on
|
|
120
|
+
ground inside the limit the controller cancels downhill drift it was not asked
|
|
121
|
+
for. Steeper than the limit the ground stops being ground — no `grounded`, no
|
|
122
|
+
jump reset — and the body slips off the face, which is what a slope limit means
|
|
123
|
+
everywhere else. 55 is a person; a goat is 80, something on wheels 25, and `90`
|
|
124
|
+
is the old no-limit behaviour. `controller.groundSlopeDeg` reports what it is
|
|
125
|
+
standing on, and `controller.groundNormal` which way that faces.
|
|
126
|
+
|
|
127
|
+
**`stepHeight` steps onto a ledge at the APEX of a jump too.** A fence 1.5 m
|
|
128
|
+
high was climbed by a jump that peaks at 1.3 m: at the apex the fence top sat
|
|
129
|
+
0.2 m above the capsule's bottom, inside `stepHeight` 0.4, and the step-up
|
|
130
|
+
lifted the character over. That is what makes landing on a ledge forgiving; a
|
|
131
|
+
wall that must stop a walker is jump apex + `stepHeight` tall (`examples/steed-3d`'s
|
|
132
|
+
fence is 2 m).
|
|
133
|
+
|
|
95
134
|
**Jump reads the button EDGE.** Holding the jump key no longer re-jumps every
|
|
96
135
|
frame — that auto-hop was never intended and it made coyote time incoherent (a
|
|
97
136
|
held button would re-fire through the whole window).
|
|
@@ -126,6 +165,248 @@ at rest: 0.01 → y 0.839, 0.05 → 0.840, 0.3 → 1.008, 0.6 → 1.308 for a ca
|
|
|
126
165
|
contact height is 0.84. Leave it at the default and use `stepHeight` for ledges (the
|
|
127
166
|
docstring's own advice: a raised `floatHeight` makes the character weightless).
|
|
128
167
|
|
|
168
|
+
## Swimming (water too deep to stand in)
|
|
169
|
+
|
|
170
|
+
A `Water3D` is a surface with no bottom, and until now the controller never
|
|
171
|
+
asked it anything: a player who walked off a beach into a four-metre lagoon
|
|
172
|
+
stood on the bed with the camera underwater and the run clip playing, in every
|
|
173
|
+
3D scene with water. Now the body SWIMS:
|
|
174
|
+
|
|
175
|
+
| prop | default | what it buys |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `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. |
|
|
178
|
+
| `swimDepth` | `null` | how far the body's ORIGIN rides below the surface. `null` derives it from the capsule so the head rides 15 cm clear and the rest is in the water — which is what treading water looks like, and what a fixed number cannot be for every character size. (It used to default to a flat `0.35`, and on the 1.68 m capsule every template ships that put the head 0.46 m clear: the character floated along on top like a boat.) A number still wins. |
|
|
179
|
+
| `diveAction` | `"dive"` | held: swim DOWN at `swimSpeed`. Not in the input map = no diving, silently. |
|
|
180
|
+
|
|
181
|
+
- **When.** The body swims when floating would put it HIGHER than standing:
|
|
182
|
+
the waterline (`surface − swimDepth`) above where the hover spring would
|
|
183
|
+
hold it on the bed it can see, or no bed within reach. That one comparison
|
|
184
|
+
is the whole wade-in/wade-out — walk down a beach and the water takes you
|
|
185
|
+
where the bed drops away; swim toward a shelf and the spring takes over
|
|
186
|
+
where it rises. No zones, no triggers.
|
|
187
|
+
- **What.** Gravity is off, a spring holds the origin at the waterline (a
|
|
188
|
+
swimmer rides the swell the way a `Buoyancy` raft does — the same
|
|
189
|
+
`heightAt`), horizontal control is full (water gives purchase), and the
|
|
190
|
+
state is `swim` for the `animations` map / `movementStateChanged`.
|
|
191
|
+
- **Diving.** Hold `dive` to go down; let go and you rise on your own. With
|
|
192
|
+
the head under (`controller.submerged`), holding `jump` swims UP. Air,
|
|
193
|
+
drowning and what is down there are your game's business — read
|
|
194
|
+
`controller.submerged` in a behavior and count.
|
|
195
|
+
- **Getting out.** A swimmer's step is measured from the WATER, not from the
|
|
196
|
+
foot: swim into a shelf, a dock or a rock whose top is no more than
|
|
197
|
+
`stepHeight` above the surface and the body CLIMBS out on its own — driven
|
|
198
|
+
up at 2 m/s until its bottom is level with the ledge, then onto it. (The
|
|
199
|
+
capsule hangs `swimDepth + halfHeight` under the surface, so from its bottom
|
|
200
|
+
a shelf a hand under the waterline would be a 0.6 m wall; a swimmer at that
|
|
201
|
+
shelf simply puts their hands on it. The hover spring could not do this —
|
|
202
|
+
it is sized to hold a capsule a hair off the ground and stalled with the
|
|
203
|
+
body's round bottom a third of a metre under the ledge, pressed to its
|
|
204
|
+
face.) Higher than `stepHeight` above the water it is a wall, exactly as on
|
|
205
|
+
land. `jump` at the surface hops — SIZED to clear the surface by
|
|
206
|
+
`stepHeight`, whatever `jumpVelocity` says — and the same climb takes a
|
|
207
|
+
ledge on the way down. So author a dock or a rock a swimmer should reach
|
|
208
|
+
**no more than `stepHeight` above the water**. A beach needs nothing: the
|
|
209
|
+
bed rises, the standing height passes the waterline, and you walk out.
|
|
210
|
+
- **Readbacks.** `controller.swimming`, `controller.submerged`.
|
|
211
|
+
- **Found on the way (fixed, same release):** the ground probe's side rays
|
|
212
|
+
start `radius` out from the capsule's centre, so pressed against a wall one
|
|
213
|
+
of them started INSIDE the wall and hit it at distance zero — a character
|
|
214
|
+
hugging a wall in mid-air read as standing, the ground jump came back while
|
|
215
|
+
falling, and a hop out of the water at a rock's face was cancelled the frame
|
|
216
|
+
it started. A zero-distance hit is ignored now. And `wallSlideSpeed` held a
|
|
217
|
+
third of a metre a second loose (the solver added one tick of gravity back
|
|
218
|
+
after the clamp); at the cap gravity stands down.
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{ "name": "Controller", "type": "CharacterController3D",
|
|
222
|
+
"props": { "view": "free", "swimSpeed": 1.8, "stepHeight": 0.5,
|
|
223
|
+
"animations": { "idle": "$idle", "run": "$run", "swim": "$anims/swim" } } }
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Composed in `examples/lagoon-3d` (a pearl dive with an air meter); the
|
|
227
|
+
mixamorig `swim.glb` clip is on the CDN beside the locomotion set.
|
|
228
|
+
|
|
229
|
+
## Ladders
|
|
230
|
+
|
|
231
|
+
Every 3D game with a second floor has one, and until now an `Area3D`
|
|
232
|
+
against a wall was a box the player walked into. A ladder is any `Area3D` in
|
|
233
|
+
the controller's `ladderGroup` — no node type, no code:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{ "name": "Ladder", "type": "Area3D", "groups": ["ladder"],
|
|
237
|
+
"props": { "position": [-3, 2.2, 0.35], "collider": { "shape": "box", "size": [1, 4.4, 0.7] } },
|
|
238
|
+
"children": [ …rails and rungs as MeshInstance3D… ] }
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
| prop | default | what it buys |
|
|
242
|
+
|---|---|---|
|
|
243
|
+
| `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.** |
|
|
244
|
+
| `ladderGroup` | `"ladder"` | which `Area3D`s are ladders |
|
|
245
|
+
|
|
246
|
+
- **Grabbing.** Push UP into a ladder you overlap and you are on it: gravity
|
|
247
|
+
off, held to the rungs, state `climb`. Walking PAST one never grabs it —
|
|
248
|
+
only pushing up does. Falling past one while holding DOWN grabs it too,
|
|
249
|
+
which is how you climb down from a ledge: stand at its edge over the
|
|
250
|
+
ladder, hold back, step off.
|
|
251
|
+
- **The top.** The same climb that takes a swimmer out of the water takes the
|
|
252
|
+
body onto the ledge: the ladder should lean on the FACE of the thing you
|
|
253
|
+
are climbing onto (a terrace, a wall with a platform on top, a loft's
|
|
254
|
+
edge) so the ledge is AHEAD of the climber — a ladder up through a hole in
|
|
255
|
+
a floor has its ledge to the sides, and the climb-out looks forward.
|
|
256
|
+
- **The bottom.** Down while standing at its foot steps off.
|
|
257
|
+
- **Letting go.** `jump` pushes off the rungs, away from the ladder, with a
|
|
258
|
+
moment's lockout so it is not grabbed straight back.
|
|
259
|
+
- **Animation.** The state is `climb` — map it in `animations`. The CDN's
|
|
260
|
+
mixamorig set has no climbing clip; `walk` reads as climbing well enough
|
|
261
|
+
on the rungs (`examples/tower-3d` does that).
|
|
262
|
+
- **Readback.** `controller.onLadder`.
|
|
263
|
+
|
|
264
|
+
Composed in `examples/tower-3d` ("Lantern Tower": three terraces, three
|
|
265
|
+
ladders, a sweeping beam and a lantern that wants three flasks).
|
|
266
|
+
|
|
267
|
+
## Mantling (the pull-up)
|
|
268
|
+
|
|
269
|
+
A jump at `jumpVelocity: 5` rises 1.27 m and the capsule's round bottom rides
|
|
270
|
+
a corner up to ~0.3 m more, so a 1.6 m roof is cleared and a 2 m one is a
|
|
271
|
+
wall — a hand short. `mantleHeight: 0.9` catches it: on the way down from
|
|
272
|
+
ANY jump, while the stick is held toward the ledge, a top ahead within that
|
|
273
|
+
height of the capsule's bottom is taken by the same climb that takes a
|
|
274
|
+
swimmer out of the water and a climber off a ladder (driven up at 2 m/s,
|
|
275
|
+
then onto it; state `climb` meanwhile). Above the reach it is still a wall;
|
|
276
|
+
a vertical hop beside a ledge is still a hop. Composed in
|
|
277
|
+
`examples/rooftops-3d`.
|
|
278
|
+
|
|
279
|
+
## Crouching (`crouchHeight`)
|
|
280
|
+
|
|
281
|
+
A duct to crawl through, a laser to duck under, a desk to hide behind:
|
|
282
|
+
every stealth game and shooter has a crouch, and the controller could not
|
|
283
|
+
make itself shorter. One prop turns it on:
|
|
284
|
+
|
|
285
|
+
```json
|
|
286
|
+
{ "name": "Ctl", "type": "CharacterController3D",
|
|
287
|
+
"props": { "crouchHeight": 0.3, "crouchSpeedMultiplier": 0.5 } }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- **`crouchHeight`** (m, `0` = no crouch) is the capsule's `height` while the
|
|
291
|
+
`crouch` key is held — the standing capsule is `height` + 2 × `radius`
|
|
292
|
+
tall (1.6 m for the default rig), the crouched one `crouchHeight` +
|
|
293
|
+
2 × `radius` (0.94 m), so a duct with a metre of headroom is crawled
|
|
294
|
+
through and stops a standing character at its mouth. The body sinks by
|
|
295
|
+
half the difference in the same step, so the feet stay put and the eye
|
|
296
|
+
drops with them in first person; the Skin's authored offset is lifted by
|
|
297
|
+
the same amount so the model's feet stay on the floor, and restored on
|
|
298
|
+
standing.
|
|
299
|
+
- **Hold-to-crouch.** The key let go stands you up — if there is headroom.
|
|
300
|
+
A ray from the capsule's top asks; under a duct you stay down until you
|
|
301
|
+
are out from under it, and `crouching` reads true the whole time. A jump
|
|
302
|
+
press while the key is held is not a hop and not a stand.
|
|
303
|
+
- **`crouchSpeedMultiplier`** (0.5) is the pace, as a fraction of the standing
|
|
304
|
+
pace. `state` reads `crouch` (still) and `sneak` (moving), so the
|
|
305
|
+
`animations` map takes clips for both.
|
|
306
|
+
- Declare the action: `"crouch": { "type": "button", "keys": ["KeyC", "ControlLeft"] }`
|
|
307
|
+
(`crouchAction` renames it). A missing action is tolerated and reported
|
|
308
|
+
once, like `sprint`.
|
|
309
|
+
|
|
310
|
+
`examples/vents-3d` is the composition: ducts, a laser grid at head height,
|
|
311
|
+
a guard's sightline you crouch under.
|
|
312
|
+
|
|
313
|
+
## A death that falls — `Ragdoll3D`
|
|
314
|
+
|
|
315
|
+
Every enemy in every example died the same way: the model played a clip, or
|
|
316
|
+
vanished. Nothing crumpled, slid down a slope or hung off the rail, because a
|
|
317
|
+
ragdoll is ten bodies and nine joints built from a skeleton nobody wanted to
|
|
318
|
+
write. One node under the body, beside the Skin:
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{ "name": "Brute", "type": "CharacterBody3D", "children": [
|
|
322
|
+
{ "name": "Skin", "type": "ModelInstance3D", "props": { "model": "$base" } },
|
|
323
|
+
{ "name": "Hp", "type": "Node", "script": { "name": "Health", "props": { "max": 50 } } },
|
|
324
|
+
{ "name": "Ragdoll", "type": "Ragdoll3D", "props": { "target": "../Skin", "lifetime": 8 } }
|
|
325
|
+
] }
|
|
326
|
+
```
|
|
327
|
+
```jsonc
|
|
328
|
+
{ "signal": "died", "from": "Brute/Hp", "to": "Brute/Ragdoll", "handler": "activate" }
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- **Idle it is nothing.** `activate()` reads the skeleton at `target`
|
|
332
|
+
(Mixamo names — `Hips`, `Spine`, `Head`, `LeftArm`… with the forgiving
|
|
333
|
+
lookup `findBone` uses), builds a capsule `RigidBody3D` per limb where the
|
|
334
|
+
bones are — torso, head, upper arms, forearms, thighs, shins — joins each
|
|
335
|
+
to its parent with a spherical `Joint3D`, gives them the velocity of the
|
|
336
|
+
body they fell out of, and from then on writes the bones' world matrices
|
|
337
|
+
from the limbs every frame. The limbs go under the scene root in the
|
|
338
|
+
`ragdoll` group; `limbs()` lists them.
|
|
339
|
+
- **Turn the rest off beside it**: the body's collider (`enabled: false`) so
|
|
340
|
+
the capsule stops holding the corpse up, the controller or the chase.
|
|
341
|
+
`Health.freeOnDeath` would delete the model the ragdoll is driving — leave
|
|
342
|
+
it off and let `lifetime` free the limbs, then free the corpse yourself on
|
|
343
|
+
the `reset` signal.
|
|
344
|
+
- `mass` (70 kg, shared out by limb), `damping` (3 — how fast the flailing
|
|
345
|
+
dies), `collide` (limbs against each other; adjacent ones never). A rig
|
|
346
|
+
without hands or a `HeadTop_End` still builds — the forearm ends at the
|
|
347
|
+
wrist bone, the head is a ball.
|
|
348
|
+
- **Headless there is no skeleton**: `activate(pose)` takes bone positions
|
|
349
|
+
(`{ Hips: [x, y, z], … }`), so a harness can build one where the model would
|
|
350
|
+
be and ask that it fell, that the elbow still joins, that the limbs carried
|
|
351
|
+
the run. `active` reads true while it is up.
|
|
352
|
+
|
|
353
|
+
## Letting go — `enabled`
|
|
354
|
+
|
|
355
|
+
The player gets into a car, sits through a cutscene, opens a menu: the
|
|
356
|
+
controller has to LET GO, and `enabled: false` is that. For getting into a car or
|
|
357
|
+
onto a horse, the whole handoff — this controller off, the body parked and
|
|
358
|
+
riding along, the Skin on the saddle, the steed's controller or `Vehicle3D`
|
|
359
|
+
on, and back — is the `Mount` gameplay behaviour (`incanto-gameplay-behaviors.md`). Off, it reads no
|
|
360
|
+
input, applies no forces (no hover spring either), drives no camera and
|
|
361
|
+
sets no animation — the body under it is an ordinary `RigidBody3D` until it
|
|
362
|
+
is on again. A game parks the character out of sight beside it:
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
ctl.enabled = false; // the controller lets go
|
|
366
|
+
player.enabled = false; // the body's collider is off — nothing bumps the empty seat
|
|
367
|
+
player.visible = false;
|
|
368
|
+
player.gravityScale = 0; // a collider-less dynamic body would fall through the floor
|
|
369
|
+
// …and each frame while driving: player.position = car.position
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
and on the way out puts it back where the door is, `enabled`/`visible` on,
|
|
373
|
+
`gravityScale` 1, THEN `ctl.enabled = true` — the controller re-finds the
|
|
374
|
+
current camera and drives it from the next frame (the camera pops to the
|
|
375
|
+
rig; smooth it yourself if the cut shows). `Vehicle3D.enabled` is the same
|
|
376
|
+
switch on the car's side, and `examples/errands-3d` flips both with one
|
|
377
|
+
`Interactable` press.
|
|
378
|
+
|
|
379
|
+
## Two players at one keyboard
|
|
380
|
+
|
|
381
|
+
A second player is a second ACTION SET, and a controller told to read it:
|
|
382
|
+
|
|
383
|
+
```jsonc
|
|
384
|
+
// the scene's input map declares both
|
|
385
|
+
"input": {
|
|
386
|
+
"move": { "type": "vector2", "keys": { "up": ["KeyW"], "down": ["KeyS"], "left": ["KeyA"], "right": ["KeyD"] } },
|
|
387
|
+
"jump": { "type": "button", "keys": ["Space"] },
|
|
388
|
+
"move2": { "type": "vector2", "keys": { "up": ["ArrowUp"], "down": ["ArrowDown"], "left": ["ArrowLeft"], "right": ["ArrowRight"] } },
|
|
389
|
+
"jump2": { "type": "button", "keys": ["ShiftRight"] }
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
```json
|
|
393
|
+
{ "name": "Controller", "type": "CharacterController3D",
|
|
394
|
+
"props": { "view": "quarter", "camera": "none",
|
|
395
|
+
"moveAction": "move2", "jumpAction": "jump2", "sprintAction": "sprint2" } }
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
- `moveAction` / `jumpAction` / `sprintAction` have been there since the
|
|
399
|
+
controller shipped and no example had ever set them. They work: measured on
|
|
400
|
+
`examples/coop-3d`, player one's keys move smith one 2.4 m and smith two
|
|
401
|
+
**0.00 m**.
|
|
402
|
+
- **Every player's controller needs `camera: "none"`**, and one camera gets a
|
|
403
|
+
`GroupCamera` (see `incanto-gameplay-behaviors.md`) — otherwise two
|
|
404
|
+
controllers and the group camera all write the same camera every frame.
|
|
405
|
+
- Anything else a player operates takes the same treatment: `Carry.action`,
|
|
406
|
+
`Interactable.action`, `Shoot`'s action — name the second player's own.
|
|
407
|
+
- The playtest bot drives EVERY controller it finds, each on its own action set,
|
|
408
|
+
so a two-player game is exercised rather than half-exercised.
|
|
409
|
+
|
|
129
410
|
## Views
|
|
130
411
|
|
|
131
412
|
| view | what it does | key props |
|
|
@@ -135,7 +416,21 @@ docstring's own advice: a raised `floatHeight` makes the character weightless).
|
|
|
135
416
|
| `quarter` | fixed isometric pitch 35.264°, NO mouse look | `camDistance` 40, `mouseLook: false` |
|
|
136
417
|
| `side` | camera at +z `camDistance`, lock movement to ±x | `mouseLook: false` |
|
|
137
418
|
|
|
138
|
-
The controller drives the scene's `current` Camera3D every frame (smoothed)
|
|
419
|
+
The controller drives the scene's `current` Camera3D every frame (smoothed) —
|
|
420
|
+
unless you take it off that duty:
|
|
421
|
+
|
|
422
|
+
```json
|
|
423
|
+
{ "name": "Controller", "type": "CharacterController3D",
|
|
424
|
+
"props": { "view": "quarter", "camera": "none" } }
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**`camera: "none"` leaves the camera exactly where the scene puts it.** A whole
|
|
428
|
+
shelf of games has a FIXED view — a board game, a bomb arena, an isometric
|
|
429
|
+
puzzle, a fixed-angle horror — and could not use this controller at all, because
|
|
430
|
+
it re-posed their camera every frame. Pair it with an authored `Camera3D` (and
|
|
431
|
+
a `FollowCamera` behaviour if the board scrolls); `incanto-check` no longer
|
|
432
|
+
calls that pair a fight. The default is `"drive"`, which is what every existing
|
|
433
|
+
scene already gets.
|
|
139
434
|
Wheel zooms only when `zoomMax > zoomMin`.
|
|
140
435
|
|
|
141
436
|
In `free` view the camera AIMS at the character every frame (`lookAt`), so the
|
|
@@ -160,7 +455,7 @@ enemy never yanks the camera in. `firstPerson` skips it (the camera is at the ey
|
|
|
160
455
|
|
|
161
456
|
## Animations
|
|
162
457
|
|
|
163
|
-
`controller.state` is `idle | walk | run | fastRun | airborne`; the
|
|
458
|
+
`controller.state` is `idle | walk | run | fastRun | airborne | swim | climb | crouch | sneak | dash`; the
|
|
164
459
|
`movementStateChanged` signal fires on transitions — map states to clips
|
|
165
460
|
(`$animation` assets are retargeted onto the skin's own rig — GLB and VRM alike — so a
|
|
166
461
|
clip never changes your character's proportions):
|
|
@@ -168,13 +463,23 @@ clip never changes your character's proportions):
|
|
|
168
463
|
```ts
|
|
169
464
|
controller.on('movementStateChanged', (state) => {
|
|
170
465
|
skin.animation = { idle: '$idle', run: '$run', fastRun: '$runFast',
|
|
171
|
-
airborne: '$jump', walk: '$walk'
|
|
466
|
+
airborne: '$jump', walk: '$walk', swim: '$swim',
|
|
467
|
+
climb: '$climb' }[state];
|
|
172
468
|
});
|
|
173
469
|
```
|
|
174
470
|
|
|
175
471
|
Setting `animation` to a new clip **crossfades** (0.2s blend) from the current
|
|
176
472
|
one — idle↔walk↔run↔jump transitions are smooth, not a hard cut.
|
|
177
473
|
|
|
474
|
+
**A state your map does not name falls back — it never leaves the last clip
|
|
475
|
+
playing.** `swim → walk → idle`, `dash → fastRun → run → walk → idle`,
|
|
476
|
+
`sneak`/`climb → walk → idle`, `crouch`/`airborne → idle`. Every chain ends at
|
|
477
|
+
`idle`, so a map with one clip in it still animates. Without this, a scene that
|
|
478
|
+
mapped only the original five (which is every scene there was) put a swimmer
|
|
479
|
+
across the bay in a frozen JUMP pose, and did the same for `dash`, `climb`,
|
|
480
|
+
`sneak` and `crouch`. Map the state itself when you have the clip: the shipped
|
|
481
|
+
water templates now declare `swim` (`mixamorig/swim.glb` on the agent8 CDN).
|
|
482
|
+
|
|
178
483
|
## Reskin one model with `tint` (many variants from one GLB)
|
|
179
484
|
|
|
180
485
|
`ModelInstance3D` has a `tint` prop (hex, `""` = off) that MULTIPLIES into every
|
|
@@ -225,7 +530,7 @@ Map movement states straight in JSON — no behavior needed:
|
|
|
225
530
|
{ "name": "Controller", "type": "CharacterController3D",
|
|
226
531
|
"props": { "view": "free",
|
|
227
532
|
"animations": { "idle": "$idle", "walk": "$walk", "run": "$run",
|
|
228
|
-
"fastRun": "$sprint", "airborne": "$jump" } } }
|
|
533
|
+
"fastRun": "$sprint", "airborne": "$jump", "swim": "$swim" } } }
|
|
229
534
|
```
|
|
230
535
|
|
|
231
536
|
The controller writes `skin.animation` on every state change (crossfaded by
|
|
@@ -233,7 +538,7 @@ the model). The `movementStateChanged` signal still fires for extras.
|
|
|
233
538
|
|
|
234
539
|
**Where those clips come from.** Locomotion clips live at
|
|
235
540
|
`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
|
|
541
|
+
(`idle-00`, `walk`, `run-medium`, `run-fast`, `jump`, `swim`) and play on any mixamorig
|
|
237
542
|
model as they are — retargeted through the humanoid map for a VRM. You do not
|
|
238
543
|
have to assemble any of this by hand: run the character URL your asset MCP gave
|
|
239
544
|
you through `bunx incanto-model <url>` and it prints the whole thing — assets,
|
|
@@ -292,6 +597,11 @@ NPCs feel alive when they track you. One node, zero code:
|
|
|
292
597
|
- `weight` (0.85) is how far the head commits; `forwardAxis` defaults to the
|
|
293
598
|
mixamo head convention (+z).
|
|
294
599
|
- Also good for chests (`bone: "Spine2"`, small weight) and turrets.
|
|
600
|
+
- **`node.engaged`** (0..1) is how far it is committed RIGHT NOW — the whole
|
|
601
|
+
observable effect of this node, and until now nothing could read it. It turns
|
|
602
|
+
a rendered bone, so it stays 0 in a headless run (no GLB, no skeleton): check
|
|
603
|
+
it in the browser, where `attachedBone` tells you the bone was found and
|
|
604
|
+
`engaged` tells you the head is following.
|
|
295
605
|
|
|
296
606
|
## Facing a direction in 3D — the +Z-FORWARD rule (read before turning ANY skin)
|
|
297
607
|
|
|
@@ -383,6 +693,12 @@ This cannot be an `auditScene` warning: a scene file has no velocity, and the bu
|
|
|
383
693
|
is a behavior writing a heading at runtime. `facingReport()` from `incanto/test`
|
|
384
694
|
is the same check for your own tests.
|
|
385
695
|
|
|
696
|
+
Like the feel numbers above it, this presses your title screen's START before
|
|
697
|
+
driving the character — a paused world moves nowhere, and `did not move —
|
|
698
|
+
nothing to compare` was the answer it gave about three shipped games whose only
|
|
699
|
+
fault was that their menu was up. When nothing on screen starts the game it says
|
|
700
|
+
THAT instead.
|
|
701
|
+
|
|
386
702
|
## Facing the mouse in a top-down view (twin-stick)
|
|
387
703
|
|
|
388
704
|
In `quarter` view the mouse does not look, and the controller turns the skin
|
|
@@ -404,29 +720,76 @@ Measured on `examples/survivor-3d`: a cursor 10 m east is yaw 90°, and a bolt
|
|
|
404
720
|
given `direction: [dx, 0, dz]` flies where the skin looks. Boot with
|
|
405
721
|
`pointer: { lockOnClick: false }` — a locked cursor is no aim at all.
|
|
406
722
|
|
|
407
|
-
## Lock-on and a dodge
|
|
723
|
+
## Lock-on (`lockOnAction`) and a dodge
|
|
408
724
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
725
|
+
Lock-on used to be a one-line recipe — the camera yaw toward the enemy, every
|
|
726
|
+
frame — and the half it never had was the one that matters: the SKIN kept
|
|
727
|
+
facing the move direction, so a locked player circling a brute showed it a
|
|
728
|
+
shoulder, and a sword hitbox hung off the skin's yaw swung sideways. The
|
|
729
|
+
controller owns it now:
|
|
413
730
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
731
|
+
| prop | default | what it does |
|
|
732
|
+
|---|---|---|
|
|
733
|
+
| `lockOnAction` | `""` | the button that toggles the lock (`""` = no lock-on) |
|
|
734
|
+
| `lockGroup` | `"enemy"` | who can be locked: the nearest live, visible member within… |
|
|
735
|
+
| `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 |
|
|
736
|
+
|
|
737
|
+
```json
|
|
738
|
+
{ "name": "Controller", "type": "CharacterController3D",
|
|
739
|
+
"props": { "view": "free", "lockOnAction": "lockOn", "lockRange": 14 } }
|
|
418
740
|
```
|
|
419
741
|
|
|
420
|
-
|
|
421
|
-
|
|
742
|
+
**Locked means the character FACES the target while its feet go anywhere.**
|
|
743
|
+
The camera yaw follows player → target every frame, so the stick — which is
|
|
744
|
+
camera-relative — strafes round it; the skin turns to the target (at
|
|
745
|
+
`turnSpeed`) even standing still; `state` stays `walk`/`run` while circling.
|
|
746
|
+
Anything you hang off the skin's yaw — `examples/melee-3d`'s `Edge` hitbox at
|
|
747
|
+
`sin/cos(skin yaw)` — swings at the target. Methods `lock(node)`, `unlock()`,
|
|
748
|
+
`toggleLock()`; readback `lockTarget`; signal `lockChanged(target | null)`.
|
|
749
|
+
`examples/boss-3d` locks onto the colossus with `lockGroup: "boss"`,
|
|
750
|
+
`lockRange: 40`.
|
|
751
|
+
|
|
752
|
+
`incanto-feel` knows: a controller with a `lockTarget` is reported as
|
|
753
|
+
`LOCKED ON` rather than measured against its travel — a strafe is the
|
|
754
|
+
intended reading, not a defect.
|
|
755
|
+
|
|
756
|
+
A dodge is a dash plus i-frames that start BEFORE the hit. The dash is the
|
|
757
|
+
controller's (`dashSpeed` — see below); the i-frames are `Health.protect`,
|
|
758
|
+
not `invulnerableFor` (which opens only after one), hung on the `dashed`
|
|
759
|
+
signal:
|
|
422
760
|
|
|
761
|
+
```json
|
|
762
|
+
{ "name": "Ctl", "type": "CharacterController3D",
|
|
763
|
+
"props": { "dashSpeed": 9, "dashSeconds": 0.35, "dashCooldown": 0.5 } }
|
|
764
|
+
```
|
|
423
765
|
```ts
|
|
424
|
-
(player.behavior as Health).protect(0.35);
|
|
425
|
-
body.linearVelocity = [dir[0] * 9, body.linearVelocity[1], dir[2] * 9];
|
|
766
|
+
controller.on('dashed', () => (player.behavior as Health).protect(0.35));
|
|
426
767
|
```
|
|
427
768
|
|
|
428
|
-
|
|
429
|
-
|
|
769
|
+
## A dash / dodge roll (`dashSpeed`)
|
|
770
|
+
|
|
771
|
+
Two examples carried the same twelve lines — a timer, a direction, and
|
|
772
|
+
`body.linearVelocity = dir × 9` written EVERY frame because the controller
|
|
773
|
+
brakes toward its own pace and a single impulse dies — while
|
|
774
|
+
`CharacterController2D` had `dashSpeed` since 0.33. Now the 3D controller
|
|
775
|
+
has it too:
|
|
776
|
+
|
|
777
|
+
| prop | default | what it does |
|
|
778
|
+
|---|---|---|
|
|
779
|
+
| `dashSpeed` | `0` | m/s for the roll; `0` = no dash |
|
|
780
|
+
| `dashSeconds` | `0.25` | how long the velocity is held |
|
|
781
|
+
| `dashCooldown` | `0` | seconds after a roll ends before the next can start |
|
|
782
|
+
| `dashAction` | `dash` | pressed = roll (tolerated when missing, like `sprint`) |
|
|
783
|
+
| `dashBackstep` | `false` | with no stick, roll AWAY from where the skin faces — the Souls-like backstep a lock-on game wants |
|
|
784
|
+
|
|
785
|
+
- The roll goes where the stick points (camera-relative), or with no stick
|
|
786
|
+
where the skin faces — or away from it with `dashBackstep`, which is what
|
|
787
|
+
a neutral roll means while locked on to something.
|
|
788
|
+
- `state` reads `dash` for the roll — map a clip to it — and `dashed` fires
|
|
789
|
+
once per roll. Not while crouched, on a ladder or swimming.
|
|
790
|
+
- The velocity is written last, every step of the roll, over whatever the
|
|
791
|
+
step's impulses did; gravity still runs, so a roll off a ledge falls.
|
|
792
|
+
- `examples/melee-3d` rolls with it and keeps its i-frames on `dashed`.
|
|
430
793
|
|
|
431
794
|
## First-person weapon viewmodel + the tracer-from-the-muzzle rule
|
|
432
795
|
|
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
|
|