incanto 0.52.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/incanto-check.mjs +56 -1
- package/dist/2d.d.ts +95 -4
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +111 -6
- package/dist/3d.js +10 -6
- package/dist/{behavior-uPEuZrUB.d.ts → behavior-CyQoSu4n.d.ts} +212 -10
- package/dist/{create-game-B_e9hJf7.js → create-game-BRt6XKmP.js} +31 -12
- package/dist/{create-game-CGnoypjL.js → create-game-Czzp6ZuE.js} +26 -14
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +1 -1
- package/dist/{duplicate-B-OtSRFL.js → duplicate-MNLMAcbz.js} +1 -1
- package/dist/editor.js +14 -0
- package/dist/{environment-presets-DSZwsKPs.js → environment-presets-BQ_QsIBY.js} +222 -11
- package/dist/{gameplay-B6jqvYeM.js → gameplay-CZ2yq37J.js} +32 -23
- package/dist/gameplay.d.ts +8 -3
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +178 -6
- package/dist/index.js +9 -9
- package/dist/{loader-B-Gft32x.js → loader-BTkHYrQn.js} +54 -18
- package/dist/{loader-B9iTqs27.d.ts → loader-DhI1jFW_.d.ts} +1 -1
- package/dist/{log-report-lxrQY9cH.js → log-report-CPFm4OXf.js} +0 -0
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/{particle-sim-BzJ1yxoE.d.ts → particle-sim-C5OfBbmU.d.ts} +11 -0
- package/dist/{pathfinding-CAR9DjQQ.d.ts → pathfinding-CXGCpRQe.d.ts} +24 -1
- package/dist/{physics-2d-Dns-oZlE.js → physics-2d-CfWAggJ1.js} +2 -2
- package/dist/{physics-3d-BhI0ehpe.js → physics-3d-Bes-GIuR.js} +3 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-Trx7WHnD.js → register-en63AEZO.js} +163 -4
- package/dist/{register-DVwlnZAZ.js → register-p48lHE2o.js} +433 -36
- package/dist/{replay-BU1CCM15.d.ts → replay-ePMz26jw.d.ts} +1 -1
- package/dist/{replay-BicPOMX0.js → replay-t1pP0gQg.js} +2 -2
- package/dist/{split-screen-paxkQs_q.d.ts → split-screen-BsdOHbzP.d.ts} +1 -1
- package/dist/{split-screen-CZ9ccBBQ.js → split-screen-CSb_uZ6W.js} +2 -2
- package/dist/{sprite-animation-D_p28jwU.js → sprite-animation-7qvUxF6Z.js} +19 -0
- package/dist/{src-5gbZO47I.js → src-vNPQeQ1W.js} +1 -1
- package/dist/{teardown-ks3d5W9n.js → teardown-C7uVSJvx.js} +30 -1
- package/dist/{test-DAojuFdb.js → test-Tnf1nQl3.js} +25 -13
- package/dist/test.d.ts +4 -4
- package/dist/test.js +2 -2
- package/dist/{touch-BoNg_MnF.js → touch-BnMyy9tr.js} +4 -4
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-CCvckvbw.js → agent8-BWaW-D85.js} +1 -1
- package/editor/assets/{debug-CLNCOnbc.js → debug-DLF8rUtr.js} +2 -2
- package/editor/assets/{index-Cb5Brupb.js → index-BYfiwbQx.js} +91 -91
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +221 -0
- package/skills/incanto-assets.md +45 -2
- package/skills/incanto-audio.md +97 -10
- package/skills/incanto-building-2d-games.md +55 -3
- package/skills/incanto-building-3d-games.md +3 -1
- package/skills/incanto-environment.md +3 -1
- package/skills/incanto-gameplay-behaviors.md +51 -4
- package/skills/incanto-hud.md +7 -0
- package/skills/incanto-node-reference.md +36 -0
- package/skills/incanto-performance.md +47 -0
- package/skills/incanto-physics-and-input.md +38 -0
- package/skills/incanto-verifying-your-game.md +50 -1
- package/skills/incanto-web-integration.md +1 -0
- package/templates-app/beacon-isle-3d/index.html +3 -3
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/index.html +3 -3
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/index.html +3 -3
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { b as effectiveOrder, f as Node } from "./loader-
|
|
2
|
-
import { t as registerCoreNodes } from "./register-
|
|
1
|
+
import { C as diagnose, b as effectiveOrder, f as Node } from "./loader-BTkHYrQn.js";
|
|
2
|
+
import { t as registerCoreNodes } from "./register-p48lHE2o.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
5
|
import { i as getNodeSchema, l as registerNode } from "./registry-C7u42TID.js";
|
|
6
|
-
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-
|
|
6
|
+
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimationAliases, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-7qvUxF6Z.js";
|
|
7
7
|
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry } from "three";
|
|
8
8
|
//#region src/2d/physics/collider-validate.ts
|
|
9
9
|
/**
|
|
@@ -305,6 +305,59 @@ var Joint2D = class extends Node2D {
|
|
|
305
305
|
}
|
|
306
306
|
};
|
|
307
307
|
//#endregion
|
|
308
|
+
//#region src/2d/sheet-grid.ts
|
|
309
|
+
/**
|
|
310
|
+
* Does this spritesheet's grid actually fit the image?
|
|
311
|
+
*
|
|
312
|
+
* `frameWidth`/`frameHeight` are taken on trust, and the frame math floors the
|
|
313
|
+
* division — so a frame size that does not divide the sheet slices every row a
|
|
314
|
+
* little further off, and an animation naming a frame past the end of the grid
|
|
315
|
+
* reads whatever texels are there. Both draw WRONG ART with no error anywhere,
|
|
316
|
+
* which is the one symptom an author cannot tell apart from having picked the
|
|
317
|
+
* wrong sheet in the first place.
|
|
318
|
+
*
|
|
319
|
+
* Pure arithmetic on numbers that are known the moment the image loads, so it
|
|
320
|
+
* costs nothing and it is exact.
|
|
321
|
+
*/
|
|
322
|
+
/**
|
|
323
|
+
* Frame sizes that divide `total` cleanly, biggest first.
|
|
324
|
+
*
|
|
325
|
+
* Bounded to grids of 2–32 cells so the suggestion is a frame size someone
|
|
326
|
+
* might actually have meant, not every factor of the number.
|
|
327
|
+
*/
|
|
328
|
+
function divisors(total) {
|
|
329
|
+
const out = [];
|
|
330
|
+
for (let cells = 2; cells <= 32; cells++) if (total % cells === 0) out.push(total / cells);
|
|
331
|
+
return out.sort((a, b) => b - a).slice(0, 6);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The problem with this grid, or null when there is none.
|
|
335
|
+
*
|
|
336
|
+
* `highestFrame` is the largest frame index the scene asks for, when it is
|
|
337
|
+
* known — an animation range that runs off the end is the other half of the
|
|
338
|
+
* same mistake.
|
|
339
|
+
*/
|
|
340
|
+
function checkSheetGrid(key, frameWidth, frameHeight, imageWidth, imageHeight, highestFrame) {
|
|
341
|
+
if (!imageWidth || !imageHeight || !frameWidth || !frameHeight) return null;
|
|
342
|
+
if (imageWidth % frameWidth !== 0 || imageHeight % frameHeight !== 0) {
|
|
343
|
+
const parts = [];
|
|
344
|
+
if (imageWidth % frameWidth !== 0) {
|
|
345
|
+
const fits = divisors(imageWidth);
|
|
346
|
+
parts.push(`${imageWidth} px wide is not a whole number of ${frameWidth} px frames` + (fits.length > 0 ? ` (${fits.join(", ")} would divide it)` : ""));
|
|
347
|
+
}
|
|
348
|
+
if (imageHeight % frameHeight !== 0) {
|
|
349
|
+
const fits = divisors(imageHeight);
|
|
350
|
+
parts.push(`${imageHeight} px tall is not a whole number of ${frameHeight} px frames` + (fits.length > 0 ? ` (${fits.join(", ")} would divide it)` : ""));
|
|
351
|
+
}
|
|
352
|
+
return `[incanto] spritesheet '${key}' does not fit its frame size: ${parts.join("; ")}. Every row after the first is cut off centre.`;
|
|
353
|
+
}
|
|
354
|
+
if (highestFrame !== void 0) {
|
|
355
|
+
const frames = imageWidth / frameWidth * (imageHeight / frameHeight);
|
|
356
|
+
if (highestFrame >= frames) return `[incanto] spritesheet '${key}' has ${frames} frames (0–${frames - 1}), and an animation asks for frame ${highestFrame}. Those frames draw whatever is at the wrong end of the sheet.`;
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
//#endregion
|
|
308
361
|
//#region src/2d/nodes/sprite-2d.ts
|
|
309
362
|
/** Shared unit quad — sprites are sized via the quad's scale, never THREE.Sprite. */
|
|
310
363
|
const UNIT_PLANE$2 = new PlaneGeometry(1, 1);
|
|
@@ -452,6 +505,30 @@ var AnimatedSprite2D = class extends Sprite2D {
|
|
|
452
505
|
}
|
|
453
506
|
this.currentFrame = this.frameList[this.frameIndex] ?? 0;
|
|
454
507
|
}
|
|
508
|
+
/** Sheets already reported, so a bad grid says so ONCE, not every frame. */
|
|
509
|
+
gridChecked = "";
|
|
510
|
+
/**
|
|
511
|
+
* Tell the author their grid does not fit, the first time it is drawn.
|
|
512
|
+
*
|
|
513
|
+
* Both halves of this were silent: a frame size that does not divide the
|
|
514
|
+
* sheet slices every row further off centre, and an animation naming a frame
|
|
515
|
+
* past the end of the grid draws whatever is at the wrong end of the image.
|
|
516
|
+
*/
|
|
517
|
+
checkGrid(ref, frameWidth, frameHeight, width, height) {
|
|
518
|
+
if (this.gridChecked === ref) return;
|
|
519
|
+
this.gridChecked = ref;
|
|
520
|
+
let highest = 0;
|
|
521
|
+
for (const [name, entry] of Object.entries(this.animations)) {
|
|
522
|
+
if (typeof entry === "string") continue;
|
|
523
|
+
try {
|
|
524
|
+
for (const frame of resolveFrames(entry.frames, name)) if (frame > highest) highest = frame;
|
|
525
|
+
} catch {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const problem = checkSheetGrid(ref, frameWidth, frameHeight, width, height, highest);
|
|
530
|
+
if (problem) diagnose(this.tree?.engine ?? null, "error", problem);
|
|
531
|
+
}
|
|
455
532
|
resolveTexture(assets) {
|
|
456
533
|
if (!this.sheet || !assets) return null;
|
|
457
534
|
const info = assets.getSheet(this.sheet);
|
|
@@ -463,6 +540,7 @@ var AnimatedSprite2D = class extends Sprite2D {
|
|
|
463
540
|
const tex = this.ownTexture;
|
|
464
541
|
const image = info.texture.image;
|
|
465
542
|
if (!image?.width || !image.height) return null;
|
|
543
|
+
this.checkGrid(this.sheet, info.frameWidth, info.frameHeight, image.width, image.height);
|
|
466
544
|
const cols = Math.max(1, Math.floor(image.width / info.frameWidth));
|
|
467
545
|
const col = this.currentFrame % cols;
|
|
468
546
|
const row = Math.floor(this.currentFrame / cols);
|
|
@@ -871,6 +949,8 @@ var Particles2D = class Particles2D extends Node2D {
|
|
|
871
949
|
options: ["custom", ...PARTICLE_PRESET_NAMES]
|
|
872
950
|
},
|
|
873
951
|
emitting: { default: true },
|
|
952
|
+
/** Particles keep the place they were born instead of following the node. */
|
|
953
|
+
worldSpace: { default: false },
|
|
874
954
|
rate: { default: 40 },
|
|
875
955
|
burst: { default: 0 },
|
|
876
956
|
lifetime: { default: [.6, 1.2] },
|
|
@@ -895,6 +975,18 @@ var Particles2D = class Particles2D extends Node2D {
|
|
|
895
975
|
};
|
|
896
976
|
preset = "custom";
|
|
897
977
|
emitting = true;
|
|
978
|
+
/**
|
|
979
|
+
* Where a particle LIVES once it is born.
|
|
980
|
+
*
|
|
981
|
+
* `false` (default) — the emitter's local space: the plume moves with the
|
|
982
|
+
* node, which is what a torch on a moving platform wants.
|
|
983
|
+
*
|
|
984
|
+
* `true` — the world: a particle keeps the place it was born, so dust is left
|
|
985
|
+
* BEHIND a running player instead of glued to it, and moving a one-shot to
|
|
986
|
+
* the next explosion (the documented `p.position = …; p.replay()` recipe)
|
|
987
|
+
* stops dragging the previous one across the level.
|
|
988
|
+
*/
|
|
989
|
+
worldSpace = false;
|
|
898
990
|
rate = 40;
|
|
899
991
|
burst = 0;
|
|
900
992
|
lifetime = [.6, 1.2];
|
|
@@ -954,11 +1046,38 @@ var Particles2D = class Particles2D extends Node2D {
|
|
|
954
1046
|
}
|
|
955
1047
|
return this.sim;
|
|
956
1048
|
}
|
|
1049
|
+
/** Particles alive this frame — an emitter that is running, told from one
|
|
1050
|
+
* that is not. `framing()` says where it IS; this says whether it is going. */
|
|
1051
|
+
get aliveCount() {
|
|
1052
|
+
return this.sim?.count ?? 0;
|
|
1053
|
+
}
|
|
1054
|
+
/** A snapshot of the live particles — positions are in whichever space
|
|
1055
|
+
* `worldSpace` selects, relative to the emitter. */
|
|
1056
|
+
particles() {
|
|
1057
|
+
const out = [];
|
|
1058
|
+
this.sim?.forEach((p) => {
|
|
1059
|
+
out.push({ ...p });
|
|
1060
|
+
});
|
|
1061
|
+
return out;
|
|
1062
|
+
}
|
|
1063
|
+
/** The emitter's world position last frame, for world-space counter-motion. */
|
|
1064
|
+
lastWorld = null;
|
|
1065
|
+
/** Last frame's `emitting`, so turning one ON is a reportable event. */
|
|
1066
|
+
wasEmitting = false;
|
|
957
1067
|
update(dt) {
|
|
958
1068
|
const sim = this._ensureSim();
|
|
959
|
-
|
|
1069
|
+
this.holdWorldSpace(sim);
|
|
1070
|
+
sim.setRate(this.emitting ? this.rate : 0);
|
|
1071
|
+
if (this.burst > 0 && !this.bursted && (this.emitting || this.armedByReplay)) {
|
|
960
1072
|
this.bursted = true;
|
|
1073
|
+
this.armedByReplay = false;
|
|
961
1074
|
sim.burst(this.burst);
|
|
1075
|
+
this.report("burst", this.burst);
|
|
1076
|
+
}
|
|
1077
|
+
const on = this.emitting && this.rate > 0;
|
|
1078
|
+
if (on !== this.wasEmitting) {
|
|
1079
|
+
this.wasEmitting = on;
|
|
1080
|
+
if (on) this.report("emit", this.rate);
|
|
962
1081
|
}
|
|
963
1082
|
sim.update(dt);
|
|
964
1083
|
if ((!this.emitting || this.rate === 0) && this.bursted && sim.done && !this.finishedEmitted) {
|
|
@@ -966,11 +1085,51 @@ var Particles2D = class Particles2D extends Node2D {
|
|
|
966
1085
|
this.emit("finished");
|
|
967
1086
|
}
|
|
968
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Undo the emitter's own motion on everything already alive.
|
|
1090
|
+
*
|
|
1091
|
+
* Ancestor-summed like the physics helpers, so it follows a moving PARENT
|
|
1092
|
+
* (dust under a player) as well as the node's own position.
|
|
1093
|
+
*/
|
|
1094
|
+
holdWorldSpace(sim) {
|
|
1095
|
+
if (!this.worldSpace) {
|
|
1096
|
+
this.lastWorld = null;
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
let x = 0;
|
|
1100
|
+
let y = 0;
|
|
1101
|
+
const z = 0;
|
|
1102
|
+
for (let n = this; n; n = n.parent) if (n instanceof Node2D) {
|
|
1103
|
+
x += n.position[0] ?? 0;
|
|
1104
|
+
y += n.position[1] ?? 0;
|
|
1105
|
+
}
|
|
1106
|
+
const prev = this.lastWorld;
|
|
1107
|
+
if (prev) sim.translateAll(prev[0] - x, prev[1] - y, prev[2] - z);
|
|
1108
|
+
this.lastWorld = [
|
|
1109
|
+
x,
|
|
1110
|
+
y,
|
|
1111
|
+
z
|
|
1112
|
+
];
|
|
1113
|
+
}
|
|
1114
|
+
/** Tell the engine an effect fired — the visual half of the audio record. */
|
|
1115
|
+
report(kind, amount) {
|
|
1116
|
+
const engine = this.tree?.engine;
|
|
1117
|
+
engine?.effects.record({
|
|
1118
|
+
kind,
|
|
1119
|
+
name: this.preset,
|
|
1120
|
+
from: this.getPath(),
|
|
1121
|
+
amount,
|
|
1122
|
+
at: engine.time
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
969
1125
|
/** Restart a one-shot (re-burst + allow finished to fire again). */
|
|
970
1126
|
replay() {
|
|
971
1127
|
this.bursted = false;
|
|
972
1128
|
this.finishedEmitted = false;
|
|
1129
|
+
this.armedByReplay = true;
|
|
973
1130
|
}
|
|
1131
|
+
/** `replay()` asked for this burst, so `emitting: false` does not hold it. */
|
|
1132
|
+
armedByReplay = false;
|
|
974
1133
|
mesh = null;
|
|
975
1134
|
/** Parsed `paletteColors`, reused; rebuilt only when the hex list changes. */
|
|
976
1135
|
paletteCache = [];
|