incanto 0.76.0 → 0.78.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/dist/index.js +1 -1
- package/dist/{src-D823V07k.js → src-DzPtpk1t.js} +1 -1
- package/dist/vite.d.ts +47 -3
- package/dist/vite.js +49 -17
- package/package.json +1 -1
- package/skills/incanto-building-2d-games.md +6 -0
- package/skills/incanto-building-3d-games.md +6 -0
- package/skills/incanto-editor.md +46 -2
- package/skills/incanto-multiplayer.md +1 -1
- package/skills/incanto-save-slots.md +1 -1
- package/skills/incanto-scene-json-authoring.md +1 -1
- package/skills/incanto-verifying-your-game.md +2 -2
- package/skills/incanto-web-integration.md +40 -2
- package/skills/incanto-your-first-game.md +6 -1
- package/templates-app/beacon-isle-3d/PROJECT/Structure.md +2 -1
- package/templates-app/beacon-isle-3d/index.html +5 -2
- package/templates-app/beacon-isle-3d/package.json +6 -1
- package/templates-app/beacon-isle-3d/src/App.tsx +77 -0
- package/templates-app/beacon-isle-3d/src/main.tsx +9 -0
- package/templates-app/beacon-isle-3d/tsconfig.json +2 -1
- package/templates-app/beacon-isle-3d/vite.config.ts +4 -1
- package/templates-app/molehill-2d/PROJECT/Structure.md +2 -1
- package/templates-app/molehill-2d/index.html +5 -2
- package/templates-app/molehill-2d/package.json +6 -1
- package/templates-app/molehill-2d/src/App.tsx +58 -0
- package/templates-app/molehill-2d/src/main.tsx +9 -0
- package/templates-app/molehill-2d/tsconfig.json +2 -1
- package/templates-app/molehill-2d/vite.config.ts +4 -1
- package/templates-app/platformer-2d/PROJECT/Context.md +1 -1
- package/templates-app/platformer-2d/PROJECT/Requirements.md +2 -2
- package/templates-app/platformer-2d/PROJECT/Status.md +1 -1
- package/templates-app/platformer-2d/PROJECT/Structure.md +2 -2
- package/templates-app/platformer-2d/docs/project-2d-rules.md +1 -1
- package/templates-app/platformer-2d/index.html +5 -2
- package/templates-app/platformer-2d/package.json +6 -1
- package/templates-app/platformer-2d/src/App.tsx +115 -0
- package/templates-app/platformer-2d/src/main.tsx +9 -0
- package/templates-app/platformer-2d/tsconfig.json +2 -1
- package/templates-app/platformer-2d/vite.config.ts +4 -1
- package/templates-app/star-survivor/PROJECT/Context.md +1 -1
- package/templates-app/star-survivor/PROJECT/Requirements.md +1 -1
- package/templates-app/star-survivor/PROJECT/Status.md +1 -1
- package/templates-app/star-survivor/PROJECT/Structure.md +2 -2
- package/templates-app/star-survivor/docs/project-2d-rules.md +1 -1
- package/templates-app/star-survivor/index.html +5 -2
- package/templates-app/star-survivor/package.json +6 -1
- package/templates-app/star-survivor/src/App.tsx +94 -0
- package/templates-app/star-survivor/src/main.tsx +9 -0
- package/templates-app/star-survivor/tsconfig.json +2 -1
- package/templates-app/star-survivor/vite.config.ts +4 -1
- package/templates-app/tps-3d/PROJECT/Status.md +1 -1
- package/templates-app/tps-3d/PROJECT/Structure.md +2 -2
- package/templates-app/tps-3d/index.html +5 -2
- package/templates-app/tps-3d/package.json +6 -1
- package/templates-app/tps-3d/src/App.tsx +124 -0
- package/templates-app/tps-3d/src/main.tsx +9 -0
- package/templates-app/tps-3d/tsconfig.json +2 -1
- package/templates-app/tps-3d/vite.config.ts +4 -1
- package/templates-app/village-quest-3d/PROJECT/Structure.md +3 -2
- package/templates-app/village-quest-3d/index.html +5 -2
- package/templates-app/village-quest-3d/package.json +6 -1
- package/templates-app/village-quest-3d/src/App.tsx +79 -0
- package/templates-app/village-quest-3d/src/main.tsx +9 -0
- package/templates-app/village-quest-3d/tsconfig.json +2 -1
- package/templates-app/village-quest-3d/vite.config.ts +4 -1
- package/templates-app/beacon-isle-3d/src/main.ts +0 -60
- package/templates-app/molehill-2d/src/main.ts +0 -41
- package/templates-app/platformer-2d/src/main.ts +0 -97
- package/templates-app/star-survivor/src/main.ts +0 -77
- package/templates-app/tps-3d/src/main.ts +0 -107
- package/templates-app/village-quest-3d/src/main.ts +0 -62
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@dimforge/rapier2d-compat": "0.19.3",
|
|
14
|
-
"incanto": "^0.
|
|
14
|
+
"incanto": "^0.78.0",
|
|
15
|
+
"react": "^18.3.1",
|
|
16
|
+
"react-dom": "^18.3.1",
|
|
15
17
|
"three": "^0.184.0"
|
|
16
18
|
},
|
|
17
19
|
"devDependencies": {
|
|
18
20
|
"@types/node": "^24.0.0",
|
|
21
|
+
"@types/react": "^18.3.12",
|
|
22
|
+
"@types/react-dom": "^18.3.1",
|
|
19
23
|
"@types/three": "^0.184.1",
|
|
24
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
20
25
|
"typescript": "^6.0.3",
|
|
21
26
|
"vite": "^8.0.16"
|
|
22
27
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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 { useEffect, useRef } from 'react';
|
|
14
|
+
import { Molehill, Scoreboard } from './behaviors';
|
|
15
|
+
import sceneJson from './game.scene.json';
|
|
16
|
+
|
|
17
|
+
export function App() {
|
|
18
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const canvas = canvasRef.current;
|
|
22
|
+
// StrictMode runs this effect twice against the SAME canvas element, and a
|
|
23
|
+
// second engine on one canvas is two renderers fighting for one context.
|
|
24
|
+
// The flag lives on the element, so it lasts exactly as long as the thing
|
|
25
|
+
// it guards — a page-level game like this one never unmounts otherwise.
|
|
26
|
+
if (!canvas || (canvas as { _incanto?: true })._incanto) return;
|
|
27
|
+
(canvas as { _incanto?: true })._incanto = true;
|
|
28
|
+
|
|
29
|
+
void (async () => {
|
|
30
|
+
/** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
|
|
31
|
+
const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const game = await createGame2D({
|
|
35
|
+
canvas,
|
|
36
|
+
scene: sceneJson,
|
|
37
|
+
behaviors: { Molehill, Scoreboard },
|
|
38
|
+
// The cursor is the input. `lockOnClick` would HIDE it, which is exactly
|
|
39
|
+
// wrong for a game you play by pointing — the pointer is attached for its
|
|
40
|
+
// position, and nothing else.
|
|
41
|
+
pointer: { lockOnClick: false },
|
|
42
|
+
debug: DEBUG,
|
|
43
|
+
// Gated on the same flag: `editor` DEFAULTS to `debug`, so a bare object
|
|
44
|
+
// here turns it on in a production build — and this file also hangs the
|
|
45
|
+
// game handle off `globalThis`, so `game.openEditor()` would be one line
|
|
46
|
+
// away in a shipped build. Every other starter gates it; this one did not.
|
|
47
|
+
editor: DEBUG && { library: true },
|
|
48
|
+
});
|
|
49
|
+
document.getElementById('loading')?.remove();
|
|
50
|
+
(globalThis as { game?: unknown }).game = game;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
showBootFailure(error);
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
return <canvas ref={canvasRef} id="game" />;
|
|
58
|
+
}
|
|
@@ -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
|
});
|
|
@@ -28,7 +28,7 @@ _Exact versions are in `package.json`._
|
|
|
28
28
|
ScoreKeeper, …).
|
|
29
29
|
- **Art**: built-in animated sheets `medieval-knight` (player) + `goblin`
|
|
30
30
|
(enemy), and `coin` + `gem` item textures — all bundler-imported from the
|
|
31
|
-
package and injected into the scene asset urls in `
|
|
31
|
+
package and injected into the scene asset urls in `App.tsx`. Ground, platforms,
|
|
32
32
|
spikes, flags and parallax castle are styled `ColorRect2D`.
|
|
33
33
|
- **Audio**: zero-asset procedural SFX presets (jump/coin/hit/hurt/powerup/
|
|
34
34
|
win/lose); an OPTIONAL `engine.music` hook for a looping track.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
- Moving platforms: `Patrol` (horizontal ferry) / `Oscillate` (vertical lift) on
|
|
32
32
|
a `StaticBody2D` in the `platform` group — the `Feet` sensor + carry logic ride
|
|
33
33
|
them. Bobbing pickups: `Oscillate` on the sprite child.
|
|
34
|
-
- Keep `
|
|
34
|
+
- Keep `App.tsx` thin: inject the four built-in asset urls (knight/goblin/coin/
|
|
35
35
|
gem), boot `createGame2D` with the scene + behaviors, optional music, the
|
|
36
36
|
on-screen JUMP button, remove the loader, expose `window.game`. There is NO
|
|
37
37
|
respawn-via-scene-reload (respawn is in-level in `PlayerController`).
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
hazards/checkpoint/goal/feet-sensor = `Area2D`.
|
|
57
57
|
- Sprites: `medieval-knight` (idle 0–5, move 6–11, attack 12–17; jump reuses a
|
|
58
58
|
move frame) and `goblin` (idle 0–6, move 7–12). Swap a sheet by changing only
|
|
59
|
-
the asset entry + the import in `
|
|
59
|
+
the asset entry + the import in `App.tsx`.
|
|
60
60
|
- The parallax "castle" is styled `ColorRect2D` bands + towers (no tilemap node
|
|
61
61
|
in v0). Background art is the obvious next polish pass; the hero sprites carry
|
|
62
62
|
the look. Music is OPTIONAL (`MUSIC_URL` empty by default; SFX presets need no
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
`Particles2D`.
|
|
27
27
|
- `connections`: each coin/gem `collected → addScore` (+ SFX); root `won/lost →
|
|
28
28
|
SfxWin/SfxLose`.
|
|
29
|
-
- `
|
|
29
|
+
- `App.tsx`: inject the four asset urls, `createGame2D`, optional music, JUMP
|
|
30
30
|
button, remove loader, expose `window.game`. Node uids omitted (loader-
|
|
31
31
|
generated).
|
|
32
32
|
|
|
@@ -13,7 +13,7 @@ THE whole level (open it with `npx incanto-editor`):
|
|
|
13
13
|
- scene header — `environment.background`, `viewport` (`design [960,540]`, `fit:
|
|
14
14
|
expand`), `physics.gravity [0,1800]`, `move`/`jump` input, and the
|
|
15
15
|
`knight`/`goblin` spritesheets + `coin`/`gem` texture `assets` (urls injected
|
|
16
|
-
in `
|
|
16
|
+
in `App.tsx`).
|
|
17
17
|
- `Game` root — `ScoreKeeper` (`scoreToWin 100000`, `lives 3`).
|
|
18
18
|
- Parallax castle backdrop — `Sky`/`Sun`/`Cloud1–3` (far), `CastleFar` band +
|
|
19
19
|
`TowerFarA–C`, `CastleNear` band + `BattlementA/B` — `ColorRect2D` +
|
|
@@ -56,7 +56,7 @@ THE whole level (open it with `npx incanto-editor`):
|
|
|
56
56
|
- `ParallaxLayer` — re-anchor a backdrop to `cameraX · factor` for depth.
|
|
57
57
|
- `HudUpdater` — score/hearts/lives Labels + win/lose banner.
|
|
58
58
|
|
|
59
|
-
## `src/main.
|
|
59
|
+
## `src/main.tsx` and `src/App.tsx`
|
|
60
60
|
|
|
61
61
|
Boot: inject the four built-in asset urls, `createGame2D({ canvas, scene,
|
|
62
62
|
behaviors })`, optional `engine.music`, the JUMP button, remove the loader,
|
|
@@ -25,7 +25,7 @@ YOU MUST follow these rules EXACTLY when vibe-coding on this Incanto template
|
|
|
25
25
|
`newUid`). NEVER hand-invent readable uid strings.
|
|
26
26
|
5. 📦 ASSETS ARE DECLARED: `assets` entries need `type` + `url`; reference them as
|
|
27
27
|
`"$key"`. The knight + goblin sheets and the coin + gem textures are BUILT-INS
|
|
28
|
-
(`incanto/assets/...`) imported in `
|
|
28
|
+
(`incanto/assets/...`) imported in `App.tsx` and injected into the asset url
|
|
29
29
|
placeholders before boot. Free extra key-values are preserved — use them for
|
|
30
30
|
notes (frame ranges, license, source).
|
|
31
31
|
6. ⬇️ Y IS DOWN: 2D space is y-down pixels, (0,0) top-left, clockwise degrees.
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
transform: rotate(360deg);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
#root {
|
|
53
|
+
display: contents;
|
|
54
|
+
}
|
|
52
55
|
</style>
|
|
53
56
|
</head>
|
|
54
57
|
<body>
|
|
@@ -56,7 +59,7 @@
|
|
|
56
59
|
<h2 class="loading-title">Castle Run</h2>
|
|
57
60
|
<div class="spinner"></div>
|
|
58
61
|
</div>
|
|
59
|
-
<
|
|
62
|
+
<div id="root"></div>
|
|
60
63
|
<script>
|
|
61
64
|
// agent8 embed harness: report the game size to a hosting iframe parent.
|
|
62
65
|
function postGameSize() {
|
|
@@ -71,6 +74,6 @@
|
|
|
71
74
|
if (e.data && e.data.type === 'REQUEST_GAME_SIZE') postGameSize();
|
|
72
75
|
});
|
|
73
76
|
</script>
|
|
74
|
-
<script type="module" src="/src/main.
|
|
77
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
75
78
|
</body>
|
|
76
79
|
</html>
|
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@dimforge/rapier2d-compat": "0.19.3",
|
|
14
|
-
"incanto": "^0.
|
|
14
|
+
"incanto": "^0.78.0",
|
|
15
|
+
"react": "^18.3.1",
|
|
16
|
+
"react-dom": "^18.3.1",
|
|
15
17
|
"three": "^0.184.0"
|
|
16
18
|
},
|
|
17
19
|
"devDependencies": {
|
|
18
20
|
"@types/node": "^24.0.0",
|
|
21
|
+
"@types/react": "^18.3.12",
|
|
22
|
+
"@types/react-dom": "^18.3.1",
|
|
19
23
|
"@types/three": "^0.184.1",
|
|
24
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
20
25
|
"typescript": "^6.0.3",
|
|
21
26
|
"vite": "^8.0.16"
|
|
22
27
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
import knightUrl from 'incanto/assets/characters/medieval-knight.png';
|
|
20
|
+
import coinUrl from 'incanto/assets/items/coin.png';
|
|
21
|
+
import gemUrl from 'incanto/assets/items/gem.png';
|
|
22
|
+
import { useEffect, useRef } from 'react';
|
|
23
|
+
import { FollowCam, GoblinSkin, HudUpdater, ParallaxLayer, PlayerController } from './behaviors';
|
|
24
|
+
import sceneJson from './game.scene.json';
|
|
25
|
+
|
|
26
|
+
export function App() {
|
|
27
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const canvas = canvasRef.current;
|
|
31
|
+
// StrictMode runs this effect twice against the SAME canvas element, and a
|
|
32
|
+
// second engine on one canvas is two renderers fighting for one context.
|
|
33
|
+
// The flag lives on the element, so it lasts exactly as long as the thing
|
|
34
|
+
// it guards — a page-level game like this one never unmounts otherwise.
|
|
35
|
+
if (!canvas || (canvas as { _incanto?: true })._incanto) return;
|
|
36
|
+
(canvas as { _incanto?: true })._incanto = true;
|
|
37
|
+
|
|
38
|
+
void (async () => {
|
|
39
|
+
// Built-in sprite art ships in the package — import the URLs and inject them into
|
|
40
|
+
// the scene's asset placeholders before boot (the engine clones the scene).
|
|
41
|
+
|
|
42
|
+
// OPTIONAL looping music (large tracks aren't bundled). Point at any URL to loop.
|
|
43
|
+
const MUSIC_URL = '';
|
|
44
|
+
|
|
45
|
+
const behaviors = { PlayerController, GoblinSkin, FollowCam, ParallaxLayer, HudUpdater };
|
|
46
|
+
|
|
47
|
+
const scene = structuredClone(sceneJson) as typeof sceneJson & {
|
|
48
|
+
assets: {
|
|
49
|
+
knight: { url: string };
|
|
50
|
+
goblin: { url: string };
|
|
51
|
+
coin: { url: string };
|
|
52
|
+
gem: { url: string };
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
scene.assets.knight.url = knightUrl;
|
|
56
|
+
scene.assets.goblin.url = goblinUrl;
|
|
57
|
+
scene.assets.coin.url = coinUrl;
|
|
58
|
+
scene.assets.gem.url = gemUrl;
|
|
59
|
+
|
|
60
|
+
/** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
|
|
61
|
+
const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
|
|
62
|
+
|
|
63
|
+
const game = await createGame2D({
|
|
64
|
+
debug: DEBUG,
|
|
65
|
+
// The editor's 📚 buttons, served by `incantoLibrary()` in vite.config.ts.
|
|
66
|
+
// BOTH halves are the opt-in, and this half was missing from every shipped
|
|
67
|
+
// template: the plugin alone left a vite config whose own comment promised
|
|
68
|
+
// "the agent8 asset catalog behind the 📚 buttons" and no button anywhere.
|
|
69
|
+
// Gated on the same flag, because `editor` DEFAULTS to `debug` and an object
|
|
70
|
+
// here would turn the editor on in a production build.
|
|
71
|
+
editor: DEBUG && { library: true },
|
|
72
|
+
canvas,
|
|
73
|
+
scene,
|
|
74
|
+
behaviors,
|
|
75
|
+
}).catch((e) => {
|
|
76
|
+
// Without this the player watches the loading overlay sit at 100% forever:
|
|
77
|
+
// `#loading` is removed on the line below, so anything that rejects here (no
|
|
78
|
+
// WebGL context, a scene that will not load) leaves the bar up and the reason
|
|
79
|
+
// in a console nobody opens.
|
|
80
|
+
showBootFailure(e);
|
|
81
|
+
throw e;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// The console handle FIRST, before any wiring of your own can throw.
|
|
85
|
+
// `game.stats()`, `game.assetErrors()`, `game.frame()` and `game.engine.log`
|
|
86
|
+
// are the entire in-page diagnostic surface, and a static build has no other.
|
|
87
|
+
// Assigned after the wiring, one mistake below took all of them with it.
|
|
88
|
+
(window as unknown as { game: typeof game }).game = game;
|
|
89
|
+
|
|
90
|
+
// And the loading overlay comes down LAST. Everything between here and there is
|
|
91
|
+
// your code; a throw in it used to leave a level that renders perfectly with no
|
|
92
|
+
// player, no error UI, and no way to ask the page what happened.
|
|
93
|
+
try {
|
|
94
|
+
if (MUSIC_URL) {
|
|
95
|
+
game.engine.music.play(MUSIC_URL, { loop: true, fadeIn: 1.5 });
|
|
96
|
+
game.engine.audio.music = 0.3;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// No hand-rolled touch button here: the scene declares `"touch": "button"` on
|
|
100
|
+
// `jump` and `"touch": "joystick"` on `move`, so the ENGINE draws both on a
|
|
101
|
+
// coarse pointer — safe-area aware, off the home-indicator band, and sized to
|
|
102
|
+
// its own rules. This template used to draw an 88 px disc of its own at a flat
|
|
103
|
+
// `bottom: 2rem`, which covered 78% of the engine's jump button and put its
|
|
104
|
+
// lower edge inside the band iOS reserves for the leave-the-app swipe.
|
|
105
|
+
} catch (e) {
|
|
106
|
+
showBootFailure(e);
|
|
107
|
+
throw e;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
document.querySelector('#loading')?.remove();
|
|
111
|
+
})();
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
return <canvas ref={canvasRef} id="game" />;
|
|
115
|
+
}
|
|
@@ -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
|
});
|
|
@@ -21,7 +21,7 @@ _Exact versions are in `package.json`._
|
|
|
21
21
|
AnimatedSprite2D, ColorRect2D, Camera2D, AudioPlayer) + `incanto/gameplay`
|
|
22
22
|
(auto-registered Spawner, Chase, Health, DamageOnContact, ScoreKeeper, …).
|
|
23
23
|
- **Art**: built-in animated sheets `medieval-knight` (player), `goblin` + `ghost`
|
|
24
|
-
(enemies), bundler-imported in `
|
|
24
|
+
(enemies), bundler-imported in `App.tsx` and injected into the scene asset urls.
|
|
25
25
|
Arena/bolts/XP shards are `ColorRect2D`.
|
|
26
26
|
- **Audio**: procedural SFX presets (shoot/coin/hurt/win/lose). HUD is DOM.
|
|
27
27
|
- **Build/Lang**: Vite, TypeScript. **Headless verify**: `incanto/test` (`runScript`).
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
- The clone path is internal, so spawning leans on built-in `Spawner` + `Director`
|
|
43
43
|
scatter; bolts/XP are managed `ColorRect2D`s (no per-projectile behavior).
|
|
44
44
|
- Sprites: `medieval-knight`/`goblin` (192px frames), `ghost` (112×128). Swap a sheet
|
|
45
|
-
by changing only its asset entry + the import in `
|
|
45
|
+
by changing only its asset entry + the import in `App.tsx`.
|
|
46
46
|
- Difficulty ramps with elapsed time (spawn rate + enemy HP/speed); tune in `Director`
|
|
47
47
|
and the `Survivor` constants. Music is OPTIONAL (`MUSIC_URL` empty; SFX need no files).
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- Custom `src/behaviors.ts`: `Survivor` (move + auto-attack managed bolts + XP magnet
|
|
15
15
|
+ level-up/upgrades + pause via `engine.stop/start` + DOM HUD), `Director`
|
|
16
16
|
(ring-scatter spawns + time escalation), `EnemyDrop`, `EnemyFace`.
|
|
17
|
-
- `
|
|
17
|
+
- `App.tsx` injects the three sheet urls; `vite.config.ts` aliases `incanto/assets`.
|
|
18
18
|
|
|
19
19
|
## Works (verified)
|
|
20
20
|
|
|
@@ -14,7 +14,7 @@ The arena + horde, mostly BUILT-INS:
|
|
|
14
14
|
|
|
15
15
|
- header — `background`, `viewport` (`design [960,540]`, `fit: expand`),
|
|
16
16
|
`physics.gravity [0,0]` (top-down), `move` input (WASD+arrows), and the
|
|
17
|
-
`knight`/`goblin`/`ghost` spritesheet `assets` (urls injected in `
|
|
17
|
+
`knight`/`goblin`/`ghost` spritesheet `assets` (urls injected in `App.tsx`).
|
|
18
18
|
- `Game` root — `ScoreKeeper` (`scoreToWin 1`, `lives 1`).
|
|
19
19
|
- `Arena`/`ArenaPad`/`ArenaRing*` — dark `ColorRect2D` floor + a centre marker.
|
|
20
20
|
- `Player` — `CharacterBody2D` (group `player`) + `Survivor`; children `Skin`
|
|
@@ -42,7 +42,7 @@ The arena + horde, mostly BUILT-INS:
|
|
|
42
42
|
group `xp` with an `_xp` value) + `Survivor.addKill()`.
|
|
43
43
|
- `EnemyFace` — flip the enemy sprite toward travel + play `move`.
|
|
44
44
|
|
|
45
|
-
## `src/main.
|
|
45
|
+
## `src/main.tsx` and `src/App.tsx`
|
|
46
46
|
|
|
47
47
|
Inject the knight/goblin/ghost urls, `createGame2D({ canvas, scene, behaviors })`,
|
|
48
48
|
optional music, remove the loader, expose `window.game`.
|
|
@@ -17,7 +17,7 @@ YOU MUST follow these rules EXACTLY when vibe-coding on this Incanto template
|
|
|
17
17
|
escalation), `EnemyDrop` (XP shard + kill on death), `EnemyFace` (sprite facing).
|
|
18
18
|
4. 🔑 UIDS ARE GENERATED: omit `uid` and the loader assigns one. Never hand-invent.
|
|
19
19
|
5. 📦 ASSETS ARE DECLARED: `assets` entries need `type` + `url`; reference as `"$key"`.
|
|
20
|
-
The knight/goblin/ghost sheets are BUILT-INS imported in `
|
|
20
|
+
The knight/goblin/ghost sheets are BUILT-INS imported in `App.tsx` and injected
|
|
21
21
|
into the asset url placeholders before boot. ⚠️ For `incanto/assets/*` imports to
|
|
22
22
|
resolve in `bun run dev`, `vite.config.ts` needs an `'incanto/assets'` ALIAS to
|
|
23
23
|
`../../packages/engine/assets` (see this template's config).
|
|
@@ -190,6 +190,9 @@
|
|
|
190
190
|
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.9);
|
|
191
191
|
transition: opacity 0.3s;
|
|
192
192
|
}
|
|
193
|
+
#root {
|
|
194
|
+
display: contents;
|
|
195
|
+
}
|
|
193
196
|
</style>
|
|
194
197
|
</head>
|
|
195
198
|
<body>
|
|
@@ -197,7 +200,7 @@
|
|
|
197
200
|
<h2 class="loading-title">Hollow Horde</h2>
|
|
198
201
|
<div class="spinner"></div>
|
|
199
202
|
</div>
|
|
200
|
-
<
|
|
203
|
+
<div id="root"></div>
|
|
201
204
|
<div id="hud">
|
|
202
205
|
<div class="bar-track" id="hp-track"><div class="bar-fill" id="hp-fill"></div></div>
|
|
203
206
|
<div id="hp-text">100</div>
|
|
@@ -227,6 +230,6 @@
|
|
|
227
230
|
if (e.data && e.data.type === 'REQUEST_GAME_SIZE') postGameSize();
|
|
228
231
|
});
|
|
229
232
|
</script>
|
|
230
|
-
<script type="module" src="/src/main.
|
|
233
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
231
234
|
</body>
|
|
232
235
|
</html>
|
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@dimforge/rapier2d-compat": "0.19.3",
|
|
14
|
-
"incanto": "^0.
|
|
14
|
+
"incanto": "^0.78.0",
|
|
15
|
+
"react": "^18.3.1",
|
|
16
|
+
"react-dom": "^18.3.1",
|
|
15
17
|
"three": "^0.184.0"
|
|
16
18
|
},
|
|
17
19
|
"devDependencies": {
|
|
18
20
|
"@types/node": "^24.0.0",
|
|
21
|
+
"@types/react": "^18.3.12",
|
|
22
|
+
"@types/react-dom": "^18.3.1",
|
|
19
23
|
"@types/three": "^0.184.1",
|
|
24
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
20
25
|
"typescript": "^6.0.3",
|
|
21
26
|
"vite": "^8.0.16"
|
|
22
27
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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 { useEffect, useRef } from 'react';
|
|
19
|
+
import { Director, EnemyDrop, EnemyFace, GridFloor, Survivor } from './behaviors';
|
|
20
|
+
import sceneJson from './game.scene.json';
|
|
21
|
+
|
|
22
|
+
export function App() {
|
|
23
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const canvas = canvasRef.current;
|
|
27
|
+
// StrictMode runs this effect twice against the SAME canvas element, and a
|
|
28
|
+
// second engine on one canvas is two renderers fighting for one context.
|
|
29
|
+
// The flag lives on the element, so it lasts exactly as long as the thing
|
|
30
|
+
// it guards — a page-level game like this one never unmounts otherwise.
|
|
31
|
+
if (!canvas || (canvas as { _incanto?: true })._incanto) return;
|
|
32
|
+
(canvas as { _incanto?: true })._incanto = true;
|
|
33
|
+
|
|
34
|
+
void (async () => {
|
|
35
|
+
const MUSIC_URL = '';
|
|
36
|
+
|
|
37
|
+
const behaviors = { Survivor, Director, EnemyDrop, EnemyFace, GridFloor };
|
|
38
|
+
|
|
39
|
+
const scene = structuredClone(sceneJson) as typeof sceneJson & {
|
|
40
|
+
assets: { knight: { url: string }; goblin: { url: string }; ghost: { url: string } };
|
|
41
|
+
};
|
|
42
|
+
scene.assets.knight.url = knightUrl;
|
|
43
|
+
scene.assets.goblin.url = goblinUrl;
|
|
44
|
+
scene.assets.ghost.url = ghostUrl;
|
|
45
|
+
|
|
46
|
+
/** Dev-only surfaces: the ☰ debug overlay, and the editor behind it. */
|
|
47
|
+
const DEBUG = import.meta.env.VITE_INCANTO_DEBUG === '1';
|
|
48
|
+
|
|
49
|
+
const game = await createGame2D({
|
|
50
|
+
debug: DEBUG,
|
|
51
|
+
// The editor's 📚 buttons, served by `incantoLibrary()` in vite.config.ts.
|
|
52
|
+
// BOTH halves are the opt-in, and this half was missing from every shipped
|
|
53
|
+
// template: the plugin alone left a vite config whose own comment promised
|
|
54
|
+
// "the agent8 asset catalog behind the 📚 buttons" and no button anywhere.
|
|
55
|
+
// Gated on the same flag, because `editor` DEFAULTS to `debug` and an object
|
|
56
|
+
// here would turn the editor on in a production build.
|
|
57
|
+
editor: DEBUG && { library: true },
|
|
58
|
+
canvas,
|
|
59
|
+
scene,
|
|
60
|
+
behaviors,
|
|
61
|
+
}).catch((e) => {
|
|
62
|
+
// Without this the player watches the loading overlay sit at 100% forever:
|
|
63
|
+
// `#loading` is removed on the line below, so anything that rejects here (no
|
|
64
|
+
// WebGL context, a scene that will not load) leaves the bar up and the reason
|
|
65
|
+
// in a console nobody opens.
|
|
66
|
+
showBootFailure(e);
|
|
67
|
+
throw e;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// The console handle FIRST, before any wiring of your own can throw.
|
|
71
|
+
// `game.stats()`, `game.assetErrors()`, `game.frame()` and `game.engine.log`
|
|
72
|
+
// are the entire in-page diagnostic surface, and a static build has no other.
|
|
73
|
+
// Assigned after the wiring, one mistake below took all of them with it.
|
|
74
|
+
(window as unknown as { game: typeof game }).game = game;
|
|
75
|
+
|
|
76
|
+
// And the loading overlay comes down LAST. Everything between here and there is
|
|
77
|
+
// your code; a throw in it used to leave a level that renders perfectly with no
|
|
78
|
+
// player, no error UI, and no way to ask the page what happened.
|
|
79
|
+
try {
|
|
80
|
+
if (MUSIC_URL) {
|
|
81
|
+
game.engine.music.play(MUSIC_URL, { loop: true, fadeIn: 1.5 });
|
|
82
|
+
game.engine.audio.music = 0.3;
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
showBootFailure(e);
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
document.querySelector('#loading')?.remove();
|
|
90
|
+
})();
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
return <canvas ref={canvasRef} id="game" />;
|
|
94
|
+
}
|
|
@@ -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
|
});
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- `src/behaviors.ts`: `Shoot` (18-round clip, hitscan within a ~9° crosshair cone
|
|
25
25
|
to 70 m, tracer + muzzle flash, face-aim on fire, reload) and `HudUpdater`
|
|
26
26
|
(HP/Kills/Wave/Ammo + health bar + SECURE/FAILED banner).
|
|
27
|
-
- `src/
|
|
27
|
+
- `src/App.tsx`: preloads the GLB + animation assets with a progress bar
|
|
28
28
|
(`preloadUrls(assetUrls(scene.assets))`), then `createGame3D` with
|
|
29
29
|
`pointer: true`, looping music, on-screen FIRE/JUMP buttons, `window.game`.
|
|
30
30
|
- `index.html`: canvas + center crosshair + HUD overlay + banner + mobile buttons.
|