incanto 0.72.0 → 0.74.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-check.mjs +114 -42
- package/bin/incanto-verify.mjs +9 -2
- package/dist/2d.d.ts +29 -4
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +137 -15
- package/dist/3d.js +6 -6
- package/dist/{agent8-BqZ0_O1u.js → agent8-CHTT4unP.js} +1 -1
- package/dist/{audio-player-ioZ7VKC6.d.ts → audio-player-BOrk4eQx.d.ts} +1 -1
- package/dist/{behavior-Bf9P9oB2.d.ts → behavior-J1cmVah0.d.ts} +8 -0
- package/dist/{create-game-CmFAtLZp.js → create-game-BAiA-FjP.js} +7 -7
- package/dist/{create-game-C9rWqqAC.js → create-game-ssvGvP7W.js} +7 -7
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +1 -1
- package/dist/editor.js +965 -893
- package/dist/{environment-presets-BeHoaGhy.js → environment-presets-BkqlWewf.js} +277 -60
- package/dist/{gameplay-BlQe-M07.js → gameplay-Bxe1sMVT.js} +611 -39
- package/dist/gameplay.d.ts +141 -7
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +6 -4
- package/dist/index.js +6 -6
- package/dist/{loader-CBfQzB6S.d.ts → loader-BAeWMYfF.d.ts} +1 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +2 -2
- package/dist/{physics-2d-DrIgTlLk.js → physics-2d-C-SfvjzH.js} +94 -11
- package/dist/{physics-3d-BzOCw1VS.js → physics-3d-rOzeg890.js} +202 -15
- package/dist/{picking-C9McVJrO.js → picking-BMV34Pjl.js} +2 -2
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-5IYfooAN.js → register-CjociOtt.js} +19 -4
- package/dist/{register-C8HuRkOf.js → register-DRQyZKGU.js} +45 -9
- package/dist/{replay-datiAE-b.js → replay-B6BbgGM_.js} +42 -6
- package/dist/{replay-CBEChqXq.d.ts → replay-wZK5Ok5c.d.ts} +1 -1
- package/dist/{save-slots-CEuJPUle.js → save-slots-CEUGeLuB.js} +11 -2
- package/dist/{split-screen-Bm5DTElG.js → split-screen-BxZ5rBe7.js} +4 -4
- package/dist/{split-screen-DkqLng6q.d.ts → split-screen-D6sx8z3Z.d.ts} +2 -2
- package/dist/{sprite-animation-CqR2o3SA.js → sprite-animation-Bz3DHD_4.js} +1 -1
- package/dist/{src-DRSsRleD.js → src-CqtvYtSN.js} +1 -1
- package/dist/{test-Djuq3Vhq.js → test-CEl0y2Sw.js} +162 -30
- package/dist/test.d.ts +56 -5
- package/dist/test.js +2 -2
- package/dist/{touch-BnCyPA0G.js → touch-CioTZB-y.js} +1 -1
- package/dist/vite.js +3 -3
- package/editor/assets/{agent8-CMKz2cax.js → agent8-BlzturGi.js} +1 -1
- package/editor/assets/{debug-DLrQm-az.js → debug-BMUi8usj.js} +1 -1
- package/editor/assets/{index-BEKuxYcE.js → index-CrUCQoaB.js} +53 -53
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +634 -10
- package/skills/incanto-3d-character.md +113 -5
- package/skills/incanto-assets.md +9 -3
- package/skills/incanto-behaviors-and-scripts.md +26 -2
- package/skills/incanto-building-2d-games.md +9 -0
- package/skills/incanto-building-3d-games.md +45 -0
- package/skills/incanto-editor.md +6 -1
- package/skills/incanto-environment.md +7 -2
- package/skills/incanto-game-feel.md +16 -11
- package/skills/incanto-gameplay-behaviors.md +179 -16
- package/skills/incanto-node-reference.md +124 -45
- package/skills/incanto-physics-and-input.md +67 -5
- package/skills/incanto-scene-json-authoring.md +9 -3
- package/skills/incanto-verifying-your-game.md +19 -2
- package/templates-app/beacon-isle-3d/PROJECT/Status.md +8 -0
- package/templates-app/beacon-isle-3d/PROJECT/Structure.md +3 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +136 -0
- package/templates-app/beacon-isle-3d/verify.ts +144 -0
- package/templates-app/molehill-2d/package.json +1 -1
- package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
- package/templates-app/platformer-2d/PROJECT/Structure.md +3 -0
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/game.scene.json +126 -0
- package/templates-app/platformer-2d/verify.ts +54 -0
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/tps-3d/PROJECT/Context.md +1 -1
- package/templates-app/tps-3d/PROJECT/Status.md +40 -1
- package/templates-app/tps-3d/PROJECT/Structure.md +19 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/behaviors.ts +9 -0
- package/templates-app/tps-3d/src/cover.scene.json +26 -0
- package/templates-app/tps-3d/src/game.scene.json +331 -75
- package/templates-app/tps-3d/src/main.ts +14 -0
- package/templates-app/tps-3d/src/pillar.scene.json +25 -0
- package/templates-app/tps-3d/verify.ts +582 -3
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -1350,6 +1350,132 @@
|
|
|
1350
1350
|
}
|
|
1351
1351
|
],
|
|
1352
1352
|
"uid": "n_i3xs3gnb3cyrar1u"
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
"name": "CourseFloor",
|
|
1356
|
+
"type": "StaticBody2D",
|
|
1357
|
+
"uid": "n_9p55c0wu33vbe194",
|
|
1358
|
+
"props": { "position": [3300, 520], "collider": { "shape": "rect", "size": [900, 120] } },
|
|
1359
|
+
"children": [
|
|
1360
|
+
{
|
|
1361
|
+
"name": "Skin",
|
|
1362
|
+
"type": "ColorRect2D",
|
|
1363
|
+
"uid": "n_56chxbokw5okl2ar",
|
|
1364
|
+
"props": { "size": [900, 120], "color": "#6b5638", "anchor": [0.5, 0.5] }
|
|
1365
|
+
}
|
|
1366
|
+
]
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
"name": "Trampoline",
|
|
1370
|
+
"type": "StaticBody2D",
|
|
1371
|
+
"uid": "n_vqjbknb54e0zq787",
|
|
1372
|
+
"props": {
|
|
1373
|
+
"position": [3000, 455],
|
|
1374
|
+
"collider": { "shape": "rect", "size": [120, 10] },
|
|
1375
|
+
"restitution": 1
|
|
1376
|
+
},
|
|
1377
|
+
"children": [
|
|
1378
|
+
{
|
|
1379
|
+
"name": "Skin",
|
|
1380
|
+
"type": "ColorRect2D",
|
|
1381
|
+
"uid": "n_bed7i5blyg8p63ij",
|
|
1382
|
+
"props": { "size": [120, 10], "color": "#3b82f6", "anchor": [0.5, 0.5] }
|
|
1383
|
+
}
|
|
1384
|
+
]
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
"name": "Bouncer",
|
|
1388
|
+
"type": "RigidBody2D",
|
|
1389
|
+
"uid": "n_8vd73zp82idtm5gx",
|
|
1390
|
+
"props": { "position": [3000, 150], "collider": { "shape": "circle", "radius": 14 } },
|
|
1391
|
+
"children": [
|
|
1392
|
+
{
|
|
1393
|
+
"name": "Skin",
|
|
1394
|
+
"type": "ColorRect2D",
|
|
1395
|
+
"uid": "n_n18pdy731ltnmb3l",
|
|
1396
|
+
"props": { "size": [28, 28], "color": "#f4f1de", "anchor": [0.5, 0.5] }
|
|
1397
|
+
}
|
|
1398
|
+
]
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
"name": "Ice",
|
|
1402
|
+
"type": "StaticBody2D",
|
|
1403
|
+
"uid": "n_zlp89ikkls0xkbwn",
|
|
1404
|
+
"props": {
|
|
1405
|
+
"position": [3400, 455],
|
|
1406
|
+
"collider": { "shape": "rect", "size": [400, 10] },
|
|
1407
|
+
"friction": 0
|
|
1408
|
+
},
|
|
1409
|
+
"children": [
|
|
1410
|
+
{
|
|
1411
|
+
"name": "Skin",
|
|
1412
|
+
"type": "ColorRect2D",
|
|
1413
|
+
"uid": "n_opic7894wramqynx",
|
|
1414
|
+
"props": { "size": [400, 10], "color": "#bfe9ff", "anchor": [0.5, 0.5] }
|
|
1415
|
+
}
|
|
1416
|
+
]
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
"name": "Sled",
|
|
1420
|
+
"type": "RigidBody2D",
|
|
1421
|
+
"uid": "n_nhcrprd8no2pzb9i",
|
|
1422
|
+
"props": {
|
|
1423
|
+
"position": [3215, 435],
|
|
1424
|
+
"collider": { "shape": "circle", "radius": 14 },
|
|
1425
|
+
"linearVelocity": [260, 0]
|
|
1426
|
+
},
|
|
1427
|
+
"children": [
|
|
1428
|
+
{
|
|
1429
|
+
"name": "Skin",
|
|
1430
|
+
"type": "ColorRect2D",
|
|
1431
|
+
"uid": "n_y2owjzo7khqchken",
|
|
1432
|
+
"props": { "size": [28, 28], "color": "#8c6a3f", "anchor": [0.5, 0.5] }
|
|
1433
|
+
}
|
|
1434
|
+
]
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
"name": "Hound",
|
|
1438
|
+
"type": "CharacterBody2D",
|
|
1439
|
+
"uid": "n_kmnt12lx070t311c",
|
|
1440
|
+
"props": { "position": [3650, 440], "collider": { "shape": "rect", "size": [30, 40] } },
|
|
1441
|
+
"children": [
|
|
1442
|
+
{
|
|
1443
|
+
"name": "AI",
|
|
1444
|
+
"type": "Node2D",
|
|
1445
|
+
"uid": "n_juz8aw6j311cstbs",
|
|
1446
|
+
"script": {
|
|
1447
|
+
"name": "Chase",
|
|
1448
|
+
"props": {
|
|
1449
|
+
"target": "/root/Player",
|
|
1450
|
+
"speed": 160,
|
|
1451
|
+
"stopRange": 24,
|
|
1452
|
+
"loseRange": 500,
|
|
1453
|
+
"moveParent": true,
|
|
1454
|
+
"ground": true
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
"name": "Skin",
|
|
1460
|
+
"type": "ColorRect2D",
|
|
1461
|
+
"uid": "n_k5h72ttrivo8vy0m",
|
|
1462
|
+
"props": { "size": [30, 40], "color": "#6b4d8a", "anchor": [0.5, 0.5] }
|
|
1463
|
+
}
|
|
1464
|
+
]
|
|
1465
|
+
},
|
|
1466
|
+
{
|
|
1467
|
+
"name": "Perch",
|
|
1468
|
+
"type": "StaticBody2D",
|
|
1469
|
+
"uid": "n_t6vi97e88isgglef",
|
|
1470
|
+
"props": { "position": [3650, 300], "collider": { "shape": "rect", "size": [160, 20] } },
|
|
1471
|
+
"children": [
|
|
1472
|
+
{
|
|
1473
|
+
"name": "Skin",
|
|
1474
|
+
"type": "ColorRect2D",
|
|
1475
|
+
"uid": "n_m7qcxz6z1sc6rfbv",
|
|
1476
|
+
"props": { "size": [160, 20], "color": "#9aa3bd", "anchor": [0.5, 0.5] }
|
|
1477
|
+
}
|
|
1478
|
+
]
|
|
1353
1479
|
}
|
|
1354
1480
|
],
|
|
1355
1481
|
"uid": "n_d9t51gs0vhpnp2jj"
|
|
@@ -273,3 +273,57 @@ interface Player {
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
console.log(process.exitCode ? '\nVERIFY FAILED' : '\nVERIFY OK — Castle Run plays end to end');
|
|
276
|
+
|
|
277
|
+
// ---- TWIN: this round's 3D surfaces, in two dimensions ----------------------------
|
|
278
|
+
// StaticBody2D.restitution/friction (#1295) and Chase.ground (#1297) were built
|
|
279
|
+
// with the 3D case in hand and 2D tests beside it; a course past the last
|
|
280
|
+
// ground slab composes them in the 2D flagship and measures each. y is DOWN.
|
|
281
|
+
{
|
|
282
|
+
type Body = { position: number[] };
|
|
283
|
+
const bouncerY: number[] = [];
|
|
284
|
+
let sledX = 0;
|
|
285
|
+
let houndY = 0;
|
|
286
|
+
let houndX = 0;
|
|
287
|
+
const result = await runScript(sceneJson, {
|
|
288
|
+
behaviors,
|
|
289
|
+
durationMs: 4200,
|
|
290
|
+
steps: [
|
|
291
|
+
...Array.from({ length: 30 }, (_, i) => ({
|
|
292
|
+
atMs: 100 + i * 100,
|
|
293
|
+
do: (ctx: { getNode(p: string): unknown }) => {
|
|
294
|
+
bouncerY.push((ctx.getNode('Bouncer') as Body).position[1] ?? 0);
|
|
295
|
+
},
|
|
296
|
+
})),
|
|
297
|
+
{
|
|
298
|
+
atMs: 3100,
|
|
299
|
+
do: (ctx) => {
|
|
300
|
+
sledX = (ctx.getNode('Sled') as unknown as Body).position[0] ?? 0;
|
|
301
|
+
// the player on the perch above the hound: a ground chaser closes in below, never up
|
|
302
|
+
const player = ctx.getNode('Player') as unknown as Body;
|
|
303
|
+
player.position = [3650, 260];
|
|
304
|
+
for (let i = 0; i < 120; i++) ctx.engine.step();
|
|
305
|
+
const hound = ctx.getNode('Hound') as unknown as Body;
|
|
306
|
+
houndX = hound.position[0] ?? 0;
|
|
307
|
+
houndY = hound.position[1] ?? 0;
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
ok('runScript reported no failures (TWIN path)', result.ok);
|
|
313
|
+
let touch = bouncerY.findIndex((y, i) => i > 0 && y < (bouncerY[i - 1] ?? 0));
|
|
314
|
+
if (touch < 1) touch = 1;
|
|
315
|
+
const low = bouncerY[touch - 1] ?? 0;
|
|
316
|
+
const rebound = low - Math.min(...bouncerY.slice(touch));
|
|
317
|
+
ok(
|
|
318
|
+
`StaticBody2D restitution 1: a dead ball climbs back ${rebound.toFixed(0)} px of its 300 px drop`,
|
|
319
|
+
rebound > 180,
|
|
320
|
+
);
|
|
321
|
+
ok(
|
|
322
|
+
`StaticBody2D friction 0: the sled slides to x ${sledX.toFixed(0)} across the ice (from 3215)`,
|
|
323
|
+
sledX > 3450,
|
|
324
|
+
);
|
|
325
|
+
ok(
|
|
326
|
+
`Chase.ground: the hound stays on the floor (y ${houndY.toFixed(0)}, floor 440) under a player on the perch, ${Math.abs(houndX - 3650).toFixed(0)} px off its x`,
|
|
327
|
+
houndY > 400 && Math.abs(houndX - 3650) < 60,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"Vanguard" — a 3D THIRD-PERSON shooter. The camera rides over your soldier's
|
|
6
6
|
shoulder (you SEE your character); the crosshair is where the hitscan rifle
|
|
7
7
|
fires. Move with WASD, look with the mouse (click locks the pointer), Shift to
|
|
8
|
-
sprint, Space to jump, click / F to fire, R to reload, Enter to restart. Survive and clear three
|
|
8
|
+
sprint, Space to jump (tap for a short hop, hold for the full jump, press again in the air for a second), click / F to fire, R to reload, Enter to restart. Survive and clear three
|
|
9
9
|
escalating waves of chasing enemies to **SECURE THE AREA**; let them drain your
|
|
10
10
|
HP to 0 and the **MISSION FAILS**.
|
|
11
11
|
|
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
sun + fill light.
|
|
8
8
|
- **Player** (RigidBody3D, group `player`, `Health` max 100 / 0.6s i-frames):
|
|
9
9
|
`Controller` (CharacterController3D `view: 'free'`, camDistance 4.2, eyeHeight
|
|
10
|
-
1.1, maxSpeed 4.2, `Shoot` script
|
|
10
|
+
1.1, maxSpeed 4.2, `Shoot` script — and the whole game-feel table: coyote
|
|
11
|
+
0.12 s, jump buffer 0.15 s, `jumpCutMultiplier` 0.45 for a variable-height
|
|
12
|
+
jump, `maxJumps` 2, `airControl` 0.5, `fallGravity` 3; `bun run verify`
|
|
13
|
+
measures each one rather than reading it); a real GLB `Skin` whose UPPER
|
|
14
|
+
BODY plays `$anims/shoot` on every shot (`animationUpper`) while the legs
|
|
15
|
+
keep the controller's run/idle clip — the two-layer animation the 3D
|
|
16
|
+
character skill documents and no example had composed; a real GLB `Skin` (ModelInstance3D
|
|
11
17
|
`base-model.glb` + mixamo idle/walk/run/jump clips); a `Muzzle` Node3D
|
|
12
18
|
(shot/kill/empty SFX + flash light).
|
|
13
19
|
- **Spawner** (WaveSpawner, 3 waves of 4/6/8) cloning a hidden `EnemyTemplate`
|
|
@@ -39,3 +45,36 @@
|
|
|
39
45
|
(the CDN set is idle/walk/run/jump).
|
|
40
46
|
- No cover/peek mechanic, no projectile-drop weapons (hitscan only). Co-op
|
|
41
47
|
multiplayer lives in the sibling `examples/tps-3d-multiplay`.
|
|
48
|
+
|
|
49
|
+
## stepHeight and jumpVelocity (2026-09-04)
|
|
50
|
+
|
|
51
|
+
The controller declares `stepHeight: 0.4` and `jumpVelocity: 5`; a 0.3 m `Curb` and a
|
|
52
|
+
0.9 m `Ledge` sit at z −12 (away from the fight) and `verify.ts` walks the player into
|
|
53
|
+
both — over the one (y 0.84 → 1.14), stopped by the other — and compares the jump apex
|
|
54
|
+
against the same scene at `jumpVelocity 4` (1.26 u vs 0.81 u).
|
|
55
|
+
|
|
56
|
+
## slopeLimitDeg, and the chaser that flew (2026-09-04)
|
|
57
|
+
|
|
58
|
+
The enemy prefab declares `slopeLimitDeg: 50` and `stepHeight: 0.7`; a 40° `Hill` and a
|
|
59
|
+
70° `Cliff` (each 4 m of slab onto a plateau) sit at the far corner (x 7–22, z −20).
|
|
60
|
+
`verify.ts` puts one enemy at the foot of each with the player on top: it climbs the
|
|
61
|
+
hill and reaches (rose 2.64 m), and is stopped by the cliff (rose 0.32 m, 3.2 m short).
|
|
62
|
+
Composing this found `Chase` flying its body straight at an elevated target; a
|
|
63
|
+
`CharacterBody3D` chaser now walks (`Chase.ground`), and the contact run went from
|
|
64
|
+
3 hits to 11 once enemies climbed the 0.6 m Ramp by `stepHeight` rather than by the
|
|
65
|
+
pile behind them.
|
|
66
|
+
|
|
67
|
+
## cameraCollision and platformCarry (2026-09-04)
|
|
68
|
+
|
|
69
|
+
Both declared on the controller; the arena gained a `Lift` (StaticBody3D walked by
|
|
70
|
+
`Patrol`, pingpong x −18 → −10). `verify.ts` asserts: a wall behind the player closes the
|
|
71
|
+
camera to 0.45 m (4.2 in the open); standing still on the lift for two seconds, it travels
|
|
72
|
+
4 m and the rider drifts under 0.25 m. `floatHeight` was tried and taken back out — the spring sags
|
|
73
|
+
~0.13 m, so 0.05 rests exactly where 0.01 does (0.84); the number is in the skill.
|
|
74
|
+
|
|
75
|
+
## Catch (2026-09-04)
|
|
76
|
+
|
|
77
|
+
`Player/Catch` (a `Respawn`, auto line) puts back a player who leaves the arena: the Hill
|
|
78
|
+
and Cliff plateaus, the Lift and a double jump reach over the 3 m walls (24 playtests fell
|
|
79
|
+
3 times before it, none after). `verify.ts` drops the player over Wall1 and asserts the catch.
|
|
80
|
+
|
|
@@ -42,3 +42,22 @@ Headless proof (`bun run verify`) that the loop plays end to end — move, waves
|
|
|
42
42
|
Chase closing in, the autopilot aiming + firing the hitscan to clear all waves
|
|
43
43
|
(WIN), and an enemy hitbox draining the player to 0 (LOSE) — via `incanto/test`'s
|
|
44
44
|
`runScript` at a seeded fixed timestep.
|
|
45
|
+
|
|
46
|
+
## Prefabs
|
|
47
|
+
|
|
48
|
+
`src/cover.scene.json` and `src/pillar.scene.json` are the two cover blocks and
|
|
49
|
+
two pillars of the arena, placed from `game.scene.json` with `instance:` and a
|
|
50
|
+
`position` override each — the first 3D prefabs in the repo. `main.ts` hands
|
|
51
|
+
the loader every `*.scene.json` beside it through vite's `import.meta.glob`;
|
|
52
|
+
`verify.ts` imports the two by name (bun has no glob). Every CLI resolves a
|
|
53
|
+
prefab relative to the scene on its own; the harness and the game are the two
|
|
54
|
+
places that say so themselves.
|
|
55
|
+
|
|
56
|
+
- `Arena/Curb`, `Arena/Ledge` — a 0.3 m step the player climbs and a 0.9 m face that stops it,
|
|
57
|
+
the harness's fixtures for `stepHeight`.
|
|
58
|
+
- `Arena/Hill`, `Arena/HillTop`, `Arena/Cliff`, `Arena/CliffTop` — a 40° slope an enemy climbs and a
|
|
59
|
+
70° face it cannot, the harness's fixtures for `slopeLimitDeg`.
|
|
60
|
+
- `Arena/Lift` — a moving platform (`Patrol` on a `StaticBody3D`), the harness's fixture for
|
|
61
|
+
`platformCarry`.
|
|
62
|
+
- `Player/Catch` — a `Respawn` that returns a player who got over the walls to the spawn.
|
|
63
|
+
|
|
@@ -140,12 +140,16 @@ export class Shoot extends Behavior {
|
|
|
140
140
|
override onReady(): void {
|
|
141
141
|
const skin = this.player.getNodeOrNull('Skin') as ModelInstance3D | null;
|
|
142
142
|
if (!skin) return;
|
|
143
|
+
this.skin = skin;
|
|
143
144
|
this.controller.on('movementStateChanged', (state) => {
|
|
144
145
|
const clip = STATE_CLIPS[state as string];
|
|
145
146
|
if (clip) skin.animation = clip;
|
|
146
147
|
});
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/** The visible soldier — the upper-body layer plays the shot on it. */
|
|
151
|
+
private skin: ModelInstance3D | null = null;
|
|
152
|
+
|
|
149
153
|
override update(dt: number): void {
|
|
150
154
|
this.clock += dt * 1000;
|
|
151
155
|
|
|
@@ -192,6 +196,11 @@ export class Shoot extends Behavior {
|
|
|
192
196
|
this.ammo--;
|
|
193
197
|
this.muzzle('ShotSfx')?.play();
|
|
194
198
|
this.flashUntil = this.clock + FLASH_MS;
|
|
199
|
+
// The shot plays on the UPPER BODY only — `animation` (idle/run, driven by
|
|
200
|
+
// the controller) keeps the legs going, so the soldier fires while running
|
|
201
|
+
// instead of freezing mid-stride. A one-shot: the layer clears itself when
|
|
202
|
+
// the clip ends, and a shot fired before that restarts it.
|
|
203
|
+
if (this.skin) this.skin.animationUpper = '$anims/shoot';
|
|
195
204
|
|
|
196
205
|
const eye = this.eye();
|
|
197
206
|
const fwd = this.forward();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"type": "scene",
|
|
4
|
+
"dimension": "3d",
|
|
5
|
+
"name": "Cover",
|
|
6
|
+
"root": {
|
|
7
|
+
"name": "Cover",
|
|
8
|
+
"type": "StaticBody3D",
|
|
9
|
+
"props": { "collider": { "shape": "box", "size": [2, 1.2, 2] } },
|
|
10
|
+
"children": [
|
|
11
|
+
{
|
|
12
|
+
"name": "Skin",
|
|
13
|
+
"type": "MeshInstance3D",
|
|
14
|
+
"props": {
|
|
15
|
+
"mesh": "box",
|
|
16
|
+
"size": [2, 1.2, 2],
|
|
17
|
+
"material": { "color": "#5b6678", "roughness": 0.85, "metalness": 0.1 },
|
|
18
|
+
"castShadow": true,
|
|
19
|
+
"receiveShadow": true
|
|
20
|
+
},
|
|
21
|
+
"uid": "n_wgm7e7zg55s8ugcn"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"uid": "n_z4ycq2ses8kh87zd"
|
|
25
|
+
}
|
|
26
|
+
}
|