incanto 0.77.0 → 0.78.1

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.
Files changed (71) hide show
  1. package/README.md +9 -4
  2. package/dist/index.js +1 -1
  3. package/dist/{src-DygJfhOb.js → src-SWXe-bub.js} +1 -1
  4. package/dist/vite.js +1 -1
  5. package/package.json +1 -1
  6. package/skills/incanto-building-2d-games.md +6 -0
  7. package/skills/incanto-building-3d-games.md +6 -0
  8. package/skills/incanto-editor.md +2 -2
  9. package/skills/incanto-multiplayer.md +1 -1
  10. package/skills/incanto-save-slots.md +1 -1
  11. package/skills/incanto-scene-json-authoring.md +1 -1
  12. package/skills/incanto-verifying-your-game.md +2 -2
  13. package/skills/incanto-web-integration.md +40 -2
  14. package/skills/incanto-your-first-game.md +6 -1
  15. package/templates-app/beacon-isle-3d/PROJECT/Structure.md +2 -1
  16. package/templates-app/beacon-isle-3d/index.html +5 -2
  17. package/templates-app/beacon-isle-3d/package.json +6 -1
  18. package/templates-app/beacon-isle-3d/src/App.tsx +77 -0
  19. package/templates-app/beacon-isle-3d/src/main.tsx +9 -0
  20. package/templates-app/beacon-isle-3d/tsconfig.json +2 -1
  21. package/templates-app/beacon-isle-3d/vite.config.ts +4 -1
  22. package/templates-app/molehill-2d/PROJECT/Structure.md +2 -1
  23. package/templates-app/molehill-2d/index.html +5 -2
  24. package/templates-app/molehill-2d/package.json +6 -1
  25. package/templates-app/molehill-2d/src/App.tsx +58 -0
  26. package/templates-app/molehill-2d/src/main.tsx +9 -0
  27. package/templates-app/molehill-2d/tsconfig.json +2 -1
  28. package/templates-app/molehill-2d/vite.config.ts +4 -1
  29. package/templates-app/platformer-2d/PROJECT/Context.md +1 -1
  30. package/templates-app/platformer-2d/PROJECT/Requirements.md +2 -2
  31. package/templates-app/platformer-2d/PROJECT/Status.md +1 -1
  32. package/templates-app/platformer-2d/PROJECT/Structure.md +2 -2
  33. package/templates-app/platformer-2d/docs/project-2d-rules.md +1 -1
  34. package/templates-app/platformer-2d/index.html +5 -2
  35. package/templates-app/platformer-2d/package.json +6 -1
  36. package/templates-app/platformer-2d/src/App.tsx +115 -0
  37. package/templates-app/platformer-2d/src/main.tsx +9 -0
  38. package/templates-app/platformer-2d/tsconfig.json +2 -1
  39. package/templates-app/platformer-2d/vite.config.ts +4 -1
  40. package/templates-app/star-survivor/PROJECT/Context.md +1 -1
  41. package/templates-app/star-survivor/PROJECT/Requirements.md +1 -1
  42. package/templates-app/star-survivor/PROJECT/Status.md +1 -1
  43. package/templates-app/star-survivor/PROJECT/Structure.md +2 -2
  44. package/templates-app/star-survivor/docs/project-2d-rules.md +1 -1
  45. package/templates-app/star-survivor/index.html +5 -2
  46. package/templates-app/star-survivor/package.json +6 -1
  47. package/templates-app/star-survivor/src/App.tsx +94 -0
  48. package/templates-app/star-survivor/src/main.tsx +9 -0
  49. package/templates-app/star-survivor/tsconfig.json +2 -1
  50. package/templates-app/star-survivor/vite.config.ts +4 -1
  51. package/templates-app/tps-3d/PROJECT/Status.md +1 -1
  52. package/templates-app/tps-3d/PROJECT/Structure.md +2 -2
  53. package/templates-app/tps-3d/index.html +5 -2
  54. package/templates-app/tps-3d/package.json +6 -1
  55. package/templates-app/tps-3d/src/App.tsx +124 -0
  56. package/templates-app/tps-3d/src/main.tsx +9 -0
  57. package/templates-app/tps-3d/tsconfig.json +2 -1
  58. package/templates-app/tps-3d/vite.config.ts +4 -1
  59. package/templates-app/village-quest-3d/PROJECT/Structure.md +3 -2
  60. package/templates-app/village-quest-3d/index.html +5 -2
  61. package/templates-app/village-quest-3d/package.json +6 -1
  62. package/templates-app/village-quest-3d/src/App.tsx +79 -0
  63. package/templates-app/village-quest-3d/src/main.tsx +9 -0
  64. package/templates-app/village-quest-3d/tsconfig.json +2 -1
  65. package/templates-app/village-quest-3d/vite.config.ts +4 -1
  66. package/templates-app/beacon-isle-3d/src/main.ts +0 -60
  67. package/templates-app/molehill-2d/src/main.ts +0 -41
  68. package/templates-app/platformer-2d/src/main.ts +0 -97
  69. package/templates-app/star-survivor/src/main.ts +0 -77
  70. package/templates-app/tps-3d/src/main.ts +0 -107
  71. package/templates-app/village-quest-3d/src/main.ts +0 -62
@@ -0,0 +1,9 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { App } from './App';
4
+
5
+ createRoot(document.getElementById('root') as HTMLElement).render(
6
+ <StrictMode>
7
+ <App />
8
+ </StrictMode>,
9
+ );
@@ -7,7 +7,8 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "resolveJsonModule": true,
10
- "types": ["vite/client", "node"]
10
+ "types": ["vite/client", "node"],
11
+ "jsx": "react-jsx"
11
12
  },
12
13
  "include": ["src", "verify.ts", "vite.config.ts"]
13
14
  }
@@ -1,12 +1,15 @@
1
+ import react from '@vitejs/plugin-react';
1
2
  import { incantoLibrary, incantoScenes } from 'incanto/vite';
2
3
  import { defineConfig } from 'vite';
3
4
 
4
5
  export default defineConfig({
5
6
  base: './',
7
+ // react(): the app entry is React — src/main.tsx mounts <App />, and App.tsx
8
+ // owns the canvas the engine draws into.
6
9
  // Dev-server only, and both are why the editor is useful here:
7
10
  // - incantoScenes(): validates every *.scene.json the moment you save it,
8
11
  // AND serves this project's scenes, so the editor (☰ debug ▸ edit this
9
12
  // scene ▸ scenes) can open, create and save any scene in the project.
10
13
  // - incantoLibrary(): the agent8 asset catalog behind the 📚 buttons.
11
- plugins: [incantoScenes(), incantoLibrary()],
14
+ plugins: [react(), incantoScenes(), incantoLibrary()],
12
15
  });
@@ -2,7 +2,8 @@
2
2
 
3
3
  ```
4
4
  index.html canvas + loading bar
5
- src/main.ts preload GLB/anim assets createGame3D (village)
5
+ src/main.tsx React entry mounts <App /> into #root
6
+ src/App.tsx preload GLB/anim assets → createGame3D (village)
6
7
  src/village.scene.json sunset village: Elder (Interactable), gate Area3D,
7
8
  player rig (controller `animations` map, sword Trail3D),
8
9
  HUD (UiText/UiBanner/UiDialogue/UiButton)
@@ -20,7 +21,7 @@ verify.ts audit + headless quest E2E + replay determinism
20
21
 
21
22
  ## Scene relationship
22
23
 
23
- `main.ts` boots the **village**. `goToScene(grove)` happens in
24
+ `App.tsx` boots the **village**. `goToScene(grove)` happens in
24
25
  VillageDirector.onGateEnter; `goToScene(village)` in GroveDirector.onPortalEnter
25
26
  and the play-again button. Both scenes redeclare the same input map and player
26
27
  rig — scenes are self-contained by design (a scene JSON is the whole truth).
@@ -54,15 +54,18 @@
54
54
  font-size: 0.85rem;
55
55
  color: #aaa;
56
56
  }
57
+ #root {
58
+ display: contents;
59
+ }
57
60
  </style>
58
61
  </head>
59
62
  <body>
60
- <canvas></canvas>
63
+ <div id="root"></div>
61
64
  <div id="loading" class="loading-container">
62
65
  <div class="loading-title">Emberwood</div>
63
66
  <div class="progress-track"><div id="progress"></div></div>
64
67
  <div id="progress-text">0%</div>
65
68
  </div>
66
- <script type="module" src="/src/main.ts"></script>
69
+ <script type="module" src="/src/main.tsx"></script>
67
70
  </body>
68
71
  </html>
@@ -13,12 +13,17 @@
13
13
  "@dimforge/rapier2d-compat": "0.19.3",
14
14
  "@dimforge/rapier3d-compat": "0.19.3",
15
15
  "@pixiv/three-vrm": "^3.5.3",
16
- "incanto": "^0.77.0",
16
+ "incanto": "^0.78.1",
17
+ "react": "^18.3.1",
18
+ "react-dom": "^18.3.1",
17
19
  "three": "^0.184.0"
18
20
  },
19
21
  "devDependencies": {
20
22
  "@types/node": "^24.0.0",
23
+ "@types/react": "^18.3.12",
24
+ "@types/react-dom": "^18.3.1",
21
25
  "@types/three": "^0.184.1",
26
+ "@vitejs/plugin-react": "^6.0.3",
22
27
  "typescript": "^6.0.3",
23
28
  "vite": "^8.0.16"
24
29
  }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Emberwood — a 3D quest vignette showcasing the 0.8 engine surface.
3
+ * Talk to the Elder (UiDialogue + choices), take the quest, sprint through the
4
+ * north gate (goToScene fade) into a hedge-maze grove where three wolves walk
5
+ * A* patrol rings (findPath + PathFollow), fell them with a sword arc
6
+ * (Trail3D), and report back. Wind is a live synth voice (startVoice) that
7
+ * rises as you run; the character animates through the controller's
8
+ * declarative `animations` map — zero animation code.
9
+ */
10
+ import { assetUrls, preloadUrls } from 'incanto';
11
+ import { createGame3D, showBootFailure } from 'incanto/3d';
12
+ import { useEffect, useRef } from 'react';
13
+ import { BEHAVIORS } from './behaviors';
14
+ import villageJson from './village.scene.json';
15
+
16
+ export function App() {
17
+ const canvasRef = useRef<HTMLCanvasElement>(null);
18
+
19
+ useEffect(() => {
20
+ const canvas = canvasRef.current;
21
+ // StrictMode runs this effect twice against the SAME canvas element, and a
22
+ // second engine on one canvas is two renderers fighting for one context.
23
+ // The flag lives on the element, so it lasts exactly as long as the thing
24
+ // it guards — a page-level game like this one never unmounts otherwise.
25
+ if (!canvas || (canvas as { _incanto?: true })._incanto) return;
26
+ (canvas as { _incanto?: true })._incanto = true;
27
+
28
+ void (async () => {
29
+ const fill = document.querySelector('#progress') as HTMLElement | null;
30
+ const text = document.querySelector('#progress-text') as HTMLElement | null;
31
+ await preloadUrls(assetUrls(villageJson.assets), (done, total) => {
32
+ const pct = total > 0 ? Math.round((done / total) * 100) : 100;
33
+ if (fill) fill.style.width = `${pct}%`;
34
+ if (text) text.textContent = `${pct}%`;
35
+ });
36
+ /** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
37
+ const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
38
+
39
+ const game = await createGame3D({
40
+ debug: DEBUG,
41
+ // The editor's 📚 buttons, served by `incantoLibrary()` in vite.config.ts.
42
+ // BOTH halves are the opt-in, and this half was missing from every shipped
43
+ // template: the plugin alone left a vite config whose own comment promised
44
+ // "the agent8 asset catalog behind the 📚 buttons" and no button anywhere.
45
+ // Gated on the same flag, because `editor` DEFAULTS to `debug` and an object
46
+ // here would turn the editor on in a production build.
47
+ editor: DEBUG && { library: true },
48
+ canvas,
49
+ scene: villageJson,
50
+ behaviors: BEHAVIORS,
51
+ pointer: true, // pointer-lock look (click the canvas)
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;
59
+ });
60
+
61
+ // The console handle FIRST, before any wiring of your own can throw.
62
+ // `game.stats()`, `game.assetErrors()`, `game.frame()` and `game.engine.log`
63
+ // are the entire in-page diagnostic surface, and a static build has no other.
64
+ // Assigned after the wiring, one mistake below took all of them with it.
65
+ (window as unknown as { game: typeof game }).game = game;
66
+
67
+ // The overlay comes down LAST, after any wiring of your own. There is none in
68
+ // this template — when you add some, put it above this line and wrap it:
69
+ //
70
+ // try { …your wiring… } catch (e) { showBootFailure(e); throw e; }
71
+ //
72
+ // A throw between the overlay coming down and the handle going up used to leave
73
+ // a level that renders perfectly with no player and no error of any kind.
74
+ document.querySelector('#loading')?.remove();
75
+ })();
76
+ }, []);
77
+
78
+ return <canvas ref={canvasRef} id="game" />;
79
+ }
@@ -0,0 +1,9 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { App } from './App';
4
+
5
+ createRoot(document.getElementById('root') as HTMLElement).render(
6
+ <StrictMode>
7
+ <App />
8
+ </StrictMode>,
9
+ );
@@ -7,7 +7,8 @@
7
7
  "noEmit": true,
8
8
  "skipLibCheck": true,
9
9
  "resolveJsonModule": true,
10
- "types": ["vite/client", "node"]
10
+ "types": ["vite/client", "node"],
11
+ "jsx": "react-jsx"
11
12
  },
12
13
  "include": ["src", "verify.ts", "vite.config.ts"]
13
14
  }
@@ -1,12 +1,15 @@
1
+ import react from '@vitejs/plugin-react';
1
2
  import { incantoLibrary, incantoScenes } from 'incanto/vite';
2
3
  import { defineConfig } from 'vite';
3
4
 
4
5
  export default defineConfig({
5
6
  base: './',
7
+ // react(): the app entry is React — src/main.tsx mounts <App />, and App.tsx
8
+ // owns the canvas the engine draws into.
6
9
  // Dev-server only, and both are why the editor is useful here:
7
10
  // - incantoScenes(): validates every *.scene.json the moment you save it,
8
11
  // AND serves this project's scenes, so the editor (☰ debug ▸ edit this
9
12
  // scene ▸ scenes) can open, create and save any scene in the project.
10
13
  // - incantoLibrary(): the agent8 asset catalog behind the 📚 buttons.
11
- plugins: [incantoScenes(), incantoLibrary()],
14
+ plugins: [react(), incantoScenes(), incantoLibrary()],
12
15
  });
@@ -1,60 +0,0 @@
1
- /**
2
- * Beacon Isle — the incanto 3D template: a generated island (terrain, sea,
3
- * groves, grass, flowers), a quest with a real NPC, shades that HUNT you
4
- * across the surface via terrain navigation, melee with a bone-mounted sword,
5
- * golden-hour grade + bloom, touch controls on phones — regenerate the whole
6
- * world with `bun run world`, the game logic never changes.
7
- */
8
- import { assetUrls, preloadUrls } from 'incanto';
9
- import { createGame3D, showBootFailure } from 'incanto/3d';
10
- import { BEHAVIORS } from './behaviors';
11
- import gameJson from './game.scene.json';
12
-
13
- const fill = document.querySelector('#progress') as HTMLElement | null;
14
- const text = document.querySelector('#progress-text') as HTMLElement | null;
15
- await preloadUrls(assetUrls(gameJson.assets), (done, total) => {
16
- const pct = total > 0 ? Math.round((done / total) * 100) : 100;
17
- if (fill) fill.style.width = `${pct}%`;
18
- if (text) text.textContent = `${pct}%`;
19
- });
20
-
21
- const canvas = document.querySelector('canvas') as HTMLCanvasElement;
22
- /** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
23
- const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
24
-
25
- const game = await createGame3D({
26
- debug: DEBUG,
27
- // The editor's 📚 buttons, served by `incantoLibrary()` in vite.config.ts.
28
- // BOTH halves are the opt-in, and this half was missing from every shipped
29
- // template: the plugin alone left a vite config whose own comment promised
30
- // "the agent8 asset catalog behind the 📚 buttons" and no button anywhere.
31
- // Gated on the same flag, because `editor` DEFAULTS to `debug` and an object
32
- // here would turn the editor on in a production build.
33
- editor: DEBUG && { library: true },
34
- canvas,
35
- scene: gameJson,
36
- behaviors: BEHAVIORS,
37
- pointer: true,
38
- }).catch((e) => {
39
- // Without this the player watches the loading overlay sit at 100% forever:
40
- // `#loading` is removed on the line below, so anything that rejects here (no
41
- // WebGL context, a scene that will not load) leaves the bar up and the reason
42
- // in a console nobody opens.
43
- showBootFailure(e);
44
- throw e;
45
- });
46
-
47
- // The console handle FIRST, before any wiring of your own can throw.
48
- // `game.stats()`, `game.assetErrors()`, `game.frame()` and `game.engine.log`
49
- // are the entire in-page diagnostic surface, and a static build has no other.
50
- // Assigned after the wiring, one mistake below took all of them with it.
51
- (window as unknown as { game: typeof game }).game = game;
52
-
53
- // The overlay comes down LAST, after any wiring of your own. There is none in
54
- // this template — when you add some, put it above this line and wrap it:
55
- //
56
- // try { …your wiring… } catch (e) { showBootFailure(e); throw e; }
57
- //
58
- // A throw between the overlay coming down and the handle going up used to leave
59
- // a level that renders perfectly with no player and no error of any kind.
60
- document.querySelector('#loading')?.remove();
@@ -1,41 +0,0 @@
1
- /**
2
- * Molehill — a whack-a-mole you play with the MOUSE and nothing else.
3
- *
4
- * No character body, no keyboard, no gravity. Nine holes, a mole that pops out
5
- * of one of them, three misses before they win. The engine's `Clickable` turns
6
- * each mole into a node that reports being clicked; the built-in `ScoreKeeper`
7
- * holds the score and the misses; the wiring between them is `game.scene.json`.
8
- *
9
- * It is the starter for a whole family the other templates do not cover —
10
- * match-3, tower defense, cards, point-and-click, RTS, board games, idle.
11
- */
12
- import { createGame2D, showBootFailure } from 'incanto/2d';
13
- import { Molehill, Scoreboard } from './behaviors';
14
- import sceneJson from './game.scene.json';
15
-
16
- /** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
17
- const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
18
-
19
- const canvas = document.querySelector('canvas') as HTMLCanvasElement;
20
-
21
- try {
22
- const game = await createGame2D({
23
- canvas,
24
- scene: sceneJson,
25
- behaviors: { Molehill, Scoreboard },
26
- // The cursor is the input. `lockOnClick` would HIDE it, which is exactly
27
- // wrong for a game you play by pointing — the pointer is attached for its
28
- // position, and nothing else.
29
- pointer: { lockOnClick: false },
30
- debug: DEBUG,
31
- // Gated on the same flag: `editor` DEFAULTS to `debug`, so a bare object
32
- // here turns it on in a production build — and this file also hangs the
33
- // game handle off `globalThis`, so `game.openEditor()` would be one line
34
- // away in a shipped build. Every other starter gates it; this one did not.
35
- editor: DEBUG && { library: true },
36
- });
37
- document.getElementById('loading')?.remove();
38
- (globalThis as { game?: unknown }).game = game;
39
- } catch (error) {
40
- showBootFailure(error);
41
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * Castle Run — a polished 2D adventure PLATFORMER on Incanto. Run a medieval
3
- * knight through a castle: A/D (or arrows) to run, Space/W to jump — tap again in
4
- * the air to DOUBLE-JUMP, and the controller forgives you with coyote-time +
5
- * jump-buffering. STOMP goblins from above (and chain-bounce), ride the moving
6
- * platform and the lift, grab coins + gems, dodge the spikes and the pit, touch
7
- * the Checkpoint flag so a fall sends you back there, and reach the gold flag to
8
- * clear the castle. Three hearts per life, three lives.
9
- *
10
- * The level is JSON: StaticBody2D platforms, Patrol/Oscillate moving platforms +
11
- * goblins, Pickup coins/gems, ScoreKeeper score/lives/win-lose, AudioPlayer SFX —
12
- * all wired in `game.scene.json`. The custom TypeScript is the game FEEL the
13
- * built-ins leave open: PlayerController (coyote/buffer/double-jump/variable
14
- * height/stomp/knockback/checkpoint-respawn/platform-carry), GoblinSkin,
15
- * FollowCam (follow + screen-shake), ParallaxLayer, HudUpdater.
16
- */
17
- import { createGame2D, showBootFailure } from 'incanto/2d';
18
- import goblinUrl from 'incanto/assets/characters/goblin.png';
19
- // Built-in sprite art ships in the package — import the URLs and inject them into
20
- // the scene's asset placeholders before boot (the engine clones the scene).
21
- import knightUrl from 'incanto/assets/characters/medieval-knight.png';
22
- import coinUrl from 'incanto/assets/items/coin.png';
23
- import gemUrl from 'incanto/assets/items/gem.png';
24
- import { FollowCam, GoblinSkin, HudUpdater, ParallaxLayer, PlayerController } from './behaviors';
25
- import sceneJson from './game.scene.json';
26
-
27
- // OPTIONAL looping music (large tracks aren't bundled). Point at any URL to loop.
28
- const MUSIC_URL = '';
29
-
30
- const behaviors = { PlayerController, GoblinSkin, FollowCam, ParallaxLayer, HudUpdater };
31
-
32
- const canvas = document.querySelector('canvas') as HTMLCanvasElement;
33
-
34
- const scene = structuredClone(sceneJson) as typeof sceneJson & {
35
- assets: {
36
- knight: { url: string };
37
- goblin: { url: string };
38
- coin: { url: string };
39
- gem: { url: string };
40
- };
41
- };
42
- scene.assets.knight.url = knightUrl;
43
- scene.assets.goblin.url = goblinUrl;
44
- scene.assets.coin.url = coinUrl;
45
- scene.assets.gem.url = gemUrl;
46
-
47
- /** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
48
- const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
49
-
50
- const game = await createGame2D({
51
- debug: DEBUG,
52
- // The editor's 📚 buttons, served by `incantoLibrary()` in vite.config.ts.
53
- // BOTH halves are the opt-in, and this half was missing from every shipped
54
- // template: the plugin alone left a vite config whose own comment promised
55
- // "the agent8 asset catalog behind the 📚 buttons" and no button anywhere.
56
- // Gated on the same flag, because `editor` DEFAULTS to `debug` and an object
57
- // here would turn the editor on in a production build.
58
- editor: DEBUG && { library: true },
59
- canvas,
60
- scene,
61
- behaviors,
62
- }).catch((e) => {
63
- // Without this the player watches the loading overlay sit at 100% forever:
64
- // `#loading` is removed on the line below, so anything that rejects here (no
65
- // WebGL context, a scene that will not load) leaves the bar up and the reason
66
- // in a console nobody opens.
67
- showBootFailure(e);
68
- throw e;
69
- });
70
-
71
- // The console handle FIRST, before any wiring of your own can throw.
72
- // `game.stats()`, `game.assetErrors()`, `game.frame()` and `game.engine.log`
73
- // are the entire in-page diagnostic surface, and a static build has no other.
74
- // Assigned after the wiring, one mistake below took all of them with it.
75
- (window as unknown as { game: typeof game }).game = game;
76
-
77
- // And the loading overlay comes down LAST. Everything between here and there is
78
- // your code; a throw in it used to leave a level that renders perfectly with no
79
- // player, no error UI, and no way to ask the page what happened.
80
- try {
81
- if (MUSIC_URL) {
82
- game.engine.music.play(MUSIC_URL, { loop: true, fadeIn: 1.5 });
83
- game.engine.audio.music = 0.3;
84
- }
85
-
86
- // No hand-rolled touch button here: the scene declares `"touch": "button"` on
87
- // `jump` and `"touch": "joystick"` on `move`, so the ENGINE draws both on a
88
- // coarse pointer — safe-area aware, off the home-indicator band, and sized to
89
- // its own rules. This template used to draw an 88 px disc of its own at a flat
90
- // `bottom: 2rem`, which covered 78% of the engine's jump button and put its
91
- // lower edge inside the band iOS reserves for the leave-the-app swipe.
92
- } catch (e) {
93
- showBootFailure(e);
94
- throw e;
95
- }
96
-
97
- document.querySelector('#loading')?.remove();
@@ -1,77 +0,0 @@
1
- /**
2
- * Hollow Horde — a top-down SURVIVORS-LIKE (Vampire Survivors genre) on Incanto.
3
- * Move with WASD / arrows; your knight AUTO-ATTACKS the nearest enemy. Goblins and
4
- * faster ghosts pour in from every side and grow tougher over time. Cut them down,
5
- * vacuum up the XP shards they drop, and on each LEVEL-UP pick one of three
6
- * upgrades. Survive 90 seconds to win — let the horde drain your HP and you fall.
7
- *
8
- * The horde is BUILT-IN gameplay wired in `game.scene.json`: two `Spawner`s clone
9
- * goblin/ghost templates, each enemy `Chase`s the player, and a `DamageOnContact`
10
- * hitbox drains `Health`. The custom TypeScript is the survivor FEEL the library
11
- * leaves open: `Survivor` (move + auto-attack + XP/level-up/upgrades + HUD),
12
- * `Director` (ring-spawn around the player + escalation), `EnemyDrop`, `EnemyFace`.
13
- */
14
- import { createGame2D, showBootFailure } from 'incanto/2d';
15
- import ghostUrl from 'incanto/assets/characters/ghost.png';
16
- import goblinUrl from 'incanto/assets/characters/goblin.png';
17
- import knightUrl from 'incanto/assets/characters/medieval-knight.png';
18
- import { Director, EnemyDrop, EnemyFace, GridFloor, Survivor } from './behaviors';
19
- import sceneJson from './game.scene.json';
20
-
21
- const MUSIC_URL = '';
22
-
23
- const behaviors = { Survivor, Director, EnemyDrop, EnemyFace, GridFloor };
24
-
25
- const canvas = document.querySelector('canvas') as HTMLCanvasElement;
26
-
27
- const scene = structuredClone(sceneJson) as typeof sceneJson & {
28
- assets: { knight: { url: string }; goblin: { url: string }; ghost: { url: string } };
29
- };
30
- scene.assets.knight.url = knightUrl;
31
- scene.assets.goblin.url = goblinUrl;
32
- scene.assets.ghost.url = ghostUrl;
33
-
34
- /** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
35
- const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
36
-
37
- const game = await createGame2D({
38
- debug: DEBUG,
39
- // The editor's 📚 buttons, served by `incantoLibrary()` in vite.config.ts.
40
- // BOTH halves are the opt-in, and this half was missing from every shipped
41
- // template: the plugin alone left a vite config whose own comment promised
42
- // "the agent8 asset catalog behind the 📚 buttons" and no button anywhere.
43
- // Gated on the same flag, because `editor` DEFAULTS to `debug` and an object
44
- // here would turn the editor on in a production build.
45
- editor: DEBUG && { library: true },
46
- canvas,
47
- scene,
48
- behaviors,
49
- }).catch((e) => {
50
- // Without this the player watches the loading overlay sit at 100% forever:
51
- // `#loading` is removed on the line below, so anything that rejects here (no
52
- // WebGL context, a scene that will not load) leaves the bar up and the reason
53
- // in a console nobody opens.
54
- showBootFailure(e);
55
- throw e;
56
- });
57
-
58
- // The console handle FIRST, before any wiring of your own can throw.
59
- // `game.stats()`, `game.assetErrors()`, `game.frame()` and `game.engine.log`
60
- // are the entire in-page diagnostic surface, and a static build has no other.
61
- // Assigned after the wiring, one mistake below took all of them with it.
62
- (window as unknown as { game: typeof game }).game = game;
63
-
64
- // And the loading overlay comes down LAST. Everything between here and there is
65
- // your code; a throw in it used to leave a level that renders perfectly with no
66
- // player, no error UI, and no way to ask the page what happened.
67
- try {
68
- if (MUSIC_URL) {
69
- game.engine.music.play(MUSIC_URL, { loop: true, fadeIn: 1.5 });
70
- game.engine.audio.music = 0.3;
71
- }
72
- } catch (e) {
73
- showBootFailure(e);
74
- throw e;
75
- }
76
-
77
- document.querySelector('#loading')?.remove();
@@ -1,107 +0,0 @@
1
- /**
2
- * Vanguard — a 3D THIRD-PERSON shooter built on Incanto. Move with WASD, look
3
- * with the mouse (click locks the pointer), Shift to sprint, Space to jump,
4
- * left-click / F to fire your hitscan rifle, R to reload, Enter to restart once
5
- * the mission ends. The camera rides over
6
- * your soldier's shoulder (CharacterController3D `view: 'free'`); the crosshair
7
- * is where you shoot. Clear three waves of chasing enemies to SECURE THE AREA;
8
- * let them drain your HP to 0 and the MISSION FAILS.
9
- *
10
- * The whole loop is composed from built-in gameplay behaviors wired in
11
- * `game.scene.json` (third-person movement+camera, enemy AI via Chase, contact
12
- * damage, WaveSpawner, Health, ScoreKeeper). The only custom TypeScript is
13
- * `Shoot` (the click-to-fire hitscan + face-aim) and `HudUpdater` (the HUD +
14
- * win/lose banner).
15
- */
16
-
17
- import type { SceneJson } from 'incanto';
18
- import { assetUrls, preloadUrls } from 'incanto';
19
- import { createGame3D, showBootFailure } from 'incanto/3d';
20
- // A bundled clip looped as low ambience (no large music files ship in the
21
- // package — see incanto-audio.md). vite resolves + hashes the real file.
22
- import musicUrl from 'incanto/assets/audio/spells_cast.mp3';
23
- import { HudUpdater, Shoot } from './behaviors';
24
- import sceneJson from './game.scene.json';
25
-
26
- /**
27
- * The sub-scenes this scene `instance:`s — `cover.scene.json` and
28
- * `pillar.scene.json` — handed to the loader by path. `loadScene` never reads
29
- * files; vite's `import.meta.glob` gathers every scene beside this one.
30
- */
31
- const prefabs = import.meta.glob('./*.scene.json', {
32
- eager: true,
33
- import: 'default',
34
- }) as Record<string, SceneJson>;
35
- const resolveScene = (path: string): SceneJson | null => prefabs[`./${path}`] ?? null;
36
-
37
- // The soldier is a real GLB character (base-model.glb) animated by mixamo clips
38
- // (idle/walk/run/jump) — declared in the scene `assets`. Warm the HTTP cache with
39
- // a progress bar before booting; the arena, enemies, SFX + music stay zero-fetch.
40
- const fill = document.querySelector('#progress') as HTMLElement | null;
41
- const text = document.querySelector('#progress-text') as HTMLElement | null;
42
- await preloadUrls(assetUrls(sceneJson.assets), (done, total) => {
43
- const pct = total > 0 ? Math.round((done / total) * 100) : 100;
44
- if (fill) fill.style.width = `${pct}%`;
45
- if (text) text.textContent = `${pct}%`;
46
- });
47
-
48
- const canvas = document.querySelector('canvas') as HTMLCanvasElement;
49
- // createGame3D boots everything: register (gameplay behaviors auto-registered) →
50
- // load → physics (auto; the scene has bodies) → keyboard + pointer-lock look →
51
- // renderer → start. We pass the two custom behaviors.
52
- /** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
53
- const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
54
-
55
- const game = await createGame3D({
56
- debug: DEBUG,
57
- // The editor's 📚 buttons, served by `incantoLibrary()` in vite.config.ts.
58
- // BOTH halves are the opt-in, and this half was missing from every shipped
59
- // template: the plugin alone left a vite config whose own comment promised
60
- // "the agent8 asset catalog behind the 📚 buttons" and no button anywhere.
61
- // Gated on the same flag, because `editor` DEFAULTS to `debug` and an object
62
- // here would turn the editor on in a production build.
63
- editor: DEBUG && { library: true },
64
- canvas,
65
- scene: sceneJson,
66
- resolveScene,
67
- behaviors: { Shoot, HudUpdater },
68
- pointer: true, // pointer-lock mouse look (lockOnClick) — drives the free-view yaw/pitch
69
- }).catch((e) => {
70
- // Without this the player watches the loading overlay sit at 100% forever:
71
- // `#loading` is removed on the line below, so anything that rejects here (no
72
- // WebGL context, a scene that will not load) leaves the bar up and the reason
73
- // in a console nobody opens.
74
- showBootFailure(e);
75
- throw e;
76
- });
77
-
78
- // The console handle FIRST, before any wiring of your own can throw.
79
- // `game.stats()`, `game.assetErrors()`, `game.frame()` and `game.engine.log`
80
- // are the entire in-page diagnostic surface, and a static build has no other.
81
- // Assigned after the wiring, one mistake below took all of them with it.
82
- (window as unknown as { game: typeof game }).game = game;
83
-
84
- // And the loading overlay comes down LAST. Everything between here and there is
85
- // your code; a throw in it used to leave a level that renders perfectly with no
86
- // player, no error UI, and no way to ask the page what happened.
87
- try {
88
- // Looping background music on the music bus (a calm tense pad). The engine
89
- // auto-resumes a gesture-blocked track on the first click/keypress.
90
- game.engine.music.play(musicUrl, { loop: true, fadeIn: 1.5 });
91
- game.engine.audio.music = 0.3;
92
-
93
- // On-screen FIRE / JUMP buttons (mobile + desktop parity).
94
- for (const [id, code] of [
95
- ['btn-fire', 'KeyF'],
96
- ['btn-jump', 'Space'],
97
- ] as const) {
98
- const el = document.querySelector(`#${id}`);
99
- el?.addEventListener('pointerdown', () => game.engine.input.handleKey(code, true));
100
- el?.addEventListener('pointerup', () => game.engine.input.handleKey(code, false));
101
- }
102
- } catch (e) {
103
- showBootFailure(e);
104
- throw e;
105
- }
106
-
107
- document.querySelector('#loading')?.remove();