incanto 0.61.0 → 0.63.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-verify.mjs +134 -55
- package/dist/2d.d.ts +42 -0
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +22 -0
- package/dist/3d.js +5 -5
- package/dist/{create-game-DH7JI5xx.js → create-game-lLeITaZ5.js} +8 -8
- package/dist/{create-game-IX5lEH0P.js → create-game-viBqXUoZ.js} +6 -6
- package/dist/{duplicate-CGqAmK2h.js → duplicate-BOOKmkQ7.js} +1 -1
- package/dist/editor.js +39 -18
- package/dist/{environment-presets-CNxCuhZF.js → environment-presets-QR7_75KJ.js} +5 -3
- package/dist/{gameplay-Dtzd2itW.js → gameplay-CuqoHHUB.js} +153 -16
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +92 -1
- package/dist/index.js +6 -6
- package/dist/{loader-D7jTvDQv.js → loader-zDynoew_.js} +160 -2
- package/dist/net.js +1 -1
- package/dist/{physics-2d-CBnor8Zf.js → physics-2d-CllJXlic.js} +11 -3
- package/dist/{physics-3d-BTUfUSWO.js → physics-3d-BL_pFJ19.js} +123 -17
- package/dist/react.js +1 -1
- package/dist/{register-DL3izw8j.js → register-1cKM8DEj.js} +60 -9
- package/dist/{register-xuSRyD6b.js → register-Btm7_Emq.js} +158 -2
- package/dist/{replay-D7-yle3s.js → replay-Bhdntkvq.js} +15 -2
- package/dist/{split-screen-5Ban4q4n.js → split-screen-Dx0LvzqS.js} +2 -2
- package/dist/{src-DozXvyZS.js → src-BlV9Sv4m.js} +1 -1
- package/dist/{teardown-B6rwJOyS.js → teardown-BwhkcNt8.js} +1 -1
- package/dist/{test-Dch_7VQD.js → test-Ct1_zf5S.js} +64 -20
- package/dist/test.d.ts +41 -0
- package/dist/test.js +2 -2
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-CF1JL2tR.js → agent8-CLZXBRoM.js} +1 -1
- package/editor/assets/{debug-3QzYhOPA.js → debug-eaSKFAKW.js} +1 -1
- package/editor/assets/{index-CAD2c5ug.js → index-D66AuRwG.js} +91 -91
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +91 -0
- package/skills/README.md +4 -0
- package/skills/incanto-behaviors-and-scripts.md +37 -0
- package/skills/incanto-building-2d-games.md +20 -1
- package/skills/incanto-building-3d-games.md +1 -1
- package/skills/incanto-game-feel.md +3 -2
- package/skills/incanto-gameplay-behaviors.md +45 -6
- package/skills/incanto-node-reference.md +12 -0
- package/skills/incanto-physics-and-input.md +53 -2
- package/skills/incanto-scene-json-authoring.md +15 -1
- package/skills/incanto-verifying-your-game.md +31 -1
- package/skills/incanto-your-first-game.md +237 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/game.scene.json +4 -2
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +3 -2
- package/templates-app/tps-3d/verify.ts +38 -0
- package/templates-app/village-quest-3d/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { k as diagnose } from "./loader-
|
|
2
|
+
import { k as diagnose } from "./loader-zDynoew_.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
|
-
import {
|
|
4
|
+
import { B as CharacterBody3D, G as validateCollider3D, H as RigidBody3D, V as PhysicsBody3D, W as Node3D, z as Area3D } from "./gameplay-CuqoHHUB.js";
|
|
5
5
|
import { n as registerDebugSource } from "./debug-draw-BM3DsvtT.js";
|
|
6
6
|
import { t as withoutRapierInitNoise } from "./quiet-rapier-BAJ4K94N.js";
|
|
7
|
-
import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-
|
|
7
|
+
import { A as Terrain3D, F as InstancedMesh3D, L as buildMeshGeometry, P as Joint3D } from "./environment-presets-QR7_75KJ.js";
|
|
8
8
|
import { Euler, Matrix4, Quaternion, Vector3 } from "three";
|
|
9
9
|
//#region src/3d/physics/collider-lines.ts
|
|
10
10
|
/**
|
|
@@ -318,6 +318,9 @@ var Physics3D = class {
|
|
|
318
318
|
rot[0] = eulerScratch.x / DEG;
|
|
319
319
|
rot[1] = eulerScratch.y / DEG;
|
|
320
320
|
rot[2] = eulerScratch.z / DEG;
|
|
321
|
+
e.lastRot[0] = rot[0];
|
|
322
|
+
e.lastRot[1] = rot[1];
|
|
323
|
+
e.lastRot[2] = rot[2];
|
|
321
324
|
}
|
|
322
325
|
const lv = e.body.linvel();
|
|
323
326
|
const nv = node.linearVelocity;
|
|
@@ -381,6 +384,8 @@ var Physics3D = class {
|
|
|
381
384
|
if (node.stickToGround && vy <= 0) this.kcc.enableSnapToGround(.1);
|
|
382
385
|
else this.kcc.disableSnapToGround();
|
|
383
386
|
this.kcc.setMaxSlopeClimbAngle(node.slopeLimitDeg * DEG);
|
|
387
|
+
if (node.stepHeight > 0) this.kcc.enableAutostep(node.stepHeight, node.stepHeight / 2, false);
|
|
388
|
+
else this.kcc.disableAutostep();
|
|
384
389
|
let carryX = 0;
|
|
385
390
|
let carryY = 0;
|
|
386
391
|
let carryZ = 0;
|
|
@@ -644,6 +649,11 @@ var Physics3D = class {
|
|
|
644
649
|
wy,
|
|
645
650
|
wz
|
|
646
651
|
],
|
|
652
|
+
lastRot: [
|
|
653
|
+
node.rotation[0] ?? 0,
|
|
654
|
+
node.rotation[1] ?? 0,
|
|
655
|
+
node.rotation[2] ?? 0
|
|
656
|
+
],
|
|
647
657
|
lastV: [
|
|
648
658
|
0,
|
|
649
659
|
0,
|
|
@@ -679,6 +689,21 @@ var Physics3D = class {
|
|
|
679
689
|
e.last[2] = p[2];
|
|
680
690
|
node._interpPrev = node._interpCurr = null;
|
|
681
691
|
}
|
|
692
|
+
const rx = node.rotation[0] ?? 0;
|
|
693
|
+
const ry = node.rotation[1] ?? 0;
|
|
694
|
+
const rz = node.rotation[2] ?? 0;
|
|
695
|
+
if (rx !== e.lastRot[0] || ry !== e.lastRot[1] || rz !== e.lastRot[2]) {
|
|
696
|
+
quatScratch.setFromEuler(eulerScratch.set(rx * DEG, ry * DEG, rz * DEG, "XYZ"));
|
|
697
|
+
e.body.setRotation({
|
|
698
|
+
x: quatScratch.x,
|
|
699
|
+
y: quatScratch.y,
|
|
700
|
+
z: quatScratch.z,
|
|
701
|
+
w: quatScratch.w
|
|
702
|
+
}, true);
|
|
703
|
+
e.lastRot[0] = rx;
|
|
704
|
+
e.lastRot[1] = ry;
|
|
705
|
+
e.lastRot[2] = rz;
|
|
706
|
+
}
|
|
682
707
|
if (node instanceof RigidBody3D) {
|
|
683
708
|
const v = [
|
|
684
709
|
node.linearVelocity[0] ?? 0,
|
|
@@ -854,15 +879,20 @@ function collectBodies(node, out) {
|
|
|
854
879
|
* Convex, always: a hull is a few planes to test against, where a triangle
|
|
855
880
|
* mesh is a tree of them, and nothing scattered across a map needs concavity.
|
|
856
881
|
*/
|
|
857
|
-
function meshShapedDesc(R, kind, size, scale) {
|
|
858
|
-
const
|
|
859
|
-
const
|
|
860
|
-
const
|
|
882
|
+
function meshShapedDesc(R, kind, size, scale, who) {
|
|
883
|
+
const sx = scale[0] ?? 1;
|
|
884
|
+
const sy = scale[1] ?? 1;
|
|
885
|
+
const sz = scale[2] ?? 1;
|
|
886
|
+
const x = (size[0] ?? 1) * sx;
|
|
887
|
+
const y = (size[1] ?? 1) * sy;
|
|
888
|
+
const z = (size[2] ?? 1) * sz;
|
|
889
|
+
const radial = Math.max(Math.abs(sx), Math.abs(sz));
|
|
890
|
+
if (who && kind !== "box" && kind !== "plane" && Math.abs(sx - sz) > 1e-6) console.warn(`[incanto] Collider on '${who}': shape 'auto' fits a ${kind} to a mesh scaled [${sx}, ${sy}, ${sz}]. A ${kind} has one radius, so the widest of x/z (${radial}) is used. Use a box, or scale x and z the same.`);
|
|
861
891
|
switch (kind) {
|
|
862
892
|
case "box": return R.ColliderDesc.cuboid(x / 2, y / 2, z / 2);
|
|
863
|
-
case "sphere": return R.ColliderDesc.ball(
|
|
864
|
-
case "capsule": return R.ColliderDesc.capsule(y / 2,
|
|
865
|
-
case "cylinder": return R.ColliderDesc.cylinder(y / 2,
|
|
893
|
+
case "sphere": return R.ColliderDesc.ball((size[0] ?? 1) * Math.max(radial, Math.abs(sy)));
|
|
894
|
+
case "capsule": return R.ColliderDesc.capsule(y / 2, (size[0] ?? 1) * radial);
|
|
895
|
+
case "cylinder": return R.ColliderDesc.cylinder(y / 2, (size[0] ?? 1) * radial);
|
|
866
896
|
case "plane": return R.ColliderDesc.cuboid(x / 2, .01, z / 2);
|
|
867
897
|
default: return R.ColliderDesc.convexHull(meshPoints(kind, [
|
|
868
898
|
x,
|
|
@@ -937,18 +967,79 @@ const TRIMESH_BUDGET = 2e4;
|
|
|
937
967
|
function isStaticShape(node) {
|
|
938
968
|
return !(node instanceof RigidBody3D || node instanceof CharacterBody3D);
|
|
939
969
|
}
|
|
940
|
-
/**
|
|
941
|
-
|
|
970
|
+
/**
|
|
971
|
+
* Every mesh a body carries, itself first — `auto` shapes itself on one.
|
|
972
|
+
*
|
|
973
|
+
* The transform chain is composed on the way down because `auto` used to drop
|
|
974
|
+
* it: the collector returned `{name, mesh, size}` and the `auto` branch built
|
|
975
|
+
* the shape at the BODY's origin, at the authored size, scale hard-coded to 1.
|
|
976
|
+
* The renderer applies all three every frame (`Node3D._syncObject3D`), so
|
|
977
|
+
* measured on a body whose plank child sits at `[0, 3, 0]`:
|
|
978
|
+
*
|
|
979
|
+
* ```
|
|
980
|
+
* drawn plank top y = 3.2
|
|
981
|
+
* ray straight down from y=10 : HIT Deck — surface at y=0.200
|
|
982
|
+
* ```
|
|
983
|
+
*
|
|
984
|
+
* and on one scaled 4×:
|
|
985
|
+
*
|
|
986
|
+
* ```
|
|
987
|
+
* drawn crate top y = 2.0
|
|
988
|
+
* ray straight down : HIT Crate — surface at y=0.500
|
|
989
|
+
* ray down inside the DRAWN 4 m crate : MISS
|
|
990
|
+
* ```
|
|
991
|
+
*
|
|
992
|
+
* You walk through the deck you can see and land on an invisible slab at ground
|
|
993
|
+
* level — from the shape whose entire promise is that "a blocker can never
|
|
994
|
+
* disagree with what the player sees". The sibling paths in this same file were
|
|
995
|
+
* already right: `syncScatters` honours every scatter row's translation, yaw
|
|
996
|
+
* and scale, and `modelTriangles` bakes a GLB's world matrices. Only the
|
|
997
|
+
* primitive-mesh branch dropped it.
|
|
998
|
+
*/
|
|
999
|
+
function meshesUnder(node, out, models, at = [
|
|
1000
|
+
0,
|
|
1001
|
+
0,
|
|
1002
|
+
0
|
|
1003
|
+
], turn = [
|
|
1004
|
+
0,
|
|
1005
|
+
0,
|
|
1006
|
+
0
|
|
1007
|
+
], scale = [
|
|
1008
|
+
1,
|
|
1009
|
+
1,
|
|
1010
|
+
1
|
|
1011
|
+
]) {
|
|
942
1012
|
if (isMountedModel(node)) models.push(node);
|
|
943
1013
|
else {
|
|
944
1014
|
const self = node;
|
|
945
1015
|
if (typeof self.mesh === "string" && Array.isArray(self.size)) out.push({
|
|
946
1016
|
name: node.name,
|
|
947
1017
|
mesh: self.mesh,
|
|
948
|
-
size: self.size
|
|
1018
|
+
size: self.size,
|
|
1019
|
+
at: [...at],
|
|
1020
|
+
turn: [...turn],
|
|
1021
|
+
scale: [...scale]
|
|
949
1022
|
});
|
|
950
1023
|
}
|
|
951
|
-
for (const child of node.children)
|
|
1024
|
+
for (const child of node.children) {
|
|
1025
|
+
const t = child;
|
|
1026
|
+
const p = Array.isArray(t.position) ? t.position : [];
|
|
1027
|
+
const r = Array.isArray(t.rotation) ? t.rotation : [];
|
|
1028
|
+
const c = Array.isArray(t.scale) ? t.scale : [];
|
|
1029
|
+
meshesUnder(child, out, models, [
|
|
1030
|
+
at[0] + (p[0] ?? 0) * scale[0],
|
|
1031
|
+
at[1] + (p[1] ?? 0) * scale[1],
|
|
1032
|
+
at[2] + (p[2] ?? 0) * scale[2]
|
|
1033
|
+
], [
|
|
1034
|
+
turn[0] + (r[0] ?? 0),
|
|
1035
|
+
turn[1] + (r[1] ?? 0),
|
|
1036
|
+
turn[2] + (r[2] ?? 0)
|
|
1037
|
+
], [
|
|
1038
|
+
scale[0] * (c[0] ?? 1),
|
|
1039
|
+
scale[1] * (c[1] ?? 1),
|
|
1040
|
+
scale[2] * (c[2] ?? 1)
|
|
1041
|
+
]);
|
|
1042
|
+
}
|
|
952
1043
|
}
|
|
953
1044
|
/** Every scatter field asking to be solid (see InstancedMesh3D.collider). */
|
|
954
1045
|
function collectScatters(node, out) {
|
|
@@ -1019,7 +1110,10 @@ function parseCollider3D(R, node) {
|
|
|
1019
1110
|
const desc = buildColliderDesc3D(R, collider, node.name, node);
|
|
1020
1111
|
if (!desc) return null;
|
|
1021
1112
|
const offset = collider.offset;
|
|
1022
|
-
if (Array.isArray(offset))
|
|
1113
|
+
if (Array.isArray(offset)) {
|
|
1114
|
+
const at = desc.translation;
|
|
1115
|
+
desc.setTranslation(at.x + (offset[0] ?? 0), at.y + (offset[1] ?? 0), at.z + (offset[2] ?? 0));
|
|
1116
|
+
}
|
|
1023
1117
|
return desc;
|
|
1024
1118
|
}
|
|
1025
1119
|
/**
|
|
@@ -1070,8 +1164,20 @@ function buildColliderDesc3D(R, collider, nodeName, node) {
|
|
|
1070
1164
|
const parts = meshes.length + models.length;
|
|
1071
1165
|
if (parts > 1) console.warn(`[incanto] Collider on '${nodeName}': shape 'auto' fits ONE mesh and took '${(mesh ?? model)?.name}'; ${parts - 1} other mesh(es) here are not solid. Give each part its own body.`);
|
|
1072
1166
|
if (mesh) {
|
|
1073
|
-
const desc = meshShapedDesc(R, mesh.mesh, mesh.size,
|
|
1074
|
-
if (desc)
|
|
1167
|
+
const desc = meshShapedDesc(R, mesh.mesh, mesh.size, mesh.scale, nodeName);
|
|
1168
|
+
if (desc) {
|
|
1169
|
+
desc.setTranslation(mesh.at[0], mesh.at[1], mesh.at[2]);
|
|
1170
|
+
if (mesh.turn[0] !== 0 || mesh.turn[1] !== 0 || mesh.turn[2] !== 0) {
|
|
1171
|
+
quatScratch.setFromEuler(eulerScratch.set(mesh.turn[0] * DEG, mesh.turn[1] * DEG, mesh.turn[2] * DEG, "XYZ"));
|
|
1172
|
+
desc.setRotation({
|
|
1173
|
+
x: quatScratch.x,
|
|
1174
|
+
y: quatScratch.y,
|
|
1175
|
+
z: quatScratch.z,
|
|
1176
|
+
w: quatScratch.w
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
return desc;
|
|
1180
|
+
}
|
|
1075
1181
|
} else if (model && node) {
|
|
1076
1182
|
const tri = modelTriangles(node, model);
|
|
1077
1183
|
if (!tri) return null;
|
package/dist/react.js
CHANGED
|
@@ -156,7 +156,7 @@ function IncantoCanvas(props) {
|
|
|
156
156
|
pointer: latest.pointer,
|
|
157
157
|
...keyboard !== void 0 ? { keyboard } : {}
|
|
158
158
|
};
|
|
159
|
-
const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-
|
|
159
|
+
const next = await (_gameFactory ?? (mode === "3d" ? async (o) => (await import("./create-game-viBqXUoZ.js").then((n) => n.n)).createGame3D(o) : async (o) => (await import("./create-game-lLeITaZ5.js").then((n) => n.n)).createGame2D(o)))(opts);
|
|
160
160
|
if (disposed) {
|
|
161
161
|
next.dispose();
|
|
162
162
|
return;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { O as Node, k as diagnose, w as effectiveOrder } from "./loader-
|
|
2
|
-
import {
|
|
1
|
+
import { O as Node, k as diagnose, w as effectiveOrder } from "./loader-zDynoew_.js";
|
|
2
|
+
import { L as translateOn, t as registerCoreNodes } from "./register-Btm7_Emq.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-CF70EArN.js";
|
|
6
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-CY-mrr1L.js";
|
|
7
|
+
import { N as localFromWorld, P as worldPosition } from "./gameplay-CuqoHHUB.js";
|
|
7
8
|
import { t as checkSheetGrid } from "./sheet-grid-BT6N_Bjs.js";
|
|
8
9
|
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, MathUtils, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, Object3D, PlaneGeometry, SRGBColorSpace } from "three";
|
|
9
10
|
//#region src/2d/physics/collider-validate.ts
|
|
@@ -94,6 +95,30 @@ var Node2D = class extends Node {
|
|
|
94
95
|
return new Object3D();
|
|
95
96
|
}
|
|
96
97
|
/** @internal Push JSON props onto the backing object. Called every frame. */
|
|
98
|
+
/**
|
|
99
|
+
* Has this node produced the thing `static: true` is about to freeze?
|
|
100
|
+
*
|
|
101
|
+
* `static` latches after the first sync, and a textured node's first sync
|
|
102
|
+
* happens BEFORE its texture has decoded — `TextureLoader.load()` returns a
|
|
103
|
+
* `Texture` with `image === undefined` and fills it asynchronously, while
|
|
104
|
+
* `Renderer2D.render()` calls `assets.load()` and `syncTree2D()` in the same
|
|
105
|
+
* frame. So the state frozen was `visible = false`, forever. Measured on two
|
|
106
|
+
* identical sprites differing only in `static`:
|
|
107
|
+
*
|
|
108
|
+
* ```
|
|
109
|
+
* frame 1 Backdrop(static) visible: false Control visible: false
|
|
110
|
+
* 600 frames later Backdrop(static) visible: false width 1
|
|
111
|
+
* Control visible: true width 64
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* Nothing throws: the texture loaded fine, so `assetErrors()` is empty,
|
|
115
|
+
* `stats().errors` is 0, and `framing` reads props rather than pixels and
|
|
116
|
+
* still calls the node on-screen. Overridden by the nodes that wait on an
|
|
117
|
+
* image; a node with nothing to wait for is ready by definition.
|
|
118
|
+
*/
|
|
119
|
+
_staticReady(_assets) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
97
122
|
_syncObject2D(_assets) {
|
|
98
123
|
const o = this._ensureObject2D();
|
|
99
124
|
o.position.set(this.position[0] ?? 0, -(this.position[1] ?? 0), 0);
|
|
@@ -393,6 +418,9 @@ var Sprite2D = class extends Node2D {
|
|
|
393
418
|
height: image.height
|
|
394
419
|
};
|
|
395
420
|
}
|
|
421
|
+
_staticReady(assets) {
|
|
422
|
+
return this.texture === "" || this.resolveTexture(assets) !== null;
|
|
423
|
+
}
|
|
396
424
|
_syncObject2D(assets) {
|
|
397
425
|
super._syncObject2D(assets);
|
|
398
426
|
const quad = this._quad();
|
|
@@ -577,18 +605,36 @@ var Camera2D = class extends Node2D {
|
|
|
577
605
|
if (!this.follow) return;
|
|
578
606
|
const target = this.getNodeOrNull(this.follow);
|
|
579
607
|
if (!(target instanceof Node2D)) return;
|
|
580
|
-
const tx =
|
|
581
|
-
const
|
|
608
|
+
const [tx = 0, ty = 0] = worldPosition(target);
|
|
609
|
+
const [px = 0, py = 0] = worldPosition(this);
|
|
582
610
|
const s = Math.min(Math.max(this.smoothing, 0), .99);
|
|
583
611
|
const f = s <= 0 ? 1 : 1 - s ** (dt * 60);
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
this.position = [px + (tx - px) * f, py + (ty - py) * f];
|
|
612
|
+
const [lx = 0, ly = 0] = localFromWorld(this, [px + (tx - px) * f, py + (ty - py) * f]);
|
|
613
|
+
this.position = [lx, ly];
|
|
587
614
|
}
|
|
588
615
|
/** View center after clamping the (vw×vh)/zoom view rect inside `limits`. */
|
|
616
|
+
/**
|
|
617
|
+
* The view centre the renderer draws around, in WORLD space.
|
|
618
|
+
*
|
|
619
|
+
* It used to read `this.position` raw — the LOCAL prop — while every other
|
|
620
|
+
* 2D node gets its ancestors composed for free by the three scene graph. So a
|
|
621
|
+
* camera parented to the player, the Godot/Phaser idiom the authoring skill
|
|
622
|
+
* explicitly permits, framed the world origin:
|
|
623
|
+
*
|
|
624
|
+
* ```
|
|
625
|
+
* player world position : [1400, 900]
|
|
626
|
+
* renderer view centre : {"x":0,"y":0}
|
|
627
|
+
* framing : camera /Level/Player/Cam centred [1400, 900]
|
|
628
|
+
* 1 in view, 0 outside it
|
|
629
|
+
* ```
|
|
630
|
+
*
|
|
631
|
+
* `framing` composes full world matrices, so the instrument the skills tell
|
|
632
|
+
* you to trust certified a view the renderer never drew.
|
|
633
|
+
*/
|
|
589
634
|
clampedCenter(vw, vh) {
|
|
590
|
-
|
|
591
|
-
let
|
|
635
|
+
const world = worldPosition(this);
|
|
636
|
+
let cx = world[0] ?? 0;
|
|
637
|
+
let cy = world[1] ?? 0;
|
|
592
638
|
if (this.limits.length !== 0 && this.limits.length !== 4) throw new IncantoError("BAD_FORMAT", `Camera2D '${this.name}' limits must be [] or [minX,minY,maxX,maxY], got length ${this.limits.length}.`);
|
|
593
639
|
if (this.limits.length === 4) {
|
|
594
640
|
const [minX, minY, maxX, maxY] = this.limits;
|
|
@@ -1435,6 +1481,11 @@ var TileMap2D = class extends Node2D {
|
|
|
1435
1481
|
}
|
|
1436
1482
|
return this.tileMesh;
|
|
1437
1483
|
}
|
|
1484
|
+
_staticReady(assets) {
|
|
1485
|
+
if (this.texture === "" || !assets) return this.texture === "";
|
|
1486
|
+
const image = assets.getTexture(this.texture).image;
|
|
1487
|
+
return Boolean(image?.width);
|
|
1488
|
+
}
|
|
1438
1489
|
_syncObject2D(assets) {
|
|
1439
1490
|
super._syncObject2D(assets);
|
|
1440
1491
|
const mesh = this._mesh();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-
|
|
2
|
+
import { C as ORDER_GROUP_BASE, E as InputMap, O as Node, S as createSaveStore, T as resolveOrderGroups, V as Signal, b as restoreBehaviors, k as diagnose, y as captureBehaviors } from "./loader-zDynoew_.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 { l as registerNode } from "./registry-CF70EArN.js";
|
|
@@ -3294,6 +3294,161 @@ var UiDialogue = class extends HudWidgetBase {
|
|
|
3294
3294
|
}
|
|
3295
3295
|
};
|
|
3296
3296
|
//#endregion
|
|
3297
|
+
//#region src/core/nodes/respawn.ts
|
|
3298
|
+
/** Out of the world, from the spawn: metres in 3D, pixels in 2D. */
|
|
3299
|
+
const AUTO_DROP_3D_M = 50;
|
|
3300
|
+
const AUTO_DROP_2D_PX = 1e3;
|
|
3301
|
+
/**
|
|
3302
|
+
* Catch whatever this node hangs off when it leaves the world, and put it back.
|
|
3303
|
+
*
|
|
3304
|
+
* Nothing in the engine did this. Not a node, not a behavior, not a scene
|
|
3305
|
+
* header — and the shipped examples prove what that costs: with the `plays`
|
|
3306
|
+
* rung finally counting defects, `basic-3d-sideview` fails 8 seeded runs out of
|
|
3307
|
+
* 8, `water-lake-3d` 7, `water-ocean-3d` 5, `water-pool-3d` 4,
|
|
3308
|
+
* `water-river-3d` 1. Every one of them the same way — the player walks off the
|
|
3309
|
+
* edge of the terrain and falls forever, and the only fix available was a
|
|
3310
|
+
* per-frame `y` check hand-written in TypeScript, which is exactly the kind of
|
|
3311
|
+
* thing this engine exists to keep out of TypeScript.
|
|
3312
|
+
*
|
|
3313
|
+
* A CHILD NODE rather than a Behavior, deliberately: a node holds one behavior
|
|
3314
|
+
* and the player's is already spoken for (`PlayerControl`, `Health`, the game's
|
|
3315
|
+
* own script). Children stack, the way `CharacterController3D` already hangs
|
|
3316
|
+
* off its body.
|
|
3317
|
+
*
|
|
3318
|
+
* ```jsonc
|
|
3319
|
+
* { "name": "Player", "type": "RigidBody3D", "props": { … },
|
|
3320
|
+
* "children": [
|
|
3321
|
+
* { "name": "Ctl", "type": "CharacterController3D" },
|
|
3322
|
+
* { "name": "Catch", "type": "Respawn" } // that is the whole thing
|
|
3323
|
+
* ] }
|
|
3324
|
+
* ```
|
|
3325
|
+
*
|
|
3326
|
+
* With no props it guards its parent, uses the same line the playtest's fall
|
|
3327
|
+
* detector uses (50 m under the spawn in 3D, 1000 px in 2D), and puts the node
|
|
3328
|
+
* back where it started. `respawned` fires each time, so a life can be taken
|
|
3329
|
+
* off a `ScoreKeeper` or a `Health` with an ordinary connection — this node
|
|
3330
|
+
* does not decide what falling COSTS, which is a game-design question and not
|
|
3331
|
+
* a physics one.
|
|
3332
|
+
*/
|
|
3333
|
+
var Respawn = class extends Node {
|
|
3334
|
+
static typeName = "Respawn";
|
|
3335
|
+
static signals = ["respawned"];
|
|
3336
|
+
static props = {
|
|
3337
|
+
target: {
|
|
3338
|
+
default: "..",
|
|
3339
|
+
nodePath: true
|
|
3340
|
+
},
|
|
3341
|
+
below: { default: null },
|
|
3342
|
+
to: { default: [] },
|
|
3343
|
+
resetVelocity: { default: true }
|
|
3344
|
+
};
|
|
3345
|
+
/**
|
|
3346
|
+
* Who is being caught. Defaults to the parent, which is the whole point of
|
|
3347
|
+
* hanging this off the thing it guards.
|
|
3348
|
+
*/
|
|
3349
|
+
target = "..";
|
|
3350
|
+
/**
|
|
3351
|
+
* The line past which the target has left the world, in the scene's own
|
|
3352
|
+
* down: 3D counts down (−y), 2D counts down the screen (+y).
|
|
3353
|
+
*
|
|
3354
|
+
* `null` is AUTO — the spawn position offset by the same distance the
|
|
3355
|
+
* playtest's own fall detector uses, so "the level says it fell" and "the
|
|
3356
|
+
* ladder says it fell" cannot disagree. Author a number when the level has a
|
|
3357
|
+
* real floor to name; leave it alone otherwise.
|
|
3358
|
+
*/
|
|
3359
|
+
below = null;
|
|
3360
|
+
/** Where to put it back. Empty = wherever it started. */
|
|
3361
|
+
to = [];
|
|
3362
|
+
/**
|
|
3363
|
+
* Zero the body's velocity on the way back.
|
|
3364
|
+
*
|
|
3365
|
+
* On by default because the alternative is invisible: a body respawned with
|
|
3366
|
+
* 40 m/s of accumulated fall still in it drops straight back through the
|
|
3367
|
+
* line on the next frame, and the level flickers instead of resuming.
|
|
3368
|
+
*/
|
|
3369
|
+
resetVelocity = true;
|
|
3370
|
+
/** How many times this has caught the target — for HUDs and tests. */
|
|
3371
|
+
falls = 0;
|
|
3372
|
+
home = [];
|
|
3373
|
+
line = null;
|
|
3374
|
+
/** +1 when "out" means y greater (2D screen-down), −1 when smaller (3D). */
|
|
3375
|
+
down = -1;
|
|
3376
|
+
onReady() {
|
|
3377
|
+
const target = this.guarded();
|
|
3378
|
+
this.home = this.to.length > 0 ? [...this.to] : [...positionOf(target)];
|
|
3379
|
+
this.spawnY = worldY(target);
|
|
3380
|
+
}
|
|
3381
|
+
spawnY = 0;
|
|
3382
|
+
/**
|
|
3383
|
+
* Which way is out, and where the line is.
|
|
3384
|
+
*
|
|
3385
|
+
* Resolved on the first `update` rather than in `onReady`, because
|
|
3386
|
+
* `onReady` runs inside `loadScene` — BEFORE `setScene` attaches the engine —
|
|
3387
|
+
* so the scene's `dimension` is not readable there yet. Reading it there
|
|
3388
|
+
* silently gives every 2D scene the 3D answer, and a line above a falling
|
|
3389
|
+
* player fires on frame one, forever.
|
|
3390
|
+
*
|
|
3391
|
+
* Not called `resolve`: `Node` has a private method by that name and
|
|
3392
|
+
* `getNodeOrNull` calls it, so a subclass method with the same name
|
|
3393
|
+
* overrides it on the prototype — `private` is a TypeScript fiction that
|
|
3394
|
+
* costs nothing at runtime — and every path lookup this node made returned a
|
|
3395
|
+
* number. `node-method-shadowing.test.ts` refuses the whole family now.
|
|
3396
|
+
*/
|
|
3397
|
+
fallLine() {
|
|
3398
|
+
if (this.line !== null) return this.line;
|
|
3399
|
+
this.down = this.tree?.engine?.scene?.dimension === "2d" ? 1 : -1;
|
|
3400
|
+
const drop = this.down > 0 ? AUTO_DROP_2D_PX : AUTO_DROP_3D_M;
|
|
3401
|
+
this.line = this.below ?? this.spawnY + this.down * drop;
|
|
3402
|
+
return this.line;
|
|
3403
|
+
}
|
|
3404
|
+
/** The node this guards, or itself if the path no longer resolves. */
|
|
3405
|
+
guarded() {
|
|
3406
|
+
return this.getNodeOrNull(this.target) ?? this;
|
|
3407
|
+
}
|
|
3408
|
+
update(_dt) {
|
|
3409
|
+
const line = this.fallLine();
|
|
3410
|
+
const target = this.guarded();
|
|
3411
|
+
if (target === this) return;
|
|
3412
|
+
const y = worldY(target);
|
|
3413
|
+
if ((y - line) * this.down <= 0) return;
|
|
3414
|
+
this.falls += 1;
|
|
3415
|
+
setPosition(target, this.home);
|
|
3416
|
+
if (this.resetVelocity) zeroVelocity(target);
|
|
3417
|
+
this.emit("respawned", target, y);
|
|
3418
|
+
}
|
|
3419
|
+
};
|
|
3420
|
+
function positionOf(node) {
|
|
3421
|
+
const p = node.position;
|
|
3422
|
+
return Array.isArray(p) ? p : [];
|
|
3423
|
+
}
|
|
3424
|
+
/** Sum of the `position[1]` of the node and every spatial ancestor. */
|
|
3425
|
+
function worldY(node) {
|
|
3426
|
+
let y = 0;
|
|
3427
|
+
let at = node;
|
|
3428
|
+
while (at) {
|
|
3429
|
+
y += positionOf(at)[1] ?? 0;
|
|
3430
|
+
at = at.parent;
|
|
3431
|
+
}
|
|
3432
|
+
return y;
|
|
3433
|
+
}
|
|
3434
|
+
function setPosition(node, to) {
|
|
3435
|
+
const slot = node;
|
|
3436
|
+
if (!Array.isArray(slot.position)) return;
|
|
3437
|
+
slot.position = slot.position.map((v, i) => to[i] ?? v);
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Stop whatever the fall built up.
|
|
3441
|
+
*
|
|
3442
|
+
* Both names are real: `RigidBody` calls it `linearVelocity` and
|
|
3443
|
+
* `CharacterBody` calls it `velocity`, and a respawn has to reach whichever
|
|
3444
|
+
* one this body has.
|
|
3445
|
+
*/
|
|
3446
|
+
function zeroVelocity(node) {
|
|
3447
|
+
const body = node;
|
|
3448
|
+
if (Array.isArray(body.linearVelocity)) body.linearVelocity = body.linearVelocity.map(() => 0);
|
|
3449
|
+
if (Array.isArray(body.velocity)) body.velocity = body.velocity.map(() => 0);
|
|
3450
|
+
}
|
|
3451
|
+
//#endregion
|
|
3297
3452
|
//#region src/core/nodes/timer.ts
|
|
3298
3453
|
/**
|
|
3299
3454
|
* The canonical serializable game clock — never `setTimeout` in game logic.
|
|
@@ -4021,6 +4176,7 @@ var UiMuteToggle = class extends UiToggle {
|
|
|
4021
4176
|
function registerCoreNodes() {
|
|
4022
4177
|
registerNode(Node);
|
|
4023
4178
|
registerNode(Timer);
|
|
4179
|
+
registerNode(Respawn);
|
|
4024
4180
|
registerNode(AudioPlayer);
|
|
4025
4181
|
registerNode(HudLayer);
|
|
4026
4182
|
registerNode(UiText);
|
|
@@ -4041,4 +4197,4 @@ function registerCoreNodes() {
|
|
|
4041
4197
|
registerNode(UiMuteToggle);
|
|
4042
4198
|
}
|
|
4043
4199
|
//#endregion
|
|
4044
|
-
export {
|
|
4200
|
+
export { UiBanner as A, isAudioContextAvailable as B, qualityRendering as C, EffectLog as D, LogManager as E, T_PREFIX as F, SFX_PRESET_NAMES as G, spatialGain as H, suggestLocale as I, MusicManager as J, sfxDuration as K, translateOn as L, UiText as M, BASE_LOCALE as N, HudLayer as O, Localization as P, AudioBuses as Q, translationKey as R, qualityEnvironment as S, suggestQuality as T, spatialPan as U, ROLLOFF_MODELS as V, SFX_PRESETS as W, crossfadeGains as X, WebAudioMusicBackend as Y, fadeGain as Z, AudioPlayer as _, UiMuteToggle as a, Settings as b, UiRenderScaleSelect as c, UiToggle as d, UiVolumeSlider as f, UiDialogue as g, UiButton as h, UiLanguageSelect as i, UiBar as j, HudWidgetBase as k, UiSelect as l, Respawn as m, UiFrameCapSelect as n, UiPanel as o, Timer as p, synthSfx as q, UiImage as r, UiQualitySelect as s, registerCoreNodes as t, UiSlider as u, Engine as v, readDeviceHints as w, qualityCaps as x, engine_exports as y, SfxEngine as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-
|
|
2
|
-
import {
|
|
1
|
+
import { D as handlerShadowWarnings, j as nodeRefWarnings } from "./loader-zDynoew_.js";
|
|
2
|
+
import { R as translationKey, k as HudWidgetBase } from "./register-Btm7_Emq.js";
|
|
3
3
|
import { o as getNodeType } from "./registry-CF70EArN.js";
|
|
4
4
|
//#region src/core/audit.ts
|
|
5
5
|
const ANIMATED_TYPES = new Set([
|
|
@@ -54,6 +54,18 @@ const LIGHT_TYPES = new Set([
|
|
|
54
54
|
"SpotLight3D"
|
|
55
55
|
]);
|
|
56
56
|
/**
|
|
57
|
+
* Nodes whose gameplay answer comes from a CPU twin built out of `size`.
|
|
58
|
+
*
|
|
59
|
+
* `Terrain3D.heightAt`, the heightfield collider and `Water3D._waterYAt` all
|
|
60
|
+
* read `size` and compose ancestor POSITIONS only, so an ancestor that scales
|
|
61
|
+
* or rotates them moves the picture and not the answer.
|
|
62
|
+
*/
|
|
63
|
+
const WORLD_QUERY_TYPES = new Set([
|
|
64
|
+
"Terrain3D",
|
|
65
|
+
"Water3D",
|
|
66
|
+
"River3D"
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
57
69
|
* Built-ins that put something in a save. A save is keyed by node uid, so one
|
|
58
70
|
* of these on a node without one is state that silently goes nowhere: the save
|
|
59
71
|
* comes back `{}` and the load reports a clean bill, because "nothing was
|
|
@@ -121,6 +133,7 @@ function auditScene(scene, opts = {}) {
|
|
|
121
133
|
if (type === "CharacterController3D") cameraDrivers.push(path);
|
|
122
134
|
if (LIGHT_TYPES.has(type)) lights += 1;
|
|
123
135
|
if (BODY_TYPES.has(type) && transformed) warnings.push(`${path}: ${type} sits under '${transformed.path}', whose ${transformed.what} physics cannot compose — the collider will not line up with what is drawn. Keep body ancestors untransformed or translation-only, or put the body outside them.`);
|
|
136
|
+
if (WORLD_QUERY_TYPES.has(type) && transformed) warnings.push(`${path}: ${type} sits under '${transformed.path}', whose ${transformed.what} it cannot compose — the ground (or water) you can see would not be the ground you can stand on. Keep its ancestors untransformed or translation-only.`);
|
|
124
137
|
if (BODY_TYPES.has(type)) {
|
|
125
138
|
const collider = props.collider;
|
|
126
139
|
if (!collider || collider.shape === void 0) warnings.push(`${path}: ${type} has no collider — physics will skip it (add "collider": { "shape": ... }).`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { O as Node, V as Signal, k as diagnose, n as loadScene, t as buildNodeJson } from "./loader-
|
|
2
|
-
import {
|
|
1
|
+
import { O as Node, V as Signal, k as diagnose, n as loadScene, t as buildNodeJson } from "./loader-zDynoew_.js";
|
|
2
|
+
import { t as registerCoreNodes, v as Engine } from "./register-Btm7_Emq.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { n as jsonEquals, t as jsonClone } from "./json-CwwhxQgb.js";
|
|
5
5
|
import { l as registerNode } from "./registry-CF70EArN.js";
|
|
@@ -214,6 +214,6 @@ function newUid() {
|
|
|
214
214
|
//#endregion
|
|
215
215
|
//#region src/index.ts
|
|
216
216
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
217
|
-
const VERSION = "0.
|
|
217
|
+
const VERSION = "0.63.0";
|
|
218
218
|
//#endregion
|
|
219
219
|
export { preloadUrls as a, preloadSceneAssets as i, newUid as n, findPath as o, assetUrls as r, gridFromRows as s, VERSION as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as AudioPlayer } from "./register-Btm7_Emq.js";
|
|
2
2
|
import { a as logReport, s as parseDrive } from "./touch-DESwnpOc.js";
|
|
3
3
|
import { a as frameSignature, i as frameImage, o as frameStats } from "./frame-report-BSMny7oe.js";
|
|
4
4
|
//#region src/core/audio-errors.ts
|