@cosmoledo/gleam 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/dist/gleam.d.ts +526 -62
- package/dist/gleam.esm.js +358 -81
- package/dist/gleam.esm.js.map +3 -3
- package/dist/gleam.js +358 -81
- package/dist/gleam.js.map +3 -3
- package/dist/gleam.min.js +7 -7
- package/dist/gleam.min.js.map +4 -4
- package/package.json +13 -11
package/dist/gleam.d.ts
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
|
+
/** Entry shape passed to {@link AudioBase.register}. */
|
|
3
4
|
export interface RegisterData {
|
|
5
|
+
/** URL or relative path to the audio file. */
|
|
4
6
|
path: string;
|
|
7
|
+
/** Identifier used by `play(name)` / `fade(name)`. */
|
|
5
8
|
name: string;
|
|
9
|
+
/** Per-song volume override in `[0, 1]`. Falls back to `register`'s `defaultVolume`. */
|
|
6
10
|
volume?: number;
|
|
7
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Shared registration, enable/disable, and teardown machinery for {@link Sound} and {@link Music}. Auto-subscribes to the `"gameloopStopped"` event so playback halts on loop teardown.
|
|
14
|
+
*/
|
|
8
15
|
export declare abstract class AudioBase {
|
|
16
|
+
/** Registered audio elements keyed by name. Subclasses read this; mutate via {@link register}. */
|
|
9
17
|
protected songs: Map<string, HTMLAudioElement>;
|
|
10
18
|
private _enabled;
|
|
11
19
|
private registered;
|
|
20
|
+
/** Whether playback is permitted. Setting to `false` invokes {@link stop} immediately. */
|
|
12
21
|
get enabled(): boolean;
|
|
22
|
+
/** Setting to `false` invokes {@link stop} immediately; subclasses (notably {@link Music}) may re-start playback when flipped back to `true`. */
|
|
13
23
|
set enabled(value: boolean);
|
|
14
24
|
constructor(enabled?: boolean);
|
|
25
|
+
/** Load and register one or more audio files. Each entry may be a bare URL string (the file's basename becomes the name) or a {@link RegisterData} object. Per-song volume falls back to `defaultVolume`. **Call once per instance** — throws on a second invocation, on non-finite volume, or on volume outside `[0, 1]`. Load failures are logged to `console.error` but don't throw. */
|
|
15
26
|
register(defaultVolume?: number, ...songs: (RegisterData | string)[]): void;
|
|
27
|
+
/** Base hook called when {@link enabled} flips to `false` and on `"gameloopStopped"`. The default is a no-op; {@link Sound} and {@link Music} override it to cut playback. Subclass overrides should call `super.stop()`. */
|
|
16
28
|
stop(): void;
|
|
17
29
|
private throwOnBadVolume;
|
|
18
30
|
}
|
|
@@ -32,64 +44,119 @@ export declare function easeOut(t: number): number;
|
|
|
32
44
|
* Identity (constant rate of change).
|
|
33
45
|
*/
|
|
34
46
|
export declare function linear(t: number): number;
|
|
47
|
+
/** Names of the built-in easing curves. Use as a key into {@link EASINGS}. */
|
|
35
48
|
export type EasingName = "ease-in" | "ease-in-out" | "ease-out" | "linear";
|
|
49
|
+
/** Lookup table from {@link EasingName} to its easing function (`t ∈ [0, 1] → eased t`). */
|
|
36
50
|
export declare const EASINGS: Record<EasingName, (t: number) => number>;
|
|
51
|
+
/**
|
|
52
|
+
* Background music with eased cross-fades. Tracks auto-cycle: when the current track ends, the next one fades in. Inherits registration, enable/disable, and volume from {@link AudioBase}.
|
|
53
|
+
*
|
|
54
|
+
* Random track picking excludes the previous two songs to avoid back-to-back repeats. If only one track is registered, it's looped instead of faded.
|
|
55
|
+
*/
|
|
37
56
|
export declare class Music extends AudioBase {
|
|
38
57
|
private last;
|
|
39
58
|
private current;
|
|
40
59
|
private next;
|
|
41
60
|
private fadeCancel;
|
|
61
|
+
/** `true` while a fade is in progress OR the current track is actively playing. */
|
|
42
62
|
get isPlaying(): boolean;
|
|
63
|
+
/** Whether music playback is permitted (inherited from {@link AudioBase}). */
|
|
43
64
|
get enabled(): boolean;
|
|
65
|
+
/** Flipping from `false` to `true` while no music is playing auto-starts a fade-in to a random track. */
|
|
44
66
|
set enabled(value: boolean);
|
|
67
|
+
/**
|
|
68
|
+
* Cross-fade to `name` (or a random unplayed track when `null`) over `fadeTime` ms. `easing.cur` controls the outgoing track's volume curve, `easing.next` the incoming one. Cancels any in-progress fade. No-op when disabled. Throws on `fadeTime <= 0`, an empty registry, or an unknown `name`. When the new track ends, the next fade fires automatically — call {@link stop} to break the cycle.
|
|
69
|
+
*/
|
|
45
70
|
fade(name?: string | null, fadeTime?: number, easing?: {
|
|
46
71
|
cur: EasingName;
|
|
47
72
|
next: EasingName;
|
|
48
73
|
}): void;
|
|
74
|
+
/** Stop everything immediately: cancels any in-flight fade, halts current and next tracks, and breaks the auto-cycle chain. Restart via {@link fade} or by flipping {@link enabled}. */
|
|
49
75
|
stop(): void;
|
|
50
76
|
private getRandom;
|
|
51
77
|
}
|
|
78
|
+
/** One-shot SFX. Each {@link play} call clones the registered `HTMLAudioElement` so the same sound can overlap itself; {@link stop} cuts every in-flight clone. Inherits registration, enable/disable, and volume from {@link AudioBase}. */
|
|
52
79
|
export declare class Sound extends AudioBase {
|
|
53
80
|
private currentSounds;
|
|
81
|
+
/** Play the registered sound `name` once. Returns a promise that resolves when playback starts (or immediately if `enabled` is `false`) and rejects on autoplay/permission errors. Throws synchronously if no sounds are registered or `name` is unknown. Each call allocates a clone, so concurrent plays of the same name overlap. */
|
|
54
82
|
play(name: string): Promise<void>;
|
|
83
|
+
/** Stop and forget every currently-playing clone. Also calls the base-class teardown. */
|
|
55
84
|
stop(): void;
|
|
56
85
|
}
|
|
86
|
+
/** Components returned by {@link Color.toHSLObject}. */
|
|
87
|
+
export interface HSLObject {
|
|
88
|
+
/** Hue in degrees, `[0, 360]`. */
|
|
89
|
+
h: number;
|
|
90
|
+
/** Saturation in percent, `[0, 100]`. */
|
|
91
|
+
s: number;
|
|
92
|
+
/** Lightness in percent, `[0, 100]`. */
|
|
93
|
+
l: number;
|
|
94
|
+
/** Alpha in `[0, 1]`. */
|
|
95
|
+
a: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* RGBA color with chainable mutators. Channels are stored clamped (`r/g/b` ∈ `[0, 255]`, `alpha` ∈ `[0, 1]`) — every mutator routes through {@link set}, so direct field writes aren't possible and clamping/rounding is uniform.
|
|
99
|
+
*
|
|
100
|
+
* **Hue unit gotcha**: {@link fromHSL} takes hue in **degrees** (CSS convention), but {@link hueRotate} takes **radians** (codebase convention). Use `Math.PI` etc. for hueRotate.
|
|
101
|
+
*
|
|
102
|
+
* All `to*` methods return CSS-compatible strings; `toHSLObject` returns the components as numbers if you need to mutate them.
|
|
103
|
+
*/
|
|
57
104
|
export declare class Color {
|
|
105
|
+
/** Parse `#rgb`, `#rgba`, `#rrggbb`, or `#rrggbbaa` (case-insensitive, `#` optional). Throws on any other shape or on non-hex characters. */
|
|
58
106
|
static fromHex(hex: string): Color;
|
|
107
|
+
/** Build from HSL(A). `h` in **degrees** (wraps mod 360), `s`/`l` in percent `[0, 100]`, `a` in `[0, 1]`. The degree convention matches CSS — note that {@link hueRotate} uses radians instead. */
|
|
59
108
|
static fromHSL(h: number, s: number, l: number, a?: number): Color;
|
|
60
109
|
private _r;
|
|
61
110
|
private _g;
|
|
62
111
|
private _b;
|
|
63
112
|
private _alpha;
|
|
113
|
+
/** Red channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */
|
|
64
114
|
get r(): number;
|
|
115
|
+
/** Green channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */
|
|
65
116
|
get g(): number;
|
|
117
|
+
/** Blue channel, `[0, 255]`. Read-only; mutate via {@link set} or any chainable transform. */
|
|
66
118
|
get b(): number;
|
|
119
|
+
/** Alpha channel, `[0, 1]`. Read-only; mutate via {@link set} (pass the fourth arg). */
|
|
67
120
|
get alpha(): number;
|
|
68
121
|
constructor(r: number, g: number, b: number, a?: number);
|
|
122
|
+
/** Primary mutator — every other transform on this class routes through it. Clamps `r`/`g`/`b` to `[0, 255]` and `a` to `[0, 1]`; alpha is snapped to exact `0` or `1` when within `approxEqual` tolerance so equality checks stay clean. Returns `this` for chaining. */
|
|
69
123
|
set(r: number, g: number, b: number, a?: number): this;
|
|
124
|
+
/** Apply a 3×3 RGB color matrix in row-major order (`m1..m9`). Alpha is unchanged. Used by {@link grayscale}, {@link hueRotate}, {@link saturate}, {@link sepia}. Mutates and returns `this`. */
|
|
70
125
|
applyMatrix(m1: number, m2: number, m3: number, m4: number, m5: number, m6: number, m7: number, m8: number, m9: number): this;
|
|
126
|
+
/** Multiply each channel by `factor`. `factor < 1` darkens, `factor > 1` brightens (clamped at 255). Mutates and returns `this`. */
|
|
71
127
|
brightness(factor: number): this;
|
|
128
|
+
/** Push each channel away from `127.5` (the midtone) by `factor`. `factor < 1` flattens contrast, `> 1` increases it, `0` collapses every channel to gray. Mutates and returns `this`. */
|
|
72
129
|
contrast(factor: number): this;
|
|
130
|
+
/** Desaturate via the standard luminance-preserving matrix. `value` in `[0, 1]`: `0` is a no-op, `1` is full grayscale. Mutates and returns `this`. */
|
|
73
131
|
grayscale(value?: number): this;
|
|
132
|
+
/** Rotate hue by `radians` (use `Math.PI / 2` etc.). Unlike {@link fromHSL}, this takes radians, not degrees. Mutates and returns `this`. */
|
|
74
133
|
hueRotate(radians: number): this;
|
|
134
|
+
/** Interpolate each channel toward its inverse (`255 - c`). `factor` in `[0, 1]`: `0` is unchanged, `1` is fully inverted. Mutates and returns `this`. */
|
|
75
135
|
invert(factor?: number): this;
|
|
136
|
+
/** Linear blend toward `other`. `amount` in `[0, 1]`: `0` keeps `this`, `1` becomes `other`. Mixes alpha too. Mutates and returns `this`. */
|
|
76
137
|
mix(other: Color, amount: number): this;
|
|
138
|
+
/** Round each RGB channel to the nearest integer. Alpha is untouched. Mutates and returns `this`. */
|
|
77
139
|
round(): this;
|
|
140
|
+
/** Saturation matrix. `value` typically in `[0, 2]`: `0` desaturates to grayscale (same as `grayscale(1)`), `1` is a no-op, `> 1` oversaturates. Mutates and returns `this`. */
|
|
78
141
|
saturate(value?: number): this;
|
|
142
|
+
/** Sepia matrix. `value` in `[0, 1]`: `0` is unchanged, `1` is full sepia. Mutates and returns `this`. */
|
|
79
143
|
sepia(value?: number): this;
|
|
144
|
+
/** Tint or shade. `percent` in `[-1, 1]`: negative shades toward black, positive tints toward white, magnitude is the amount. Mutates and returns `this`. */
|
|
80
145
|
shade(percent: number): this;
|
|
146
|
+
/** CSS hex string. `#rrggbb` when alpha is exactly `1`, `#rrggbbaa` otherwise. Channels are rounded. */
|
|
81
147
|
toHex(): string;
|
|
148
|
+
/** CSS HSL string. `hsl(h, s%, l%)` when alpha is exactly `1`, `hsla(...)` otherwise. Hue is in degrees. */
|
|
82
149
|
toHSL(): string;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
l: number;
|
|
87
|
-
a: number;
|
|
88
|
-
};
|
|
150
|
+
/** HSL(A) components as numbers — see {@link HSLObject}. Use when you need to compute against the values rather than render them as a string. */
|
|
151
|
+
toHSLObject(): HSLObject;
|
|
152
|
+
/** CSS RGB string. `rgb(r, g, b)` when alpha is exactly `1`, `rgba(r, g, b, a)` otherwise. Channels are rounded. */
|
|
89
153
|
toRGB(): string;
|
|
154
|
+
/** New `Color` with the same channels. */
|
|
90
155
|
clone(): Color;
|
|
156
|
+
/** Approximate equality (within `approxEqual` tolerance). Pass `compareAlpha: false` to ignore the alpha channel. */
|
|
91
157
|
equals(other: Color, compareAlpha?: boolean): boolean;
|
|
92
158
|
}
|
|
159
|
+
/** `[r, g, b]` tuple of integer channel values, each in `[0, 255]`. */
|
|
93
160
|
export type RGB = [
|
|
94
161
|
number,
|
|
95
162
|
number,
|
|
@@ -134,42 +201,69 @@ export declare function randomRgb(min?: number, max?: number): RGB;
|
|
|
134
201
|
* console.log(colorShifter(randomRgb(1, 10)));
|
|
135
202
|
*/
|
|
136
203
|
export declare function colorShifter(rgb: RGB): string;
|
|
204
|
+
/** Result of a {@link Polygon.collide} call. */
|
|
137
205
|
export interface PolygonCollisionResult {
|
|
206
|
+
/** `true` if the polygons currently overlap. */
|
|
138
207
|
intersect: boolean;
|
|
208
|
+
/** Smallest displacement that would separate the polygons (zero `Vec2` when they're disjoint). */
|
|
139
209
|
minimumTranslationVector: Vec2;
|
|
210
|
+
/** `true` if applying the `velocity` passed to `collide` would put the polygons into contact. */
|
|
140
211
|
willIntersect: boolean;
|
|
141
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Convex 2D polygon. **The collision API ({@link Polygon.collide}) uses SAT, which is mathematically defined only for convex polygons** — feeding it a concave polygon silently produces wrong results.
|
|
215
|
+
*/
|
|
142
216
|
export declare class Polygon {
|
|
143
217
|
/**
|
|
144
|
-
* `angle` is the simplification threshold in radians: vertices whose turn
|
|
145
|
-
*
|
|
218
|
+
* Trace an outline around the opaque pixels of `canvas` via four directional sweeps (top, right, bottom, left). `detail` is the pixel stride (≥ 2) between scanline samples — higher = faster but coarser. `angle` is the simplification threshold in radians: vertices whose turn angle wraps to within ±`angle` of straight are dropped. Throws if fewer than 3 vertices survive.
|
|
219
|
+
*
|
|
220
|
+
* **Convex shapes only.** The sweep ignores anything an outer-perimeter ray can't reach: holes (donuts), inward bays (a "C" opening sideways), or any row/column with multiple disjoint opaque spans. For those inputs the result is either a broken polygon or simply the outer hull, and {@link Polygon.collide} relies on convexity anyway.
|
|
146
221
|
*/
|
|
147
222
|
static fromCanvas(canvas: HTMLCanvasElement, detail: number, angle: number): Polygon;
|
|
223
|
+
/** Regular convex polygon with `edges` vertices, inscribed in a bounding box of `size` (number = square). */
|
|
148
224
|
static fromEdges(edges: number, size: Vec2 | number): Polygon;
|
|
225
|
+
/** Polygon from the four corners of `rect`. */
|
|
149
226
|
static fromRect(rect: Rect): Polygon;
|
|
150
227
|
private _center;
|
|
151
228
|
private _points;
|
|
152
229
|
private edges;
|
|
230
|
+
/** Centroid (mean of vertex positions). Recomputed whenever the vertex set changes. */
|
|
153
231
|
get center(): Readonly<Vec2>;
|
|
232
|
+
/** Read-only view of the current vertex list. Use `addPoint`/`offset`/`rotate` to mutate. */
|
|
154
233
|
get points(): Readonly<Vec2[]>;
|
|
155
234
|
constructor(...points: Vec2[]);
|
|
235
|
+
/** Stroke the polygon to `context`, shifted by `offset`. Coordinates are truncated to integers (via `| 0`) for crisp lines. */
|
|
156
236
|
draw(context: CanvasRenderingContext2D, offset?: Vec2): void;
|
|
237
|
+
/** Append a single vertex at `(x, y)`. Mutates and returns `this`. */
|
|
157
238
|
addPoint(x: number, y: number): Polygon;
|
|
239
|
+
/** Append cloned copies of every passed vertex. Mutates and returns `this`. */
|
|
158
240
|
addPoints(...points: Vec2[]): Polygon;
|
|
241
|
+
/** Translate every vertex by `(x, y)`. Mutates and returns `this`. */
|
|
159
242
|
offset(x?: number, y?: number): Polygon;
|
|
243
|
+
/** Rotate by `angle` radians around `pos` (defaults to the centroid). Mutates and returns `this`. */
|
|
160
244
|
rotate(angle: number, pos?: Readonly<Vec2>): this;
|
|
245
|
+
/** SAT collision against `otherPolygon`. **Both polygons must be convex** — SAT silently misses collisions for concave shapes. Pass a non-zero `velocity` to also compute whether the polygons would intersect after that displacement. Warns and returns a no-collision result if either polygon has zero edges. */
|
|
161
246
|
collide(otherPolygon: Polygon, velocity?: Vec2): PolygonCollisionResult;
|
|
247
|
+
/** New `Polygon` with the same vertices. */
|
|
162
248
|
clone(): Polygon;
|
|
163
249
|
private update;
|
|
164
250
|
}
|
|
251
|
+
/** Derived geometry returned by {@link Rect.sides}. */
|
|
165
252
|
export interface Sides {
|
|
253
|
+
/** Lower edge (`y + h`). */
|
|
166
254
|
bottom: number;
|
|
255
|
+
/** Center point. */
|
|
167
256
|
centerPos: Vec2;
|
|
257
|
+
/** Half the width and height. */
|
|
168
258
|
halfSize: Vec2;
|
|
259
|
+
/** Right edge (`x + w`). */
|
|
169
260
|
right: number;
|
|
170
261
|
}
|
|
262
|
+
/** Axis-aligned 2D rectangle (`x`, `y`, `w`, `h`). */
|
|
171
263
|
export declare class Rect {
|
|
264
|
+
/** Build from an `HTMLElement` (via `getBoundingClientRect`) or a `DOMRect`. */
|
|
172
265
|
static fromBoundingClientRect(rect: DOMRect | HTMLElement): Rect;
|
|
266
|
+
/** Axis-aligned bounding box of a polygon's points. Throws if the polygon has no points. */
|
|
173
267
|
static fromPolygon(polygon: Polygon): Rect;
|
|
174
268
|
private _h;
|
|
175
269
|
private _w;
|
|
@@ -177,35 +271,64 @@ export declare class Rect {
|
|
|
177
271
|
private _y;
|
|
178
272
|
private _sides;
|
|
179
273
|
private sideIsDirty;
|
|
274
|
+
/** Height. */
|
|
180
275
|
get h(): number;
|
|
276
|
+
/** Height. */
|
|
181
277
|
set h(value: number);
|
|
278
|
+
/** Width. */
|
|
182
279
|
get w(): number;
|
|
280
|
+
/** Width. */
|
|
183
281
|
set w(value: number);
|
|
282
|
+
/** Top-left x. */
|
|
184
283
|
get x(): number;
|
|
284
|
+
/** Top-left x. */
|
|
185
285
|
set x(value: number);
|
|
286
|
+
/** Top-left y. */
|
|
186
287
|
get y(): number;
|
|
288
|
+
/** Top-left y. */
|
|
187
289
|
set y(value: number);
|
|
290
|
+
/** Derived sides/center/halfSize. Lazily recomputed after any `x`/`y`/`w`/`h` change. */
|
|
188
291
|
get sides(): Readonly<Sides>;
|
|
189
292
|
constructor(x?: number, y?: number, w?: number, h?: number);
|
|
293
|
+
/** Grow on every side by `delta` (`x`/`y` shift in, `w`/`h` grow by `2*delta`). Pass a negative value to shrink. Mutates and returns `this`. */
|
|
190
294
|
inflate(delta: number): Rect;
|
|
295
|
+
/** Round `x` and `y` to the nearest integer. `w`/`h` are unchanged. Mutates and returns `this`. */
|
|
191
296
|
round(): Rect;
|
|
297
|
+
/**
|
|
298
|
+
* Replace fields. The first arg may be a `Vector4` (sets all four), a `Vector2` (sets `x`/`y` only, unless explicit `w`/`h` follow), or `x` as a number with separate `y`/`w`/`h`. Mutates and returns `this`.
|
|
299
|
+
*/
|
|
192
300
|
set(x?: Vector4 | Vector2 | number, y?: number, w?: number, h?: number): Rect;
|
|
301
|
+
/** AABB-vs-AABB overlap test (inclusive of touching edges). */
|
|
193
302
|
collide(rect: Rect): boolean;
|
|
303
|
+
/** `true` when `rect` is fully inside `this`. */
|
|
194
304
|
collideFull(rect: Rect): boolean;
|
|
305
|
+
/** `true` when `vec` lies inside `this` (inclusive of edges). */
|
|
195
306
|
collidePoint(vec: Vector2): boolean;
|
|
307
|
+
/** Side of `this` that `rect` overlaps from, or `"none"` if disjoint. Useful for picking a bounce axis. */
|
|
196
308
|
collideSide(rect: Rect): "none" | "top" | "bottom" | "left" | "right";
|
|
309
|
+
/** Top-left corner as a new `Vec2`. */
|
|
197
310
|
pos(): Vec2;
|
|
311
|
+
/** Width and height as a new `Vec2`. */
|
|
198
312
|
size(): Vec2;
|
|
313
|
+
/** Debug string like `"Rect [x: 0, y: 0, w: 10, h: 20]"`. */
|
|
199
314
|
toString(): string;
|
|
315
|
+
/** New `Rect` with the same values. */
|
|
200
316
|
clone(): Rect;
|
|
317
|
+
/** Approximate equality. Pass `withSize: false` to compare position only. */
|
|
201
318
|
equals(other: Rect, withSize?: boolean): boolean;
|
|
202
319
|
}
|
|
320
|
+
/** Object literal compatible with `Vec2` (e.g. `{ x, y }`). */
|
|
203
321
|
export interface Vector2 {
|
|
322
|
+
/** Horizontal component. */
|
|
204
323
|
x: number;
|
|
324
|
+
/** Vertical component. */
|
|
205
325
|
y: number;
|
|
206
326
|
}
|
|
327
|
+
/** {@link Vector2} plus `w`/`h` for AABB-style values. */
|
|
207
328
|
export interface Vector4 extends Vector2 {
|
|
329
|
+
/** Width. */
|
|
208
330
|
w: number;
|
|
331
|
+
/** Height. */
|
|
209
332
|
h: number;
|
|
210
333
|
}
|
|
211
334
|
/**
|
|
@@ -214,16 +337,24 @@ export interface Vector4 extends Vector2 {
|
|
|
214
337
|
* both. Pass `(x, y)` or a `Vector2` for per-axis values.
|
|
215
338
|
*/
|
|
216
339
|
export declare class Vec2 {
|
|
340
|
+
/** Unit vector at angle `rad` (radians), scaled per-axis. `scaleY` defaults to `scaleX`. */
|
|
217
341
|
static fromAngle(rad: number, scaleX?: number, scaleY?: number): Vec2;
|
|
342
|
+
/** Horizontal component. */
|
|
218
343
|
x: number;
|
|
344
|
+
/** Vertical component. */
|
|
219
345
|
y: number;
|
|
220
346
|
constructor(x?: Vector2 | number, y?: number);
|
|
347
|
+
/** Replace components. Mutates and returns `this`. */
|
|
221
348
|
set(v: Vector2): Vec2;
|
|
222
349
|
set(x: number, y?: number): Vec2;
|
|
350
|
+
/** Set each component to its absolute value. Mutates and returns `this`. */
|
|
223
351
|
abs(): Vec2;
|
|
352
|
+
/** Per-axis add. Mutates and returns `this`. */
|
|
224
353
|
add(v: Vector2): Vec2;
|
|
225
354
|
add(x: number, y?: number): Vec2;
|
|
355
|
+
/** Round each component up. Mutates and returns `this`. */
|
|
226
356
|
ceil(): Vec2;
|
|
357
|
+
/** Clamp each axis to its `[min, max]` range. `y` defaults to `x`. Mutates and returns `this`. */
|
|
227
358
|
clamp(x: [
|
|
228
359
|
number,
|
|
229
360
|
number
|
|
@@ -231,70 +362,125 @@ export declare class Vec2 {
|
|
|
231
362
|
number,
|
|
232
363
|
number
|
|
233
364
|
]): Vec2;
|
|
365
|
+
/** Per-axis divide. Mutates and returns `this`. */
|
|
234
366
|
div(v: Vector2): Vec2;
|
|
235
367
|
div(x: number, y?: number): Vec2;
|
|
368
|
+
/** Round each component down. Mutates and returns `this`. */
|
|
236
369
|
floor(): Vec2;
|
|
370
|
+
/**
|
|
371
|
+
* Apply `callback` to each component (`index` is `0` for x, `1` for y). Mutates and returns `this`.
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```ts
|
|
375
|
+
* new Vec2(3.6, -2.1).map(Math.trunc); // Vec2 { x: 3, y: -2 }
|
|
376
|
+
* new Vec2(2, 5).map((v, i) => v * (i + 1)); // Vec2 { x: 2, y: 10 }
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
237
379
|
map(callback: (value: number, index: number) => number): Vec2;
|
|
380
|
+
/** Per-axis Euclidean modulo (result sign matches the divisor). Mutates and returns `this`. */
|
|
238
381
|
mod(v: Vector2): Vec2;
|
|
239
382
|
mod(x: number, y?: number): Vec2;
|
|
383
|
+
/** Per-axis multiply. Mutates and returns `this`. */
|
|
240
384
|
mult(v: Vector2): Vec2;
|
|
241
385
|
mult(x: number, y?: number): Vec2;
|
|
386
|
+
/** Flip the sign of both components (same as `mult(-1)`). Mutates and returns `this`. */
|
|
242
387
|
negate(): Vec2;
|
|
388
|
+
/** Scale to unit length. Zero-length vectors are left untouched and warn (throttled). Mutates and returns `this`. */
|
|
243
389
|
normalize(): Vec2;
|
|
390
|
+
/** Scale so `|x| + |y| === 1`. Zero-length vectors are left untouched. Mutates and returns `this`. */
|
|
244
391
|
normalizeManhattan(): Vec2;
|
|
392
|
+
/** Per-axis remainder (JavaScript `%`, sign follows the dividend). Mutates and returns `this`. */
|
|
245
393
|
rem(v: Vector2): Vec2;
|
|
246
394
|
rem(x: number, y?: number): Vec2;
|
|
395
|
+
/** Round each component to the nearest integer. Mutates and returns `this`. */
|
|
247
396
|
round(): Vec2;
|
|
397
|
+
/** Per-axis subtract. Mutates and returns `this`. */
|
|
248
398
|
sub(v: Vector2): Vec2;
|
|
249
399
|
sub(x: number, y?: number): Vec2;
|
|
400
|
+
/** Angle in radians. No arg: angle of `this` from origin. With `other`: angle from `this` toward `other`. */
|
|
250
401
|
angle(other?: Vector2): number;
|
|
402
|
+
/** Euclidean distance to `other`. */
|
|
251
403
|
distance(other: Vector2): number;
|
|
404
|
+
/** Manhattan distance (`|dx| + |dy|`) to `other`. */
|
|
252
405
|
distanceManhattan(other: Vector2): number;
|
|
406
|
+
/** Dot product with `other`. */
|
|
253
407
|
dotProduct(other: Vector2): number;
|
|
408
|
+
/** `true` when both components are finite (rules out `NaN` and `±Infinity`). */
|
|
254
409
|
isValid(): boolean;
|
|
410
|
+
/** Euclidean magnitude (`sqrt(x² + y²)`). */
|
|
255
411
|
length(): number;
|
|
412
|
+
/** Manhattan magnitude (`|x| + |y|`). */
|
|
256
413
|
lengthManhattan(): number;
|
|
414
|
+
/** Larger of the two components. */
|
|
257
415
|
max(): number;
|
|
416
|
+
/** Smaller of the two components. */
|
|
258
417
|
min(): number;
|
|
418
|
+
/** Tuple `[x, y]`. */
|
|
259
419
|
toArray(): [
|
|
260
420
|
number,
|
|
261
421
|
number
|
|
262
422
|
];
|
|
423
|
+
/** Build a `Rect` with the argument as position and `this` as size. */
|
|
263
424
|
toRectAddPos(v: Vector2): Rect;
|
|
264
425
|
toRectAddPos(x: number, y?: number): Rect;
|
|
426
|
+
/** Build a `Rect` with `this` as position and the argument as size. */
|
|
265
427
|
toRectAddSize(v: Vector2): Rect;
|
|
266
428
|
toRectAddSize(x: number, y?: number): Rect;
|
|
429
|
+
/** Debug string like `"Vec2 [x: 1, y: 2]"`. */
|
|
267
430
|
toString(): string;
|
|
431
|
+
/** New `Vec2` with the same components. */
|
|
268
432
|
clone(): Vec2;
|
|
433
|
+
/** Approximate equality (within `approxEqual` tolerance). Scalar broadcasts. */
|
|
269
434
|
equals(v: Vector2): boolean;
|
|
270
435
|
equals(x: number, y?: number): boolean;
|
|
271
436
|
private calculate;
|
|
272
437
|
private concat;
|
|
273
438
|
private getValues;
|
|
274
439
|
}
|
|
440
|
+
/** A named animation: a list of frame sprites and the per-frame `timing` (seconds). */
|
|
275
441
|
export interface SpriteAnimation {
|
|
442
|
+
/** Mark this animation as the default — played by {@link Animator.reset} and right after {@link Animator.add} when registered. At most one per Animator. */
|
|
276
443
|
default?: boolean;
|
|
444
|
+
/** Unique identifier. Pass to {@link Animator.play}. */
|
|
277
445
|
name: string;
|
|
446
|
+
/** Frame images in playback order. Uniform size is assumed within a single animation. */
|
|
278
447
|
sprites: HTMLCanvasElement[] | HTMLImageElement[];
|
|
448
|
+
/** Seconds each frame stays visible before advancing. */
|
|
279
449
|
timing: number;
|
|
280
450
|
}
|
|
451
|
+
/** Fires once after the last frame of an animation plays. Cleared after firing. */
|
|
281
452
|
export type onEndType = () => void;
|
|
453
|
+
/** Map of `frameIndex → callback`. The callback for a given frame fires once when that frame becomes active, then is removed from the map. */
|
|
282
454
|
export type onFrameType = Record<number, () => void>;
|
|
455
|
+
/** Minimum shape an entity must satisfy to be animated by {@link Animator}. */
|
|
283
456
|
export interface BaseEntity {
|
|
457
|
+
/** Top-left position used as the draw anchor. */
|
|
284
458
|
pos: Vec2;
|
|
459
|
+
/** Optional horizontal anchor offset for flipped frames. Defaults to the current sprite width on first render. */
|
|
285
460
|
flipX?: number;
|
|
286
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Sprite-sheet animator. Hosts a list of named animations (registered via {@link add} / {@link addAnimation}) and drives them per-frame from `update(dt)`. Pre-renders each frame to a 2×-wide cached canvas keyed by `${namespace}.${animationName}` — so multiple entities sharing a `namespace` reuse the same rendered images.
|
|
464
|
+
*
|
|
465
|
+
* Use {@link play} to switch animations, {@link playOnce} for one-shot animations that fall back to the previous one, and {@link Animator.onEnd}/`onFrame` callbacks for frame- or animation-end hooks.
|
|
466
|
+
*/
|
|
287
467
|
export declare class Animator {
|
|
288
468
|
private static spriteCache;
|
|
289
469
|
/**
|
|
290
470
|
* Drop cached rendered sprites. Pass a namespace to evict only that prefix; omit to clear all.
|
|
291
471
|
*/
|
|
292
472
|
static clearSpriteCache(namespace?: string): void;
|
|
473
|
+
/** When `false`, {@link update} is a no-op. Set automatically to `false` after a single-frame animation finishes and via {@link reset} when no default animation exists. */
|
|
293
474
|
active: boolean;
|
|
475
|
+
/** Current rendered frame (after flip processing). Pulled from the cache by {@link setImage} every time the frame advances. */
|
|
294
476
|
image: HTMLCanvasElement;
|
|
477
|
+
/** Index of the current frame within the current animation's `sprites` array. */
|
|
295
478
|
imageId: number;
|
|
479
|
+
/** Flip the rendered sprite horizontally. Caches a separate "flipped" bucket so toggling is cheap. */
|
|
296
480
|
lookLeft: boolean;
|
|
481
|
+
/** One-shot callback that fires when the current animation's last frame finishes. Cleared after firing. */
|
|
297
482
|
onEnd: onEndType | undefined;
|
|
483
|
+
/** Current sprite's `(width, height)`. Updated by {@link setImage}. */
|
|
298
484
|
size: Vec2;
|
|
299
485
|
private animations;
|
|
300
486
|
private currentAnimation;
|
|
@@ -304,73 +490,195 @@ export declare class Animator {
|
|
|
304
490
|
private onFrame?;
|
|
305
491
|
private playVersion;
|
|
306
492
|
private timer;
|
|
493
|
+
/** The currently-playing animation. `undefined` if no animations have been added yet. */
|
|
307
494
|
get current(): SpriteAnimation;
|
|
308
495
|
/**
|
|
309
|
-
*
|
|
310
|
-
* So if you have the same entity multiple times, the images get computed once and then shared.
|
|
311
|
-
* This reduces overhead and frees time up for other important computations.
|
|
312
|
-
*
|
|
313
|
-
* `namespace` is the cache key prefix for these rendered images.
|
|
314
|
-
* So pass a different key for different Animators; otherwise, you will see wrong images.
|
|
496
|
+
* Bind to `entity` and stamp `namespace` as the prefix for cache keys (`${namespace}.${animationName}`). **Use distinct namespaces for animators whose sprite sets differ** — sharing a namespace across mismatched sprite sets serves the wrong cached frames.
|
|
315
497
|
*/
|
|
316
498
|
constructor(entity: BaseEntity, namespace: string);
|
|
499
|
+
/** Blit the current cached frame at `entity.pos + offset`, shifted left by `size.x` to compensate for the 2×-wide cache canvas. */
|
|
317
500
|
draw(context: CanvasRenderingContext2D, offset?: Vec2): void;
|
|
501
|
+
/** Advance the timer; when it crosses `current.timing`, step to the next frame, fire any `onFrame[index]` callback, and on rollover fire `onEnd` and queue `lastPlayed` (set by {@link playOnce}). No-op when {@link active} is `false`. */
|
|
318
502
|
update(dt: number): void;
|
|
503
|
+
/** Register a new animation. Logs an error (but still registers) if `defaultAnim` is true while another default already exists, or if `name` collides with an existing animation. Auto-plays the new animation when `defaultAnim` is `true`. */
|
|
319
504
|
add(name: string, sprites: HTMLCanvasElement[] | HTMLImageElement[], timing: number, defaultAnim?: boolean): void;
|
|
505
|
+
/** Convenience wrapper around {@link add} that takes a packed {@link SpriteAnimation}. `defaultAnim` is OR'd with `anim.default`. */
|
|
320
506
|
addAnimation(anim: SpriteAnimation, defaultAnim?: boolean): void;
|
|
507
|
+
/** Draw the current frame rotated by `angle` radians around a sprite-relative pivot (75% width, 50% height). Uses `setTransform` and resets the transform on exit. */
|
|
321
508
|
drawRotated(context: CanvasRenderingContext2D, angle: number, offset?: Vec2): void;
|
|
509
|
+
/** Switch to the named animation, rewinding timer and frame index. Optionally register `onEnd` (fires after the last frame) and `onFrame` (frame-indexed callbacks). Any previous {@link Animator.onEnd} fires before the new one is set. Throws if `name` isn't registered. */
|
|
322
510
|
play(name: string, onEnd?: onEndType, onFrame?: onFrameType): void;
|
|
511
|
+
/** {@link play} only if `name` isn't already the current animation. Returns `true` if it started a new playback, `false` if it was already playing. */
|
|
323
512
|
playIfNot(name: string, onEnd?: onEndType, onFrame?: onFrameType): boolean;
|
|
513
|
+
/** Queue an animation to play once the current one finishes. Pass `undefined` to cancel the queue. Used internally by {@link playOnce}. */
|
|
324
514
|
playNextOnce(name: string | undefined): void;
|
|
325
515
|
/**
|
|
326
516
|
* Play `name` once, then return to the previously-playing animation. Calling with the currently-playing name re-loops it indefinitely (lastPlayed restores to itself).
|
|
327
517
|
*/
|
|
328
518
|
playOnce(name: string, onEnd?: onEndType, onFrame?: onFrameType): void;
|
|
519
|
+
/** Randomize the frame timer to a value in `[0, current.timing)`. Useful when spawning many instances of the same animation to break phase lockstep. */
|
|
329
520
|
randomTimer(): void;
|
|
521
|
+
/** Drop every registered animation and clear this instance's cached frames. {@link active} resets to `true`; pending callbacks are cleared. */
|
|
330
522
|
removeAllAnimations(): void;
|
|
523
|
+
/** Switch back to the default animation if one was registered (marked via `defaultAnim`); otherwise just stop animating ({@link active} = `false`). */
|
|
331
524
|
reset(): void;
|
|
525
|
+
/** `true` when `name` matches the currently-playing animation. */
|
|
332
526
|
isPlaying(name: string): boolean;
|
|
333
527
|
/**
|
|
334
528
|
* Update `image` and `size` from the current sprite. Assumes uniform sprite size within an animation. Caches rendered canvases per (animation, frame, lookLeft) — if you mutate `entity.flipX` after first render, call `removeAllAnimations()` or recreate the Animator to invalidate.
|
|
335
529
|
*/
|
|
336
530
|
protected setImage(): void;
|
|
337
531
|
}
|
|
532
|
+
/** Button indices for the standard gamepad mapping (W3C Gamepad spec). Use as the index into {@link Controller.buttons}. */
|
|
533
|
+
export declare const CONTROLLER_KEYS: {
|
|
534
|
+
/** Bottom face button — `A` on Xbox, `×` on PlayStation. */
|
|
535
|
+
readonly A: 0;
|
|
536
|
+
/** Right face button — `B` on Xbox, `○` on PlayStation. */
|
|
537
|
+
readonly B: 1;
|
|
538
|
+
/** Left face button — `X` on Xbox, `□` on PlayStation. */
|
|
539
|
+
readonly X: 2;
|
|
540
|
+
/** Top face button — `Y` on Xbox, `△` on PlayStation. */
|
|
541
|
+
readonly Y: 3;
|
|
542
|
+
/** Left bumper / shoulder. */
|
|
543
|
+
readonly LB: 4;
|
|
544
|
+
/** Right bumper / shoulder. */
|
|
545
|
+
readonly RB: 5;
|
|
546
|
+
/** Left trigger. The digital pressed-state lives here; the analog value is on the underlying `Gamepad.buttons[6].value`. */
|
|
547
|
+
readonly LT: 6;
|
|
548
|
+
/** Right trigger. The digital pressed-state lives here; the analog value is on the underlying `Gamepad.buttons[7].value`. */
|
|
549
|
+
readonly RT: 7;
|
|
550
|
+
/** Back / Select / Share. */
|
|
551
|
+
readonly SELECT: 8;
|
|
552
|
+
/** Start / Options / Menu. */
|
|
553
|
+
readonly START: 9;
|
|
554
|
+
/** Left stick click (L3). */
|
|
555
|
+
readonly LEFT_STICK: 10;
|
|
556
|
+
/** Right stick click (R3). */
|
|
557
|
+
readonly RIGHT_STICK: 11;
|
|
558
|
+
/** D-pad up. */
|
|
559
|
+
readonly UP: 12;
|
|
560
|
+
/** D-pad down. */
|
|
561
|
+
readonly DOWN: 13;
|
|
562
|
+
/** D-pad left. */
|
|
563
|
+
readonly LEFT: 14;
|
|
564
|
+
/** D-pad right. */
|
|
565
|
+
readonly RIGHT: 15;
|
|
566
|
+
/** Guide / Home / PS button. Not exposed by all browsers. */
|
|
567
|
+
readonly GUIDE: 16;
|
|
568
|
+
};
|
|
569
|
+
/**
|
|
570
|
+
* Gamepad input. The first connected gamepad becomes "our" gamepad; later ones are ignored until ours disconnects. Connection / disconnection fires {@link EventSystem} `"inputControllerConnected"` / `"inputControllerDisconnected"`.
|
|
571
|
+
*
|
|
572
|
+
* Poll {@link buttons} (indexed via {@link CONTROLLER_KEYS}) and {@link poll} from your `update`. State is cleared on `window` blur and on disconnect so held buttons / non-neutral sticks don't stay live across focus loss. Visualizing is not the controller's responsibility — see {@link ControllerCursor} for the built-in on-screen visualization, or read {@link poll} directly to drive your own.
|
|
573
|
+
*
|
|
574
|
+
* Logs to `console.error` if the browser doesn't expose the Gamepad API.
|
|
575
|
+
*/
|
|
576
|
+
export declare class Controller {
|
|
577
|
+
/** Pressed-state per button, indexed in the same order as the underlying `Gamepad.buttons`. Index with {@link CONTROLLER_KEYS}. Updated by {@link poll}; empty until the first non-cached poll. */
|
|
578
|
+
buttons: boolean[];
|
|
579
|
+
private axes;
|
|
580
|
+
private index;
|
|
581
|
+
private lastTime;
|
|
582
|
+
constructor();
|
|
583
|
+
/** Read the current gamepad state and return one {@link Vec2} per stick pair with a circular deadzone applied (`0.25` inner radius, output magnitude clamped to `[0, 1]`). The returned array (and each `Vec2` in it) is reused across calls — clone if you need to retain. Also refreshes {@link buttons}. Returns the cached array unchanged when the gamepad timestamp hasn't advanced. */
|
|
584
|
+
poll(): Vec2[];
|
|
585
|
+
/** Clear {@link buttons} and the cached stick axes. Called automatically on `window` blur and on gamepad disconnect. */
|
|
586
|
+
reset(): void;
|
|
587
|
+
/** Trigger a 400 ms full-strength dual-rumble pulse. Returns `false` when no gamepad is connected or the pad has no `vibrationActuator`; `true` when the effect was dispatched. */
|
|
588
|
+
vibrate(): boolean;
|
|
589
|
+
private getGamepad;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* On-screen crosshairs driven by a {@link Controller}'s analog sticks. Each anchor in `sticks` gets its own crosshair that follows the corresponding stick's deflection with frame-rate-independent exponential smoothing (50 ms half-life). The caller owns the anchor positions and the crosshair image — any `CanvasImageSource` works (a loaded `HTMLImageElement`, a procedurally-drawn `HTMLCanvasElement`, an `ImageBitmap`, …).
|
|
593
|
+
*
|
|
594
|
+
* {@link update} polls the controller for you; don't call {@link Controller.poll} again from the same `update` step.
|
|
595
|
+
*/
|
|
596
|
+
export declare class ControllerCursor {
|
|
597
|
+
private controller;
|
|
598
|
+
private crosshair;
|
|
599
|
+
private range;
|
|
600
|
+
private sticks;
|
|
601
|
+
/**
|
|
602
|
+
* @param controller Gamepad input source.
|
|
603
|
+
* @param crosshair Drawn at each cursor position via `CanvasRenderingContext2D.drawImage`. The image's top-left is the draw origin — center the visible reticle inside the image, or offset the anchors to compensate.
|
|
604
|
+
* @param sticks Anchor positions, one per stick to track. Cloned at construction so caller mutation is harmless.
|
|
605
|
+
* @param range Max pixel deflection from anchor at full stick. Default `80`.
|
|
606
|
+
*/
|
|
607
|
+
constructor(controller: Controller, crosshair: CanvasImageSource, sticks: Vec2[], range?: number);
|
|
608
|
+
/** Draw a crosshair at `anchor + offset` for each tracked stick. */
|
|
609
|
+
draw(context: CanvasRenderingContext2D): void;
|
|
610
|
+
/** Pull fresh stick state via {@link Controller.poll} and smooth each crosshair's offset toward `stickAxis * range`. Frame-rate independent — 50 ms to cover half the remaining distance. */
|
|
611
|
+
update(dt: number): void;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Single particle drawn as a filled circle. The constructor seeds a random velocity (random angle, per-axis speed in `[50, 150]` px/s) and a random `maxLifeTime` in `[0.5, 1.5]` s — spawn many at once for spark/dust effects. Each tick `update(dt)` advances `lifetime` and `pos`; the particle is "dead" when {@link alive} flips to `false`.
|
|
615
|
+
*/
|
|
338
616
|
export declare class Particle {
|
|
617
|
+
/** CSS color string passed to `context.fillStyle` in {@link draw}. */
|
|
339
618
|
protected color: string;
|
|
619
|
+
/** Accumulated time (seconds) since spawn or last {@link resetLifetime}. */
|
|
340
620
|
protected lifetime: number;
|
|
621
|
+
/** Lifetime cap in seconds, randomized to `[0.5, 1.5]` at construction. */
|
|
341
622
|
protected maxLifeTime: number;
|
|
623
|
+
/** Top-left position. Cloned from the constructor arg so the caller's `Vec2` isn't aliased. */
|
|
342
624
|
protected pos: Vec2;
|
|
625
|
+
/** Circle radius (pixels). */
|
|
343
626
|
protected size: number;
|
|
627
|
+
/** Velocity in px/s. Seeded randomly by the constructor (random angle, random magnitude per axis). */
|
|
344
628
|
protected vel: Vec2;
|
|
629
|
+
/** Backing storage for the {@link rect} getter. Subclasses can read it; the public-facing accessor is {@link rect}. */
|
|
345
630
|
protected _rect: Rect;
|
|
631
|
+
/** `false` once {@link lifetime} reaches {@link maxLifeTime}. Owners typically filter dead particles out of their list each frame, or call {@link resetLifetime} to recycle. */
|
|
346
632
|
get alive(): boolean;
|
|
633
|
+
/** Read-only AABB tracking `pos` and the particle's `size`. Recomputed each {@link update}. */
|
|
347
634
|
get rect(): Readonly<Rect>;
|
|
348
635
|
constructor(pos: Vec2, color: string, size?: number);
|
|
636
|
+
/** Fill a circle at `pos + offset` using {@link color}. `offset` is useful for shifting by a camera/world transform without mutating `pos`. */
|
|
349
637
|
draw(context: CanvasRenderingContext2D, offset?: Vec2): void;
|
|
638
|
+
/** Integrate lifetime, position, and bounding rect. */
|
|
350
639
|
update(dt: number): void;
|
|
640
|
+
/** Recycle a dead particle by subtracting `maxLifeTime` from `lifetime` — preserves any overshoot so a pool of pre-allocated particles can stay phase-stable across loops. Note this doesn't re-randomize `vel` or `pos`; mutate those externally if you want a fresh trajectory. */
|
|
351
641
|
resetLifetime(): void;
|
|
352
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* A self-propelled sprite — `update(dt)` advances `pos` along `vel * speed`, accumulates `lifetime`, and flips {@link alive} to `false` once `lifetime >= maxLifetime`. The image is pre-baked to a rotated canvas matching the velocity direction; call {@link rebuildRotation} after re-aiming.
|
|
645
|
+
*
|
|
646
|
+
* The `T` generic types the optional {@link payload} so callers can attach typed metadata (damage, owner, etc.) without losing inference.
|
|
647
|
+
*/
|
|
353
648
|
export declare class Projectile<T = unknown> {
|
|
649
|
+
/** Seconds after which {@link alive} flips to `false`. Defaults to `Infinity` — no natural expiry. */
|
|
354
650
|
maxLifetime: number;
|
|
651
|
+
/** Caller-supplied data. Typed via the class generic so consumers can read `projectile.payload` without casting. */
|
|
355
652
|
payload?: T;
|
|
653
|
+
/** Magnitude multiplier applied to `vel` each update: `pos += vel * speed * dt`. Pass a unit-length `vel` to make this read as "pixels per second". */
|
|
356
654
|
speed: number;
|
|
655
|
+
/** Current pre-rotated sprite. Re-baked by {@link rebuildRotation} from the un-rotated `originalImage`. */
|
|
357
656
|
protected image: HTMLCanvasElement;
|
|
657
|
+
/** Accumulated time (seconds). Drives the {@link alive} check against {@link maxLifetime}. */
|
|
358
658
|
protected lifetime: number;
|
|
659
|
+
/** Top-left position. Cloned from the constructor arg so the caller's `Vec2` isn't aliased. */
|
|
359
660
|
protected pos: Vec2;
|
|
661
|
+
/** Current sprite rotation in radians, kept in sync with `vel` by {@link rebuildRotation}. */
|
|
360
662
|
protected rotation: number;
|
|
663
|
+
/** Velocity direction vector. Multiplied by {@link speed} each update — pass a unit vector for `speed`-as-px-per-second semantics. Cloned from the constructor arg. */
|
|
361
664
|
protected vel: Vec2;
|
|
665
|
+
/** Backing storage for the {@link rect} getter. Subclasses can read it; mutate via `pos`/{@link rebuildRotation} instead of touching it directly. */
|
|
362
666
|
protected _rect: Rect;
|
|
363
667
|
private originalImage;
|
|
668
|
+
/** `false` once {@link lifetime} reaches {@link maxLifetime}. Owners typically filter dead projectiles out of their list each frame. */
|
|
364
669
|
get alive(): boolean;
|
|
670
|
+
/** Read-only AABB tracking `pos` and the (rotated) image size. Recomputed in {@link update} and {@link rebuildRotation}. */
|
|
365
671
|
get rect(): Readonly<Rect>;
|
|
366
672
|
constructor(pos: Vec2, image: HTMLCanvasElement, vel?: Vec2);
|
|
673
|
+
/** Blit the pre-rotated image at `pos + offset`. `offset` is useful for shifting by a camera/world transform without mutating `pos`. */
|
|
367
674
|
draw(context: CanvasRenderingContext2D, offset?: Vec2): void;
|
|
675
|
+
/** Integrate motion and advance lifetime. Doesn't re-bake the rotation — call {@link rebuildRotation} after mutating `vel`. */
|
|
368
676
|
update(dt: number): void;
|
|
369
677
|
/**
|
|
370
|
-
* Allocates a fresh rotated canvas
|
|
371
|
-
* Caching by quantized rotation could be a feature when projectiles need to re-aim every tick (homing/seeking).
|
|
678
|
+
* Re-bake the sprite to match the current `vel` direction (rotation = `atan2(vel.y, vel.x)`) and update `rect` to the new bounds. Allocates a fresh rotated canvas every call — no internal cache, so heavy re-aiming (homing/seeking) is a candidate for adding quantized caching.
|
|
372
679
|
*/
|
|
373
680
|
rebuildRotation(): void;
|
|
681
|
+
/** Force {@link alive} to `false` immediately (sets `lifetime` past `maxLifetime`). Use when the projectile should die on collision/impact, not from natural expiry. */
|
|
374
682
|
remove(): void;
|
|
375
683
|
}
|
|
376
684
|
declare global {
|
|
@@ -485,8 +793,11 @@ declare global {
|
|
|
485
793
|
toImage(): Promise<HTMLImageElement>;
|
|
486
794
|
}
|
|
487
795
|
}
|
|
796
|
+
/** A `<canvas>` element paired with its 2D rendering context. Returned by {@link createNewCanvas} / {@link getCanvasConstruct} and inherited by {@link CanvasHolder}. */
|
|
488
797
|
export interface CanvasConstruct {
|
|
798
|
+
/** The `<canvas>` element. */
|
|
489
799
|
canvas: HTMLCanvasElement;
|
|
800
|
+
/** Its 2D rendering context. */
|
|
490
801
|
context: CanvasRenderingContext2D;
|
|
491
802
|
}
|
|
492
803
|
/**
|
|
@@ -523,118 +834,234 @@ export declare function splitSpriteSheet(img: HTMLCanvasElement, elementsX: numb
|
|
|
523
834
|
* `removeLowerThan` / `removeHigherThan` drop entries outside the range; `0` disables either bound.
|
|
524
835
|
*/
|
|
525
836
|
export declare function getUsedColors(image: HTMLCanvasElement, pixelAmount?: number, removeLowerThan?: number, removeHigherThan?: number): Map<string, number>;
|
|
837
|
+
/** Role tags for canvases passed to {@link CanvasManager.setupCanvas}. Only `MAIN` is enforced (exactly one); the others are free-form labels you can use to group background/overlay canvases. */
|
|
526
838
|
export declare const CANVAS_TYPES: {
|
|
839
|
+
/** Catch-all tag for canvases without a specific role. */
|
|
527
840
|
readonly ANY: symbol;
|
|
841
|
+
/** Generic placeholder tag — distinct from `ANY` so consumers can differentiate. */
|
|
528
842
|
readonly DEFAULT: symbol;
|
|
843
|
+
/** Background canvas (drawn behind the main one). */
|
|
529
844
|
readonly BACKGROUND: symbol;
|
|
845
|
+
/** Primary render target. Exactly one canvas must be registered with this type before {@link CanvasManager.finishSetup}. */
|
|
530
846
|
readonly MAIN: symbol;
|
|
531
847
|
};
|
|
848
|
+
/** Entry stored in {@link CanvasManager.canvasHolder} for each registered canvas. */
|
|
532
849
|
export interface CanvasHolder extends CanvasConstruct {
|
|
850
|
+
/** Selector the canvas was registered under (also the map key). */
|
|
533
851
|
id: string;
|
|
852
|
+
/** Whether this canvas participates in {@link CanvasManager.resize} (rescaled to fit the window while preserving its buffer aspect ratio). */
|
|
534
853
|
resize: boolean;
|
|
854
|
+
/** Role tag — one of {@link CANVAS_TYPES}. */
|
|
535
855
|
type: symbol;
|
|
536
856
|
}
|
|
857
|
+
/**
|
|
858
|
+
* Tracks registered canvases and exposes the main 2D context. The width/height accessors and `size` getter all refer to the **buffer** dimensions (the canvas's `width`/`height` attributes — drawing-space pixels), while {@link resizedSize} and {@link ratio} describe the **display** size after CSS scaling.
|
|
859
|
+
*
|
|
860
|
+
* Owned by `Game` (`game.canman`). Lifecycle: subclass registers canvases via {@link setupCanvas} in its constructor, then `preInit()` calls {@link finishSetup}.
|
|
861
|
+
*/
|
|
537
862
|
export declare class CanvasManager {
|
|
863
|
+
/** Cached `getBoundingClientRect()` of the main canvas. Refreshed in {@link resize}. Used to map pointer client coords into canvas space. */
|
|
538
864
|
canvasBoundingClientRect: DOMRect;
|
|
865
|
+
/** Registry of every {@link setupCanvas}-registered canvas, keyed by selector. */
|
|
539
866
|
canvasHolder: Record<string, CanvasHolder>;
|
|
867
|
+
/** Display-to-buffer scale factor after the last {@link resize} (`displayWidth / bufferWidth`). `1` until the first resize. */
|
|
540
868
|
ratio: number;
|
|
869
|
+
/** Display (CSS-pixel) size of the main canvas after the last {@link resize}. Independent of the buffer dimensions in {@link width}/{@link height}. */
|
|
541
870
|
resizedSize: Vec2;
|
|
542
871
|
private mainHolder;
|
|
872
|
+
/** Main canvas element (the one registered with `CANVAS_TYPES.MAIN`). */
|
|
543
873
|
get canvas(): HTMLCanvasElement;
|
|
874
|
+
/** Main canvas 2D rendering context. */
|
|
544
875
|
get canvasContext(): CanvasRenderingContext2D;
|
|
876
|
+
/** Main canvas **buffer** height (the drawing surface, not the CSS display size). */
|
|
545
877
|
get height(): number;
|
|
878
|
+
/** Main canvas **buffer** height (the drawing surface, not the CSS display size). */
|
|
546
879
|
set height(height: number);
|
|
880
|
+
/** Main canvas buffer dimensions as a new `Vec2`. */
|
|
547
881
|
get size(): Vec2;
|
|
882
|
+
/** Main canvas **buffer** width (the drawing surface, not the CSS display size). */
|
|
548
883
|
get width(): number;
|
|
884
|
+
/** Main canvas **buffer** width (the drawing surface, not the CSS display size). */
|
|
549
885
|
set width(width: number);
|
|
886
|
+
/** Finalize the canvas registry. Called once by `Game.preInit()`. Validates that exactly one `CANVAS_TYPES.MAIN` canvas is registered and that its buffer is non-zero, caches its bounding rect, and wires the `"resized"` listener if `Settings.enableResize`. Throws on duplicate calls or invalid registry state. */
|
|
550
887
|
finishSetup(): void;
|
|
888
|
+
/** Rescale every opt-in canvas (`holder.resize === true`) to fit the window while preserving its buffer aspect ratio. Updates `style.width`/`style.height` only — buffer dimensions don't change. Refreshes {@link canvasBoundingClientRect}, {@link resizedSize}, and {@link ratio} from the main canvas. */
|
|
551
889
|
resize(): void;
|
|
890
|
+
/** Set the main context's `font` to `${size}px "${font}"`. Defaults the family to `Settings.font`. */
|
|
552
891
|
setFontSize(size: number, font?: string): void;
|
|
892
|
+
/** Register a canvas at `selector` with the given role tag. Initializes its context (`fillStyle`/`strokeStyle` = white, font = `12px Arial`) and returns the {@link CanvasHolder}. `resize` defaults to `Settings.enableResize`. Throws if the selector doesn't match an element or has already been registered. */
|
|
553
893
|
setupCanvas(canvasType: symbol, selector: string, resize?: boolean): CanvasHolder;
|
|
554
894
|
}
|
|
895
|
+
/** rAF gaps larger than this (s) reset the accumulator instead of running catch-up steps — keeps a backgrounded tab or paused debugger from fast-forwarding the simulation on resume. */
|
|
896
|
+
export declare const MAX_DT_SECONDS = 0.25;
|
|
897
|
+
/** Hard cap on update steps per rendered frame. If simulation can't keep up, it falls behind in `levelTime` rather than blocking the main thread. */
|
|
898
|
+
export declare const MAX_STEPS_PER_FRAME = 5;
|
|
899
|
+
/**
|
|
900
|
+
* Fixed-step game loop. Owned by `Game` (`game.gameloop`) — usually started automatically by `preInit()` when `Settings.autoloop` is `true`. Each rendered frame:
|
|
901
|
+
*
|
|
902
|
+
* 1. Accumulates real time
|
|
903
|
+
* 2. Runs `game.update(Settings.fps)` zero-or-more times until the accumulator is drained (bounded by {@link MAX_STEPS_PER_FRAME} to avoid runaway catch-up)
|
|
904
|
+
* 3. Clears the canvas (per `Settings.doNotClear` / `Settings.useClearRect`) and calls `game.draw(context)`
|
|
905
|
+
*
|
|
906
|
+
* Fires the {@link EventSystem} `"gameloopStopped"` event when teardown completes (not when {@link stopLoop} is called).
|
|
907
|
+
*/
|
|
555
908
|
export declare class Gameloop {
|
|
909
|
+
/** Simulation time in milliseconds. Advances by `Settings.fps * 1000` per update step, so it reflects simulated time, not wall-clock — paused/dropped frames don't add. Use this for time-driven spawning, animations, etc. */
|
|
556
910
|
levelTime: number;
|
|
557
911
|
private _isLooping;
|
|
558
912
|
private accumulator;
|
|
559
913
|
private game;
|
|
560
914
|
private stop;
|
|
915
|
+
/** `true` while the rAF callback is registered. Goes `false` only after the final frame fires the `"gameloopStopped"` event. */
|
|
561
916
|
get isLooping(): boolean;
|
|
562
917
|
constructor(game: Game);
|
|
918
|
+
/** Begin the rAF loop. Throws if {@link stopLoop} was called but teardown hasn't completed yet — wait for the `"gameloopStopped"` event before restarting. */
|
|
563
919
|
startLoop(): void;
|
|
920
|
+
/** Request that the loop stop on its next tick. Asynchronous — the loop tears down on the following frame and dispatches `"gameloopStopped"` when done. */
|
|
564
921
|
stopLoop(): void;
|
|
565
922
|
private draw;
|
|
566
923
|
private looper;
|
|
567
924
|
}
|
|
925
|
+
/** `KeyboardEvent.code` constants for the keys Gleam tracks by name. Use as the argument to {@link Keyboard.isPressed} / {@link Keyboard.stopPress} or as a string key into {@link Keyboard.keys}. */
|
|
568
926
|
export declare const KEYBOARD_KEYS: {
|
|
927
|
+
/** Digit row `0`. */
|
|
569
928
|
readonly KEY_0: "Digit0";
|
|
929
|
+
/** Digit row `1`. */
|
|
570
930
|
readonly KEY_1: "Digit1";
|
|
931
|
+
/** Digit row `2`. */
|
|
571
932
|
readonly KEY_2: "Digit2";
|
|
933
|
+
/** Digit row `3`. */
|
|
572
934
|
readonly KEY_3: "Digit3";
|
|
935
|
+
/** Digit row `4`. */
|
|
573
936
|
readonly KEY_4: "Digit4";
|
|
937
|
+
/** Digit row `5`. */
|
|
574
938
|
readonly KEY_5: "Digit5";
|
|
939
|
+
/** Digit row `6`. */
|
|
575
940
|
readonly KEY_6: "Digit6";
|
|
941
|
+
/** Digit row `7`. */
|
|
576
942
|
readonly KEY_7: "Digit7";
|
|
943
|
+
/** Digit row `8`. */
|
|
577
944
|
readonly KEY_8: "Digit8";
|
|
945
|
+
/** Digit row `9`. */
|
|
578
946
|
readonly KEY_9: "Digit9";
|
|
947
|
+
/** Letter `A`. */
|
|
579
948
|
readonly KEY_A: "KeyA";
|
|
949
|
+
/** Letter `B`. */
|
|
580
950
|
readonly KEY_B: "KeyB";
|
|
951
|
+
/** Letter `C`. */
|
|
581
952
|
readonly KEY_C: "KeyC";
|
|
953
|
+
/** Letter `D`. */
|
|
582
954
|
readonly KEY_D: "KeyD";
|
|
955
|
+
/** Down arrow. */
|
|
583
956
|
readonly KEY_DOWN: "ArrowDown";
|
|
957
|
+
/** Letter `E`. */
|
|
584
958
|
readonly KEY_E: "KeyE";
|
|
959
|
+
/** Enter / Return. */
|
|
585
960
|
readonly KEY_ENTER: "Enter";
|
|
961
|
+
/** Escape. Note: in `Settings.debug` mode this stops the gameloop. */
|
|
586
962
|
readonly KEY_ESCAPE: "Escape";
|
|
963
|
+
/** Letter `F`. */
|
|
587
964
|
readonly KEY_F: "KeyF";
|
|
965
|
+
/** Letter `G`. */
|
|
588
966
|
readonly KEY_G: "KeyG";
|
|
967
|
+
/** Letter `H`. */
|
|
589
968
|
readonly KEY_H: "KeyH";
|
|
969
|
+
/** Letter `I`. */
|
|
590
970
|
readonly KEY_I: "KeyI";
|
|
971
|
+
/** Letter `J`. */
|
|
591
972
|
readonly KEY_J: "KeyJ";
|
|
973
|
+
/** Letter `K`. */
|
|
592
974
|
readonly KEY_K: "KeyK";
|
|
975
|
+
/** Letter `L`. */
|
|
593
976
|
readonly KEY_L: "KeyL";
|
|
977
|
+
/** Left arrow. */
|
|
594
978
|
readonly KEY_LEFT: "ArrowLeft";
|
|
979
|
+
/** Letter `M`. */
|
|
595
980
|
readonly KEY_M: "KeyM";
|
|
981
|
+
/** Letter `N`. */
|
|
596
982
|
readonly KEY_N: "KeyN";
|
|
983
|
+
/** Letter `O`. */
|
|
597
984
|
readonly KEY_O: "KeyO";
|
|
985
|
+
/** Letter `P`. */
|
|
598
986
|
readonly KEY_P: "KeyP";
|
|
987
|
+
/** Letter `Q`. */
|
|
599
988
|
readonly KEY_Q: "KeyQ";
|
|
989
|
+
/** Letter `R`. */
|
|
600
990
|
readonly KEY_R: "KeyR";
|
|
991
|
+
/** Right arrow. */
|
|
601
992
|
readonly KEY_RIGHT: "ArrowRight";
|
|
993
|
+
/** Letter `S`. */
|
|
602
994
|
readonly KEY_S: "KeyS";
|
|
995
|
+
/** Space bar. */
|
|
603
996
|
readonly KEY_SPACE: "Space";
|
|
997
|
+
/** Letter `T`. */
|
|
604
998
|
readonly KEY_T: "KeyT";
|
|
999
|
+
/** Tab. */
|
|
605
1000
|
readonly KEY_TAB: "Tab";
|
|
1001
|
+
/** Letter `U`. */
|
|
606
1002
|
readonly KEY_U: "KeyU";
|
|
1003
|
+
/** Up arrow. */
|
|
607
1004
|
readonly KEY_UP: "ArrowUp";
|
|
1005
|
+
/** Letter `V`. */
|
|
608
1006
|
readonly KEY_V: "KeyV";
|
|
1007
|
+
/** Letter `W`. */
|
|
609
1008
|
readonly KEY_W: "KeyW";
|
|
1009
|
+
/** Letter `X`. */
|
|
610
1010
|
readonly KEY_X: "KeyX";
|
|
1011
|
+
/** Letter `Y`. */
|
|
611
1012
|
readonly KEY_Y: "KeyY";
|
|
1013
|
+
/** Letter `Z`. */
|
|
612
1014
|
readonly KEY_Z: "KeyZ";
|
|
613
1015
|
};
|
|
1016
|
+
/**
|
|
1017
|
+
* Keyboard state. Wired into `Game` automatically. The preferred way to consume input is to poll {@link isPressed} from `update` — game input is held-state-based ("is W held this frame?"), and combining with {@link stopPress} handles one-shot actions cleanly. The {@link EventSystem} `"inputKeyboard"` event (payload: `(keys, code, pressed)`) is available for cases that genuinely need edge-triggered handling.
|
|
1018
|
+
*
|
|
1019
|
+
* State is cleared on `window` blur and on `gameloopStopped` so held keys don't stay "pressed" when focus or the loop is lost. In `Settings.debug` mode, pressing Escape stops the gameloop.
|
|
1020
|
+
*/
|
|
614
1021
|
export declare class Keyboard {
|
|
1022
|
+
/** Live map of `KeyboardEvent.code` → pressed state. Codes only appear after the key has been touched at least once; missing codes read as `undefined` (use {@link isPressed} for a safe `boolean`). */
|
|
615
1023
|
keys: Record<string, boolean>;
|
|
616
1024
|
constructor(game: Game);
|
|
1025
|
+
/** Mark every tracked key as released. Called automatically on `window` blur and on `gameloopStopped`. */
|
|
617
1026
|
reset(): void;
|
|
1027
|
+
/** Mark a single key as released — used to consume a press so subsequent ticks don't re-trigger one-shot actions while the key is still held. */
|
|
618
1028
|
stopPress(code: string): void;
|
|
1029
|
+
/** `true` when `code` is currently held. Safe for untouched keys (returns `false` rather than `undefined`). */
|
|
619
1030
|
isPressed(code: string): boolean;
|
|
620
1031
|
}
|
|
1032
|
+
/** Subset of writable Settings fields that {@link Settings.init} accepts (everything except the methods and persisted-storage view). Pass to `super()` when subclassing {@link Game}. */
|
|
621
1033
|
export type SettingsOverrides = Partial<Omit<typeof Settings, "prototype" | "init" | "setLocalStorage" | "localStorage">>;
|
|
1034
|
+
/** Engine-wide configuration. A static-class singleton — read/write top-level fields directly (`Settings.fps = 1 / 30`). Initialised once via {@link init} from `Game`'s constructor; calling `init` twice throws. */
|
|
622
1035
|
export declare class Settings {
|
|
1036
|
+
/** Enable smoothing on the main canvas context. Default `false` for crisp pixel art. */
|
|
623
1037
|
static antialias: boolean;
|
|
1038
|
+
/** Start the gameloop automatically after `init()` resolves. Disable to drive `gameloop.startLoop()` manually. Default `true`. */
|
|
624
1039
|
static autoloop: boolean;
|
|
1040
|
+
/** CSS color used when {@link useClearRect} is `false`. Default `"#444"`. */
|
|
625
1041
|
static backgroundColor: string;
|
|
1042
|
+
/** Debug mode: assigns the `Game` instance to `window.game` and lets {@link Keyboard} Escape stop the loop. Default `false`. */
|
|
626
1043
|
static debug: boolean;
|
|
1044
|
+
/** Skip the per-frame canvas clear. Use for trail/decay effects where you manage clearing yourself. Default `false`. */
|
|
627
1045
|
static doNotClear: boolean;
|
|
1046
|
+
/** Stretch the main canvas to fill the window on resize while preserving its aspect ratio. Default `true`. */
|
|
628
1047
|
static enableResize: boolean;
|
|
1048
|
+
/** Default font family for `canman.setFontSize`. Default `"Arial"`. */
|
|
629
1049
|
static font: string;
|
|
1050
|
+
/** **Seconds per fixed step**, not frames per second — `1 / 60` = 60 Hz, `1 / 30` = 30 Hz. Must be finite and `> 0` or {@link init} throws. */
|
|
630
1051
|
static fps: number;
|
|
1052
|
+
/** Callback invoked from the `beforeunload` handler when {@link warnBeforeClose} is `true`. Useful for "are you sure?" autosave logic. */
|
|
631
1053
|
static triedToClose?: () => void;
|
|
1054
|
+
/** Clear the canvas with `clearRect` (transparent) when `true`, or `fillRect` with {@link backgroundColor} when `false`. Default `true`. */
|
|
632
1055
|
static useClearRect: boolean;
|
|
1056
|
+
/** Show a browser "are you sure?" dialog on tab close. Required for {@link triedToClose} to fire. Default `false`. */
|
|
633
1057
|
static warnBeforeClose: boolean;
|
|
634
1058
|
private static initialized;
|
|
635
1059
|
private static readonly _localStorage;
|
|
1060
|
+
/** Read-only view of the persisted localStorage blob. Writes go through {@link setLocalStorage}. */
|
|
636
1061
|
static get localStorage(): Readonly<typeof Settings._localStorage>;
|
|
1062
|
+
/** One-time setup — called by `Game`'s constructor with the overrides passed to `super()`. Validates {@link fps}, loads the persisted localStorage blob, derives `language` from `navigator.language`, and wires the close-warning handler if {@link warnBeforeClose}. Throws if called twice or if `fps` isn't a finite positive number. */
|
|
637
1063
|
static init(overrides: SettingsOverrides, game: Game): void;
|
|
1064
|
+
/** Typed setter for the persisted localStorage blob. Writes both in-memory and to actual `localStorage` (under a single JSON key — `"gleam"`). The only supported way to mutate persisted state. */
|
|
638
1065
|
static setLocalStorage<K extends keyof typeof Settings._localStorage>(key: K, value: (typeof Settings._localStorage)[K]): void;
|
|
639
1066
|
}
|
|
640
1067
|
declare global {
|
|
@@ -643,7 +1070,11 @@ declare global {
|
|
|
643
1070
|
t(key: string): string;
|
|
644
1071
|
}
|
|
645
1072
|
}
|
|
1073
|
+
/** Translation tables keyed by `languageCode → translationKey → text` (e.g. `{ en: { hello: "Hi" }, de: { hello: "Hallo" } }`). */
|
|
646
1074
|
export type Languages = Record<string, Record<string, string>>;
|
|
1075
|
+
/**
|
|
1076
|
+
* Install the global `window.t(key)` translator. Picks the active language from `Settings.localStorage.language` (seeded from `navigator.language` and overridable via `Settings.setLocalStorage("language", ...)`). Falls back to `defaultLanguage` when the active language isn't registered; returns the key itself when a translation is missing. Both fallback cases log a throttled `console.warn`. Logs `console.error` per missing key during preparation if some languages don't cover every key. Throws if `defaultLanguage` isn't in `languages`.
|
|
1077
|
+
*/
|
|
647
1078
|
export declare function prepareLanguage(languages: Languages, defaultLanguage?: string): void;
|
|
648
1079
|
declare global {
|
|
649
1080
|
interface HTMLAudioElement {
|
|
@@ -791,141 +1222,168 @@ declare global {
|
|
|
791
1222
|
subImage(x: number, y: number, w?: number, h?: number): HTMLCanvasElement;
|
|
792
1223
|
}
|
|
793
1224
|
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Abstract base for a Gleam game. Subclass it and implement {@link init}, {@link update}, and {@link draw}. The constructor wires up {@link CanvasManager}, {@link Gameloop}, {@link Keyboard}, and {@link Pointer}; the subclass must register at least one canvas with `canman.setupCanvas(...)` and then call {@link preInit} to start everything.
|
|
1227
|
+
*
|
|
1228
|
+
* **Singleton-per-page.** The framework registers global listeners on `window`/`document` and writes `history.scrollRestoration`; multiple instances on the same page will fight each other.
|
|
1229
|
+
*/
|
|
794
1230
|
export declare abstract class Game {
|
|
1231
|
+
/** Canvas registry + 2D context exposure. Register canvases here from the constructor (`canman.setupCanvas(CANVAS_TYPES.MAIN, "#game")`) before calling {@link preInit}. */
|
|
795
1232
|
canman: CanvasManager;
|
|
1233
|
+
/** The fixed-step driver. Started automatically by `preInit` when `Settings.autoloop` is `true`. */
|
|
796
1234
|
gameloop: Gameloop;
|
|
1235
|
+
/** Live keyboard state. See {@link Keyboard}. */
|
|
797
1236
|
keyboard: Keyboard;
|
|
1237
|
+
/** Live pointer (mouse / pen / touch) state. See {@link Pointer}. */
|
|
798
1238
|
pointer: Pointer;
|
|
799
1239
|
private initialized;
|
|
800
1240
|
constructor(settingOverrides?: SettingsOverrides);
|
|
1241
|
+
/** Render the current frame. Called by {@link Gameloop} after the canvas is cleared. Subclasses must override — the default throws. */
|
|
801
1242
|
draw(_context: CanvasRenderingContext2D): void;
|
|
1243
|
+
/** Advance the simulation by `dt` seconds (= `Settings.fps`). Called by {@link Gameloop} zero-or-more times per frame depending on real-time accumulation. Subclasses must override — the default throws. */
|
|
802
1244
|
update(_dt: number): void;
|
|
1245
|
+
/** One-time setup hook (assets, world build) invoked by {@link preInit}. Can be `async`; the loop waits for it to resolve before starting. **Do not call directly** — kick off via {@link preInit} from the constructor. Subclasses must override — the default throws. */
|
|
803
1246
|
init(): Promise<void>;
|
|
1247
|
+
/** Finalise engine wiring and start the loop. Call once from the subclass constructor *after* registering canvases. Steps: `canman.finishSetup()` → install debounced `window.resize` → reset `gameloop.levelTime` → `await this.init()` (if `doInit`) → dispatch `"resized"` → start the loop if `Settings.autoloop`. Throws if called twice. Pass `doInit: false` to skip the `init()` await (useful for tests). */
|
|
804
1248
|
protected preInit(doInit?: boolean): Promise<void>;
|
|
805
1249
|
}
|
|
1250
|
+
/** Button indices that match `PointerEvent.button` and index into {@link Pointer.pressed}. */
|
|
806
1251
|
export declare const POINTER_KEYS: {
|
|
1252
|
+
/** Primary button (left for right-handers). */
|
|
807
1253
|
readonly LEFT: 0;
|
|
1254
|
+
/** Middle button / wheel click. */
|
|
808
1255
|
readonly MIDDLE: 1;
|
|
1256
|
+
/** Secondary button (right for right-handers). */
|
|
809
1257
|
readonly RIGHT: 2;
|
|
1258
|
+
/** "Back" side button (browser back). */
|
|
810
1259
|
readonly PREV: 3;
|
|
1260
|
+
/** "Forward" side button. */
|
|
811
1261
|
readonly FORWARD: 4;
|
|
812
1262
|
};
|
|
1263
|
+
/**
|
|
1264
|
+
* Pointer (mouse / pen / touch) state. Wired into `Game` automatically. The preferred way to consume input is to subscribe to the {@link EventSystem} `"inputPointer"` event — it fires on every move and button transition, with this `Pointer` instance as the payload. If you need the latest state on a frame boundary instead, poll `game.pointer.posScaled` and `game.pointer.pressed[POINTER_KEYS.LEFT]` from `update`.
|
|
1265
|
+
*
|
|
1266
|
+
* Suppresses the browser context menu on right-click globally.
|
|
1267
|
+
*/
|
|
813
1268
|
export declare class Pointer {
|
|
1269
|
+
/** Dirty bit set to `true` on every move and never cleared by the engine — flip it back to `false` after reading to detect "moved since last check". */
|
|
814
1270
|
hasMoved: boolean;
|
|
1271
|
+
/** Last raw `PointerEvent` received. `null` until any pointer event fires. Use for properties not surfaced as Vec2/booleans (pressure, pointerType, etc.). */
|
|
815
1272
|
lastEvent: PointerEvent | null;
|
|
1273
|
+
/** Viewport-space coordinates (`event.clientX/Y` — CSS pixels relative to the page). */
|
|
816
1274
|
posReal: Vec2;
|
|
1275
|
+
/** Previous tick's {@link posReal}. Subtract for a per-frame delta. */
|
|
817
1276
|
posRealLast: Vec2;
|
|
1277
|
+
/** Canvas-space coordinates, mapped from the bounding rect into the main canvas's pixel buffer and clamped to its size. This is the position to use for in-game logic. */
|
|
818
1278
|
posScaled: Vec2;
|
|
1279
|
+
/** Previous tick's {@link posScaled}. */
|
|
819
1280
|
posScaledLast: Vec2;
|
|
1281
|
+
/** Per-button pressed state. Index with {@link POINTER_KEYS} (e.g. `pressed[POINTER_KEYS.LEFT]`). Sparse — unindexed entries are `undefined`, not `false`. */
|
|
820
1282
|
pressed: boolean[];
|
|
821
1283
|
private game;
|
|
822
1284
|
constructor(game: Game);
|
|
1285
|
+
/** Clear all pressed-button state. Called automatically on `window` blur so held buttons don't stay "pressed" forever when focus is lost. */
|
|
823
1286
|
reset(): void;
|
|
824
1287
|
private update;
|
|
825
1288
|
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Type-safe registry of engine events and their payload tuples. Both {@link EventSystem.addEventListener} and {@link EventSystem.dispatchEvent} are generic over this map, so the listener callback and dispatched args are checked against the declared shape.
|
|
1291
|
+
*/
|
|
826
1292
|
export interface GameEventMap {
|
|
1293
|
+
/** Fired by {@link Gameloop} once teardown completes, after `stopLoop()` is called. */
|
|
827
1294
|
gameloopStopped: [
|
|
828
1295
|
];
|
|
1296
|
+
/** Fired by {@link Controller} when a gamepad is connected. Payload is the native `Gamepad`. */
|
|
829
1297
|
inputControllerConnected: [
|
|
830
1298
|
event: Gamepad
|
|
831
1299
|
];
|
|
1300
|
+
/** Fired by {@link Controller} when *our* tracked gamepad disconnects. Other gamepads disconnecting are logged but don't dispatch. */
|
|
832
1301
|
inputControllerDisconnected: [
|
|
833
1302
|
];
|
|
1303
|
+
/** Fired by {@link Keyboard} on every key down/up with the live `keys` map, the `code` that changed, and its new pressed state. */
|
|
834
1304
|
inputKeyboard: [
|
|
835
1305
|
keys: Record<string, boolean>,
|
|
836
1306
|
code: string,
|
|
837
1307
|
pressed: boolean
|
|
838
1308
|
];
|
|
1309
|
+
/** Fired by {@link Pointer} on every move and button transition. Payload is the `Pointer` instance — read `posScaled`/`pressed` from it. */
|
|
839
1310
|
inputPointer: [
|
|
840
1311
|
pointer: Pointer
|
|
841
1312
|
];
|
|
1313
|
+
/** Fired by {@link Game.preInit} once at startup and on every debounced `window.resize` thereafter. The canonical "viewport changed" signal. */
|
|
842
1314
|
resized: [
|
|
843
1315
|
];
|
|
844
1316
|
}
|
|
1317
|
+
/** Options for {@link EventSystem.addEventListener}. */
|
|
845
1318
|
export interface EventSystemOptions {
|
|
1319
|
+
/** Auto-dispose the listener after the first dispatch. */
|
|
846
1320
|
once?: boolean;
|
|
1321
|
+
/** Dispose the listener when the signal aborts. Already-aborted signals make `addEventListener` a no-op. */
|
|
847
1322
|
signal?: AbortSignal;
|
|
848
1323
|
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Synchronous, type-safe pub/sub for engine-wide events. Static-only — call as `EventSystem.addEventListener(...)` / `EventSystem.dispatchEvent(...)`. Event names and payloads are constrained by {@link GameEventMap}.
|
|
1326
|
+
*
|
|
1327
|
+
* Guarantees:
|
|
1328
|
+
*
|
|
1329
|
+
* - Listeners registered during a dispatch are deferred to the next dispatch (won't fire in the round that registered them).
|
|
1330
|
+
* - `once` listeners are removed *before* their callback runs, so nested dispatches and throwing callbacks can't double-fire them.
|
|
1331
|
+
* - A throwing callback is caught and logged (throttled per `eventName:message`); siblings still receive the event.
|
|
1332
|
+
*/
|
|
849
1333
|
export declare class EventSystem {
|
|
850
1334
|
private static eventListener;
|
|
851
1335
|
private static logListenerError;
|
|
852
1336
|
private static nextId;
|
|
1337
|
+
/**
|
|
1338
|
+
* Register a listener for `eventName`. Returns a dispose function — the primary teardown path. Multiple disposers (returned, `once`, `signal.abort`) are idempotent. Use {@link EventSystemOptions} for `once` and `signal` behavior.
|
|
1339
|
+
*/
|
|
853
1340
|
static addEventListener<K extends keyof GameEventMap>(eventName: K, callback: (...args: GameEventMap[K]) => void, options?: EventSystemOptions): () => void;
|
|
1341
|
+
/** Synchronously fire `eventName` with the typed payload. Listeners are invoked in registration order; nested dispatches and self-disposing listeners are handled safely. */
|
|
854
1342
|
static dispatchEvent<K extends keyof GameEventMap>(eventName: K, ...params: GameEventMap[K]): void;
|
|
855
1343
|
}
|
|
1344
|
+
/** String-valued keys of `CSSStyleDeclaration` — the ones safe to assign a `string` value to via {@link CssProxy}. */
|
|
856
1345
|
export type CssStyleKey = {
|
|
857
1346
|
[K in keyof CSSStyleDeclaration]: K extends string ? CSSStyleDeclaration[K] extends string ? K : never : never;
|
|
858
1347
|
}[keyof CSSStyleDeclaration];
|
|
1348
|
+
/** Setter handed to {@link ShakeType.update} that writes a CSS value. Snapshots the prior value on first write per key so it can be restored on dispose. */
|
|
859
1349
|
export type CssProxy = (key: CssStyleKey, value: string) => void;
|
|
1350
|
+
/** Shape for a custom shake recipe. */
|
|
860
1351
|
export interface ShakeType {
|
|
1352
|
+
/** Decay rate applied each frame: `timer -= step * dt`. Higher = shorter shake (3 ≈ ⅓s, 15 ≈ 1/15s). */
|
|
861
1353
|
step: number;
|
|
1354
|
+
/** Per-frame mutator. `time` decays from `1` to `0` over the shake — multiply your intensity by it for a natural fall-off. */
|
|
862
1355
|
update: (updateCss: CssProxy, time: number) => void;
|
|
863
1356
|
}
|
|
1357
|
+
/** Built-in shake recipes. Pass one to {@link Screenshake.shake}. */
|
|
864
1358
|
export declare const SHAKE_TYPES: {
|
|
1359
|
+
/** ~0.33 s wobble combining a small random rotation with a blur fall-off. */
|
|
865
1360
|
NORMAL: {
|
|
1361
|
+
/** Decay rate — see {@link ShakeType.step}. */
|
|
866
1362
|
step: number;
|
|
1363
|
+
/** Per-frame mutator — see {@link ShakeType.update}. */
|
|
867
1364
|
update(updateCss: CssProxy, time: number): void;
|
|
868
1365
|
};
|
|
1366
|
+
/** ~0.07 s impact: blur-only fall-off, no rotation. */
|
|
869
1367
|
FAST: {
|
|
1368
|
+
/** Decay rate — see {@link ShakeType.step}. */
|
|
870
1369
|
step: number;
|
|
1370
|
+
/** Per-frame mutator — see {@link ShakeType.update}. */
|
|
871
1371
|
update(updateCss: CssProxy, time: number): void;
|
|
872
1372
|
};
|
|
873
1373
|
};
|
|
874
1374
|
/**
|
|
875
|
-
*
|
|
876
|
-
*
|
|
1375
|
+
* Shake an element by mutating its inline CSS each rAF tick. One shake per instance — re-calling {@link shake} while one is active returns `null`. The returned dispose function (and natural timer expiry) restores every CSS key the shake touched.
|
|
1376
|
+
*
|
|
1377
|
+
* Only the built-in {@link SHAKE_TYPES} (`NORMAL`, `FAST`) are supported today. Letting callers define their own would be a natural extension — impact pulses, slow rumble, directional jolts — since {@link ShakeType} is already public.
|
|
877
1378
|
*/
|
|
878
1379
|
export declare class Screenshake {
|
|
879
1380
|
private isShaking;
|
|
880
1381
|
private shakeType;
|
|
881
1382
|
private style;
|
|
882
1383
|
constructor(element: HTMLElement);
|
|
883
|
-
/**
|
|
1384
|
+
/** Start a shake of the given `shakeType`. Returns a dispose function that stops the shake early and restores every CSS key it touched, or `null` if a shake is already active on this instance. Auto-stops and restores when the timer reaches zero. */
|
|
884
1385
|
shake(shakeType?: ShakeType): null | (() => void);
|
|
885
1386
|
}
|
|
886
|
-
export declare class ControllerCursor {
|
|
887
|
-
private axisId;
|
|
888
|
-
private controller;
|
|
889
|
-
private game;
|
|
890
|
-
private pos;
|
|
891
|
-
get centerPos(): Vec2;
|
|
892
|
-
constructor(controller: Controller, game: Game, axisId: number);
|
|
893
|
-
draw(context: CanvasRenderingContext2D): void;
|
|
894
|
-
update(dt: number): void;
|
|
895
|
-
}
|
|
896
|
-
export declare const CONTROLLER_KEYS: {
|
|
897
|
-
readonly A: 0;
|
|
898
|
-
readonly B: 1;
|
|
899
|
-
readonly X: 2;
|
|
900
|
-
readonly Y: 3;
|
|
901
|
-
readonly LB: 4;
|
|
902
|
-
readonly RB: 5;
|
|
903
|
-
readonly LT: 6;
|
|
904
|
-
readonly RT: 7;
|
|
905
|
-
readonly SELECT: 8;
|
|
906
|
-
readonly START: 9;
|
|
907
|
-
readonly LEFT_STICK: 10;
|
|
908
|
-
readonly RIGHT_STICK: 11;
|
|
909
|
-
readonly UP: 12;
|
|
910
|
-
readonly DOWN: 13;
|
|
911
|
-
readonly LEFT: 14;
|
|
912
|
-
readonly RIGHT: 15;
|
|
913
|
-
readonly GUIDE: 16;
|
|
914
|
-
};
|
|
915
|
-
export declare class Controller {
|
|
916
|
-
buttons: boolean[];
|
|
917
|
-
cursors: ControllerCursor[];
|
|
918
|
-
private axes;
|
|
919
|
-
private index;
|
|
920
|
-
private lastTime;
|
|
921
|
-
constructor(game: Game);
|
|
922
|
-
draw(context: CanvasRenderingContext2D): void;
|
|
923
|
-
update(dt: number): void;
|
|
924
|
-
reset(): void;
|
|
925
|
-
vibrate(): boolean;
|
|
926
|
-
stick(index: number): Vec2;
|
|
927
|
-
private getGamepad;
|
|
928
|
-
}
|
|
929
1387
|
/**
|
|
930
1388
|
* Validates URL format and protocol before any side effects.
|
|
931
1389
|
* Throws on invalid URLs or disallowed protocols.
|
|
@@ -986,9 +1444,13 @@ export declare function remove<T>(arr: T[], item: T): void;
|
|
|
986
1444
|
* Shuffle array using Fisher-Yates algorithm with custom random signer
|
|
987
1445
|
*/
|
|
988
1446
|
export declare function shuffle<T>(arr: ReadonlyArray<T>): T[];
|
|
1447
|
+
/** `get`/`set` helpers for CSS custom properties (`--name`) on `:root`. Build via {@link initCSSVariables}. */
|
|
989
1448
|
export interface CSSVariables {
|
|
1449
|
+
/** The `:root` element these helpers read from / write to. */
|
|
990
1450
|
root: HTMLElement;
|
|
1451
|
+
/** Read the computed value of `--${name}`. */
|
|
991
1452
|
get(name: string): string;
|
|
1453
|
+
/** Write `value` to `--${name}` on the root element's inline style. */
|
|
992
1454
|
set(name: string, value: string): void;
|
|
993
1455
|
}
|
|
994
1456
|
/**
|
|
@@ -1074,6 +1536,7 @@ export declare function convert1DTo2D(index: number, width: number): Vector2;
|
|
|
1074
1536
|
* Convert 2D `(x, y)` coordinates to a 1D index for a grid of the given row width.
|
|
1075
1537
|
*/
|
|
1076
1538
|
export declare function convert2DTo1D(indexX: number, indexY: number, width: number): number;
|
|
1539
|
+
/** Primitive cell types accepted by {@link generateGrid}'s overload that takes a default value (used so every cell can safely share the same primitive without aliasing). */
|
|
1077
1540
|
export type GridPrimitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
1078
1541
|
/**
|
|
1079
1542
|
* Generate a `height × width` 2D grid; every cell holds `defaultValue`. Restricted to primitives at the type level — for object/array cells use the factory overload to avoid every cell sharing the same reference.
|
|
@@ -1178,6 +1641,7 @@ export declare function toDotted(value: number): string;
|
|
|
1178
1641
|
* Bounds are swapped if passed in reverse order. Throws when `min ≈ max` (degenerate range, via `approxEqual`).
|
|
1179
1642
|
*/
|
|
1180
1643
|
export declare function wrapValue(value: number, min: number, max: number): number;
|
|
1644
|
+
/** Conditional helper used by {@link defineMethod}: given `T[K]` is a function, produces a corresponding function type with `this: T` bound to the prototype owner. Non-function members resolve to `never` so prototype patches can't target accessors or fields by mistake. */
|
|
1181
1645
|
export type Method<T, K extends keyof T> = T[K] extends (...args: infer A) => infer R ? (this: T, ...args: A) => R : never;
|
|
1182
1646
|
/**
|
|
1183
1647
|
* Define `name` as a non-enumerable method on `proto`. Carries the declared signature so impl `this` and parameters are inferred from the merged declaration.
|