incanto 0.74.0 → 0.76.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-multiplay.mjs +52 -4
- package/dist/2d.d.ts +83 -7
- package/dist/2d.js +4 -3
- package/dist/3d.d.ts +861 -167
- package/dist/3d.js +6 -6
- package/dist/{agent8-CHTT4unP.js → agent8-B-bh3n0X.js} +1 -1
- package/dist/{audio-player-BOrk4eQx.d.ts → audio-player-BRo2uvG6.d.ts} +23 -2
- package/dist/{behavior-J1cmVah0.d.ts → behavior-B_245qRy.d.ts} +15 -1
- package/dist/{create-game-ssvGvP7W.js → create-game-BMIPKrQW.js} +84 -8
- package/dist/{create-game-BAiA-FjP.js → create-game-Dai5vfVu.js} +80 -11
- package/dist/debug.d.ts +1 -1
- package/dist/{editor-switch-CFU9mCec.d.ts → editor-switch-Bzt0GzVp.d.ts} +13 -3
- package/dist/editor.js +1600 -1529
- package/dist/env.d.ts +1 -1
- package/dist/{environment-presets-BkqlWewf.js → environment-presets-e_9YCnGu.js} +2361 -104
- package/dist/{gameplay-Bxe1sMVT.js → gameplay-Bfff_beb.js} +10919 -6798
- package/dist/gameplay.d.ts +573 -31
- package/dist/gameplay.js +3 -2
- package/dist/index.d.ts +150 -6
- package/dist/index.js +6 -6
- package/dist/{loader-BAeWMYfF.d.ts → loader-CbkVdXL8.d.ts} +2 -2
- package/dist/net.d.ts +2 -2
- package/dist/net.js +2 -2
- package/dist/{physics-2d-C-SfvjzH.js → physics-2d-Tuf5LAyq.js} +250 -31
- package/dist/{physics-3d-rOzeg890.js → physics-3d-C5MOujfQ.js} +673 -51
- package/dist/{picking-BMV34Pjl.js → picking-DIwE48sU.js} +10 -7
- package/dist/{save-slots-CEUGeLuB.js → pose-ByFB_J3O.js} +217 -11
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/{register-DRQyZKGU.js → register-7E3P1gz6.js} +191 -430
- package/dist/{register-CjociOtt.js → register-B768Qedj.js} +596 -2
- package/dist/{replay-B6BbgGM_.js → replay-DrqQ6dZj.js} +133 -18
- package/dist/{replay-wZK5Ok5c.d.ts → replay-W5nCw_cU.d.ts} +21 -2
- package/dist/{rng-Bb-IutXB.d.ts → rng-BsXZg3D6.d.ts} +7 -0
- package/dist/{split-screen-BxZ5rBe7.js → split-screen-B-dIcQsQ.js} +10 -5
- package/dist/{split-screen-D6sx8z3Z.d.ts → split-screen-CiLJf2-C.d.ts} +3 -3
- package/dist/{sprite-animation-Bz3DHD_4.js → sprite-animation-SQa5gIu2.js} +8 -6
- package/dist/{src-CqtvYtSN.js → src-D823V07k.js} +1 -1
- package/dist/{test-CEl0y2Sw.js → test-CVbxnXlv.js} +487 -82
- package/dist/test.d.ts +90 -12
- package/dist/test.js +2 -2
- package/dist/vite.js +34 -5
- package/editor/assets/{agent8-BlzturGi.js → agent8-DA-I5qhv.js} +1 -1
- package/editor/assets/{debug-BMUi8usj.js → debug-99tEPoGm.js} +1 -1
- package/editor/assets/index-Db74LZY-.js +11579 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +1639 -280
- package/skills/incanto-3d-character.md +383 -20
- package/skills/incanto-audio.md +24 -2
- package/skills/incanto-building-2d-games.md +21 -2
- package/skills/incanto-building-3d-games.md +38 -3
- package/skills/incanto-editor.md +29 -8
- package/skills/incanto-environment.md +257 -14
- package/skills/incanto-game-feel.md +60 -1
- package/skills/incanto-gameplay-behaviors.md +668 -20
- package/skills/incanto-hud.md +116 -0
- package/skills/incanto-multiplayer.md +141 -2
- package/skills/incanto-node-reference.md +398 -16
- package/skills/incanto-performance.md +32 -0
- package/skills/incanto-physics-and-input.md +241 -13
- package/skills/incanto-playtesting.md +2 -2
- package/skills/incanto-save-slots.md +61 -2
- package/skills/incanto-scene-json-authoring.md +13 -0
- package/skills/incanto-verifying-your-game.md +86 -3
- package/templates-app/beacon-isle-3d/PROJECT/Status.md +12 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/beacon-isle-3d/src/game.scene.json +157 -3
- package/templates-app/molehill-2d/PROJECT/Status.md +7 -0
- package/templates-app/molehill-2d/package.json +1 -1
- package/templates-app/molehill-2d/src/game.scene.json +129 -2
- package/templates-app/platformer-2d/PROJECT/Status.md +7 -0
- package/templates-app/platformer-2d/docs/project-2d-rules.md +3 -2
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/platformer-2d/src/behaviors.ts +40 -26
- package/templates-app/platformer-2d/src/game.scene.json +144 -2
- package/templates-app/platformer-2d/verify.ts +18 -1
- package/templates-app/star-survivor/PROJECT/Status.md +7 -0
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/star-survivor/src/behaviors.ts +9 -8
- package/templates-app/star-survivor/src/game.scene.json +142 -4
- package/templates-app/star-survivor/verify.ts +13 -0
- package/templates-app/tps-3d/PROJECT/Status.md +6 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/tps-3d/src/game.scene.json +71 -10
- package/templates-app/village-quest-3d/PROJECT/Status.md +7 -0
- package/templates-app/village-quest-3d/package.json +1 -1
- package/templates-app/village-quest-3d/src/behaviors.ts +8 -16
- package/templates-app/village-quest-3d/src/grove.scene.json +121 -5
- package/templates-app/village-quest-3d/src/village.scene.json +126 -2
- package/templates-app/village-quest-3d/verify.ts +52 -2
- package/editor/assets/index-CrUCQoaB.js +0 -11046
|
@@ -1,422 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { $ as Node, et as diagnose, i as worldPosition, n as localFromWorld } from "./pose-ByFB_J3O.js";
|
|
2
|
+
import { P as translateOn, t as registerCoreNodes } from "./register-B768Qedj.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
|
-
import { c as getNodeSchema,
|
|
5
|
-
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimations, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-
|
|
6
|
-
import {
|
|
4
|
+
import { c as getNodeSchema, p as registerNode } from "./touch-CioTZB-y.js";
|
|
5
|
+
import { i as ParticleSim, n as resolveFrames, o as PARTICLE_PRESET_NAMES, r as validateAnimations, s as applyParticlePreset, t as resolveAnimation } from "./sprite-animation-SQa5gIu2.js";
|
|
6
|
+
import { Q as tolerateUnknownAction, V as effectRng, _ as Node2D, f as Area2D, g as StaticBody2D, h as RigidBody2D, m as PhysicsBody2D, p as CharacterBody2D, vt as findHealth } from "./gameplay-Bfff_beb.js";
|
|
7
7
|
import { t as checkSheetGrid } from "./sheet-grid-Cea343VO.js";
|
|
8
|
-
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh,
|
|
9
|
-
//#region src/2d/physics/collider-validate.ts
|
|
10
|
-
/**
|
|
11
|
-
* Hard-validate a 2D collider prop — called at LOAD time (via the loader's
|
|
12
|
-
* static validateJson hook) so bad shapes fail when the scene is authored,
|
|
13
|
-
* not minutes later when physics starts. An EMPTY collider is fine: a body
|
|
14
|
-
* without a collider is a work-in-progress node, not an error.
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Every key a 2D collider may carry.
|
|
18
|
-
*
|
|
19
|
-
* The shape was checked thoroughly and everything else let through, so
|
|
20
|
-
* `{"shape": "circle", "radius": 8, "offest": [0, -24]}` loaded clean with its
|
|
21
|
-
* hitbox at the body's origin — the exact defect `verify:playable` was written
|
|
22
|
-
* for after it found hitboxes 155 px behind their own bodies in a starter.
|
|
23
|
-
*/
|
|
24
|
-
const COLLIDER_KEYS_2D = [
|
|
25
|
-
"shape",
|
|
26
|
-
"size",
|
|
27
|
-
"radius",
|
|
28
|
-
"height",
|
|
29
|
-
"offset",
|
|
30
|
-
"oneWay"
|
|
31
|
-
];
|
|
32
|
-
function validateCollider2D(collider, nodeName) {
|
|
33
|
-
const shape = collider.shape;
|
|
34
|
-
for (const key of Object.keys(collider)) {
|
|
35
|
-
if (COLLIDER_KEYS_2D.includes(key)) continue;
|
|
36
|
-
throw new IncantoError("BAD_FORMAT", `Collider on '${nodeName}' has an unknown key "${key}".` + didYouMean(key, COLLIDER_KEYS_2D) + ` A collider is [${COLLIDER_KEYS_2D.join(", ")}] — friction, restitution and mass are props of the BODY, not of its collider.`, {
|
|
37
|
-
prop: key,
|
|
38
|
-
validOptions: [...COLLIDER_KEYS_2D]
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
if (shape === void 0) return;
|
|
42
|
-
if (collider.oneWay !== void 0 && typeof collider.oneWay !== "boolean") throw new IncantoError("BAD_FORMAT", `Collider on '${nodeName}': "oneWay" must be a boolean (platforms you jump up through), got ${JSON.stringify(collider.oneWay)}.`);
|
|
43
|
-
if (shape === "rect") {
|
|
44
|
-
const size = collider.size;
|
|
45
|
-
if (!Array.isArray(size) || size.length !== 2 || size.some((v) => typeof v !== "number")) throw new IncantoError("BAD_FORMAT", `Collider on '${nodeName}': rect needs "size": [width, height].`);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
if (shape === "circle") {
|
|
49
|
-
if (typeof collider.radius !== "number") throw new IncantoError("BAD_FORMAT", `Collider on '${nodeName}': circle needs "radius".`);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (shape === "capsule") {
|
|
53
|
-
if (typeof collider.radius !== "number" || typeof collider.height !== "number") throw new IncantoError("BAD_FORMAT", `Collider on '${nodeName}': capsule needs "radius" and "height".`);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
throw new IncantoError("BAD_FORMAT", `Collider on '${nodeName}': "shape" must be 'rect', 'circle', or 'capsule', got ${JSON.stringify(shape)}.`);
|
|
57
|
-
}
|
|
58
|
-
//#endregion
|
|
59
|
-
//#region src/2d/nodes/node-2d.ts
|
|
60
|
-
/**
|
|
61
|
-
* Base of every 2D node. Convention (Phaser/Godot prior):
|
|
62
|
-
* **1 unit = 1 px, +y DOWN, (0,0) top-left, positive rotation = clockwise.**
|
|
63
|
-
* Internally mapped onto three's y-up space by negating y and the z-spin.
|
|
64
|
-
*/
|
|
65
|
-
var Node2D = class extends Node {
|
|
66
|
-
static typeName = "Node2D";
|
|
67
|
-
static props = {
|
|
68
|
-
position: { default: [0, 0] },
|
|
69
|
-
rotation: { default: 0 },
|
|
70
|
-
static: { default: false },
|
|
71
|
-
scale: { default: [1, 1] },
|
|
72
|
-
renderOrder: { default: 0 },
|
|
73
|
-
orderGroup: {
|
|
74
|
-
default: "default",
|
|
75
|
-
options: [
|
|
76
|
-
"background",
|
|
77
|
-
"terrain",
|
|
78
|
-
"default",
|
|
79
|
-
"characters",
|
|
80
|
-
"effects",
|
|
81
|
-
"overlay"
|
|
82
|
-
]
|
|
83
|
-
},
|
|
84
|
-
visible: { default: true }
|
|
85
|
-
};
|
|
86
|
-
/** Legacy alias: old 2D scenes (and `zIndex`-trained agents) keep loading —
|
|
87
|
-
* `zIndex` resolves to `renderOrder` at load. The schema exposes only
|
|
88
|
-
* `renderOrder` so 2D and 3D share ONE render-order name. */
|
|
89
|
-
static propAliases = { zIndex: "renderOrder" };
|
|
90
|
-
/** Pixels, y-down. */
|
|
91
|
-
position = [0, 0];
|
|
92
|
-
/** Degrees, clockwise. */
|
|
93
|
-
rotation = 0;
|
|
94
|
-
/** Frozen after first sync — see Node3D.static (same semantics in 2D). */
|
|
95
|
-
static = false;
|
|
96
|
-
scale = [1, 1];
|
|
97
|
-
/** Draw order among 2D drawables (higher = on top); matches 3D `renderOrder`. */
|
|
98
|
-
renderOrder = 0;
|
|
99
|
-
/** Named draw-order band; `renderOrder` is the fine offset inside it. */
|
|
100
|
-
orderGroup = "default";
|
|
101
|
-
/** What three actually sorts by: band base + renderOrder. */
|
|
102
|
-
get effectiveRenderOrder() {
|
|
103
|
-
return effectiveOrder(this.orderGroup, this.renderOrder, this.tree?.engine?.orderGroups);
|
|
104
|
-
}
|
|
105
|
-
visible = true;
|
|
106
|
-
_object2D = null;
|
|
107
|
-
/** @internal The backing three object (lazily created). */
|
|
108
|
-
_ensureObject2D() {
|
|
109
|
-
if (!this._object2D) {
|
|
110
|
-
this._object2D = this._createObject2D();
|
|
111
|
-
this._object2D.userData.incantoNode = this;
|
|
112
|
-
}
|
|
113
|
-
return this._object2D;
|
|
114
|
-
}
|
|
115
|
-
/** @internal Override point. */
|
|
116
|
-
_createObject2D() {
|
|
117
|
-
return new Object3D();
|
|
118
|
-
}
|
|
119
|
-
/** @internal Push JSON props onto the backing object. Called every frame. */
|
|
120
|
-
/**
|
|
121
|
-
* Has this node produced the thing `static: true` is about to freeze?
|
|
122
|
-
*
|
|
123
|
-
* `static` latches after the first sync, and a textured node's first sync
|
|
124
|
-
* happens BEFORE its texture has decoded — `TextureLoader.load()` returns a
|
|
125
|
-
* `Texture` with `image === undefined` and fills it asynchronously, while
|
|
126
|
-
* `Renderer2D.render()` calls `assets.load()` and `syncTree2D()` in the same
|
|
127
|
-
* frame. So the state frozen was `visible = false`, forever. Measured on two
|
|
128
|
-
* identical sprites differing only in `static`:
|
|
129
|
-
*
|
|
130
|
-
* ```
|
|
131
|
-
* frame 1 Backdrop(static) visible: false Control visible: false
|
|
132
|
-
* 600 frames later Backdrop(static) visible: false width 1
|
|
133
|
-
* Control visible: true width 64
|
|
134
|
-
* ```
|
|
135
|
-
*
|
|
136
|
-
* Nothing throws: the texture loaded fine, so `assetErrors()` is empty,
|
|
137
|
-
* `stats().errors` is 0, and `framing` reads props rather than pixels and
|
|
138
|
-
* still calls the node on-screen. Overridden by the nodes that wait on an
|
|
139
|
-
* image; a node with nothing to wait for is ready by definition.
|
|
140
|
-
*/
|
|
141
|
-
_staticReady(_assets) {
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
_syncObject2D(_assets) {
|
|
145
|
-
const o = this._ensureObject2D();
|
|
146
|
-
o.position.set(this.position[0] ?? 0, -(this.position[1] ?? 0), 0);
|
|
147
|
-
o.rotation.z = -MathUtils.degToRad(this.rotation);
|
|
148
|
-
o.scale.set(this.scale[0] ?? 1, this.scale[1] ?? 1, 1);
|
|
149
|
-
o.visible = this.visible;
|
|
150
|
-
}
|
|
151
|
-
free() {
|
|
152
|
-
super.free();
|
|
153
|
-
this._object2D?.removeFromParent();
|
|
154
|
-
this._object2D = null;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
//#endregion
|
|
158
|
-
//#region src/2d/nodes/bodies-2d.ts
|
|
159
|
-
/**
|
|
160
|
-
* Shared base for physics-backed 2D nodes. Colliders are NODE PROPS:
|
|
161
|
-
* `{shape:'rect', size:[w,h]}` · `{shape:'circle', radius}` ·
|
|
162
|
-
* `{shape:'capsule', radius, height}` — never child shape nodes.
|
|
163
|
-
*/
|
|
164
|
-
var PhysicsBody2D = class extends Node2D {
|
|
165
|
-
static props = {
|
|
166
|
-
collider: {
|
|
167
|
-
default: {},
|
|
168
|
-
variants: {
|
|
169
|
-
tag: "shape",
|
|
170
|
-
byTag: {
|
|
171
|
-
rect: {
|
|
172
|
-
shape: "rect",
|
|
173
|
-
size: [32, 32]
|
|
174
|
-
},
|
|
175
|
-
circle: {
|
|
176
|
-
shape: "circle",
|
|
177
|
-
radius: 16
|
|
178
|
-
},
|
|
179
|
-
capsule: {
|
|
180
|
-
shape: "capsule",
|
|
181
|
-
radius: 12,
|
|
182
|
-
height: 32
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
/**
|
|
188
|
-
* Whether this body's collider takes part in physics right now.
|
|
189
|
-
*
|
|
190
|
-
* A melee hitbox is off most of the time and live for a few frames of a
|
|
191
|
-
* swing, and there was no way to say so: games expressed it by REPLACING
|
|
192
|
-
* `collider` with `{}`, which tears the rigid body down and builds a new
|
|
193
|
-
* one twice per swing, and makes the engine warn `has no collider —
|
|
194
|
-
* physics skips it` about a scene that is correct. Keep the real collider
|
|
195
|
-
* and turn it off.
|
|
196
|
-
*
|
|
197
|
-
* Off means no contacts and no `triggerEnter`/`triggerExit`. The body
|
|
198
|
-
* itself stays, so nothing is rebuilt and re-arming is one call.
|
|
199
|
-
*/
|
|
200
|
-
enabled: { default: true }
|
|
201
|
-
};
|
|
202
|
-
/** The unified collision model: every collider participant can emit these. */
|
|
203
|
-
static signals = ["triggerEnter", "triggerExit"];
|
|
204
|
-
_collider = {};
|
|
205
|
-
/** @internal Physics resync flag — raised when `collider` is REPLACED. */
|
|
206
|
-
_colliderChanged = false;
|
|
207
|
-
_enabled = true;
|
|
208
|
-
/** @internal Physics resync flag — raised when `enabled` is written. */
|
|
209
|
-
_enabledChanged = false;
|
|
210
|
-
/**
|
|
211
|
-
* Collider shape prop. Change it by REPLACING the object
|
|
212
|
-
* (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
|
|
213
|
-
* body on replacement; in-place mutation of the old object is not watched.
|
|
214
|
-
*/
|
|
215
|
-
get collider() {
|
|
216
|
-
return this._collider;
|
|
217
|
-
}
|
|
218
|
-
set collider(value) {
|
|
219
|
-
this._collider = value;
|
|
220
|
-
this._colliderChanged = true;
|
|
221
|
-
}
|
|
222
|
-
/** See the `enabled` prop. Writing it arms or disarms the existing collider. */
|
|
223
|
-
get enabled() {
|
|
224
|
-
return this._enabled;
|
|
225
|
-
}
|
|
226
|
-
set enabled(value) {
|
|
227
|
-
if (value === this._enabled) return;
|
|
228
|
-
this._enabled = value;
|
|
229
|
-
this._enabledChanged = true;
|
|
230
|
-
}
|
|
231
|
-
/** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
|
|
232
|
-
static validateJson(node) {
|
|
233
|
-
validateCollider2D(node.collider, node.name);
|
|
234
|
-
}
|
|
235
|
-
/** @internal Set by Physics2D when the body is created. */
|
|
236
|
-
_physics = null;
|
|
237
|
-
};
|
|
238
|
-
/**
|
|
239
|
-
* Immovable collider (ground, walls, platforms).
|
|
240
|
-
*
|
|
241
|
-
* It also carries the SURFACE it is: `friction` and `restitution` used to
|
|
242
|
-
* live on `RigidBody2D` alone, so a trampoline, an ice patch or a sticky
|
|
243
|
-
* landing could not be written in scene JSON, and Rapier's AVERAGE rule
|
|
244
|
-
* halved whatever a ball brought to a plain floor. A static body's authored
|
|
245
|
-
* value WINS the pair — restitution above 0 combines by Max, friction below
|
|
246
|
-
* the default by Min and above it by Max — so the pad decides, whatever lands
|
|
247
|
-
* on it. Left at the defaults, nothing changes for any existing scene.
|
|
248
|
-
*/
|
|
249
|
-
var StaticBody2D = class extends PhysicsBody2D {
|
|
250
|
-
static typeName = "StaticBody2D";
|
|
251
|
-
static props = {
|
|
252
|
-
friction: { default: .5 },
|
|
253
|
-
restitution: { default: 0 }
|
|
254
|
-
};
|
|
255
|
-
/** 0.5 plain · 0 ice (slides on and on) · 1+ glue. Away from 0.5 it wins the pair. */
|
|
256
|
-
friction = .5;
|
|
257
|
-
/** 0 dead · 1 trampoline. Above 0 it wins the pair: a dead ball still bounces. */
|
|
258
|
-
restitution = 0;
|
|
259
|
-
};
|
|
260
|
-
/**
|
|
261
|
-
* Sensor volume. Emits `triggerEnter(other)` / `triggerExit(other)` — the
|
|
262
|
-
* unified collision model (solid bodies emit the same signals on contact).
|
|
263
|
-
*/
|
|
264
|
-
var Area2D = class extends PhysicsBody2D {
|
|
265
|
-
static typeName = "Area2D";
|
|
266
|
-
/**
|
|
267
|
-
* Who is inside RIGHT NOW — the standing answer behind the enter/exit pair.
|
|
268
|
-
* See `Area3D.overlapping`. Empty in a scene with no physics world.
|
|
269
|
-
*/
|
|
270
|
-
overlapping(group = void 0) {
|
|
271
|
-
const all = this._physics?.overlapping(this) ?? [];
|
|
272
|
-
return group === void 0 ? all : all.filter((n) => n.isInGroup(group));
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
/** Dynamic simulated body. */
|
|
276
|
-
var RigidBody2D = class extends PhysicsBody2D {
|
|
277
|
-
static typeName = "RigidBody2D";
|
|
278
|
-
static props = {
|
|
279
|
-
mass: { default: 1 },
|
|
280
|
-
gravityScale: { default: 1 },
|
|
281
|
-
fixedRotation: { default: false },
|
|
282
|
-
friction: { default: .5 },
|
|
283
|
-
restitution: { default: 0 },
|
|
284
|
-
linearVelocity: { default: [0, 0] },
|
|
285
|
-
angularVelocity: { default: 0 },
|
|
286
|
-
linearDamping: {
|
|
287
|
-
default: 0,
|
|
288
|
-
range: { min: 0 }
|
|
289
|
-
},
|
|
290
|
-
angularDamping: {
|
|
291
|
-
default: 0,
|
|
292
|
-
range: { min: 0 }
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
mass = 1;
|
|
296
|
-
gravityScale = 1;
|
|
297
|
-
fixedRotation = false;
|
|
298
|
-
friction = .5;
|
|
299
|
-
restitution = 0;
|
|
300
|
-
/** Drag, per second: velocity decays by `e^(-linearDamping·t)`. `0` = none. */
|
|
301
|
-
linearDamping = 0;
|
|
302
|
-
/** Rolling resistance, per second, on the spin — see `RigidBody3D.angularDamping`. */
|
|
303
|
-
angularDamping = 0;
|
|
304
|
-
/** px/s, y-down. Read back every step; write to launch. */
|
|
305
|
-
linearVelocity = [0, 0];
|
|
306
|
-
/**
|
|
307
|
-
* rad/s about the z axis — the same sign as `rotation`, which is CLOCKWISE
|
|
308
|
-
* in this y-down space. Read back every step; write to spin.
|
|
309
|
-
*
|
|
310
|
-
* `RigidBody3D` got its `angularVelocity` because "a puzzle builder could
|
|
311
|
-
* neither launch a spinning body nor read how fast a lever was swinging —
|
|
312
|
-
* only its angle, differenced by hand". Same sentence, same defect, other
|
|
313
|
-
* adapter: 2D had `linearVelocity` and no counterpart. `fixedRotation` pins
|
|
314
|
-
* it at zero.
|
|
315
|
-
*/
|
|
316
|
-
angularVelocity = 0;
|
|
317
|
-
/**
|
|
318
|
-
* The other name for `linearVelocity`, because it is the name every skill,
|
|
319
|
-
* every sibling node and every other engine uses.
|
|
320
|
-
*
|
|
321
|
-
* `body.velocity = [0, -400]` is what `incanto-physics-and-input.md` taught
|
|
322
|
-
* for years. On a plain TS class that assignment SUCCEEDS — it just writes a
|
|
323
|
-
* stray field nothing reads — so a jump that never happened produced no
|
|
324
|
-
* error, no warning and nothing to search for. `CharacterBody2D.velocity`
|
|
325
|
-
* next to `RigidBody2D.linearVelocity` is the kind of near-miss a builder
|
|
326
|
-
* cannot be expected to notice.
|
|
327
|
-
*
|
|
328
|
-
* Scene JSON still names the prop `linearVelocity` and rejects `velocity`
|
|
329
|
-
* loudly, with the valid keys listed — a hard error corrects itself.
|
|
330
|
-
*/
|
|
331
|
-
get velocity() {
|
|
332
|
-
return this.linearVelocity;
|
|
333
|
-
}
|
|
334
|
-
set velocity(v) {
|
|
335
|
-
this.linearVelocity = v;
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* Impulse in px·kg/s (y-down) — the 3D twin has had this since it shipped,
|
|
339
|
-
* and calling it here was a TypeError.
|
|
340
|
-
*/
|
|
341
|
-
applyImpulse(impulse) {
|
|
342
|
-
this._physics?.applyImpulse(this, impulse);
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
/**
|
|
346
|
-
* Kinematic body on Rapier's character controller. Gravity is NOT applied
|
|
347
|
-
* automatically (Godot semantics) — integrate `velocity` yourself, then call
|
|
348
|
-
* `moveAndSlide()` from `fixedUpdate`.
|
|
349
|
-
*/
|
|
350
|
-
var CharacterBody2D = class extends PhysicsBody2D {
|
|
351
|
-
static typeName = "CharacterBody2D";
|
|
352
|
-
static props = {
|
|
353
|
-
velocity: { default: [0, 0] },
|
|
354
|
-
stickToGround: { default: true },
|
|
355
|
-
slopeLimitDeg: { default: 45 },
|
|
356
|
-
stepHeight: {
|
|
357
|
-
default: 35,
|
|
358
|
-
range: { min: 0 }
|
|
359
|
-
},
|
|
360
|
-
collideWithCharacters: { default: true }
|
|
361
|
-
};
|
|
362
|
-
/**
|
|
363
|
-
* Renamed to match the 3D body, where `snapToGround` had to give the name
|
|
364
|
-
* back to `Node3D`'s PLACEMENT prop. 2D has no placement prop, so nothing is
|
|
365
|
-
* ambiguous here and old scenes keep loading unchanged.
|
|
366
|
-
*/
|
|
367
|
-
static propAliases = { snapToGround: "stickToGround" };
|
|
368
|
-
/** px/s, y-down (up = -y). */
|
|
369
|
-
velocity = [0, 0];
|
|
370
|
-
/** Keep contact on slopes/steps while not moving upward. */
|
|
371
|
-
stickToGround = true;
|
|
372
|
-
/** Max climbable slope angle. */
|
|
373
|
-
slopeLimitDeg = 45;
|
|
374
|
-
/**
|
|
375
|
-
* How high a step this body walks up without jumping, in PIXELS.
|
|
376
|
-
*
|
|
377
|
-
* Rapier's character controller does not autostep unless it is asked, and in
|
|
378
|
-
* 2D nothing asked: a ledge of ANY height — measured, ONE pixel — stopped a
|
|
379
|
-
* walking body dead, while the mirrored `CharacterBody3D` has cleared 0.35 m
|
|
380
|
-
* by default since 0.63. The default here is that same 0.35 m at the 2D
|
|
381
|
-
* world's own scale (100 px = 1 m, which is why gravity defaults to 980).
|
|
382
|
-
* `0` restores the old behaviour.
|
|
383
|
-
*/
|
|
384
|
-
stepHeight = 35;
|
|
385
|
-
/** See CharacterBody3D.collideWithCharacters — off, a crowd ignores itself and costs nothing. */
|
|
386
|
-
collideWithCharacters = true;
|
|
387
|
-
/** @internal Updated by Physics2D.moveAndSlide. */
|
|
388
|
-
_grounded = false;
|
|
389
|
-
moveAndSlide() {
|
|
390
|
-
if (!this._physics) throw new IncantoError("TREE_VIOLATION", this.collider.shape === void 0 ? `CharacterBody2D '${this.name}' has no collider — set a collider prop (physics skips collider-less bodies).` : `CharacterBody2D '${this.name}' has no physics world — await enablePhysics2D(engine) first.`);
|
|
391
|
-
this._physics.moveAndSlide(this);
|
|
392
|
-
}
|
|
393
|
-
/** @internal Set by the physics step from the KCC's resolved collisions. */
|
|
394
|
-
_onWall = 0;
|
|
395
|
-
/** @internal */
|
|
396
|
-
_onCeiling = false;
|
|
397
|
-
/**
|
|
398
|
-
* Touching a wall, and which side (-1 left, +1 right, 0 neither).
|
|
399
|
-
*
|
|
400
|
-
* Wall jump and wall slide are unbuildable without it — not awkward to build,
|
|
401
|
-
* impossible: `isOnFloor()` was the only surface a character could report, so
|
|
402
|
-
* even a hand-written behavior had nothing to ask.
|
|
403
|
-
*/
|
|
404
|
-
isOnWall() {
|
|
405
|
-
return this._onWall !== 0;
|
|
406
|
-
}
|
|
407
|
-
/** -1 the wall is to the left, +1 to the right, 0 no wall. */
|
|
408
|
-
wallSide() {
|
|
409
|
-
return this._onWall;
|
|
410
|
-
}
|
|
411
|
-
/** Head hit something — cut the jump short, the way every platformer does. */
|
|
412
|
-
isOnCeiling() {
|
|
413
|
-
return this._onCeiling;
|
|
414
|
-
}
|
|
415
|
-
isOnFloor() {
|
|
416
|
-
return this._grounded;
|
|
417
|
-
}
|
|
418
|
-
};
|
|
419
|
-
//#endregion
|
|
8
|
+
import { AdditiveBlending, BufferAttribute, BufferGeometry, CanvasTexture, Color, DynamicDrawUsage, Group, InstancedMesh, Matrix4, Mesh, MeshBasicMaterial, NearestFilter, NormalBlending, PlaneGeometry, SRGBColorSpace } from "three";
|
|
420
9
|
//#region src/2d/nodes/joint-2d.ts
|
|
421
10
|
/**
|
|
422
11
|
* A physics joint linking its PARENT body to `target` (a node path to the
|
|
@@ -438,6 +27,7 @@ var Joint2D = class extends Node2D {
|
|
|
438
27
|
options: [
|
|
439
28
|
"fixed",
|
|
440
29
|
"revolute",
|
|
30
|
+
"prismatic",
|
|
441
31
|
"rope",
|
|
442
32
|
"spring"
|
|
443
33
|
]
|
|
@@ -449,6 +39,17 @@ var Joint2D = class extends Node2D {
|
|
|
449
39
|
},
|
|
450
40
|
anchor: { default: [0, 0] },
|
|
451
41
|
targetAnchor: { default: [0, 0] },
|
|
42
|
+
/** The line of a `prismatic` slide, in this body's local frame (y-down: `[0, -1]` is up). */
|
|
43
|
+
axis: { default: [0, 1] },
|
|
44
|
+
/** The stops: `[minDeg, maxDeg]` for a `revolute`, `[min, max]` px for a `prismatic`; empty = free. */
|
|
45
|
+
limits: { default: [] },
|
|
46
|
+
/** The motor's target speed — degrees a second (`revolute`) or px a second (`prismatic`); 0 = no motor. */
|
|
47
|
+
motorSpeed: { default: 0 },
|
|
48
|
+
/** How hard the motor holds its speed (acceleration per rad/s of error). */
|
|
49
|
+
motorStrength: {
|
|
50
|
+
default: 20,
|
|
51
|
+
range: { min: 0 }
|
|
52
|
+
},
|
|
452
53
|
length: { default: 0 },
|
|
453
54
|
stiffness: { default: 50 },
|
|
454
55
|
damping: { default: 5 },
|
|
@@ -461,6 +62,27 @@ var Joint2D = class extends Node2D {
|
|
|
461
62
|
target = "";
|
|
462
63
|
anchor = [0, 0];
|
|
463
64
|
targetAnchor = [0, 0];
|
|
65
|
+
/**
|
|
66
|
+
* `[min, max]` in degrees for a `revolute` swing; `[]` = no stops. Measured
|
|
67
|
+
* as `angle` is: this body turned relative to the target, `rotation`'s sign.
|
|
68
|
+
*/
|
|
69
|
+
limits = [];
|
|
70
|
+
/** Degrees a second the motor drives the pin at; `0` = no motor (free). */
|
|
71
|
+
motorSpeed = 0;
|
|
72
|
+
/** The motor's gain — how quickly it reaches and holds `motorSpeed`. */
|
|
73
|
+
motorStrength = 20;
|
|
74
|
+
/**
|
|
75
|
+
* The pin's current angle in degrees — this body's rotation relative to the
|
|
76
|
+
* target, written by physics every step. `0` unless `revolute` and simulated.
|
|
77
|
+
*/
|
|
78
|
+
angle = 0;
|
|
79
|
+
/** The line of a `prismatic` slide (local to this body). */
|
|
80
|
+
axis = [0, 1];
|
|
81
|
+
/**
|
|
82
|
+
* A `prismatic` slide's current travel in px — this body's anchor along
|
|
83
|
+
* `axis` relative to the target's; `0` where the anchors coincide.
|
|
84
|
+
*/
|
|
85
|
+
travel = 0;
|
|
464
86
|
/** rope/spring rest length in px; 0 = measure body distance at creation. */
|
|
465
87
|
length = 0;
|
|
466
88
|
stiffness = 50;
|
|
@@ -493,6 +115,8 @@ var Joint2D = class extends Node2D {
|
|
|
493
115
|
onEnterTree() {
|
|
494
116
|
if (!(this.parent instanceof PhysicsBody2D)) throw new IncantoError("TREE_VIOLATION", `Joint2D '${this.name}' must be a child of a physics body (its body A); parent is '${this.parent?.name ?? "none"}'.`);
|
|
495
117
|
if (this.target === "") throw new IncantoError("BAD_FORMAT", `Joint2D '${this.name}' needs a "target" node path to the body it links to.`);
|
|
118
|
+
if (this.type === "prismatic" && !(Math.hypot(this.axis[0] ?? 0, this.axis[1] ?? 0) > 1e-6)) throw new IncantoError("BAD_FORMAT", `Joint2D '${this.name}': a prismatic joint needs a non-zero "axis" (got ${JSON.stringify(this.axis)}).`);
|
|
119
|
+
if ((this.type === "revolute" || this.type === "prismatic") && this.limits.length !== 0 && this.limits.length !== 2) throw new IncantoError("BAD_FORMAT", `Joint2D '${this.name}': "limits" is [minDeg, maxDeg] or [] (got ${JSON.stringify(this.limits)}).`);
|
|
496
120
|
}
|
|
497
121
|
/** @internal The two bodies, resolved (throws on a bad target at step time). */
|
|
498
122
|
_resolveBodies() {
|
|
@@ -749,7 +373,7 @@ var AnimatedSprite2D = class extends Sprite2D {
|
|
|
749
373
|
* with exponential smoothing; `limits [minX,minY,maxX,maxY]` clamp the view
|
|
750
374
|
* rect inside a world region (applied by the renderer via `clampedCenter`).
|
|
751
375
|
*/
|
|
752
|
-
var Camera2D = class extends Node2D {
|
|
376
|
+
var Camera2D = class Camera2D extends Node2D {
|
|
753
377
|
static typeName = "Camera2D";
|
|
754
378
|
static props = {
|
|
755
379
|
follow: {
|
|
@@ -759,7 +383,15 @@ var Camera2D = class extends Node2D {
|
|
|
759
383
|
smoothing: { default: 0 },
|
|
760
384
|
zoom: { default: 1 },
|
|
761
385
|
limits: { default: [] },
|
|
762
|
-
current: { default: false }
|
|
386
|
+
current: { default: false },
|
|
387
|
+
/**
|
|
388
|
+
* Seconds the view FLIES from the last drawn centre and zoom to this camera
|
|
389
|
+
* when it becomes current (0 = a cut) — the 2D twin of `Camera3D.blendSeconds`.
|
|
390
|
+
*/
|
|
391
|
+
blendSeconds: {
|
|
392
|
+
default: 0,
|
|
393
|
+
range: { min: 0 }
|
|
394
|
+
}
|
|
763
395
|
};
|
|
764
396
|
/** NodePath of a Node2D to track. */
|
|
765
397
|
follow = "";
|
|
@@ -769,6 +401,25 @@ var Camera2D = class extends Node2D {
|
|
|
769
401
|
/** [minX, minY, maxX, maxY] world px; empty = unlimited. */
|
|
770
402
|
limits = [];
|
|
771
403
|
current = false;
|
|
404
|
+
/**
|
|
405
|
+
* Make THIS the scene's camera and no other: the renderer takes the first
|
|
406
|
+
* camera whose `current` is true in tree order, so setting your own without
|
|
407
|
+
* clearing the gameplay camera's changed nothing visible. A handler a
|
|
408
|
+
* connection can name — `triggerEnter → DoorCam.makeCurrent`.
|
|
409
|
+
*/
|
|
410
|
+
makeCurrent(blendSeconds = void 0) {
|
|
411
|
+
const root = this.tree?.root ?? this.getRoot();
|
|
412
|
+
const walk = (n) => {
|
|
413
|
+
if (n instanceof Camera2D) n.current = n === this;
|
|
414
|
+
for (const c of n.children) walk(c);
|
|
415
|
+
};
|
|
416
|
+
walk(root);
|
|
417
|
+
this.current = true;
|
|
418
|
+
this._blendRequest = blendSeconds;
|
|
419
|
+
}
|
|
420
|
+
blendSeconds = 0;
|
|
421
|
+
/** @internal `makeCurrent(seconds)` — read once by the renderer. */
|
|
422
|
+
_blendRequest = void 0;
|
|
772
423
|
/** `zoom` clamped to a positive floor — 0/negative zoom must never produce NaN views. */
|
|
773
424
|
get effectiveZoom() {
|
|
774
425
|
return Math.max(.01, this.zoom);
|
|
@@ -860,7 +511,8 @@ function clampCentered(v, lo, hi) {
|
|
|
860
511
|
const DEFAULT_ACTIONS = {
|
|
861
512
|
moveAction: "move",
|
|
862
513
|
jumpAction: "jump",
|
|
863
|
-
dashAction: "dash"
|
|
514
|
+
dashAction: "dash",
|
|
515
|
+
crouchAction: "crouch"
|
|
864
516
|
};
|
|
865
517
|
var CharacterController2D = class extends Node {
|
|
866
518
|
static typeName = "CharacterController2D";
|
|
@@ -888,7 +540,25 @@ var CharacterController2D = class extends Node {
|
|
|
888
540
|
/** 0 = no wall slide. Terminal fall speed while against a wall (px/s). */
|
|
889
541
|
wallSlideSpeed: { default: 0 },
|
|
890
542
|
/** 0 = no wall jump. `[away, up]` impulse in px/s. */
|
|
891
|
-
wallJumpImpulse: { default: [0, 0] }
|
|
543
|
+
wallJumpImpulse: { default: [0, 0] },
|
|
544
|
+
/**
|
|
545
|
+
* The collider's height (px) while the crouch key is held — a rect's
|
|
546
|
+
* `size[1]`, a capsule's `height`; 0 = no crouch. The body drops by half
|
|
547
|
+
* the difference so the feet stay put, a gap lower than the standing body
|
|
548
|
+
* is ducked through, and a ceiling that will not let you stand keeps you
|
|
549
|
+
* down until you are out from under it. Platformer mode only.
|
|
550
|
+
*/
|
|
551
|
+
crouchHeight: {
|
|
552
|
+
default: 0,
|
|
553
|
+
range: { min: 0 }
|
|
554
|
+
},
|
|
555
|
+
/** Pace while crouched, as a fraction of the standing pace. */
|
|
556
|
+
crouchSpeedMultiplier: {
|
|
557
|
+
default: .5,
|
|
558
|
+
range: { min: 0 }
|
|
559
|
+
},
|
|
560
|
+
/** Held = crouch. Missing from the input map = the character never crouches. */
|
|
561
|
+
crouchAction: { default: "crouch" }
|
|
892
562
|
};
|
|
893
563
|
static signals = ["movementStateChanged"];
|
|
894
564
|
mode = "platformer";
|
|
@@ -904,6 +574,13 @@ var CharacterController2D = class extends Node {
|
|
|
904
574
|
dashSpeed = 0;
|
|
905
575
|
dashSeconds = .15;
|
|
906
576
|
dashAction = "dash";
|
|
577
|
+
crouchHeight = 0;
|
|
578
|
+
crouchSpeedMultiplier = .5;
|
|
579
|
+
crouchAction = "crouch";
|
|
580
|
+
/** Read-only: shorter right now — by the key, or by a ceiling. */
|
|
581
|
+
crouching = false;
|
|
582
|
+
/** The collider height to stand back up to. */
|
|
583
|
+
standingHeight = 0;
|
|
907
584
|
wallSlideSpeed = 0;
|
|
908
585
|
wallJumpImpulse = [0, 0];
|
|
909
586
|
/** What the character is doing — drive `AnimatedSprite2D.play(state)` off it. */
|
|
@@ -928,13 +605,20 @@ var CharacterController2D = class extends Node {
|
|
|
928
605
|
x: 0,
|
|
929
606
|
y: 0
|
|
930
607
|
}, "the character will not move");
|
|
608
|
+
const health = findHealth(body)?.health ?? null;
|
|
609
|
+
if (health?.staggered ?? false) {
|
|
610
|
+
dir.x = 0;
|
|
611
|
+
dir.y = 0;
|
|
612
|
+
}
|
|
613
|
+
const kick = health?.knocked ? health.kickVelocity : [];
|
|
614
|
+
const lift = health?.knocked ? health.takeLift() : 0;
|
|
931
615
|
if (this.mode === "topDown") {
|
|
932
|
-
body.velocity = [dir.x * this.maxSpeed, dir.y * this.maxSpeed];
|
|
616
|
+
body.velocity = kick.length > 0 ? [kick[0] ?? 0, kick[1] ?? 0] : [dir.x * this.maxSpeed, dir.y * this.maxSpeed];
|
|
933
617
|
body.moveAndSlide();
|
|
934
618
|
return;
|
|
935
619
|
}
|
|
936
620
|
const gravityY = (engine.scene.physics?.gravity)?.[1] ?? 980;
|
|
937
|
-
const grounded = body.isOnFloor();
|
|
621
|
+
const grounded = body.isOnFloor() && !(this.rising && (body.velocity[1] ?? 0) < 0);
|
|
938
622
|
const jumpImpulse = -Math.sqrt(2 * gravityY * this.jumpHeight);
|
|
939
623
|
if (this.dashLeft > 0) {
|
|
940
624
|
this.dashLeft -= dt;
|
|
@@ -953,7 +637,24 @@ var CharacterController2D = class extends Node {
|
|
|
953
637
|
this.setState("dash");
|
|
954
638
|
return;
|
|
955
639
|
}
|
|
956
|
-
body.velocity[0] = dir.x * this.maxSpeed;
|
|
640
|
+
body.velocity[0] = kick.length > 0 ? kick[0] ?? 0 : dir.x * this.maxSpeed;
|
|
641
|
+
const wantCrouch = this.crouchHeight > 0 && this.readAction("crouchAction", () => input.isPressed(this.crouchAction), false, "the character will never crouch");
|
|
642
|
+
if (!this.crouching) {
|
|
643
|
+
const standing = colliderHeight(body.collider);
|
|
644
|
+
if (wantCrouch && grounded && this.crouchHeight < standing) {
|
|
645
|
+
this.standingHeight = standing;
|
|
646
|
+
this.crouching = true;
|
|
647
|
+
setColliderHeight(body, this.crouchHeight, standing);
|
|
648
|
+
}
|
|
649
|
+
} else if (!wantCrouch) {
|
|
650
|
+
const rise = this.standingHeight - this.crouchHeight;
|
|
651
|
+
const top = (body.position[1] ?? 0) - halfExtent(body.collider) + 1;
|
|
652
|
+
if (!body._physics?.castRay([body.position[0] ?? 0, top], [0, -1], rise + 2, body)) {
|
|
653
|
+
this.crouching = false;
|
|
654
|
+
setColliderHeight(body, this.standingHeight, this.crouchHeight);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (this.crouching) body.velocity[0] = (body.velocity[0] ?? 0) * this.crouchSpeedMultiplier;
|
|
957
658
|
if (grounded) {
|
|
958
659
|
this.coyoteLeft = this.coyoteSeconds;
|
|
959
660
|
this.jumpsUsed = 0;
|
|
@@ -964,7 +665,11 @@ var CharacterController2D = class extends Node {
|
|
|
964
665
|
const jumpEdge = this.readAction("jumpAction", () => input.justPressed(this.jumpAction), false, "the character will never jump");
|
|
965
666
|
if (jumpEdge) this.bufferLeft = Math.max(this.jumpBufferSeconds, 0);
|
|
966
667
|
else this.bufferLeft = Math.max(0, this.bufferLeft - dt);
|
|
967
|
-
|
|
668
|
+
let wantsJump = this.bufferLeft > 0 || jumpEdge;
|
|
669
|
+
if (this.crouching) {
|
|
670
|
+
wantsJump = false;
|
|
671
|
+
this.bufferLeft = 0;
|
|
672
|
+
}
|
|
968
673
|
const onWall = this.wallSlideSpeed > 0 || this.wallJumpImpulse.some((v) => v !== 0) ? body.isOnWall() : false;
|
|
969
674
|
if (wantsJump && onWall && !grounded && this.wallJumpImpulse.some((v) => v !== 0)) {
|
|
970
675
|
const away = -body.wallSide();
|
|
@@ -992,6 +697,10 @@ var CharacterController2D = class extends Node {
|
|
|
992
697
|
if ((body.velocity[1] ?? 0) >= 0) this.rising = false;
|
|
993
698
|
if (this.wallSlideSpeed > 0 && onWall && (body.velocity[1] ?? 0) > this.wallSlideSpeed) body.velocity[1] = this.wallSlideSpeed;
|
|
994
699
|
}
|
|
700
|
+
if (lift !== 0) {
|
|
701
|
+
body.velocity[1] = lift;
|
|
702
|
+
if (lift < 0) this.rising = true;
|
|
703
|
+
}
|
|
995
704
|
if (body.isOnCeiling() && (body.velocity[1] ?? 0) < 0) {
|
|
996
705
|
body.velocity[1] = 0;
|
|
997
706
|
this.rising = false;
|
|
@@ -999,8 +708,10 @@ var CharacterController2D = class extends Node {
|
|
|
999
708
|
body.moveAndSlide();
|
|
1000
709
|
const vy = body.velocity[1] ?? 0;
|
|
1001
710
|
if (this.wallSlideSpeed > 0 && !body.isOnFloor() && body.isOnWall() && vy > 0) this.setState("wallSlide");
|
|
1002
|
-
else if (body.isOnFloor())
|
|
1003
|
-
|
|
711
|
+
else if (body.isOnFloor()) {
|
|
712
|
+
const moving = Math.abs(dir.x) > .01;
|
|
713
|
+
this.setState(this.crouching ? moving ? "sneak" : "crouch" : moving ? "run" : "idle");
|
|
714
|
+
} else this.setState(vy < 0 ? "jump" : "fall");
|
|
1004
715
|
}
|
|
1005
716
|
/**
|
|
1006
717
|
* Emits `movementStateChanged(state)` on a change — the 3D sibling has had
|
|
@@ -1045,6 +756,40 @@ var CharacterController2D = class extends Node {
|
|
|
1045
756
|
}, action, read, fallback);
|
|
1046
757
|
}
|
|
1047
758
|
};
|
|
759
|
+
/** The collider's height along y: a rect's size[1], a capsule's straight part; a circle cannot crouch (0). */
|
|
760
|
+
function colliderHeight(c) {
|
|
761
|
+
if (c.shape === "rect") return c.size?.[1] ?? 0;
|
|
762
|
+
if (c.shape === "capsule") return c.height ?? 0;
|
|
763
|
+
return 0;
|
|
764
|
+
}
|
|
765
|
+
/** Half the collider's full extent along y — where its top and bottom edges are. */
|
|
766
|
+
function halfExtent(c) {
|
|
767
|
+
if (c.shape === "rect") return (c.size?.[1] ?? 0) / 2;
|
|
768
|
+
if (c.shape === "capsule") return (c.height ?? 0) / 2 + (c.radius ?? 0);
|
|
769
|
+
return c.radius ?? 0;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Swap the collider's height and keep the feet where they are: y-down, so a
|
|
773
|
+
* shorter body's centre moves DOWN by half the difference (and back up on
|
|
774
|
+
* standing). The adapter rebuilds the body at the new place next step.
|
|
775
|
+
*/
|
|
776
|
+
function setColliderHeight(body, next, from) {
|
|
777
|
+
const c = body.collider;
|
|
778
|
+
if (c.shape === "rect") {
|
|
779
|
+
const size = c.size ?? [32, from];
|
|
780
|
+
body.collider = {
|
|
781
|
+
...c,
|
|
782
|
+
size: [size[0] ?? 32, next]
|
|
783
|
+
};
|
|
784
|
+
} else if (c.shape === "capsule") body.collider = {
|
|
785
|
+
...c,
|
|
786
|
+
height: next
|
|
787
|
+
};
|
|
788
|
+
else return;
|
|
789
|
+
const d = (from - next) / 2;
|
|
790
|
+
const p = body.position;
|
|
791
|
+
body.position = [p[0] ?? 0, (p[1] ?? 0) + d];
|
|
792
|
+
}
|
|
1048
793
|
//#endregion
|
|
1049
794
|
//#region src/2d/nodes/color-rect-2d.ts
|
|
1050
795
|
/** Shared unit quad (same discipline as sprites — never THREE.Sprite). */
|
|
@@ -1307,7 +1052,11 @@ var Particles2D = class Particles2D extends Node2D {
|
|
|
1307
1052
|
default: "add",
|
|
1308
1053
|
options: ["add", "normal"]
|
|
1309
1054
|
},
|
|
1310
|
-
maxParticles: { default: 256 }
|
|
1055
|
+
maxParticles: { default: 256 },
|
|
1056
|
+
/** Half-extents (px) of a box the particles are born in, around the node; `[0,0]` = a point. */
|
|
1057
|
+
emitBox: { default: [0, 0] },
|
|
1058
|
+
/** A steady velocity (px/s, y+ = down) every particle is born with — wind on smoke. */
|
|
1059
|
+
drift: { default: [0, 0] }
|
|
1311
1060
|
};
|
|
1312
1061
|
preset = "custom";
|
|
1313
1062
|
emitting = true;
|
|
@@ -1345,6 +1094,8 @@ var Particles2D = class Particles2D extends Node2D {
|
|
|
1345
1094
|
shimmer = 0;
|
|
1346
1095
|
blend = "add";
|
|
1347
1096
|
maxParticles = 256;
|
|
1097
|
+
emitBox = [0, 0];
|
|
1098
|
+
drift = [0, 0];
|
|
1348
1099
|
/** Loader hook: unknown presets and bad blends fail at LOAD. */
|
|
1349
1100
|
static validateJson(node) {
|
|
1350
1101
|
const p = node;
|
|
@@ -1377,7 +1128,17 @@ var Particles2D = class Particles2D extends Node2D {
|
|
|
1377
1128
|
0
|
|
1378
1129
|
],
|
|
1379
1130
|
drag: this.drag,
|
|
1380
|
-
maxParticles: this.maxParticles
|
|
1131
|
+
maxParticles: this.maxParticles,
|
|
1132
|
+
emitBox: [
|
|
1133
|
+
this.emitBox[0] ?? 0,
|
|
1134
|
+
this.emitBox[1] ?? 0,
|
|
1135
|
+
0
|
|
1136
|
+
],
|
|
1137
|
+
drift: [
|
|
1138
|
+
this.drift[0] ?? 0,
|
|
1139
|
+
this.drift[1] ?? 0,
|
|
1140
|
+
0
|
|
1141
|
+
]
|
|
1381
1142
|
}, rng);
|
|
1382
1143
|
}
|
|
1383
1144
|
return this.sim;
|
|
@@ -2125,4 +1886,4 @@ function registerNodes2D() {
|
|
|
2125
1886
|
registerNode(TileMap2D);
|
|
2126
1887
|
}
|
|
2127
1888
|
//#endregion
|
|
2128
|
-
export {
|
|
1889
|
+
export { parseCells as a, ColorRect2D as c, AnimatedSprite2D as d, Sprite2D as f, mergeSolidRects as i, CharacterController2D as l, UILayer as n, Particles2D as o, Joint2D as p, TileMap2D as r, Label as s, registerNodes2D as t, Camera2D as u };
|