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.
- package/README.md +9 -4
- package/dist/index.js +1 -1
- package/dist/{src-DygJfhOb.js → src-SWXe-bub.js} +1 -1
- package/dist/vite.js +1 -1
- 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 +2 -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
package/README.md
CHANGED
|
@@ -21,9 +21,14 @@ rest declared in JSON. `bun run verify` plays the entire quest **headlessly**
|
|
|
21
21
|
and replays recorded input bit-identically: the agent loop is author → verify →
|
|
22
22
|
fix, no browser needed.
|
|
23
23
|
|
|
24
|
-
`bunx incanto new --list` shows all
|
|
25
|
-
3D (island adventure, third-person shooter, quest vignette) and
|
|
26
|
-
(platformer, survivors-like).
|
|
24
|
+
`bunx incanto new --list` shows all six starters, grouped by dimension: three
|
|
25
|
+
3D (island adventure, third-person shooter, quest vignette) and three 2D
|
|
26
|
+
(platformer, survivors-like, and a whack-a-mole played with the mouse alone).
|
|
27
|
+
|
|
28
|
+
Every starter is **TypeScript + Vite + React**: `src/main.tsx` mounts `<App />`
|
|
29
|
+
and `src/App.tsx` owns the canvas the engine draws into. React is the shell, not
|
|
30
|
+
the game — `incanto/react`'s `<IncantoCanvas>` is the other shape, for putting a
|
|
31
|
+
game inside an app you already have.
|
|
27
32
|
|
|
28
33
|
## Or wire the engine yourself
|
|
29
34
|
|
|
@@ -53,7 +58,7 @@ engine.start();
|
|
|
53
58
|
| `incanto/net` | multiplayer over a pluggable `NetworkTransport` — built-in offline Loopback + [@agent8/gameserver](https://www.npmjs.com/package/@agent8/gameserver) adapter (optional peer); custom backends implement one interface |
|
|
54
59
|
|
|
55
60
|
- `schemas/scene.schema.json` — generated JSON Schema for scene files (the agent contract)
|
|
56
|
-
- `templates/agent8-server.
|
|
61
|
+
- `templates/agent8-server.ts` — the multiplayer server kernel for the agent8 platform (`.js` beside it for the legacy single-file server)
|
|
57
62
|
|
|
58
63
|
## Debug mode
|
|
59
64
|
|
package/dist/index.js
CHANGED
|
@@ -9,5 +9,5 @@ import { t as showBootFailure } from "./boot-failure-CKYrEcGF.js";
|
|
|
9
9
|
import { a as logText, i as logReport, o as parseDrive, r as resolveRendering, t as isWebGLAvailable } from "./webgl-unavailable-C8aDbGmR.js";
|
|
10
10
|
import { t as createNoise2D } from "./noise-D3nPpmFg.js";
|
|
11
11
|
import { a as PARTICLE_PRESETS, i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-SQa5gIu2.js";
|
|
12
|
-
import { a as findPath, i as preloadUrls, n as assetUrls, o as gridFromRows, r as preloadSceneAssets, t as VERSION } from "./src-
|
|
12
|
+
import { a as findPath, i as preloadUrls, n as assetUrls, o as gridFromRows, r as preloadSceneAssets, t as VERSION } from "./src-SWXe-bub.js";
|
|
13
13
|
export { AudioBuses, AudioPlayer, BASE_LOCALE, Behavior, CONST_REF_KEY, EffectLog, Engine, HudLayer, IncantoError, InputMap, Localization, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Respawn, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, SaveSlots, Scene, SceneTree, Settings, SfxEngine, Signal, T_PREFIX, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiFrameCapSelect, UiImage, UiLanguageSelect, UiMinimap, UiMuteToggle, UiPanel, UiQualitySelect, UiRenderScaleSelect, UiSelect, UiSlider, UiText, UiToggle, UiVolumeSlider, UiWaypoint, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, behaviorSchema, behaviorSignals, behaviorsWithoutSave, captureBehaviors, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, describeRefProblem, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, isWebGLAvailable, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, logReport, logText, mergeStaticSignals, newUid, nodeRefWarnings, parseDrive, parseNodePath, preloadSceneAssets, preloadUrls, qualityEnvironment, readDeviceHints, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveAnimation, resolveConstants, resolveFrames, resolveOrderGroups, resolveRefInJson, resolveRendering, resolveViewport, restoreBehaviors, savesWithoutUid, serializeNode, sfxDuration, showBootFailure, spatialGain, spatialPan, startRecording, suggestLocale, suggestQuality, synthSfx, translationKey };
|
|
@@ -199,6 +199,6 @@ async function preloadSceneAssets(assets, title) {
|
|
|
199
199
|
//#endregion
|
|
200
200
|
//#region src/index.ts
|
|
201
201
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
202
|
-
const VERSION = "0.
|
|
202
|
+
const VERSION = "0.78.1";
|
|
203
203
|
//#endregion
|
|
204
204
|
export { findPath as a, preloadUrls as i, assetUrls as n, gridFromRows as o, preloadSceneAssets as r, VERSION as t };
|
package/dist/vite.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as parseJsonText } from "./json-CfTjpvW8.js";
|
|
2
2
|
import { P as newUid } from "./pose-ByFB_J3O.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
|
-
import { t as VERSION } from "./src-
|
|
4
|
+
import { t as VERSION } from "./src-SWXe-bub.js";
|
|
5
5
|
import { n as diffSignatures } from "./frame-report-D-_7YF2G.js";
|
|
6
6
|
import { s as validateScene } from "./test-CVbxnXlv.js";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
package/package.json
CHANGED
|
@@ -44,6 +44,12 @@ const game = await createGame2D({
|
|
|
44
44
|
// game.dispose() — ONE call tears everything down (SPA unmount)
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
A scaffolded project calls this from `src/App.tsx` — React owns the page, the
|
|
48
|
+
component owns the canvas, and the boot runs in an effect. `canvas` comes from a
|
|
49
|
+
ref there rather than a `querySelector`. See `incanto-web-integration.md` for
|
|
50
|
+
that shell and for the `<IncantoCanvas>` component you use instead when the game
|
|
51
|
+
is one part of a larger site.
|
|
52
|
+
|
|
47
53
|
One call does register → load → physics → input → renderer → start. Physics is
|
|
48
54
|
`'auto'`: Rapier is enabled iff the tree has physics bodies (force with
|
|
49
55
|
`physics: true|false`). Keyboard attaches to `window` (`keyboard: false`
|
|
@@ -52,6 +52,12 @@ const game = await createGame3D({
|
|
|
52
52
|
// game.dispose() — ONE call tears everything down (SPA unmount)
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
A scaffolded project calls this from `src/App.tsx` — React owns the page, the
|
|
56
|
+
component owns the canvas, and the boot runs in an effect. `canvas` comes from a
|
|
57
|
+
ref there rather than a `querySelector`. See `incanto-web-integration.md` for
|
|
58
|
+
that shell and for the `<IncantoCanvas>` component you use instead when the game
|
|
59
|
+
is one part of a larger site.
|
|
60
|
+
|
|
55
61
|
One call does register → load → physics → input → renderer → start. Physics is
|
|
56
62
|
`'auto'`: Rapier is enabled iff the tree has physics bodies (force with
|
|
57
63
|
`physics: true|false`). Keyboard attaches to `window` (`keyboard: false`
|
package/skills/incanto-editor.md
CHANGED
|
@@ -363,7 +363,7 @@ import { incantoLibrary, incantoScenes } from 'incanto/vite';
|
|
|
363
363
|
export default defineConfig({ plugins: [incantoScenes(), incantoLibrary()] });
|
|
364
364
|
```
|
|
365
365
|
```ts
|
|
366
|
-
//
|
|
366
|
+
// App.tsx
|
|
367
367
|
createGame3D({
|
|
368
368
|
...,
|
|
369
369
|
debug: import.meta.env.VITE_INCANTO_DEBUG === '1',
|
|
@@ -407,7 +407,7 @@ pass `editor.save` — otherwise edits live as long as the session does.
|
|
|
407
407
|
|
|
408
408
|
**Saving keeps you in the editor.** It used to throw you out of it: the save
|
|
409
409
|
writes the scene through `incantoScenes()`'s `PUT /api/scene`, vite saw its own
|
|
410
|
-
watched file change, and a JSON module imported by `
|
|
410
|
+
watched file change, and a JSON module imported by `App.tsx` cannot be
|
|
411
411
|
hot-swapped — so vite full-page-RELOADED, the page came back as the game, and
|
|
412
412
|
the selection, the camera and the panel you were working in were gone. The
|
|
413
413
|
plugin now recognises a write it just made and lets the page be. A hand edit in
|
|
@@ -447,7 +447,7 @@ the composition:
|
|
|
447
447
|
in the browser that offline pose went out as the client's FIRST replicated
|
|
448
448
|
state, on top of the other player's car, which the kinematic copy then lifted
|
|
449
449
|
off the ground. Boot with `autoStart: false` and call `game.engine.start()`
|
|
450
|
-
once the manager exists (`examples/race-mp-3d/src/
|
|
450
|
+
once the manager exists (`examples/race-mp-3d/src/App.tsx`):
|
|
451
451
|
|
|
452
452
|
```ts
|
|
453
453
|
const game = await createGame3D({ canvas, scene, behaviors, autoStart: false });
|
|
@@ -267,7 +267,7 @@ signature of a save format change, so the hunt started in the wrong place
|
|
|
267
267
|
entirely.
|
|
268
268
|
|
|
269
269
|
**To keep the continue INSIDE the game** (where a harness can reach it —
|
|
270
|
-
`runScript` boots the scene, never your `
|
|
270
|
+
`runScript` boots the scene, never your `App.tsx`), do it on the first frame:
|
|
271
271
|
|
|
272
272
|
```ts
|
|
273
273
|
class Game extends Behavior {
|
|
@@ -428,7 +428,7 @@ it does not load. Every CLI (`incanto-check`, `-playtest`, `-verify`, `-feel`,
|
|
|
428
428
|
game and your harness are the two places you write it yourself:
|
|
429
429
|
|
|
430
430
|
```ts
|
|
431
|
-
//
|
|
431
|
+
// App.tsx — vite. Every *.scene.json beside this file, keyed by the path an
|
|
432
432
|
// `instance:` names.
|
|
433
433
|
const prefabs = import.meta.glob('./*.scene.json', { eager: true, import: 'default' });
|
|
434
434
|
const resolveScene = (path: string): unknown => prefabs[`./${path}`] ?? null;
|
|
@@ -914,8 +914,8 @@ thing gating it: keep it `false` (or dev-gated) in anything you publish.
|
|
|
914
914
|
Everything above measures a dev server. The artifact a player gets is
|
|
915
915
|
`bun run build` — a bundled, minified `dist/` with different asset paths and a
|
|
916
916
|
different module graph — and the boot path is the only code a BUILD adds over a
|
|
917
|
-
SCENE. Two of the nastiest defects this engine has had lived exactly there: a
|
|
918
|
-
|
|
917
|
+
SCENE. Two of the nastiest defects this engine has had lived exactly there: a boot
|
|
918
|
+
file whose scene swap left the game with no physics, and a boot that threw
|
|
919
919
|
behind a loading overlay that never came down.
|
|
920
920
|
|
|
921
921
|
`incanto-frame` and `incanto-logs` ask a dev server, and the dev server asks the
|
|
@@ -9,8 +9,46 @@ description: Embed an Incanto game in a React/Next/any web app — the IncantoCa
|
|
|
9
9
|
> installed engine version. Sibling skills live in `node_modules/incanto/skills/`.
|
|
10
10
|
|
|
11
11
|
Incanto's core is framework-free and every entry imports cleanly in Node (SSR
|
|
12
|
-
safe).
|
|
13
|
-
`
|
|
12
|
+
safe). React is an OPTIONAL peer of the package — but every project
|
|
13
|
+
`incanto-new` scaffolds already ships it, because the whole stack is
|
|
14
|
+
**TypeScript + Vite + React**.
|
|
15
|
+
|
|
16
|
+
## Two shapes, and which one you want
|
|
17
|
+
|
|
18
|
+
| | `src/App.tsx` (what a scaffold gives you) | `<IncantoCanvas>` (`incanto/react`) |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| owns | the whole page | one box inside a bigger app |
|
|
21
|
+
| canvas | yours, one `<canvas ref>` | the component's |
|
|
22
|
+
| boot | your own `createGame2D/3D` call | the component's |
|
|
23
|
+
| gives you | every boot option — `editor`, asset preloading with a progress bar, `showBootFailure` | scene, behaviors, and the common options |
|
|
24
|
+
| HUD | `HudLayer` nodes, or DOM in `index.html` | `children`, with `useGame()` inside |
|
|
25
|
+
|
|
26
|
+
**A scaffolded game keeps its own `App.tsx`.** It is ten lines and it can pass
|
|
27
|
+
anything `createGame3D` takes:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
export function App() {
|
|
31
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const canvas = canvasRef.current;
|
|
34
|
+
// StrictMode runs this twice against the SAME element — boot once.
|
|
35
|
+
if (!canvas || (canvas as { _incanto?: true })._incanto) return;
|
|
36
|
+
(canvas as { _incanto?: true })._incanto = true;
|
|
37
|
+
void (async () => {
|
|
38
|
+
const game = await createGame3D({ canvas, scene: sceneJson, behaviors });
|
|
39
|
+
(window as unknown as { game: typeof game }).game = game;
|
|
40
|
+
document.querySelector('#loading')?.remove();
|
|
41
|
+
})();
|
|
42
|
+
}, []);
|
|
43
|
+
return <canvas ref={canvasRef} id="game" />;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`index.html` holds `<div id="root"></div>` styled `display: contents`, so every
|
|
48
|
+
CSS rule that used to target the canvas as a body child still applies.
|
|
49
|
+
|
|
50
|
+
Reach for `IncantoCanvas` instead when the game is a COMPONENT of a site you
|
|
51
|
+
already have — a page with a header, a route in a Next app, a card in a grid.
|
|
14
52
|
|
|
15
53
|
## React: the component
|
|
16
54
|
|
|
@@ -51,10 +51,15 @@ point-and-click or an RTS starts from).
|
|
|
51
51
|
```
|
|
52
52
|
src/game.scene.json ← the game. Nodes, props, connections. This is most of it.
|
|
53
53
|
src/behaviors.ts ← the parts JSON cannot express. Usually very little.
|
|
54
|
-
src/
|
|
54
|
+
src/App.tsx ← boot. The React component that owns the canvas. Rarely touched.
|
|
55
|
+
src/main.tsx ← mounts <App /> into #root. Never touched.
|
|
55
56
|
verify.ts ← the harness that proves it works. You WILL touch it.
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
Every project is **TypeScript + Vite + React**: `main.tsx` mounts the app,
|
|
60
|
+
`App.tsx` renders one `<canvas>` and boots the engine into it from an effect.
|
|
61
|
+
React is the shell, not the game — the game is still the JSON.
|
|
62
|
+
|
|
58
63
|
The claim that "all structure is JSON" is not marketing: one of the four builds
|
|
59
64
|
shipped a whole 3D world with **zero** gameplay TypeScript, and another needed
|
|
60
65
|
90 lines for one melee swing. Reach for a built-in behavior before you write a
|
|
@@ -5,7 +5,8 @@ generate-world.ts the WORLD AUTHORING script (bun run world):
|
|
|
5
5
|
island terrain + sea + groves/grass/flowers +
|
|
6
6
|
quest sites on walkable ground → src/game.scene.json
|
|
7
7
|
index.html canvas + loading bar
|
|
8
|
-
src/main.
|
|
8
|
+
src/main.tsx React entry — mounts <App /> into #root
|
|
9
|
+
src/App.tsx preload → createGame3D
|
|
9
10
|
src/game.scene.json GENERATED — do not hand-edit; edit generate-world.ts
|
|
10
11
|
src/behaviors.ts IsleDirector (quest FSM, terrain-nav shade AI,
|
|
11
12
|
ward relighting, fireworks, respawn) + SwordStrike
|
|
@@ -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
|
-
<
|
|
63
|
+
<div id="root"></div>
|
|
61
64
|
<div id="loading" class="loading-container">
|
|
62
65
|
<div class="loading-title">Beacon Isle</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.
|
|
69
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
67
70
|
</body>
|
|
68
71
|
</html>
|
|
@@ -14,12 +14,17 @@
|
|
|
14
14
|
"@dimforge/rapier2d-compat": "0.19.3",
|
|
15
15
|
"@dimforge/rapier3d-compat": "0.19.3",
|
|
16
16
|
"@pixiv/three-vrm": "^3.5.3",
|
|
17
|
-
"incanto": "^0.
|
|
17
|
+
"incanto": "^0.78.1",
|
|
18
|
+
"react": "^18.3.1",
|
|
19
|
+
"react-dom": "^18.3.1",
|
|
18
20
|
"three": "^0.184.0"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
23
|
"@types/node": "^24.0.0",
|
|
24
|
+
"@types/react": "^18.3.12",
|
|
25
|
+
"@types/react-dom": "^18.3.1",
|
|
22
26
|
"@types/three": "^0.184.1",
|
|
27
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
23
28
|
"typescript": "^6.0.3",
|
|
24
29
|
"vite": "^8.0.16"
|
|
25
30
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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 { useEffect, useRef } from 'react';
|
|
11
|
+
import { BEHAVIORS } from './behaviors';
|
|
12
|
+
import gameJson from './game.scene.json';
|
|
13
|
+
|
|
14
|
+
export function App() {
|
|
15
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const canvas = canvasRef.current;
|
|
19
|
+
// StrictMode runs this effect twice against the SAME canvas element, and a
|
|
20
|
+
// second engine on one canvas is two renderers fighting for one context.
|
|
21
|
+
// The flag lives on the element, so it lasts exactly as long as the thing
|
|
22
|
+
// it guards — a page-level game like this one never unmounts otherwise.
|
|
23
|
+
if (!canvas || (canvas as { _incanto?: true })._incanto) return;
|
|
24
|
+
(canvas as { _incanto?: true })._incanto = true;
|
|
25
|
+
|
|
26
|
+
void (async () => {
|
|
27
|
+
const fill = document.querySelector('#progress') as HTMLElement | null;
|
|
28
|
+
const text = document.querySelector('#progress-text') as HTMLElement | null;
|
|
29
|
+
await preloadUrls(assetUrls(gameJson.assets), (done, total) => {
|
|
30
|
+
const pct = total > 0 ? Math.round((done / total) * 100) : 100;
|
|
31
|
+
if (fill) fill.style.width = `${pct}%`;
|
|
32
|
+
if (text) text.textContent = `${pct}%`;
|
|
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 createGame3D({
|
|
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: gameJson,
|
|
48
|
+
behaviors: BEHAVIORS,
|
|
49
|
+
pointer: true,
|
|
50
|
+
}).catch((e) => {
|
|
51
|
+
// Without this the player watches the loading overlay sit at 100% forever:
|
|
52
|
+
// `#loading` is removed on the line below, so anything that rejects here (no
|
|
53
|
+
// WebGL context, a scene that will not load) leaves the bar up and the reason
|
|
54
|
+
// in a console nobody opens.
|
|
55
|
+
showBootFailure(e);
|
|
56
|
+
throw e;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// The console handle FIRST, before any wiring of your own can throw.
|
|
60
|
+
// `game.stats()`, `game.assetErrors()`, `game.frame()` and `game.engine.log`
|
|
61
|
+
// are the entire in-page diagnostic surface, and a static build has no other.
|
|
62
|
+
// Assigned after the wiring, one mistake below took all of them with it.
|
|
63
|
+
(window as unknown as { game: typeof game }).game = game;
|
|
64
|
+
|
|
65
|
+
// The overlay comes down LAST, after any wiring of your own. There is none in
|
|
66
|
+
// this template — when you add some, put it above this line and wrap it:
|
|
67
|
+
//
|
|
68
|
+
// try { …your wiring… } catch (e) { showBootFailure(e); throw e; }
|
|
69
|
+
//
|
|
70
|
+
// A throw between the overlay coming down and the handle going up used to leave
|
|
71
|
+
// a level that renders perfectly with no player and no error of any kind.
|
|
72
|
+
document.querySelector('#loading')?.remove();
|
|
73
|
+
})();
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
return <canvas ref={canvasRef} id="game" />;
|
|
77
|
+
}
|
|
@@ -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
|
});
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
```
|
|
4
4
|
src/game.scene.json the whole game's structure — holes, moles, HUD, wiring
|
|
5
5
|
src/behaviors.ts Molehill (when a mole pops) + Scoreboard (what the HUD says)
|
|
6
|
-
src/main.
|
|
6
|
+
src/main.tsx React entry — mounts <App /> into #root
|
|
7
|
+
src/App.tsx createGame2D, the pointer, the two behaviours
|
|
7
8
|
verify.ts the headless proof — drives the MOUSE
|
|
8
9
|
coverage.json the gate's two questions: can it hurt you, can it be lost
|
|
9
10
|
```
|
|
@@ -59,6 +59,9 @@
|
|
|
59
59
|
transform: rotate(360deg);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
#root {
|
|
63
|
+
display: contents;
|
|
64
|
+
}
|
|
62
65
|
</style>
|
|
63
66
|
</head>
|
|
64
67
|
<body>
|
|
@@ -67,7 +70,7 @@
|
|
|
67
70
|
<div class="spinner"></div>
|
|
68
71
|
<div class="hint">click the moles — twelve to win, three misses and they win</div>
|
|
69
72
|
</div>
|
|
70
|
-
<
|
|
71
|
-
<script type="module" src="/src/main.
|
|
73
|
+
<div id="root"></div>
|
|
74
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
72
75
|
</body>
|
|
73
76
|
</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.1",
|
|
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.
|