incanto 0.67.0 → 0.68.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/_pick-server.mjs +48 -0
- package/bin/incanto-check.mjs +16 -2
- package/bin/incanto-frame.mjs +10 -25
- package/bin/incanto-logs.mjs +8 -18
- package/dist/2d.d.ts +6 -6
- package/dist/2d.js +6 -4
- package/dist/3d.d.ts +30 -6
- package/dist/3d.js +8 -6
- package/dist/{audio-player-BNUHo0JF.d.ts → audio-player-DOrq7sP-.d.ts} +62 -4
- package/dist/{behavior-ZnfzKDo3.d.ts → behavior-DoFPYrgo.d.ts} +78 -1
- package/dist/boot-failure-CKYrEcGF.js +47 -0
- package/dist/{create-game-FAXdHta3.js → create-game-DbWtVTxD.js} +21 -25
- package/dist/{create-game-DMetd3vR.js → create-game-IZIydDwI.js} +9 -9
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-B8QnX2-r.js → duplicate-IWIqk0HJ.js} +1 -1
- package/dist/{editor-switch-DAvWQeld.d.ts → editor-switch-CnIOiyNJ.d.ts} +1 -1
- package/dist/editor.js +11 -5
- package/dist/env.d.ts +1 -1
- package/dist/{environment-presets-C-5g2pMh.js → environment-presets-CybQXNqS.js} +57 -8
- package/dist/{gameplay-CGSv-9XR.js → gameplay-DM1eu_cV.js} +20 -2
- package/dist/gameplay.d.ts +51 -3
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.js +8 -7
- package/dist/{loader-KZESOZmE.js → loader-BC4PNtJX.js} +26 -1
- package/dist/{loader-DyT30ArB.d.ts → loader-CcB533FR.d.ts} +2 -2
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{physics-2d-_B6zj3zU.js → physics-2d-B7Y6dPZO.js} +2 -2
- package/dist/{physics-3d-i1IeAY0F.js → physics-3d-bG3n70Ky.js} +3 -3
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{register-Cs53CY7s.js → register-3ta-2Xig.js} +68 -7
- package/dist/{register-vIxU3Xbv.js → register-ibCjm-wH.js} +3 -3
- package/dist/{replay-BkpDV51O.js → replay-CYvhVHHN.js} +28 -5
- package/dist/{replay-D0GcEHYU.d.ts → replay-Dvn8aeBd.d.ts} +38 -4
- package/dist/{schema-CFeioQRE.d.ts → schema-B6ugCV1Q.d.ts} +11 -0
- package/dist/{split-screen-DS8RaUV_.js → split-screen-CYwDkbLF.js} +2 -2
- package/dist/{split-screen-DD5eYIgW.d.ts → split-screen-DhrSzZIB.d.ts} +3 -3
- package/dist/{src-BLlsGJW9.js → src-D7RIqXYF.js} +1 -1
- package/dist/{teardown-LB9cTnXJ.js → teardown-D2NEmxPB.js} +2 -2
- package/dist/{test-81uZ967a.js → test-8hoHeRmo.js} +26 -21
- package/dist/test.d.ts +6 -6
- package/dist/test.js +3 -3
- package/dist/vite.js +2 -2
- package/dist/{rendering-options-ByAdi8PF.js → webgl-unavailable-N9nQqesw.js} +45 -1
- package/editor/assets/{agent8-C2jtNKbx.js → agent8-BrrHOjMJ.js} +1 -1
- package/editor/assets/{debug-DoU3KeqS.js → debug-BBhKuBNV.js} +1 -1
- package/editor/assets/{index-CNqHojcC.js → index-eVd0BToA.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +4 -0
- package/skills/incanto-3d-character.md +1 -0
- package/skills/incanto-audio.md +49 -0
- package/skills/incanto-building-2d-games.md +37 -0
- package/skills/incanto-building-3d-games.md +37 -0
- package/skills/incanto-gameplay-behaviors.md +28 -1
- package/skills/incanto-node-reference.md +1 -0
- package/skills/incanto-physics-and-input.md +23 -0
- package/skills/incanto-scene-json-authoring.md +12 -1
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/main.ts +8 -1
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/main.ts +8 -1
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/main.ts +8 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/main.ts +8 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/main.ts +8 -1
package/editor/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Incanto Scene Editor</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' rx='3' fill='%236ee7dc'/><text x='8' y='12' text-anchor='middle' font-size='11' font-family='monospace' fill='%230e1018'>i</text></svg>" />
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-eVd0BToA.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/GameServer-C56iOUgF.js">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -54,6 +54,7 @@ did not until now.
|
|
|
54
54
|
| `maxJumps` | `1` | `2` = double jump. Extra jumps work in mid-air. |
|
|
55
55
|
| `airControl` | `0.2` | how much ground control you keep airborne (0 = committed, 1 = full). |
|
|
56
56
|
| `fallGravity` | `2.5` | gravity multiplier while falling. Higher = snappier arc. |
|
|
57
|
+
| `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). |
|
|
57
58
|
|
|
58
59
|
Every one defaults to OFF (`0` / `1`) or to the previous hard-coded constant, so
|
|
59
60
|
a scene that asks for nothing behaves exactly as before.
|
package/skills/incanto-audio.md
CHANGED
|
@@ -396,6 +396,55 @@ changed under it. `engine.music` deliberately survives a scene swap (it belongs
|
|
|
396
396
|
to the engine, not the scene): call `engine.music.stop(1)` or `crossfadeTo` when
|
|
397
397
|
the music should change.
|
|
398
398
|
|
|
399
|
+
## Sounds on a GRID: rhythm games and anything charted to a soundtrack
|
|
400
|
+
|
|
401
|
+
A sound fired from `update()` cannot land closer than one frame to where a chart
|
|
402
|
+
wants it — 16.67 ms at 60 Hz, 33.33 ms at 30, and that is the whole difference
|
|
403
|
+
between a rhythm game that feels tight and one that does not. The engine's frame
|
|
404
|
+
clock is exact (3600 steps land on `engine.time` 60.000000000 s and beat error
|
|
405
|
+
never compounds); the last 16 ms is the gap.
|
|
406
|
+
|
|
407
|
+
Schedule on the AUDIO clock instead. Queue a short LOOKAHEAD ahead of now, every
|
|
408
|
+
frame, and let Web Audio place the sound:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
const LEAD = 0.08; // schedule this far ahead of the speaker
|
|
412
|
+
let cursor = 0; // next un-queued note
|
|
413
|
+
|
|
414
|
+
engine.updated.connect(() => {
|
|
415
|
+
const horizon = engine.sfx.now + LEAD;
|
|
416
|
+
while (cursor < chart.length && startedAt + chart[cursor].atSec <= horizon) {
|
|
417
|
+
hit.play(startedAt + chart[cursor].atSec); // AudioPlayer.play(at)
|
|
418
|
+
cursor++;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
- **`engine.sfx.now`** — seconds on the audio clock, the one the sound is
|
|
424
|
+
actually placed on. `0` headless and before the first sound.
|
|
425
|
+
- **`AudioPlayer.play(at)`** and **`engine.sfx.play(params, gain, { when })`** —
|
|
426
|
+
PRESETS only. A `src` clip goes through an `<audio>` element, which has no
|
|
427
|
+
scheduling clock; `at` is ignored for one rather than approximated.
|
|
428
|
+
- A time already past plays immediately (Web Audio's own rule), so a scheduler
|
|
429
|
+
that ran late is late, not silent.
|
|
430
|
+
- Everything else still applies: the bus gain, `engine.audio.recent()`, and
|
|
431
|
+
`muted`. A scheduler hand-rolled on a raw `AudioContext` hits the same
|
|
432
|
+
accuracy and loses all three.
|
|
433
|
+
|
|
434
|
+
Measured: 0 of 64 notes were given a scheduled start on the frame path; the same
|
|
435
|
+
64 queued this way land at **|mean| 0.000000 ms**.
|
|
436
|
+
|
|
437
|
+
**Charting against a music FILE** needs the playhead of the thing you can hear,
|
|
438
|
+
not a counter you started next to `music.play()` (a different clock, which keeps
|
|
439
|
+
counting through a gesture-block, a stall or a seek):
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
const songSeconds = engine.music.playhead; // null = nothing playing, or no clock
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
It is the raw element time, so a looping track wraps to 0 each pass — a loop
|
|
446
|
+
boundary to chart against, not a fault.
|
|
447
|
+
|
|
399
448
|
## Decision guide
|
|
400
449
|
|
|
401
450
|
- **Need a quick game sound (coin/jump/hit/explosion/…)** → set `preset`. Done.
|
|
@@ -485,3 +485,40 @@ which is why a wall jump was not awkward to write but impossible.
|
|
|
485
485
|
headless bot sessions and reports what could not be reached, which wires never
|
|
486
486
|
fired, and whether anything in the level can hurt the player. See
|
|
487
487
|
`incanto-playtesting.md`.
|
|
488
|
+
|
|
489
|
+
## When the boot FAILS: say so, or the bar sits at 100% forever
|
|
490
|
+
|
|
491
|
+
`createGame2D`/`createGame2D` reject like any promise, and the loading overlay
|
|
492
|
+
every starter shows is removed on the line *after* the await. So a boot that
|
|
493
|
+
fails leaves the bar at 100% with no text, and the reason in a console the
|
|
494
|
+
player will never open. Measured with WebGL denied on a pristine scaffold: the
|
|
495
|
+
overlay was still reading `Emberwood 100%` 8.6 seconds later.
|
|
496
|
+
|
|
497
|
+
One `.catch` fixes it, and every shipped template now carries it:
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
import { createGame2D, showBootFailure } from 'incanto/2d';
|
|
501
|
+
|
|
502
|
+
const game = await createGame2D({ canvas, scene: sceneJson }).catch((e) => {
|
|
503
|
+
showBootFailure(e); // paints the reason into #loading (or its own panel)
|
|
504
|
+
throw e;
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
document.querySelector('#loading')?.remove();
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**No WebGL is the case worth naming** — the 2D renderer is WebGL too. A machine
|
|
511
|
+
with acceleration off, a blocklisted driver, a locked-down or headless browser, or a page that already
|
|
512
|
+
holds too many contexts cannot make one, and three throws
|
|
513
|
+
`Error('Error creating WebGL context.')` with nothing to branch on. The engine
|
|
514
|
+
rethrows it as an `IncantoError` with code **`WEBGL_UNAVAILABLE`** and a message
|
|
515
|
+
naming the likely causes, so you can show a real screen:
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
import { isWebGLAvailable } from 'incanto/2d';
|
|
519
|
+
|
|
520
|
+
if (!isWebGLAvailable()) showMyOwnBrowserTooOldScreen(); // ask BEFORE booting
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
`isWebGLAvailable()` probes a throwaway canvas and never touches yours; it
|
|
524
|
+
returns `false` outside a browser.
|
|
@@ -648,3 +648,40 @@ It stubs unregistered behaviors (it measures geometry, not logic; a scene with
|
|
|
648
648
|
any custom behavior used to read as clean — measured 0 floating before
|
|
649
649
|
`registerBehavior`, 26 after) and throws when it cannot measure. A level built
|
|
650
650
|
from mesh floors has no terrain height to read: use a physics raycast instead.
|
|
651
|
+
|
|
652
|
+
## When the boot FAILS: say so, or the bar sits at 100% forever
|
|
653
|
+
|
|
654
|
+
`createGame2D`/`createGame3D` reject like any promise, and the loading overlay
|
|
655
|
+
every starter shows is removed on the line *after* the await. So a boot that
|
|
656
|
+
fails leaves the bar at 100% with no text, and the reason in a console the
|
|
657
|
+
player will never open. Measured with WebGL denied on a pristine scaffold: the
|
|
658
|
+
overlay was still reading `Emberwood 100%` 8.6 seconds later.
|
|
659
|
+
|
|
660
|
+
One `.catch` fixes it, and every shipped template now carries it:
|
|
661
|
+
|
|
662
|
+
```ts
|
|
663
|
+
import { createGame3D, showBootFailure } from 'incanto/3d';
|
|
664
|
+
|
|
665
|
+
const game = await createGame3D({ canvas, scene: sceneJson }).catch((e) => {
|
|
666
|
+
showBootFailure(e); // paints the reason into #loading (or its own panel)
|
|
667
|
+
throw e;
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
document.querySelector('#loading')?.remove();
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
**No WebGL is the case worth naming.** A machine with acceleration off, a
|
|
674
|
+
blocklisted driver, a locked-down or headless browser, or a page that already
|
|
675
|
+
holds too many contexts cannot make one, and three throws
|
|
676
|
+
`Error('Error creating WebGL context.')` with nothing to branch on. The engine
|
|
677
|
+
rethrows it as an `IncantoError` with code **`WEBGL_UNAVAILABLE`** and a message
|
|
678
|
+
naming the likely causes, so you can show a real screen:
|
|
679
|
+
|
|
680
|
+
```ts
|
|
681
|
+
import { isWebGLAvailable } from 'incanto/3d';
|
|
682
|
+
|
|
683
|
+
if (!isWebGLAvailable()) showMyOwnBrowserTooOldScreen(); // ask BEFORE booting
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
`isWebGLAvailable()` probes a throwaway canvas and never touches yours; it
|
|
687
|
+
returns `false` outside a browser.
|
|
@@ -1211,7 +1211,7 @@ spawners, music, or monster aggression to it. The `hour` field (0-24) is
|
|
|
1211
1211
|
writable: jump to a time of day in one assignment. For one-off look changes
|
|
1212
1212
|
use `setEnvironment3D(engine, patch)` from `incanto/3d` directly.
|
|
1213
1213
|
|
|
1214
|
-
## Boot loading overlay
|
|
1214
|
+
## Boot loading overlay
|
|
1215
1215
|
|
|
1216
1216
|
```ts
|
|
1217
1217
|
import { preloadSceneAssets } from 'incanto';
|
|
@@ -1221,6 +1221,33 @@ await preloadSceneAssets(sceneJson.assets); // black screen + bar + %, auto-re
|
|
|
1221
1221
|
Replaces the ~125-line hand-rolled loading screen; `createLoadingOverlay()`
|
|
1222
1222
|
gives you the raw overlay when you preload something else.
|
|
1223
1223
|
|
|
1224
|
+
## Where a node actually IS (`position` is parent-relative)
|
|
1225
|
+
|
|
1226
|
+
`node.position` is the offset from its PARENT, not the world. A scene nests
|
|
1227
|
+
freely — `Terrain/Clouds/Cloud1/Puff1` four deep is what the engine's own
|
|
1228
|
+
generator writes — so a distance check reading `position` on a nested node
|
|
1229
|
+
measures from the wrong origin and says nothing about it. Measured on a crate
|
|
1230
|
+
parented to a rock: `position` is `[0, 3.02, 0]` and the world point is
|
|
1231
|
+
`[13, 14.31, 80.18]` — **82 m apart**.
|
|
1232
|
+
|
|
1233
|
+
```ts
|
|
1234
|
+
import { localFromWorld, worldPosition } from 'incanto/gameplay';
|
|
1235
|
+
|
|
1236
|
+
const here = worldPosition(crate); // [13, 14.31, 80.18]
|
|
1237
|
+
crate.position = localFromWorld(crate, [0, 20, 0]); // put it at world [0,20,0]
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
Both work headless and in both dimensions — arity comes from the node (2 for a
|
|
1241
|
+
2D node, 3 for a 3D one), and an ancestor with no `position` contributes
|
|
1242
|
+
nothing. Do NOT reach for `_ensureObject3D().matrixWorld`: it is internal, and
|
|
1243
|
+
in a headless verify script there is no renderer to parent the object, so it
|
|
1244
|
+
hands back the LOCAL position with no warning.
|
|
1245
|
+
|
|
1246
|
+
**Translation only.** They sum ancestor positions and ignore ancestor rotation
|
|
1247
|
+
and scale — the same contract physics already uses (`worldPosition2D` /
|
|
1248
|
+
`worldPosition3D`) and that `applyGroundSnap` assumes. A node under a ROTATING
|
|
1249
|
+
parent needs the renderer's matrix, not this.
|
|
1250
|
+
|
|
1224
1251
|
## Persistence (high scores, unlocks, settings)
|
|
1225
1252
|
|
|
1226
1253
|
```ts
|
|
@@ -297,6 +297,7 @@ Signals: `movementStateChanged(state)`
|
|
|
297
297
|
| `pitchMin` | `-1.3` | number |
|
|
298
298
|
| `pitchMax` | `1.5` | number |
|
|
299
299
|
| `cameraCollision` | `true` | boolean |
|
|
300
|
+
| `platformCarry` | `true` | boolean |
|
|
300
301
|
| `turnSpeed` | `100` | number |
|
|
301
302
|
| `camLerp` | `25` | number |
|
|
302
303
|
| `moveAction` | `"move"` | string |
|
|
@@ -479,6 +479,29 @@ rotating discs. It is what makes a floating-island game work.
|
|
|
479
479
|
Author it the obvious way: move the platform's `position` (a behavior, a
|
|
480
480
|
`PathFollow`, an `Oscillate`). Nothing else to declare.
|
|
481
481
|
|
|
482
|
+
**The 3D PLAYER is a different rig, and it needs one prop.** A
|
|
483
|
+
`CharacterController3D` must sit under a dynamic `RigidBody3D` (hard error
|
|
484
|
+
otherwise), not a `CharacterBody3D` — the character bodies in a 3D game are its
|
|
485
|
+
enemies and NPCs. The controller steers toward a target velocity every fixed
|
|
486
|
+
step, so pressing nothing means "target zero", which in world space is a brake
|
|
487
|
+
aimed at the platform's own motion. `platformCarry` (default **true**) makes
|
|
488
|
+
that target relative to the floor instead. Measured on a 400 m deck moving
|
|
489
|
+
4 m/s, rider pressing nothing: **95.9%** of the travel kept, **0.0%** with
|
|
490
|
+
`platformCarry: false`.
|
|
491
|
+
|
|
492
|
+
```json
|
|
493
|
+
{ "name": "Ctl", "type": "CharacterController3D",
|
|
494
|
+
"props": { "view": "free", "platformCarry": false } }
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Turn it off for a conveyor you want to be scenery. Vertical carry needs no prop
|
|
498
|
+
either way — the hover spring rides whatever surface is under it, which is why
|
|
499
|
+
a 3D elevator always looked right while a conveyor did not.
|
|
500
|
+
|
|
501
|
+
The carry comes from the platform's POSITION DELTA, not its `linearVelocity`,
|
|
502
|
+
because writing `position` calls `setTranslation` — a teleport that transfers no
|
|
503
|
+
momentum to anything. Both authoring styles work.
|
|
504
|
+
|
|
482
505
|
The carry stops the moment the character is no longer grounded on it, so walking
|
|
483
506
|
off an edge or jumping is not "sticky", and it is vertical as well as horizontal
|
|
484
507
|
— an elevator lifts you.
|
|
@@ -26,6 +26,7 @@ wiring, and composition are all in the file. TS is for *behavior only* (attached
|
|
|
26
26
|
"constants": { "UI": 1000, "Background": -100 },
|
|
27
27
|
"input": { "<action>": { "...": "...", "touch": "joystick|button (optional — mobile on-screen controls)" } },
|
|
28
28
|
"multiplayer": { "room": "auto" },
|
|
29
|
+
"fragment": false,
|
|
29
30
|
"root": { "name": "Level1", "type": "Node", "children": [] },
|
|
30
31
|
"connections": []
|
|
31
32
|
}
|
|
@@ -33,6 +34,13 @@ wiring, and composition are all in the file. TS is for *behavior only* (attached
|
|
|
33
34
|
|
|
34
35
|
- `format` MUST be `1`. `type` MUST be `"scene"`. `name` non-empty.
|
|
35
36
|
- `dimension` is optional: `"2d"` or `"3d"` only.
|
|
37
|
+
- `fragment` (optional, default `false`) says this file is a PIECE of another
|
|
38
|
+
scene — a spawner's prefab, a sub-scene something instances — and is never
|
|
39
|
+
rendered on its own. It changes nothing at runtime; it tells the checker to
|
|
40
|
+
stop asking a prefab where its camera and its sun are. Set it on any scene
|
|
41
|
+
you load from TypeScript and hand to a spawner. (A scene another scene
|
|
42
|
+
embeds with `instance` needs no key — `incanto-check` walks the project and
|
|
43
|
+
can see that for itself.)
|
|
36
44
|
- `viewport` (optional) makes scene JSON own responsive layout: author the world
|
|
37
45
|
in fixed `design` pixels (`[width, height]`, positive numbers) and the renderer
|
|
38
46
|
maps them onto any canvas size. `fit`: `"expand"` (design rect always fully
|
|
@@ -158,7 +166,10 @@ level 1's layers.
|
|
|
158
166
|
## Parents and children
|
|
159
167
|
|
|
160
168
|
EVERY node type can hold children — `children` is universal, so any node works
|
|
161
|
-
as a grouping container.
|
|
169
|
+
as a grouping container. **A child's `position` is relative to its parent**, so
|
|
170
|
+
nesting is also how you move a group: shift the parent and everything under it
|
|
171
|
+
follows. From code, `worldPosition(node)` (`incanto/gameplay`) is where a nested
|
|
172
|
+
node actually is; `node.position` is the local offset. The inverse is not true: some types demand a SPECIFIC
|
|
162
173
|
parent and are therefore also invalid as the root. Today's rule:
|
|
163
174
|
|
|
164
175
|
- `CharacterController2D` must be a direct child of a `CharacterBody2D` —
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* world with `bun run world`, the game logic never changes.
|
|
7
7
|
*/
|
|
8
8
|
import { assetUrls, preloadUrls } from 'incanto';
|
|
9
|
-
import { createGame3D } from 'incanto/3d';
|
|
9
|
+
import { createGame3D, showBootFailure } from 'incanto/3d';
|
|
10
10
|
import { BEHAVIORS } from './behaviors';
|
|
11
11
|
import gameJson from './game.scene.json';
|
|
12
12
|
|
|
@@ -25,6 +25,13 @@ const game = await createGame3D({
|
|
|
25
25
|
scene: gameJson,
|
|
26
26
|
behaviors: BEHAVIORS,
|
|
27
27
|
pointer: true,
|
|
28
|
+
}).catch((e) => {
|
|
29
|
+
// Without this the player watches the loading overlay sit at 100% forever:
|
|
30
|
+
// `#loading` is removed on the line below, so anything that rejects here (no
|
|
31
|
+
// WebGL context, a scene that will not load) leaves the bar up and the reason
|
|
32
|
+
// in a console nobody opens.
|
|
33
|
+
showBootFailure(e);
|
|
34
|
+
throw e;
|
|
28
35
|
});
|
|
29
36
|
|
|
30
37
|
document.querySelector('#loading')?.remove();
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* height/stomp/knockback/checkpoint-respawn/platform-carry), GoblinSkin,
|
|
15
15
|
* FollowCam (follow + screen-shake), ParallaxLayer, HudUpdater.
|
|
16
16
|
*/
|
|
17
|
-
import { createGame2D } from 'incanto/2d';
|
|
17
|
+
import { createGame2D, showBootFailure } from 'incanto/2d';
|
|
18
18
|
import goblinUrl from 'incanto/assets/characters/goblin.png';
|
|
19
19
|
// Built-in sprite art ships in the package — import the URLs and inject them into
|
|
20
20
|
// the scene's asset placeholders before boot (the engine clones the scene).
|
|
@@ -49,6 +49,13 @@ const game = await createGame2D({
|
|
|
49
49
|
canvas,
|
|
50
50
|
scene,
|
|
51
51
|
behaviors,
|
|
52
|
+
}).catch((e) => {
|
|
53
|
+
// Without this the player watches the loading overlay sit at 100% forever:
|
|
54
|
+
// `#loading` is removed on the line below, so anything that rejects here (no
|
|
55
|
+
// WebGL context, a scene that will not load) leaves the bar up and the reason
|
|
56
|
+
// in a console nobody opens.
|
|
57
|
+
showBootFailure(e);
|
|
58
|
+
throw e;
|
|
52
59
|
});
|
|
53
60
|
|
|
54
61
|
document.querySelector('#loading')?.remove();
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* leaves open: `Survivor` (move + auto-attack + XP/level-up/upgrades + HUD),
|
|
12
12
|
* `Director` (ring-spawn around the player + escalation), `EnemyDrop`, `EnemyFace`.
|
|
13
13
|
*/
|
|
14
|
-
import { createGame2D } from 'incanto/2d';
|
|
14
|
+
import { createGame2D, showBootFailure } from 'incanto/2d';
|
|
15
15
|
import ghostUrl from 'incanto/assets/characters/ghost.png';
|
|
16
16
|
import goblinUrl from 'incanto/assets/characters/goblin.png';
|
|
17
17
|
import knightUrl from 'incanto/assets/characters/medieval-knight.png';
|
|
@@ -36,6 +36,13 @@ const game = await createGame2D({
|
|
|
36
36
|
canvas,
|
|
37
37
|
scene,
|
|
38
38
|
behaviors,
|
|
39
|
+
}).catch((e) => {
|
|
40
|
+
// Without this the player watches the loading overlay sit at 100% forever:
|
|
41
|
+
// `#loading` is removed on the line below, so anything that rejects here (no
|
|
42
|
+
// WebGL context, a scene that will not load) leaves the bar up and the reason
|
|
43
|
+
// in a console nobody opens.
|
|
44
|
+
showBootFailure(e);
|
|
45
|
+
throw e;
|
|
39
46
|
});
|
|
40
47
|
|
|
41
48
|
document.querySelector('#loading')?.remove();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* win/lose banner).
|
|
14
14
|
*/
|
|
15
15
|
import { assetUrls, preloadUrls } from 'incanto';
|
|
16
|
-
import { createGame3D } from 'incanto/3d';
|
|
16
|
+
import { createGame3D, showBootFailure } from 'incanto/3d';
|
|
17
17
|
// A bundled clip looped as low ambience (no large music files ship in the
|
|
18
18
|
// package — see incanto-audio.md). vite resolves + hashes the real file.
|
|
19
19
|
import musicUrl from 'incanto/assets/audio/spells_cast.mp3';
|
|
@@ -41,6 +41,13 @@ const game = await createGame3D({
|
|
|
41
41
|
scene: sceneJson,
|
|
42
42
|
behaviors: { Shoot, HudUpdater },
|
|
43
43
|
pointer: true, // pointer-lock mouse look (lockOnClick) — drives the free-view yaw/pitch
|
|
44
|
+
}).catch((e) => {
|
|
45
|
+
// Without this the player watches the loading overlay sit at 100% forever:
|
|
46
|
+
// `#loading` is removed on the line below, so anything that rejects here (no
|
|
47
|
+
// WebGL context, a scene that will not load) leaves the bar up and the reason
|
|
48
|
+
// in a console nobody opens.
|
|
49
|
+
showBootFailure(e);
|
|
50
|
+
throw e;
|
|
44
51
|
});
|
|
45
52
|
|
|
46
53
|
document.querySelector('#loading')?.remove();
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* declarative `animations` map — zero animation code.
|
|
9
9
|
*/
|
|
10
10
|
import { assetUrls, preloadUrls } from 'incanto';
|
|
11
|
-
import { createGame3D } from 'incanto/3d';
|
|
11
|
+
import { createGame3D, showBootFailure } from 'incanto/3d';
|
|
12
12
|
import { BEHAVIORS } from './behaviors';
|
|
13
13
|
import villageJson from './village.scene.json';
|
|
14
14
|
|
|
@@ -27,6 +27,13 @@ const game = await createGame3D({
|
|
|
27
27
|
scene: villageJson,
|
|
28
28
|
behaviors: BEHAVIORS,
|
|
29
29
|
pointer: true, // pointer-lock look (click the canvas)
|
|
30
|
+
}).catch((e) => {
|
|
31
|
+
// Without this the player watches the loading overlay sit at 100% forever:
|
|
32
|
+
// `#loading` is removed on the line below, so anything that rejects here (no
|
|
33
|
+
// WebGL context, a scene that will not load) leaves the bar up and the reason
|
|
34
|
+
// in a console nobody opens.
|
|
35
|
+
showBootFailure(e);
|
|
36
|
+
throw e;
|
|
30
37
|
});
|
|
31
38
|
|
|
32
39
|
document.querySelector('#loading')?.remove();
|