incanto 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/2d.d.ts +74 -6
- package/dist/2d.js +4 -4
- package/dist/3d.d.ts +174 -16
- package/dist/3d.js +4 -4
- package/dist/{audio-player-DqUR3XFs.d.ts → audio-player-DkBqRTs4.d.ts} +1 -1
- package/dist/{behavior-BAQq7HGM.d.ts → behavior-CWhW3oa6.d.ts} +54 -0
- package/dist/{create-game-CZHROKcT.js → create-game-BFESHv1X.js} +37 -25
- package/dist/{create-game-CMS7DiPi.js → create-game-D8UvwXme.js} +231 -8
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-DP2WPYom.js → duplicate-BEvGBtb_.js} +1 -1
- package/dist/{gameplay-Ccruc3Wd.js → gameplay-CDFgSG6z.js} +293 -24
- package/dist/gameplay.d.ts +113 -2
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +139 -5
- package/dist/index.js +59 -6
- package/dist/{loader-CGs_G-r0.js → loader-B4OEXDZ8.js} +60 -0
- package/dist/{loader-Mo0KghCv.d.ts → loader-D2u0fVW2.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +1 -1
- package/dist/{particle-sim-DYuSUxvK.js → particle-sim-CFkILGwh.js} +84 -3
- package/dist/{particle-sim-CbN4YUuH.d.ts → particle-sim-CwJ5rI_P.d.ts} +3 -0
- package/dist/{physics-2d-KuMWPTf6.js → physics-2d-KXn1N1jF.js} +95 -10
- package/dist/{physics-3d-CXOBOUKG.js → physics-3d-C58oQzTX.js} +121 -43
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-B0gq63VW.js → register-C35HDZpm.js} +2 -2
- package/dist/{register-DPEV9_9t.js → register-D71C4rDC.js} +76 -5
- package/dist/{register-BuUV1_KB.js → register-Dl_ixIJe.js} +275 -2
- package/dist/{register-02aSywx2.js → register-DvPHJdVj.js} +390 -59
- package/dist/test.d.ts +2 -2
- package/dist/test.js +10 -10
- package/editor/assets/{agent8-DUVZGcuO.js → agent8-DruKhR22.js} +1 -1
- package/editor/assets/index-D2qufqUo.js +7417 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +847 -133
- package/skills/incanto-3d-models.md +24 -1
- package/skills/incanto-building-3d-games.md +35 -3
- package/skills/incanto-gameplay-behaviors.md +60 -0
- package/skills/incanto-hud.md +58 -0
- package/skills/incanto-node-reference.md +113 -0
- package/skills/incanto-physics-and-input.md +47 -0
- package/editor/assets/index-DsM2Yp9F.js +0 -7365
package/dist/gameplay.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as JsonValue } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { T as Node, l as PropSchema, n as BehaviorCtor, t as Behavior } from "./behavior-
|
|
2
|
+
import { T as Node, l as PropSchema, n as BehaviorCtor, t as Behavior, v as Engine } from "./behavior-CWhW3oa6.js";
|
|
3
3
|
|
|
4
4
|
//#region src/gameplay/chase.d.ts
|
|
5
5
|
/**
|
|
@@ -154,6 +154,11 @@ declare class FollowCamera extends Behavior {
|
|
|
154
154
|
smoothing: number;
|
|
155
155
|
deadzone: number;
|
|
156
156
|
override onReady(): void;
|
|
157
|
+
private shakeMag;
|
|
158
|
+
private shakeT;
|
|
159
|
+
private shakeFalloff;
|
|
160
|
+
/** Kick the camera (impacts, explosions). Composes with the follow. */
|
|
161
|
+
shake(magnitude: number, seconds?: number): void;
|
|
157
162
|
override update(dt: number): void;
|
|
158
163
|
}
|
|
159
164
|
//#endregion
|
|
@@ -528,6 +533,112 @@ declare class ZombieAI extends Behavior {
|
|
|
528
533
|
private pickGoal;
|
|
529
534
|
}
|
|
530
535
|
//#endregion
|
|
536
|
+
//#region src/gameplay/game-flow.d.ts
|
|
537
|
+
/**
|
|
538
|
+
* Reload the CURRENT scene from its source JSON — fresh nodes, reset physics,
|
|
539
|
+
* rewired input. The restart primitive every game-over screen wants.
|
|
540
|
+
*/
|
|
541
|
+
declare function restartScene(engine: Engine): void;
|
|
542
|
+
type GameFlowState = "playing" | "paused" | "gameover" | "won";
|
|
543
|
+
/**
|
|
544
|
+
* The win/lose/restart state machine 6+ examples hand-rolled as `over`/`win`
|
|
545
|
+
* booleans. Attach to any node (the scene root is natural):
|
|
546
|
+
*
|
|
547
|
+
* const flow = root.behavior as GameFlow; // script: { "name": "GameFlow" }
|
|
548
|
+
* flow.gameOver('YOU DIED'); // freezes time, sticky banner
|
|
549
|
+
* flow.win('AREA CLEAR');
|
|
550
|
+
* flow.pause(); flow.resume();
|
|
551
|
+
*
|
|
552
|
+
* While in `gameover`/`won`, pressing `restartAction` (default action name
|
|
553
|
+
* 'restart' — declare it in the scene input map, or leave undeclared and call
|
|
554
|
+
* `flow.restart()` yourself) reloads the scene from source. Banners render
|
|
555
|
+
* through a `%Banner` UiBanner when one exists; otherwise states are silent
|
|
556
|
+
* (drive your own UI off the `flowChanged` signal).
|
|
557
|
+
*/
|
|
558
|
+
declare class GameFlow extends Behavior {
|
|
559
|
+
static readonly signals: readonly string[];
|
|
560
|
+
static readonly props: Record<string, {
|
|
561
|
+
default: string | boolean;
|
|
562
|
+
}>;
|
|
563
|
+
/** Input action that restarts from gameover/won (declare it in `input{}`). */
|
|
564
|
+
restartAction: string;
|
|
565
|
+
/** Freeze `engine.timeScale` on gameover/won (banner UI keeps rendering). */
|
|
566
|
+
freezeOnEnd: boolean;
|
|
567
|
+
/** Where the flow looks for a UiBanner ('' = never). */
|
|
568
|
+
bannerPath: string;
|
|
569
|
+
state: GameFlowState;
|
|
570
|
+
private frozeScale;
|
|
571
|
+
gameOver(text?: string, color?: string): void;
|
|
572
|
+
win(text?: string, color?: string): void;
|
|
573
|
+
pause(): void;
|
|
574
|
+
resume(): void;
|
|
575
|
+
restart(): void;
|
|
576
|
+
private transition;
|
|
577
|
+
private freeze;
|
|
578
|
+
private thaw;
|
|
579
|
+
override update(): void;
|
|
580
|
+
}
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/gameplay/juice.d.ts
|
|
583
|
+
/**
|
|
584
|
+
* Game-feel primitives ("juice"): the weapon cooldown, camera shake, screen
|
|
585
|
+
* flash and hit-stop that action examples kept hand-rolling.
|
|
586
|
+
*/
|
|
587
|
+
/**
|
|
588
|
+
* A fire-rate / ability cooldown — replaces the copy-pasted
|
|
589
|
+
* `this.clock += dt * 1000; if (now > nextFire)` pattern:
|
|
590
|
+
*
|
|
591
|
+
* private gun = new Cooldown(0.2);
|
|
592
|
+
* override update(dt: number): void {
|
|
593
|
+
* this.gun.tick(dt);
|
|
594
|
+
* if (this.engine.input.isPressed('fire') && this.gun.tryUse()) this.shoot();
|
|
595
|
+
* }
|
|
596
|
+
*/
|
|
597
|
+
declare class Cooldown {
|
|
598
|
+
readonly seconds: number;
|
|
599
|
+
private remaining;
|
|
600
|
+
constructor(seconds: number);
|
|
601
|
+
/** Advance time. Call once per update with the frame dt. */
|
|
602
|
+
tick(dt: number): void;
|
|
603
|
+
get ready(): boolean;
|
|
604
|
+
/** 0..1 — how far through the cooldown we are (1 = ready). UiBar-friendly. */
|
|
605
|
+
get progress(): number;
|
|
606
|
+
/** Consume if ready. Returns whether the action should fire. */
|
|
607
|
+
tryUse(): boolean;
|
|
608
|
+
/** Force-ready (pickups that reset your reload). */
|
|
609
|
+
reset(): void;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Camera shake as a standalone behavior for cameras WITHOUT another script.
|
|
613
|
+
* (FollowCamera has this built in — one script per node.) Composes with any
|
|
614
|
+
* other position writer by applying only the DELTA of its own offset, so the
|
|
615
|
+
* camera returns exactly to where the other writer left it.
|
|
616
|
+
*
|
|
617
|
+
* (camera.behavior as CameraShake).shake(8); // pixels (2D) / meters·100 feel (3D: use ~0.2)
|
|
618
|
+
*/
|
|
619
|
+
declare class CameraShake extends Behavior {
|
|
620
|
+
static readonly props: Record<string, {
|
|
621
|
+
default: number;
|
|
622
|
+
}>;
|
|
623
|
+
/** Seconds a shake takes to decay to zero. */
|
|
624
|
+
falloff: number;
|
|
625
|
+
private magnitude;
|
|
626
|
+
private t;
|
|
627
|
+
private prev;
|
|
628
|
+
shake(magnitude: number, seconds?: number): void;
|
|
629
|
+
override update(dt: number): void;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Full-screen color flash (damage red, pickup white). DOM overlay above
|
|
633
|
+
* everything; headless no-op. Repeated calls restart the fade.
|
|
634
|
+
*/
|
|
635
|
+
declare function screenFlash(color?: string, opacity?: number, seconds?: number): void;
|
|
636
|
+
/**
|
|
637
|
+
* Hit-stop: freeze game time for `seconds` of REAL time, then restore the
|
|
638
|
+
* previous timeScale. Stacking calls extend the freeze instead of fighting.
|
|
639
|
+
*/
|
|
640
|
+
declare function hitStop(engine: Engine, seconds?: number): void;
|
|
641
|
+
//#endregion
|
|
531
642
|
//#region src/gameplay/index.d.ts
|
|
532
643
|
/** Every built-in gameplay behavior, keyed by its registration name. */
|
|
533
644
|
declare const GAMEPLAY_BEHAVIORS: Readonly<Record<string, BehaviorCtor>>;
|
|
@@ -540,4 +651,4 @@ declare function registerGameplayBehaviors(opts?: {
|
|
|
540
651
|
replace?: boolean;
|
|
541
652
|
}): void;
|
|
542
653
|
//#endregion
|
|
543
|
-
export { Chase, Collector, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, registerGameplayBehaviors };
|
|
654
|
+
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, type GameFlowState, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, hitStop, registerGameplayBehaviors, restartScene, screenFlash };
|
package/dist/gameplay.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { Chase, Collector, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, registerGameplayBehaviors };
|
|
1
|
+
import { C as Health, S as DamageOnContact, T as Chase, _ as screenFlash, a as Wander, b as restartScene, c as Projectile, d as Oscillate, f as MoveTo, g as hitStop, h as Cooldown, i as WaveSpawner, l as Pickup, m as CameraShake, n as registerGameplayBehaviors, o as Spawner, p as Lifetime, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as Interactable, w as Collector, x as FollowCamera, y as GameFlow } from "./gameplay-CDFgSG6z.js";
|
|
2
|
+
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, hitStop, registerGameplayBehaviors, restartScene, screenFlash };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as Rng, c as JsonValue, d as jsonKind, i as SceneJson, l as jsonClone, n as NodeJson, o as JsonKind, r as SCENE_FORMAT, s as JsonObject, t as ConnectionJson, u as jsonEquals } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { $ as BusName, A as LogManager, B as spatialGain, C as RendererStats, D as SceneTree, E as NodeLifecycle, F as Listener, G as SfxWave, H as SFX_PRESETS, I as ROLLOFF_MODELS, J as MusicBackend, K as SynthOptions, L as RolloffModel, M as SfxEngine, N as SfxPlayOptions, O as LogEntry, P as isAudioContextAvailable, Q as AudioBuses, R as SpatialParams, S as GameStats, T as Node, U as SFX_PRESET_NAMES, V as spatialPan, W as SfxParams, X as MusicTrack, Y as MusicManager, Z as PlayMusicOptions, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as Signal, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as InputMap, k as LogLevel, l as PropSchema, m as getNodeType, n as BehaviorCtor, o as registeredBehaviors, p as getNodeSignals, q as synthSfx, r as clearBehaviors, s as NodeCtor, t as Behavior, tt as SignalListener, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as Vec3 } from "./behavior-
|
|
3
|
-
import { n as loadScene, t as LoadSceneOptions } from "./loader-
|
|
4
|
-
import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-
|
|
5
|
-
import { n as AudioPlayer, t as AudioElementLike } from "./audio-player-
|
|
2
|
+
import { $ as BusName, A as LogManager, B as spatialGain, C as RendererStats, D as SceneTree, E as NodeLifecycle, F as Listener, G as SfxWave, H as SFX_PRESETS, I as ROLLOFF_MODELS, J as MusicBackend, K as SynthOptions, L as RolloffModel, M as SfxEngine, N as SfxPlayOptions, O as LogEntry, P as isAudioContextAvailable, Q as AudioBuses, R as SpatialParams, S as GameStats, T as Node, U as SFX_PRESET_NAMES, V as spatialPan, W as SfxParams, X as MusicTrack, Y as MusicManager, Z as PlayMusicOptions, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as Signal, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as InputMap, k as LogLevel, l as PropSchema, m as getNodeType, n as BehaviorCtor, o as registeredBehaviors, p as getNodeSignals, q as synthSfx, r as clearBehaviors, s as NodeCtor, t as Behavior, tt as SignalListener, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as Vec3 } from "./behavior-CWhW3oa6.js";
|
|
3
|
+
import { n as loadScene, t as LoadSceneOptions } from "./loader-D2u0fVW2.js";
|
|
4
|
+
import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
|
|
5
|
+
import { n as AudioPlayer, t as AudioElementLike } from "./audio-player-DkBqRTs4.js";
|
|
6
6
|
import { n as IncantoErrorCode, r as IncantoErrorDetails, t as IncantoError } from "./errors-BMFaY68Q.js";
|
|
7
7
|
|
|
8
8
|
//#region src/core/audio/crossfade.d.ts
|
|
@@ -72,6 +72,121 @@ type ParsedNodePath = {
|
|
|
72
72
|
};
|
|
73
73
|
declare function parseNodePath(path: string): ParsedNodePath;
|
|
74
74
|
//#endregion
|
|
75
|
+
//#region src/core/nodes/hud.d.ts
|
|
76
|
+
/**
|
|
77
|
+
* DOM-backed HUD widgets — the layer 18 of 22 example games hand-rolled.
|
|
78
|
+
*
|
|
79
|
+
* These are CORE nodes (no three.js): they render into a fixed, pointer-
|
|
80
|
+
* transparent overlay ABOVE the canvas, so the same JSON works over the 2D
|
|
81
|
+
* and 3D renderers alike. Headless (no `document`) every widget is a silent
|
|
82
|
+
* no-op — scenes stay fully testable in plain node.
|
|
83
|
+
*
|
|
84
|
+
* { "name": "HUD", "type": "HudLayer", "children": [
|
|
85
|
+
* { "name": "Health", "type": "UiBar",
|
|
86
|
+
* "props": { "anchor": "topLeft", "value": 100, "max": 100, "label": "HP" } },
|
|
87
|
+
* { "name": "Score", "type": "UiText",
|
|
88
|
+
* "props": { "anchor": "topRight", "text": "0", "size": 22 } },
|
|
89
|
+
* { "name": "Banner", "type": "UiBanner" }
|
|
90
|
+
* ]}
|
|
91
|
+
*
|
|
92
|
+
* Behaviors talk to them like any node:
|
|
93
|
+
* (this.node.getNode('%Score') as UiText).text = `${score}`;
|
|
94
|
+
* (this.node.getNode('%Banner') as UiBanner).show('WAVE 2', { color: '#f66' });
|
|
95
|
+
*/
|
|
96
|
+
type HudAnchor = "topLeft" | "top" | "topRight" | "left" | "center" | "right" | "bottomLeft" | "bottom" | "bottomRight";
|
|
97
|
+
/**
|
|
98
|
+
* The overlay container. One per scene is plenty; widgets mount into its
|
|
99
|
+
* anchor slots. `zIndex` lifts it above game canvases; the layer never eats
|
|
100
|
+
* pointer events (widgets that need clicks opt in individually).
|
|
101
|
+
*/
|
|
102
|
+
declare class HudLayer extends Node {
|
|
103
|
+
static override readonly typeName: string;
|
|
104
|
+
static readonly props: PropSchema;
|
|
105
|
+
zIndex: number;
|
|
106
|
+
visible: boolean;
|
|
107
|
+
/** @internal root overlay element (null headless). */
|
|
108
|
+
_element: HTMLElement | null;
|
|
109
|
+
private readonly slots;
|
|
110
|
+
override onEnterTree(): void;
|
|
111
|
+
override onExitTree(): void;
|
|
112
|
+
override update(): void;
|
|
113
|
+
/** @internal Widgets mount into per-anchor flex columns. */
|
|
114
|
+
_slot(anchor: HudAnchor): HTMLElement | null;
|
|
115
|
+
}
|
|
116
|
+
/** Shared plumbing: mount into the parent HudLayer's anchor slot. */
|
|
117
|
+
declare abstract class HudWidget extends Node {
|
|
118
|
+
static readonly props: PropSchema;
|
|
119
|
+
anchor: HudAnchor;
|
|
120
|
+
visible: boolean;
|
|
121
|
+
/** @internal */
|
|
122
|
+
_element: HTMLElement | null;
|
|
123
|
+
protected abstract _build(): HTMLElement;
|
|
124
|
+
private layer;
|
|
125
|
+
override onReady(): void;
|
|
126
|
+
override onExitTree(): void;
|
|
127
|
+
override update(_dt: number): void;
|
|
128
|
+
protected _sync(): void;
|
|
129
|
+
}
|
|
130
|
+
/** A text line (score, timer, hints). Set `.text` from behaviors. */
|
|
131
|
+
declare class UiText extends HudWidget {
|
|
132
|
+
static override readonly typeName: string;
|
|
133
|
+
static override readonly props: PropSchema;
|
|
134
|
+
text: string;
|
|
135
|
+
size: number;
|
|
136
|
+
color: string;
|
|
137
|
+
shadow: boolean;
|
|
138
|
+
private last;
|
|
139
|
+
protected _build(): HTMLElement;
|
|
140
|
+
protected override _sync(): void;
|
|
141
|
+
}
|
|
142
|
+
/** A labeled progress bar (health, stamina, reload, boss HP). */
|
|
143
|
+
declare class UiBar extends HudWidget {
|
|
144
|
+
static override readonly typeName: string;
|
|
145
|
+
static override readonly props: PropSchema;
|
|
146
|
+
value: number;
|
|
147
|
+
max: number;
|
|
148
|
+
width: number;
|
|
149
|
+
height: number;
|
|
150
|
+
color: string;
|
|
151
|
+
lowColor: string;
|
|
152
|
+
lowThreshold: number;
|
|
153
|
+
background: string;
|
|
154
|
+
label: string;
|
|
155
|
+
private fill;
|
|
156
|
+
private lastRatio;
|
|
157
|
+
/** Current fill ratio 0..1 (what the bar shows). */
|
|
158
|
+
get ratio(): number;
|
|
159
|
+
protected _build(): HTMLElement;
|
|
160
|
+
protected override _sync(): void;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Center-screen announcements ("WAVE 2", "YOU DIED", "LEVEL UP") with fade
|
|
164
|
+
* in/out and a queue — call `.show(text, { color, seconds })`; `sticky:true`
|
|
165
|
+
* (seconds: 0) keeps it until the next show(). Emits `bannerShown(text)`.
|
|
166
|
+
*/
|
|
167
|
+
declare class UiBanner extends HudWidget {
|
|
168
|
+
static override readonly typeName: string;
|
|
169
|
+
static readonly signals: string[];
|
|
170
|
+
static override readonly props: PropSchema;
|
|
171
|
+
override anchor: HudAnchor;
|
|
172
|
+
size: number;
|
|
173
|
+
/** Default display time (per-show override via options). */
|
|
174
|
+
seconds: number;
|
|
175
|
+
private queue;
|
|
176
|
+
private current;
|
|
177
|
+
private remaining;
|
|
178
|
+
/** What the banner is showing right now ('' when idle) — test-friendly. */
|
|
179
|
+
get showing(): string;
|
|
180
|
+
show(text: string, opts?: {
|
|
181
|
+
color?: string;
|
|
182
|
+
seconds?: number;
|
|
183
|
+
}): void;
|
|
184
|
+
/** Drop everything (scene transitions). */
|
|
185
|
+
clear(): void;
|
|
186
|
+
protected _build(): HTMLElement;
|
|
187
|
+
override update(dt: number): void;
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
75
190
|
//#region src/core/nodes/timer.d.ts
|
|
76
191
|
/**
|
|
77
192
|
* The canonical serializable game clock — never `setTimeout` in game logic.
|
|
@@ -178,6 +293,25 @@ declare function resolveRendering(environment: JsonObject | undefined, fallback:
|
|
|
178
293
|
pixelRatio?: number;
|
|
179
294
|
}): ResolvedRendering;
|
|
180
295
|
//#endregion
|
|
296
|
+
//#region src/core/save.d.ts
|
|
297
|
+
/**
|
|
298
|
+
* Namespaced game persistence — high scores, unlocks, settings. localStorage
|
|
299
|
+
* in the browser, in-memory headless (tests and SSR stay green). Values are
|
|
300
|
+
* JSON round-tripped, so what you get back is what you saved.
|
|
301
|
+
*
|
|
302
|
+
* const save = createSaveStore('my-game');
|
|
303
|
+
* save.set('highScore', 4200);
|
|
304
|
+
* const best = save.get('highScore', 0);
|
|
305
|
+
*/
|
|
306
|
+
interface SaveStore {
|
|
307
|
+
get<T extends JsonValue>(key: string, fallback: T): T;
|
|
308
|
+
set(key: string, value: JsonValue): void;
|
|
309
|
+
remove(key: string): void;
|
|
310
|
+
/** Wipe THIS namespace only. */
|
|
311
|
+
clear(): void;
|
|
312
|
+
}
|
|
313
|
+
declare function createSaveStore(namespace: string): SaveStore;
|
|
314
|
+
//#endregion
|
|
181
315
|
//#region src/core/scene/constants.d.ts
|
|
182
316
|
/** The single reserved key marking a prop value as a constant reference. */
|
|
183
317
|
declare const CONST_REF_KEY = "@const";
|
|
@@ -302,4 +436,4 @@ declare function computeViewport(canvasW: number, canvasH: number, viewport: {
|
|
|
302
436
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
303
437
|
declare const VERSION: string;
|
|
304
438
|
//#endregion
|
|
305
|
-
export { AudioBuses, type AudioElementLike, AudioPlayer, Behavior, type BehaviorCtor, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, Engine, type EngineOptions, type EngineStats, type GameStats, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LoadSceneOptions, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, ROLLOFF_MODELS, type RendererStats, type ResolvedRendering, type ResolvedViewport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, type SceneJson, SceneTree, type Scheduler, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, Timer, TouchControls, type TouchControlsOptions, VERSION, type Vec3, type ViewportFit, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, crossfadeGains, duplicateNode, fadeGain, getBehavior, getNodeSchema, getNodeSignals, getNodeType, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, synthSfx };
|
|
439
|
+
export { AudioBuses, type AudioElementLike, AudioPlayer, Behavior, type BehaviorCtor, type BusName, CONST_REF_KEY, type ComputedViewport, type ConnectionJson, type CrossfadeGains, type DebugLineSource, Engine, type EngineOptions, type EngineStats, type GameStats, type HudAnchor, HudLayer, IncantoError, type IncantoErrorCode, type IncantoErrorDetails, InputMap, type JsonKind, type JsonObject, type JsonValue, type Listener, type LoadSceneOptions, type LogEntry, type LogLevel, LogManager, type MusicBackend, MusicManager, type MusicTrack, Node, type NodeCtor, type NodeJson, type NodeLifecycle, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, type ParsedNodePath, type ParticlePresetValues, ParticleSim, type ParticleSimConfig, type ParticleView, type PlayMusicOptions, type PreloadResult, type PropDef, type PropSchema, ROLLOFF_MODELS, type RendererStats, type ResolvedRendering, type ResolvedViewport, Rng, type RolloffModel, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, type SaveStore, Scene, type SceneJson, SceneTree, type Scheduler, SfxEngine, type SfxParams, type SfxPlayOptions, type SfxWave, Signal, type SignalListener, type SpatialParams, type SynthOptions, Timer, TouchControls, type TouchControlsOptions, UiBanner, UiBar, UiText, VERSION, type Vec3, type ViewportFit, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, fadeGain, getBehavior, getNodeSchema, getNodeSignals, getNodeType, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, synthSfx };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { _ as registerBehavior, a as SCENE_FORMAT, c as SceneTree, d as resolveConstants, f as Node, g as getBehavior, h as clearBehaviors, i as serializeNode, l as CONST_REF_KEY, m as Behavior, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as registeredBehaviors, y as Signal } from "./loader-
|
|
2
|
-
import { a as Engine, c as SfxEngine, d as WebAudioMusicBackend, f as crossfadeGains, i as applyParticlePreset, l as isAudioContextAvailable, m as AudioBuses, n as PARTICLE_PRESETS, o as LogManager, p as fadeGain, r as PARTICLE_PRESET_NAMES, s as InputMap, t as ParticleSim, u as MusicManager } from "./particle-sim-
|
|
1
|
+
import { _ as registerBehavior, a as SCENE_FORMAT, c as SceneTree, d as resolveConstants, f as Node, g as getBehavior, h as clearBehaviors, i as serializeNode, l as CONST_REF_KEY, m as Behavior, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as registeredBehaviors, y as Signal } from "./loader-B4OEXDZ8.js";
|
|
2
|
+
import { a as Engine, c as SfxEngine, d as WebAudioMusicBackend, f as crossfadeGains, i as applyParticlePreset, l as isAudioContextAvailable, m as AudioBuses, n as PARTICLE_PRESETS, o as LogManager, p as fadeGain, r as PARTICLE_PRESET_NAMES, s as InputMap, t as ParticleSim, u as MusicManager } from "./particle-sim-CFkILGwh.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
|
-
import { a as
|
|
5
|
+
import { a as UiBar, c as ROLLOFF_MODELS, d as SFX_PRESETS, f as SFX_PRESET_NAMES, i as UiBanner, l as spatialGain, n as Timer, o as UiText, p as synthSfx, r as HudLayer, s as AudioPlayer, t as registerCoreNodes, u as spatialPan } from "./register-Dl_ixIJe.js";
|
|
6
6
|
import { n as jsonEquals, r as jsonKind, t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
7
7
|
import { a as getNodeSignals, c as mergeStaticSignals, i as getNodeSchema, l as registerNode, n as clearRegistry, o as getNodeType, r as createNode, u as registeredTypes } from "./registry-BVJ2HbCn.js";
|
|
8
8
|
import { t as createNoise2D } from "./noise-CGUMx44x.js";
|
|
9
9
|
import { i as resolveRendering, n as attachTouchControls, r as joystickVector, t as TouchControls } from "./touch-031PxtCR.js";
|
|
10
|
-
import { t as duplicateNode } from "./duplicate-
|
|
10
|
+
import { t as duplicateNode } from "./duplicate-BEvGBtb_.js";
|
|
11
11
|
//#region src/core/preload.ts
|
|
12
12
|
/**
|
|
13
13
|
* Warm the HTTP cache for a list of asset urls with a progress callback —
|
|
@@ -40,6 +40,59 @@ function assetUrls(assets) {
|
|
|
40
40
|
return Object.values(assets ?? {}).map((a) => a.url).filter((u) => typeof u === "string" && !u.startsWith("data:"));
|
|
41
41
|
}
|
|
42
42
|
//#endregion
|
|
43
|
+
//#region src/core/save.ts
|
|
44
|
+
const memoryStores = /* @__PURE__ */ new Map();
|
|
45
|
+
function createSaveStore(namespace) {
|
|
46
|
+
const prefix = `incanto:${namespace}:`;
|
|
47
|
+
const local = storageOrNull();
|
|
48
|
+
if (!local) {
|
|
49
|
+
let mem = memoryStores.get(namespace);
|
|
50
|
+
if (!mem) {
|
|
51
|
+
mem = /* @__PURE__ */ new Map();
|
|
52
|
+
memoryStores.set(namespace, mem);
|
|
53
|
+
}
|
|
54
|
+
const backing = mem;
|
|
55
|
+
return {
|
|
56
|
+
get: (key, fallback) => parse(backing.get(key), fallback),
|
|
57
|
+
set: (key, value) => void backing.set(key, JSON.stringify(value)),
|
|
58
|
+
remove: (key) => void backing.delete(key),
|
|
59
|
+
clear: () => backing.clear()
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
get: (key, fallback) => parse(local.getItem(prefix + key) ?? void 0, fallback),
|
|
64
|
+
set: (key, value) => local.setItem(prefix + key, JSON.stringify(value)),
|
|
65
|
+
remove: (key) => local.removeItem(prefix + key),
|
|
66
|
+
clear: () => {
|
|
67
|
+
const doomed = [];
|
|
68
|
+
for (let i = 0; i < local.length; i++) {
|
|
69
|
+
const k = local.key(i);
|
|
70
|
+
if (k?.startsWith(prefix)) doomed.push(k);
|
|
71
|
+
}
|
|
72
|
+
for (const k of doomed) local.removeItem(k);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function parse(raw, fallback) {
|
|
77
|
+
if (raw === void 0 || raw === null) return fallback;
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(raw);
|
|
80
|
+
} catch {
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function storageOrNull() {
|
|
85
|
+
try {
|
|
86
|
+
if (typeof localStorage === "undefined") return null;
|
|
87
|
+
const probe = "__incanto_probe__";
|
|
88
|
+
localStorage.setItem(probe, "1");
|
|
89
|
+
localStorage.removeItem(probe);
|
|
90
|
+
return localStorage;
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
43
96
|
//#region src/core/uid-gen.ts
|
|
44
97
|
/**
|
|
45
98
|
* The ONE way to mint a node uid: crypto-strength, `n_` + 16 base36 chars
|
|
@@ -57,6 +110,6 @@ function newUid() {
|
|
|
57
110
|
//#endregion
|
|
58
111
|
//#region src/index.ts
|
|
59
112
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
60
|
-
const VERSION = "0.
|
|
113
|
+
const VERSION = "0.5.0";
|
|
61
114
|
//#endregion
|
|
62
|
-
export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, IncantoError, InputMap, LogManager, MusicManager, Node, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, crossfadeGains, duplicateNode, fadeGain, getBehavior, getNodeSchema, getNodeSignals, getNodeType, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, synthSfx };
|
|
115
|
+
export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, fadeGain, getBehavior, getNodeSchema, getNodeSignals, getNodeType, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, synthSfx };
|
|
@@ -192,8 +192,13 @@ var Node = class {
|
|
|
192
192
|
}
|
|
193
193
|
set name(value) {
|
|
194
194
|
validateName(value);
|
|
195
|
+
const old = this._name;
|
|
195
196
|
if (this._parent) this._name = uniqueSiblingName(value, this._parent._children, this);
|
|
196
197
|
else this._name = value;
|
|
198
|
+
if (this._tree && this._name !== old) {
|
|
199
|
+
this._tree._unindexName(this, old);
|
|
200
|
+
this._tree._indexName(this);
|
|
201
|
+
}
|
|
197
202
|
}
|
|
198
203
|
get parent() {
|
|
199
204
|
return this._parent;
|
|
@@ -260,6 +265,10 @@ var Node = class {
|
|
|
260
265
|
}
|
|
261
266
|
/** Every node in THIS subtree named `name` — names repeat, so a list. */
|
|
262
267
|
getNodesByName(name) {
|
|
268
|
+
if (this._tree && this._parent === null) {
|
|
269
|
+
const set = this._tree._nodesNamed(name);
|
|
270
|
+
return set ? [...set] : [];
|
|
271
|
+
}
|
|
263
272
|
const out = [];
|
|
264
273
|
const walk = (node) => {
|
|
265
274
|
if (node.name === name) out.push(node);
|
|
@@ -284,6 +293,17 @@ var Node = class {
|
|
|
284
293
|
resolve(path) {
|
|
285
294
|
const parsed = parseNodePath(path);
|
|
286
295
|
if (parsed.kind === "unique") {
|
|
296
|
+
const tree = this._tree;
|
|
297
|
+
if (tree) {
|
|
298
|
+
const set = tree._nodesNamed(parsed.name);
|
|
299
|
+
if (!set || set.size === 0) return null;
|
|
300
|
+
if (set.size > 1) {
|
|
301
|
+
const paths = [];
|
|
302
|
+
for (const m of set) paths.push(m.getPath());
|
|
303
|
+
throw new IncantoError("DUPLICATE_UNIQUE_NAME", `'%${parsed.name}' is ambiguous: ${set.size} nodes named '${parsed.name}' (${paths.join(", ")}).`);
|
|
304
|
+
}
|
|
305
|
+
for (const m of set) return m;
|
|
306
|
+
}
|
|
287
307
|
const matches = [];
|
|
288
308
|
collectByName(this.getRoot(), parsed.name, matches);
|
|
289
309
|
if (matches.length > 1) throw new IncantoError("DUPLICATE_UNIQUE_NAME", `'%${parsed.name}' is ambiguous: ${matches.length} nodes named '${parsed.name}' (${matches.map((m) => m.getPath()).join(", ")}).`);
|
|
@@ -388,6 +408,7 @@ var Node = class {
|
|
|
388
408
|
/** @internal */
|
|
389
409
|
_propagateEnterTree(tree) {
|
|
390
410
|
this._tree = tree;
|
|
411
|
+
tree._indexName(this);
|
|
391
412
|
this.onEnterTree();
|
|
392
413
|
this.behavior?.onEnterTree?.();
|
|
393
414
|
for (const c of [...this._children]) if (c._tree !== tree) c._propagateEnterTree(tree);
|
|
@@ -406,6 +427,7 @@ var Node = class {
|
|
|
406
427
|
for (const c of [...this._children]) c._propagateExitTree();
|
|
407
428
|
this.onExitTree();
|
|
408
429
|
this.behavior?.onExitTree?.();
|
|
430
|
+
this._tree?._unindexName(this);
|
|
409
431
|
this._tree = null;
|
|
410
432
|
}
|
|
411
433
|
/** @internal */
|
|
@@ -507,6 +529,18 @@ var SceneTree = class {
|
|
|
507
529
|
_root = null;
|
|
508
530
|
_engine = null;
|
|
509
531
|
freeQueue = /* @__PURE__ */ new Set();
|
|
532
|
+
/**
|
|
533
|
+
* name → attached nodes with that name. Maintained by Node enter/exit/rename
|
|
534
|
+
* so `%name` targeting and root-level getNodesByName are O(1) — behaviors
|
|
535
|
+
* resolve targets every frame and a full-tree walk per chaser melted horde
|
|
536
|
+
* scenes (N chasers × whole tree, 60×/s).
|
|
537
|
+
*/
|
|
538
|
+
_nameIndex = /* @__PURE__ */ new Map();
|
|
539
|
+
/**
|
|
540
|
+
* Bumped whenever ANY node attaches or detaches — cheap "did the tree
|
|
541
|
+
* change shape?" check for per-step body gathers and similar caches.
|
|
542
|
+
*/
|
|
543
|
+
_structureVersion = 0;
|
|
510
544
|
_frameId = 0;
|
|
511
545
|
/**
|
|
512
546
|
* Monotonic update-frame counter — bumped once per `update()`. Per-frame
|
|
@@ -516,6 +550,29 @@ var SceneTree = class {
|
|
|
516
550
|
get frameId() {
|
|
517
551
|
return this._frameId;
|
|
518
552
|
}
|
|
553
|
+
/** @internal Node attach hook. */
|
|
554
|
+
_indexName(node) {
|
|
555
|
+
this._structureVersion++;
|
|
556
|
+
let set = this._nameIndex.get(node.name);
|
|
557
|
+
if (!set) {
|
|
558
|
+
set = /* @__PURE__ */ new Set();
|
|
559
|
+
this._nameIndex.set(node.name, set);
|
|
560
|
+
}
|
|
561
|
+
set.add(node);
|
|
562
|
+
}
|
|
563
|
+
/** @internal Node detach/rename hook (`name` = the entry to remove). */
|
|
564
|
+
_unindexName(node, name = node.name) {
|
|
565
|
+
this._structureVersion++;
|
|
566
|
+
const set = this._nameIndex.get(name);
|
|
567
|
+
if (set) {
|
|
568
|
+
set.delete(node);
|
|
569
|
+
if (set.size === 0) this._nameIndex.delete(name);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/** @internal Every ATTACHED node named `name` (undefined = none). */
|
|
573
|
+
_nodesNamed(name) {
|
|
574
|
+
return this._nameIndex.get(name);
|
|
575
|
+
}
|
|
519
576
|
/**
|
|
520
577
|
* @internal Opaque per-frame scratch the 3d layer parks its shared moving-body
|
|
521
578
|
* snapshot on (kept here so it is per-tree-instance, never a module global —
|
|
@@ -709,7 +766,10 @@ var Scene = class {
|
|
|
709
766
|
/** Design-resolution viewport (consumed by renderers via resolveViewport). */
|
|
710
767
|
viewport;
|
|
711
768
|
connections;
|
|
769
|
+
/** The full source JSON this scene was loaded from — `restartScene` fuel. */
|
|
770
|
+
source;
|
|
712
771
|
constructor(source, root, tree) {
|
|
772
|
+
this.source = jsonClone(source);
|
|
713
773
|
this.name = source.name;
|
|
714
774
|
this.dimension = source.dimension;
|
|
715
775
|
this.root = root;
|
package/dist/net.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { T as Node, et as Signal, l as PropSchema, v as Engine } from "./behavior-
|
|
2
|
+
import { T as Node, et as Signal, l as PropSchema, v as Engine } from "./behavior-CWhW3oa6.js";
|
|
3
3
|
|
|
4
4
|
//#region src/net/types.d.ts
|
|
5
5
|
type Unsubscribe = () => void;
|
package/dist/net.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
2
|
import { t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
3
3
|
import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
|
|
4
|
-
import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-
|
|
4
|
+
import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-C35HDZpm.js";
|
|
5
5
|
//#region src/net/loopback.ts
|
|
6
6
|
/**
|
|
7
7
|
* In-memory multiplayer hub implementing the SAME kernel contract as
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { y as Signal } from "./loader-
|
|
1
|
+
import { y as Signal } from "./loader-B4OEXDZ8.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
3
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
4
|
-
import {
|
|
4
|
+
import { p as synthSfx } from "./register-Dl_ixIJe.js";
|
|
5
5
|
import { n as jsonEquals } from "./json-BLk7H2Qa.js";
|
|
6
6
|
//#region src/core/audio/buses.ts
|
|
7
7
|
/**
|
|
@@ -684,6 +684,75 @@ var InputMap = class {
|
|
|
684
684
|
* wheel → wheelDelta. `lockOnClick` requests pointer lock on mousedown
|
|
685
685
|
* (the FPS pattern).
|
|
686
686
|
*/
|
|
687
|
+
padAxesState = {
|
|
688
|
+
lx: 0,
|
|
689
|
+
ly: 0,
|
|
690
|
+
rx: 0,
|
|
691
|
+
ry: 0
|
|
692
|
+
};
|
|
693
|
+
padButtonsDown = 0;
|
|
694
|
+
/**
|
|
695
|
+
* Feed one polled gamepad snapshot (standard mapping). Buttons become
|
|
696
|
+
* codes `Pad0`..`Pad16` in the same space as keys — declare them in input
|
|
697
|
+
* actions like any key ("jump": ["Space", "Pad0"]). Axes land in
|
|
698
|
+
* `padAxes()` with a deadzone. Pass null when no pad is connected.
|
|
699
|
+
*/
|
|
700
|
+
pollGamepad(pad) {
|
|
701
|
+
const dead = .15;
|
|
702
|
+
const shape = (v) => Math.abs(v) < dead ? 0 : v;
|
|
703
|
+
if (!pad) {
|
|
704
|
+
for (let i = 0; i < 17; i++) if (this.padButtonsDown & 1 << i) this.handleKey(`Pad${i}`, false);
|
|
705
|
+
this.padButtonsDown = 0;
|
|
706
|
+
this.padAxesState.lx = 0;
|
|
707
|
+
this.padAxesState.ly = 0;
|
|
708
|
+
this.padAxesState.rx = 0;
|
|
709
|
+
this.padAxesState.ry = 0;
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
for (let i = 0; i < Math.min(17, pad.buttons.length); i++) {
|
|
713
|
+
const down = pad.buttons[i]?.pressed === true;
|
|
714
|
+
if (down !== ((this.padButtonsDown & 1 << i) !== 0)) {
|
|
715
|
+
this.handleKey(`Pad${i}`, down);
|
|
716
|
+
this.padButtonsDown = down ? this.padButtonsDown | 1 << i : this.padButtonsDown & ~(1 << i);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
this.padAxesState.lx = shape(pad.axes[0] ?? 0);
|
|
720
|
+
this.padAxesState.ly = shape(pad.axes[1] ?? 0);
|
|
721
|
+
this.padAxesState.rx = shape(pad.axes[2] ?? 0);
|
|
722
|
+
this.padAxesState.ry = shape(pad.axes[3] ?? 0);
|
|
723
|
+
}
|
|
724
|
+
/** Deadzoned analog sticks: 0 = left {x,y}, 1 = right. */
|
|
725
|
+
padAxes(stick = 0) {
|
|
726
|
+
return stick === 0 ? {
|
|
727
|
+
x: this.padAxesState.lx,
|
|
728
|
+
y: this.padAxesState.ly
|
|
729
|
+
} : {
|
|
730
|
+
x: this.padAxesState.rx,
|
|
731
|
+
y: this.padAxesState.ry
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Poll the browser Gamepad API every frame (the first connected pad).
|
|
736
|
+
* Wire once at boot: `engine.input.attachGamepad(engine)`. Headless no-op.
|
|
737
|
+
*/
|
|
738
|
+
attachGamepad(engine) {
|
|
739
|
+
if (typeof navigator === "undefined" || typeof navigator.getGamepads !== "function") return () => {};
|
|
740
|
+
const disconnect = engine.updated.connect(() => {
|
|
741
|
+
const pads = navigator.getGamepads();
|
|
742
|
+
let pad = null;
|
|
743
|
+
for (const p of pads) if (p?.connected) {
|
|
744
|
+
pad = p;
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
this.pollGamepad(pad);
|
|
748
|
+
});
|
|
749
|
+
const prev = this.detach;
|
|
750
|
+
this.detach = () => {
|
|
751
|
+
prev?.();
|
|
752
|
+
disconnect();
|
|
753
|
+
};
|
|
754
|
+
return disconnect;
|
|
755
|
+
}
|
|
687
756
|
attachPointer(target, opts) {
|
|
688
757
|
let buttonsHeld = 0;
|
|
689
758
|
const onDown = (e) => {
|
|
@@ -936,6 +1005,12 @@ var Engine = class {
|
|
|
936
1005
|
accumulator = 0;
|
|
937
1006
|
disposeScheduler = null;
|
|
938
1007
|
frameStats = new FrameStatsRing();
|
|
1008
|
+
/**
|
|
1009
|
+
* Game-time multiplier: 1 = realtime, 0.5 = slow motion, 0 = frozen
|
|
1010
|
+
* (hit-stop / pause). Scales BOTH variable and fixed updates — physics,
|
|
1011
|
+
* timers, behaviors all breathe together. See `gameplay` `hitStop()`.
|
|
1012
|
+
*/
|
|
1013
|
+
timeScale = 1;
|
|
939
1014
|
constructor(opts = {}) {
|
|
940
1015
|
this.fixedStep = 1 / (opts.fixedHz ?? 60);
|
|
941
1016
|
this.maxFixedSteps = opts.maxFixedStepsPerTick ?? 5;
|
|
@@ -1032,7 +1107,7 @@ var Engine = class {
|
|
|
1032
1107
|
this.lastMs = nowMs;
|
|
1033
1108
|
return;
|
|
1034
1109
|
}
|
|
1035
|
-
const dt = Math.min((nowMs - this.lastMs) / 1e3, MAX_DT_SECONDS);
|
|
1110
|
+
const dt = Math.min((nowMs - this.lastMs) / 1e3, MAX_DT_SECONDS) * this.timeScale;
|
|
1036
1111
|
this.lastMs = nowMs;
|
|
1037
1112
|
const scene = this._scene;
|
|
1038
1113
|
if (!scene) return;
|
|
@@ -1228,6 +1303,12 @@ var ParticleSim = class {
|
|
|
1228
1303
|
this.rng = rng;
|
|
1229
1304
|
this.data = new Float64Array(Math.max(1, config.maxParticles) * FIELDS);
|
|
1230
1305
|
}
|
|
1306
|
+
/** Change the emission rate LIVE (particles/sec; 0 pauses emission) — lets an
|
|
1307
|
+
* emitter toggle on/off at runtime (drift smoke, throttle flames). */
|
|
1308
|
+
setRate(rate) {
|
|
1309
|
+
this.config.rate = rate;
|
|
1310
|
+
if (rate <= 0) this.spawnAccumulator = 0;
|
|
1311
|
+
}
|
|
1231
1312
|
get count() {
|
|
1232
1313
|
return this.alive;
|
|
1233
1314
|
}
|