minimojs 1.0.0-alpha.5 → 1.0.0-alpha.6

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.
Files changed (37) hide show
  1. package/README.md +1 -0
  2. package/dist/internal/AnimationSystem.js +332 -0
  3. package/dist/internal/CanvasSystem.d.ts +1 -0
  4. package/dist/internal/CanvasSystem.js +37 -0
  5. package/dist/internal/ExplosionSystem.d.ts +1 -0
  6. package/dist/internal/ExplosionSystem.js +214 -0
  7. package/dist/internal/InputSystem.d.ts +1 -0
  8. package/dist/internal/InputSystem.js +247 -0
  9. package/dist/internal/LoopSystem.d.ts +1 -0
  10. package/dist/internal/LoopSystem.js +52 -0
  11. package/dist/internal/PhysicsSystem.d.ts +1 -0
  12. package/dist/internal/PhysicsSystem.js +165 -0
  13. package/dist/internal/RenderSystem.d.ts +1 -0
  14. package/dist/internal/RenderSystem.js +185 -0
  15. package/dist/internal/SoundSystem.d.ts +1 -0
  16. package/dist/internal/SoundSystem.js +53 -0
  17. package/dist/internal/SpriteSystem.d.ts +1 -0
  18. package/dist/internal/SpriteSystem.js +27 -0
  19. package/dist/internal/TextSystem.d.ts +1 -0
  20. package/dist/internal/TextSystem.js +15 -0
  21. package/dist/internal/TimerSystem.d.ts +1 -0
  22. package/dist/internal/TimerSystem.js +41 -0
  23. package/dist/internal/TrailSystem.d.ts +1 -0
  24. package/dist/internal/TrailSystem.js +111 -0
  25. package/dist/minimo.d.ts +291 -20
  26. package/dist/minimo.js +445 -737
  27. package/package.json +1 -1
  28. package/dist/animations.js +0 -30
  29. package/dist/audio.js +0 -17
  30. package/dist/game.js +0 -1105
  31. package/dist/input.js +0 -185
  32. package/dist/internal-types.js +0 -4
  33. package/dist/physics.js +0 -10
  34. package/dist/render.js +0 -75
  35. package/dist/sprite.js +0 -149
  36. package/dist/timers.js +0 -23
  37. /package/dist/{pointer-info.js → internal/AnimationSystem.d.ts} +0 -0
package/README.md CHANGED
@@ -70,6 +70,7 @@ Note: `drawText()` uses `"Press Start 2P", monospace`. Load the font in your HTM
70
70
  - `examples/space-invader/`
71
71
  - `examples/super-minimo-bros/`
72
72
  - `examples/scale-shift/`
73
+ - `examples/animations/`
73
74
 
74
75
  Run locally from the `minimojs` directory:
75
76
 
@@ -0,0 +1,332 @@
1
+ /** @internal */
2
+ export class AnimationSystem {
3
+ constructor() {
4
+ this._animations = [];
5
+ this._deformAnimations = [];
6
+ this._motionAnimations = [];
7
+ this._shakeAnimations = [];
8
+ this._blinkAnimations = [];
9
+ this._flickerAnimations = [];
10
+ this._renderDataSprites = new Set();
11
+ }
12
+ update(dtMs) {
13
+ const animations = this._animations;
14
+ const toRemove = [];
15
+ for (let i = 0; i < animations.length; i++) {
16
+ const anim = animations[i];
17
+ anim.elapsed += dtMs;
18
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
19
+ anim.sprite[anim.property] = anim.from + (anim.to - anim.from) * t;
20
+ if (t >= 1) {
21
+ toRemove.push(i);
22
+ anim.onComplete?.();
23
+ }
24
+ }
25
+ for (let i = toRemove.length - 1; i >= 0; i--) {
26
+ animations.splice(toRemove[i], 1);
27
+ }
28
+ const deformAnimations = this._deformAnimations;
29
+ const deformToRemove = [];
30
+ for (let i = 0; i < deformAnimations.length; i++) {
31
+ const anim = deformAnimations[i];
32
+ anim.elapsed += dtMs;
33
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
34
+ if (t >= 1) {
35
+ deformToRemove.push(i);
36
+ anim.onComplete?.();
37
+ }
38
+ }
39
+ for (let i = deformToRemove.length - 1; i >= 0; i--) {
40
+ deformAnimations.splice(deformToRemove[i], 1);
41
+ }
42
+ const motionAnimations = this._motionAnimations;
43
+ const motionToRemove = [];
44
+ for (let i = 0; i < motionAnimations.length; i++) {
45
+ const anim = motionAnimations[i];
46
+ anim.elapsed += dtMs;
47
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
48
+ if (anim.kind === "arc") {
49
+ anim.sprite.x = anim.fromX + (anim.toX - anim.fromX) * t;
50
+ anim.sprite.y =
51
+ anim.fromY +
52
+ (anim.toY - anim.fromY) * t -
53
+ anim.magnitude * 4 * t * (1 - t);
54
+ }
55
+ else if (anim.kind === "bounce") {
56
+ anim.sprite.x = anim.fromX;
57
+ anim.sprite.y = anim.fromY - anim.magnitude * 4 * t * (1 - t);
58
+ }
59
+ else {
60
+ anim.sprite.x = anim.fromX;
61
+ anim.sprite.y = anim.fromY - Math.sin(t * Math.PI) * anim.magnitude;
62
+ }
63
+ if (t >= 1) {
64
+ motionToRemove.push(i);
65
+ anim.onComplete?.();
66
+ }
67
+ }
68
+ for (let i = motionToRemove.length - 1; i >= 0; i--) {
69
+ motionAnimations.splice(motionToRemove[i], 1);
70
+ }
71
+ this.rebuildRenderData(dtMs);
72
+ }
73
+ animateAlpha(sprite, to, durationMs, onComplete) {
74
+ const animations = this._animations.filter((a) => !(a.sprite === sprite && a.property === "alpha"));
75
+ animations.push({
76
+ sprite,
77
+ property: "alpha",
78
+ from: sprite.alpha,
79
+ to,
80
+ durationMs,
81
+ elapsed: 0,
82
+ onComplete,
83
+ });
84
+ this._animations = animations;
85
+ }
86
+ animateRotation(sprite, to, durationMs, onComplete) {
87
+ const animations = this._animations.filter((a) => !(a.sprite === sprite && a.property === "rotation"));
88
+ animations.push({
89
+ sprite,
90
+ property: "rotation",
91
+ from: sprite.rotation,
92
+ to,
93
+ durationMs,
94
+ elapsed: 0,
95
+ onComplete,
96
+ });
97
+ this._animations = animations;
98
+ }
99
+ animateDeform(sprite, toScaleX, toScaleY, pivotX, pivotY, durationMs, onComplete) {
100
+ const current = sprite._renderData;
101
+ const fromScaleX = current?.scaleX ?? 1;
102
+ const fromScaleY = current?.scaleY ?? 1;
103
+ const deformAnimations = this._deformAnimations.filter((a) => a.sprite !== sprite);
104
+ deformAnimations.push({
105
+ sprite,
106
+ fromScaleX,
107
+ fromScaleY,
108
+ toScaleX: this.sanitizeScale(toScaleX),
109
+ toScaleY: this.sanitizeScale(toScaleY),
110
+ pivotX: this.sanitizePivot(pivotX),
111
+ pivotY: this.sanitizePivot(pivotY),
112
+ durationMs,
113
+ elapsed: 0,
114
+ onComplete,
115
+ });
116
+ this._deformAnimations = deformAnimations;
117
+ }
118
+ animateShake(sprite, intensity, durationMs, onComplete) {
119
+ const animations = this._shakeAnimations.filter((a) => a.sprite !== sprite);
120
+ animations.push({
121
+ sprite,
122
+ intensity: this.sanitizeNonNegative(intensity),
123
+ durationMs: this.sanitizeDuration(durationMs),
124
+ elapsed: 0,
125
+ onComplete,
126
+ });
127
+ this._shakeAnimations = animations;
128
+ }
129
+ animateBounce(sprite, height, durationMs, onComplete) {
130
+ this.replaceMotionAnimation({
131
+ sprite,
132
+ kind: "bounce",
133
+ fromX: sprite.x,
134
+ fromY: sprite.y,
135
+ toX: sprite.x,
136
+ toY: sprite.y,
137
+ magnitude: this.sanitizeNonNegative(height),
138
+ durationMs: this.sanitizeDuration(durationMs),
139
+ elapsed: 0,
140
+ onComplete,
141
+ });
142
+ }
143
+ animateFloat(sprite, distance, durationMs, onComplete) {
144
+ this.replaceMotionAnimation({
145
+ sprite,
146
+ kind: "float",
147
+ fromX: sprite.x,
148
+ fromY: sprite.y,
149
+ toX: sprite.x,
150
+ toY: sprite.y,
151
+ magnitude: this.sanitizeNonNegative(distance),
152
+ durationMs: this.sanitizeDuration(durationMs),
153
+ elapsed: 0,
154
+ onComplete,
155
+ });
156
+ }
157
+ animateArc(sprite, toX, toY, arcHeight, durationMs, onComplete) {
158
+ this.replaceMotionAnimation({
159
+ sprite,
160
+ kind: "arc",
161
+ fromX: sprite.x,
162
+ fromY: sprite.y,
163
+ toX,
164
+ toY,
165
+ magnitude: this.sanitizeNonNegative(arcHeight),
166
+ durationMs: this.sanitizeDuration(durationMs),
167
+ elapsed: 0,
168
+ onComplete,
169
+ });
170
+ }
171
+ animateBlink(sprite, times, durationMs, onComplete) {
172
+ const animations = this._blinkAnimations.filter((a) => a.sprite !== sprite);
173
+ animations.push({
174
+ sprite,
175
+ times: Math.max(1, Math.round(Number.isFinite(times) ? times : 1)),
176
+ durationMs: this.sanitizeDuration(durationMs),
177
+ elapsed: 0,
178
+ onComplete,
179
+ });
180
+ this._blinkAnimations = animations;
181
+ }
182
+ animateFlicker(sprite, durationMs, onComplete) {
183
+ const animations = this._flickerAnimations.filter((a) => a.sprite !== sprite);
184
+ animations.push({
185
+ sprite,
186
+ durationMs: this.sanitizeDuration(durationMs),
187
+ elapsed: 0,
188
+ onComplete,
189
+ });
190
+ this._flickerAnimations = animations;
191
+ }
192
+ clearSpriteAnimations(sprite) {
193
+ this._animations = this._animations.filter((a) => a.sprite !== sprite);
194
+ this._deformAnimations = this._deformAnimations.filter((a) => a.sprite !== sprite);
195
+ this._motionAnimations = this._motionAnimations.filter((a) => a.sprite !== sprite);
196
+ this._shakeAnimations = this._shakeAnimations.filter((a) => a.sprite !== sprite);
197
+ this._blinkAnimations = this._blinkAnimations.filter((a) => a.sprite !== sprite);
198
+ this._flickerAnimations = this._flickerAnimations.filter((a) => a.sprite !== sprite);
199
+ this._renderDataSprites.delete(sprite);
200
+ sprite._renderData = null;
201
+ }
202
+ clearAll() {
203
+ for (const sprite of this._renderDataSprites) {
204
+ sprite._renderData = null;
205
+ }
206
+ this._animations = [];
207
+ this._deformAnimations = [];
208
+ this._motionAnimations = [];
209
+ this._shakeAnimations = [];
210
+ this._blinkAnimations = [];
211
+ this._flickerAnimations = [];
212
+ this._renderDataSprites.clear();
213
+ }
214
+ sanitizeScale(value) {
215
+ if (!Number.isFinite(value))
216
+ return 1;
217
+ return Math.max(0, value);
218
+ }
219
+ sanitizeDuration(value) {
220
+ if (!Number.isFinite(value))
221
+ return 1;
222
+ return Math.max(1, value);
223
+ }
224
+ sanitizeNonNegative(value) {
225
+ if (!Number.isFinite(value))
226
+ return 0;
227
+ return Math.max(0, value);
228
+ }
229
+ sanitizePivot(value) {
230
+ if (!Number.isFinite(value))
231
+ return 0.5;
232
+ return Math.max(0, Math.min(1, value));
233
+ }
234
+ replaceMotionAnimation(entry) {
235
+ const animations = this._motionAnimations.filter((a) => a.sprite !== entry.sprite);
236
+ animations.push(entry);
237
+ this._motionAnimations = animations;
238
+ }
239
+ rebuildRenderData(dtMs) {
240
+ const next = new Map();
241
+ const nextSprites = new Set();
242
+ const upsert = (sprite) => {
243
+ let state = next.get(sprite);
244
+ if (!state) {
245
+ state = {
246
+ scaleX: 1,
247
+ scaleY: 1,
248
+ pivotX: 0.5,
249
+ pivotY: 0.5,
250
+ offsetX: 0,
251
+ offsetY: 0,
252
+ alphaMultiplier: 1,
253
+ };
254
+ next.set(sprite, state);
255
+ nextSprites.add(sprite);
256
+ }
257
+ return state;
258
+ };
259
+ for (const anim of this._deformAnimations) {
260
+ const state = upsert(anim.sprite);
261
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
262
+ state.scaleX = anim.fromScaleX + (anim.toScaleX - anim.fromScaleX) * t;
263
+ state.scaleY = anim.fromScaleY + (anim.toScaleY - anim.fromScaleY) * t;
264
+ state.pivotX = anim.pivotX;
265
+ state.pivotY = anim.pivotY;
266
+ }
267
+ const shakeToRemove = [];
268
+ for (let i = 0; i < this._shakeAnimations.length; i++) {
269
+ const anim = this._shakeAnimations[i];
270
+ anim.elapsed += dtMs;
271
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
272
+ const decay = 1 - t;
273
+ const state = upsert(anim.sprite);
274
+ state.offsetX += (Math.random() * 2 - 1) * anim.intensity * decay;
275
+ state.offsetY += (Math.random() * 2 - 1) * anim.intensity * decay;
276
+ if (t >= 1) {
277
+ shakeToRemove.push(i);
278
+ anim.onComplete?.();
279
+ }
280
+ }
281
+ for (let i = shakeToRemove.length - 1; i >= 0; i--) {
282
+ this._shakeAnimations.splice(shakeToRemove[i], 1);
283
+ }
284
+ const blinkToRemove = [];
285
+ for (let i = 0; i < this._blinkAnimations.length; i++) {
286
+ const anim = this._blinkAnimations[i];
287
+ anim.elapsed += dtMs;
288
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
289
+ const state = upsert(anim.sprite);
290
+ const step = Math.floor(t * anim.times * 2);
291
+ state.alphaMultiplier *= step % 2 === 0 ? 1 : 0;
292
+ if (t >= 1) {
293
+ blinkToRemove.push(i);
294
+ anim.onComplete?.();
295
+ }
296
+ }
297
+ for (let i = blinkToRemove.length - 1; i >= 0; i--) {
298
+ this._blinkAnimations.splice(blinkToRemove[i], 1);
299
+ }
300
+ const flickerToRemove = [];
301
+ for (let i = 0; i < this._flickerAnimations.length; i++) {
302
+ const anim = this._flickerAnimations[i];
303
+ anim.elapsed += dtMs;
304
+ const t = Math.min(anim.elapsed / anim.durationMs, 1);
305
+ const state = upsert(anim.sprite);
306
+ state.alphaMultiplier *= 0.25 + Math.random() * 0.75;
307
+ if (t >= 1) {
308
+ flickerToRemove.push(i);
309
+ anim.onComplete?.();
310
+ }
311
+ }
312
+ for (let i = flickerToRemove.length - 1; i >= 0; i--) {
313
+ this._flickerAnimations.splice(flickerToRemove[i], 1);
314
+ }
315
+ for (const sprite of this._renderDataSprites) {
316
+ if (!nextSprites.has(sprite)) {
317
+ sprite._renderData = null;
318
+ }
319
+ }
320
+ for (const [sprite, state] of next.entries()) {
321
+ const isDefault = state.scaleX === 1 &&
322
+ state.scaleY === 1 &&
323
+ state.pivotX === 0.5 &&
324
+ state.pivotY === 0.5 &&
325
+ state.offsetX === 0 &&
326
+ state.offsetY === 0 &&
327
+ state.alphaMultiplier === 1;
328
+ sprite._renderData = isDefault ? null : state;
329
+ }
330
+ this._renderDataSprites = nextSprites;
331
+ }
332
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ /** @internal */
2
+ export class CanvasSystem {
3
+ constructor(canvas) {
4
+ this.canvas = canvas;
5
+ this.onResize = () => this.applyResponsiveCanvasLayout();
6
+ }
7
+ initialize() {
8
+ const mountCanvas = () => {
9
+ if (document.body && !this.canvas.isConnected) {
10
+ document.body.appendChild(this.canvas);
11
+ }
12
+ this.applyResponsiveCanvasLayout();
13
+ };
14
+ if (document.body) {
15
+ mountCanvas();
16
+ }
17
+ else {
18
+ window.addEventListener("DOMContentLoaded", mountCanvas, { once: true });
19
+ }
20
+ window.addEventListener("resize", this.onResize);
21
+ }
22
+ applyResponsiveCanvasLayout() {
23
+ if (!document.body)
24
+ return;
25
+ document.body.style.margin = "0";
26
+ document.body.style.minHeight = "100vh";
27
+ document.body.style.display = "grid";
28
+ document.body.style.placeItems = "center";
29
+ document.body.style.touchAction = "manipulation";
30
+ const viewportW = Math.max(1, window.innerWidth);
31
+ const viewportH = Math.max(1, window.innerHeight);
32
+ const scale = Math.min(viewportW / this.canvas.width, viewportH / this.canvas.height);
33
+ const safeScale = Number.isFinite(scale) && scale > 0 ? scale : 1;
34
+ this.canvas.style.width = `${Math.floor(this.canvas.width * safeScale)}px`;
35
+ this.canvas.style.height = `${Math.floor(this.canvas.height * safeScale)}px`;
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,214 @@
1
+ const DEFAULT_OPTIONS = {
2
+ rows: 3,
3
+ cols: 3,
4
+ durationMs: 500,
5
+ speed: 260,
6
+ gravityY: 900,
7
+ spin: 220,
8
+ fade: true,
9
+ destroySprite: true,
10
+ };
11
+ /** @internal */
12
+ export class ExplosionSystem {
13
+ constructor() {
14
+ this._explosions = [];
15
+ }
16
+ animateExplode(sprite, glyphCanvas, options, onComplete) {
17
+ this.clearSpriteExplosions(sprite);
18
+ const rows = this.sanitizeCount(options?.rows, DEFAULT_OPTIONS.rows);
19
+ const cols = this.sanitizeCount(options?.cols, DEFAULT_OPTIONS.cols);
20
+ const durationMs = this.sanitizeDuration(options?.durationMs, DEFAULT_OPTIONS.durationMs);
21
+ const speed = this.sanitizeNonNegative(options?.speed, DEFAULT_OPTIONS.speed);
22
+ const gravityY = this.sanitizeNumber(options?.gravityY, DEFAULT_OPTIONS.gravityY);
23
+ const spin = this.sanitizeNonNegative(options?.spin, DEFAULT_OPTIONS.spin);
24
+ const fade = options?.fade ?? DEFAULT_OPTIONS.fade;
25
+ const destroySprite = options?.destroySprite ?? DEFAULT_OPTIONS.destroySprite;
26
+ const originalVisible = sprite.visible;
27
+ const renderData = sprite._renderData;
28
+ const deformScaleX = this.getSafeScale(renderData?.scaleX);
29
+ const deformScaleY = this.getSafeScale(renderData?.scaleY);
30
+ const pivotX = this.getSafePivot(renderData?.pivotX);
31
+ const pivotY = this.getSafePivot(renderData?.pivotY);
32
+ const offsetX = this.getSafeNumber(renderData?.offsetX, 0);
33
+ const offsetY = this.getSafeNumber(renderData?.offsetY, 0);
34
+ const alphaMultiplier = this.getSafeNumber(renderData?.alphaMultiplier, 1);
35
+ const baseDisplaySize = sprite.displaySize;
36
+ const pivotOffsetX = (pivotX - 0.5) * baseDisplaySize * (1 - deformScaleX);
37
+ const pivotOffsetY = (pivotY - 0.5) * baseDisplaySize * (1 - deformScaleY);
38
+ const signedScaleX = this.getSafeScale(sprite.scale) * deformScaleX * (sprite.flipX ? -1 : 1);
39
+ const signedScaleY = this.getSafeScale(sprite.scale) * deformScaleY * (sprite.flipY ? -1 : 1);
40
+ const absScaleX = Math.abs(signedScaleX);
41
+ const absScaleY = Math.abs(signedScaleY);
42
+ const rotationRad = (sprite.rotation * Math.PI) / 180;
43
+ const baseX = sprite.x + pivotOffsetX + offsetX;
44
+ const baseY = sprite.y + pivotOffsetY + offsetY;
45
+ const baseAlpha = Math.max(0, Math.min(1, sprite.alpha * alphaMultiplier));
46
+ const pieces = [];
47
+ for (let row = 0; row < rows; row++) {
48
+ const sy = Math.floor((row * glyphCanvas.height) / rows);
49
+ const nextSy = Math.floor(((row + 1) * glyphCanvas.height) / rows);
50
+ const sh = Math.max(1, nextSy - sy);
51
+ for (let col = 0; col < cols; col++) {
52
+ const sx = Math.floor((col * glyphCanvas.width) / cols);
53
+ const nextSx = Math.floor(((col + 1) * glyphCanvas.width) / cols);
54
+ const sw = Math.max(1, nextSx - sx);
55
+ const localX = (sx + sw / 2 - glyphCanvas.width / 2) * signedScaleX;
56
+ const localY = (sy + sh / 2 - glyphCanvas.height / 2) * signedScaleY;
57
+ const rotated = this.rotate(localX, localY, rotationRad);
58
+ const velocity = this.getPieceVelocity(rotated.x, rotated.y, speed);
59
+ pieces.push({
60
+ x: baseX + rotated.x,
61
+ y: baseY + rotated.y,
62
+ rotation: sprite.rotation,
63
+ alpha: baseAlpha,
64
+ flipX: sprite.flipX,
65
+ flipY: sprite.flipY,
66
+ sx,
67
+ sy,
68
+ sw,
69
+ sh,
70
+ drawWidth: sw * absScaleX,
71
+ drawHeight: sh * absScaleY,
72
+ vx: velocity.x,
73
+ vy: velocity.y,
74
+ angularVelocity: (Math.random() * 2 - 1) * spin,
75
+ });
76
+ }
77
+ }
78
+ if (!destroySprite) {
79
+ sprite.visible = false;
80
+ }
81
+ this._explosions.push({
82
+ sprite,
83
+ canvas: glyphCanvas,
84
+ layer: sprite.layer,
85
+ ignoreScroll: sprite.ignoreScroll,
86
+ pieces,
87
+ elapsedMs: 0,
88
+ durationMs,
89
+ gravityY,
90
+ fade,
91
+ baseAlpha,
92
+ restoreVisibility: !destroySprite,
93
+ originalVisible,
94
+ onComplete,
95
+ });
96
+ }
97
+ update(dt, dtMs) {
98
+ const explosions = this._explosions;
99
+ const toRemove = [];
100
+ for (let i = 0; i < explosions.length; i++) {
101
+ const explosion = explosions[i];
102
+ explosion.elapsedMs += dtMs;
103
+ const t = Math.min(explosion.elapsedMs / explosion.durationMs, 1);
104
+ for (const piece of explosion.pieces) {
105
+ piece.vy += explosion.gravityY * dt;
106
+ piece.x += piece.vx * dt;
107
+ piece.y += piece.vy * dt;
108
+ piece.rotation += piece.angularVelocity * dt;
109
+ piece.alpha = explosion.fade
110
+ ? explosion.baseAlpha * (1 - t)
111
+ : explosion.baseAlpha;
112
+ }
113
+ if (t >= 1) {
114
+ toRemove.push(i);
115
+ if (explosion.restoreVisibility) {
116
+ explosion.sprite.visible = explosion.originalVisible;
117
+ }
118
+ explosion.onComplete?.();
119
+ }
120
+ }
121
+ for (let i = toRemove.length - 1; i >= 0; i--) {
122
+ explosions.splice(toRemove[i], 1);
123
+ }
124
+ }
125
+ getRenderEntries() {
126
+ return this._explosions;
127
+ }
128
+ clearAll() {
129
+ for (const explosion of this._explosions) {
130
+ if (explosion.restoreVisibility) {
131
+ explosion.sprite.visible = explosion.originalVisible;
132
+ }
133
+ }
134
+ this._explosions = [];
135
+ }
136
+ clearSpriteExplosions(sprite) {
137
+ const remaining = [];
138
+ for (const explosion of this._explosions) {
139
+ if (explosion.sprite === sprite) {
140
+ if (explosion.restoreVisibility) {
141
+ explosion.sprite.visible = explosion.originalVisible;
142
+ }
143
+ }
144
+ else {
145
+ remaining.push(explosion);
146
+ }
147
+ }
148
+ this._explosions = remaining;
149
+ }
150
+ getPieceVelocity(x, y, speed) {
151
+ const magnitude = Math.hypot(x, y);
152
+ let dirX = 0;
153
+ let dirY = 0;
154
+ if (magnitude > 0.001) {
155
+ dirX = x / magnitude;
156
+ dirY = y / magnitude;
157
+ }
158
+ else {
159
+ const angle = Math.random() * Math.PI * 2;
160
+ dirX = Math.cos(angle);
161
+ dirY = Math.sin(angle);
162
+ }
163
+ const jitterAngle = (Math.random() - 0.5) * (Math.PI / 2);
164
+ const jittered = this.rotate(dirX, dirY, jitterAngle);
165
+ const pieceSpeed = speed * (0.65 + Math.random() * 0.7);
166
+ return {
167
+ x: jittered.x * pieceSpeed,
168
+ y: jittered.y * pieceSpeed,
169
+ };
170
+ }
171
+ rotate(x, y, radians) {
172
+ const cos = Math.cos(radians);
173
+ const sin = Math.sin(radians);
174
+ return {
175
+ x: x * cos - y * sin,
176
+ y: x * sin + y * cos,
177
+ };
178
+ }
179
+ getSafeScale(value) {
180
+ if (!Number.isFinite(value))
181
+ return 1;
182
+ return Math.max(0, value);
183
+ }
184
+ getSafePivot(value) {
185
+ if (!Number.isFinite(value))
186
+ return 0.5;
187
+ return Math.max(0, Math.min(1, value));
188
+ }
189
+ getSafeNumber(value, fallback) {
190
+ if (!Number.isFinite(value))
191
+ return fallback;
192
+ return value;
193
+ }
194
+ sanitizeCount(value, fallback) {
195
+ if (!Number.isFinite(value))
196
+ return fallback;
197
+ return Math.max(1, Math.round(value));
198
+ }
199
+ sanitizeDuration(value, fallback) {
200
+ if (!Number.isFinite(value))
201
+ return fallback;
202
+ return Math.max(1, value);
203
+ }
204
+ sanitizeNonNegative(value, fallback) {
205
+ if (!Number.isFinite(value))
206
+ return fallback;
207
+ return Math.max(0, value);
208
+ }
209
+ sanitizeNumber(value, fallback) {
210
+ if (!Number.isFinite(value))
211
+ return fallback;
212
+ return value;
213
+ }
214
+ }
@@ -0,0 +1 @@
1
+ export {};