minimojs 1.0.0-alpha.17 → 1.0.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/AnimationSystem.js +13 -0
- package/dist/internal/AssetSystem.js +4 -0
- package/dist/internal/BackgroundSystem.js +1 -0
- package/dist/internal/CanvasSystem.js +4 -1
- package/dist/internal/ExplosionSystem.js +20 -0
- package/dist/internal/InputSystem.js +19 -1
- package/dist/internal/LoopSystem.js +7 -1
- package/dist/internal/PhysicsSystem.js +5 -0
- package/dist/internal/RenderSystem.js +61 -1
- package/dist/internal/SoundSystem.js +2 -0
- package/dist/internal/SpriteSystem.js +4 -1
- package/dist/internal/TextSystem.js +1 -0
- package/dist/internal/TimerSystem.js +2 -0
- package/dist/internal/TrailSystem.js +5 -0
- package/dist/internal/TransitionSystem.js +4 -0
- package/dist/minimo-arcaderacer.d.ts +735 -0
- package/dist/minimo-arcaderacer.js +1897 -0
- package/dist/minimo.d.ts +0 -13
- package/dist/minimo.js +9 -0
- package/package.json +1 -1
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class AnimationSystem {
|
|
3
3
|
constructor() {
|
|
4
|
+
/** @internal */
|
|
4
5
|
this._animations = [];
|
|
6
|
+
/** @internal */
|
|
5
7
|
this._deformAnimations = [];
|
|
8
|
+
/** @internal */
|
|
6
9
|
this._motionAnimations = [];
|
|
10
|
+
/** @internal */
|
|
7
11
|
this._shakeAnimations = [];
|
|
12
|
+
/** @internal */
|
|
8
13
|
this._blinkAnimations = [];
|
|
14
|
+
/** @internal */
|
|
9
15
|
this._flickerAnimations = [];
|
|
16
|
+
/** @internal */
|
|
10
17
|
this._renderDataSprites = new Set();
|
|
11
18
|
}
|
|
12
19
|
update(dtMs) {
|
|
@@ -211,31 +218,37 @@ export class AnimationSystem {
|
|
|
211
218
|
this._flickerAnimations = [];
|
|
212
219
|
this._renderDataSprites.clear();
|
|
213
220
|
}
|
|
221
|
+
/** @internal */
|
|
214
222
|
sanitizeScale(value) {
|
|
215
223
|
if (!Number.isFinite(value))
|
|
216
224
|
return 1;
|
|
217
225
|
return Math.max(0, value);
|
|
218
226
|
}
|
|
227
|
+
/** @internal */
|
|
219
228
|
sanitizeDuration(value) {
|
|
220
229
|
if (!Number.isFinite(value))
|
|
221
230
|
return 1;
|
|
222
231
|
return Math.max(1, value);
|
|
223
232
|
}
|
|
233
|
+
/** @internal */
|
|
224
234
|
sanitizeNonNegative(value) {
|
|
225
235
|
if (!Number.isFinite(value))
|
|
226
236
|
return 0;
|
|
227
237
|
return Math.max(0, value);
|
|
228
238
|
}
|
|
239
|
+
/** @internal */
|
|
229
240
|
sanitizePivot(value) {
|
|
230
241
|
if (!Number.isFinite(value))
|
|
231
242
|
return 0.5;
|
|
232
243
|
return Math.max(0, Math.min(1, value));
|
|
233
244
|
}
|
|
245
|
+
/** @internal */
|
|
234
246
|
replaceMotionAnimation(entry) {
|
|
235
247
|
const animations = this._motionAnimations.filter((a) => a.sprite !== entry.sprite);
|
|
236
248
|
animations.push(entry);
|
|
237
249
|
this._motionAnimations = animations;
|
|
238
250
|
}
|
|
251
|
+
/** @internal */
|
|
239
252
|
rebuildRenderData(dtMs) {
|
|
240
253
|
const next = new Map();
|
|
241
254
|
const nextSprites = new Set();
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class AssetSystem {
|
|
3
3
|
constructor() {
|
|
4
|
+
/** @internal */
|
|
4
5
|
this._queuedImages = new Map();
|
|
6
|
+
/** @internal */
|
|
5
7
|
this._loadedImages = new Map();
|
|
8
|
+
/** @internal */
|
|
6
9
|
this._imageSources = new Map();
|
|
7
10
|
}
|
|
8
11
|
queueImage(key, src) {
|
|
@@ -51,6 +54,7 @@ export class AssetSystem {
|
|
|
51
54
|
}));
|
|
52
55
|
this._queuedImages.clear();
|
|
53
56
|
}
|
|
57
|
+
/** @internal */
|
|
54
58
|
loadImage(src) {
|
|
55
59
|
return new Promise((resolve, reject) => {
|
|
56
60
|
const image = new Image();
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class CanvasSystem {
|
|
3
|
-
constructor(canvas) {
|
|
3
|
+
constructor(/** @internal */ canvas) {
|
|
4
4
|
this.canvas = canvas;
|
|
5
|
+
/** @internal */
|
|
5
6
|
this.onResize = () => this.applyResponsiveCanvasLayout();
|
|
7
|
+
/** @internal */
|
|
6
8
|
this.onViewportChange = () => this.applyResponsiveCanvasLayout();
|
|
7
9
|
}
|
|
8
10
|
initialize() {
|
|
@@ -22,6 +24,7 @@ export class CanvasSystem {
|
|
|
22
24
|
window.visualViewport?.addEventListener("resize", this.onViewportChange);
|
|
23
25
|
window.visualViewport?.addEventListener("scroll", this.onViewportChange);
|
|
24
26
|
}
|
|
27
|
+
/** @internal */
|
|
25
28
|
applyResponsiveCanvasLayout() {
|
|
26
29
|
if (!document.body)
|
|
27
30
|
return;
|
|
@@ -45,7 +45,9 @@ const DEFAULT_WIPE_OPTIONS = {
|
|
|
45
45
|
/** @internal */
|
|
46
46
|
export class ExplosionSystem {
|
|
47
47
|
constructor() {
|
|
48
|
+
/** @internal */
|
|
48
49
|
this._pieceEffects = [];
|
|
50
|
+
/** @internal */
|
|
49
51
|
this._wipes = [];
|
|
50
52
|
}
|
|
51
53
|
animateExplode(sprite, glyphCanvas, options, onComplete) {
|
|
@@ -190,6 +192,7 @@ export class ExplosionSystem {
|
|
|
190
192
|
this._pieceEffects = [];
|
|
191
193
|
this._wipes = [];
|
|
192
194
|
}
|
|
195
|
+
/** @internal */
|
|
193
196
|
spawnRadialEffect(mode, sprite, glyphCanvas, context, options, forceHideSprite, restoreVisibility, finalVisible, onComplete) {
|
|
194
197
|
this.clearSpriteEffects(sprite);
|
|
195
198
|
if (forceHideSprite || restoreVisibility) {
|
|
@@ -241,6 +244,7 @@ export class ExplosionSystem {
|
|
|
241
244
|
onComplete,
|
|
242
245
|
});
|
|
243
246
|
}
|
|
247
|
+
/** @internal */
|
|
244
248
|
spawnDirectionalEffect(mode, sprite, glyphCanvas, context, options, forceHideSprite, restoreVisibility, finalVisible, onComplete) {
|
|
245
249
|
this.clearSpriteEffects(sprite);
|
|
246
250
|
if (forceHideSprite || restoreVisibility) {
|
|
@@ -298,6 +302,7 @@ export class ExplosionSystem {
|
|
|
298
302
|
onComplete,
|
|
299
303
|
});
|
|
300
304
|
}
|
|
305
|
+
/** @internal */
|
|
301
306
|
buildPieceGrid(glyphCanvas, context, rows, cols, builder, mode) {
|
|
302
307
|
const pieces = [];
|
|
303
308
|
const rotationRad = (context.rotation * Math.PI) / 180;
|
|
@@ -362,6 +367,7 @@ export class ExplosionSystem {
|
|
|
362
367
|
}
|
|
363
368
|
return pieces;
|
|
364
369
|
}
|
|
370
|
+
/** @internal */
|
|
365
371
|
buildPieceSpriteContext(sprite, glyphCanvas) {
|
|
366
372
|
const renderData = sprite._renderData;
|
|
367
373
|
const deformScaleX = this.getSafeScale(renderData?.scaleX);
|
|
@@ -392,6 +398,7 @@ export class ExplosionSystem {
|
|
|
392
398
|
absScaleY: Math.abs(signedScaleY),
|
|
393
399
|
};
|
|
394
400
|
}
|
|
401
|
+
/** @internal */
|
|
395
402
|
clearSpriteEffects(sprite) {
|
|
396
403
|
const remainingPieces = [];
|
|
397
404
|
for (const effect of this._pieceEffects) {
|
|
@@ -418,6 +425,7 @@ export class ExplosionSystem {
|
|
|
418
425
|
}
|
|
419
426
|
this._wipes = remainingWipes;
|
|
420
427
|
}
|
|
428
|
+
/** @internal */
|
|
421
429
|
getDirectionalProgress(row, col, rows, cols, direction) {
|
|
422
430
|
const rowProgress = rows <= 1 ? 0 : row / (rows - 1);
|
|
423
431
|
const colProgress = cols <= 1 ? 0 : col / (cols - 1);
|
|
@@ -432,6 +440,7 @@ export class ExplosionSystem {
|
|
|
432
440
|
return 1 - rowProgress;
|
|
433
441
|
}
|
|
434
442
|
}
|
|
443
|
+
/** @internal */
|
|
435
444
|
getDirectionVector(direction) {
|
|
436
445
|
switch (direction) {
|
|
437
446
|
case "left-to-right":
|
|
@@ -444,6 +453,7 @@ export class ExplosionSystem {
|
|
|
444
453
|
return { x: 0, y: -1 };
|
|
445
454
|
}
|
|
446
455
|
}
|
|
456
|
+
/** @internal */
|
|
447
457
|
getRadialVelocity(x, y) {
|
|
448
458
|
const magnitude = Math.hypot(x, y);
|
|
449
459
|
let dirX = 0;
|
|
@@ -460,6 +470,7 @@ export class ExplosionSystem {
|
|
|
460
470
|
const jitterAngle = (Math.random() - 0.5) * (Math.PI / 2);
|
|
461
471
|
return this.rotate(dirX, dirY, jitterAngle);
|
|
462
472
|
}
|
|
473
|
+
/** @internal */
|
|
463
474
|
easePieceProgress(mode, t) {
|
|
464
475
|
const clamped = Math.max(0, Math.min(1, t));
|
|
465
476
|
switch (mode) {
|
|
@@ -471,6 +482,7 @@ export class ExplosionSystem {
|
|
|
471
482
|
return clamped * clamped * (3 - 2 * clamped);
|
|
472
483
|
}
|
|
473
484
|
}
|
|
485
|
+
/** @internal */
|
|
474
486
|
rotate(x, y, radians) {
|
|
475
487
|
const cos = Math.cos(radians);
|
|
476
488
|
const sin = Math.sin(radians);
|
|
@@ -479,39 +491,47 @@ export class ExplosionSystem {
|
|
|
479
491
|
y: x * sin + y * cos,
|
|
480
492
|
};
|
|
481
493
|
}
|
|
494
|
+
/** @internal */
|
|
482
495
|
lerp(from, to, t) {
|
|
483
496
|
return from + (to - from) * t;
|
|
484
497
|
}
|
|
498
|
+
/** @internal */
|
|
485
499
|
getSafeScale(value) {
|
|
486
500
|
if (!Number.isFinite(value))
|
|
487
501
|
return 1;
|
|
488
502
|
return Math.max(0, value);
|
|
489
503
|
}
|
|
504
|
+
/** @internal */
|
|
490
505
|
getSafePivot(value) {
|
|
491
506
|
if (!Number.isFinite(value))
|
|
492
507
|
return 0.5;
|
|
493
508
|
return Math.max(0, Math.min(1, value));
|
|
494
509
|
}
|
|
510
|
+
/** @internal */
|
|
495
511
|
getSafeNumber(value, fallback) {
|
|
496
512
|
if (!Number.isFinite(value))
|
|
497
513
|
return fallback;
|
|
498
514
|
return value;
|
|
499
515
|
}
|
|
516
|
+
/** @internal */
|
|
500
517
|
sanitizeCount(value, fallback) {
|
|
501
518
|
if (!Number.isFinite(value))
|
|
502
519
|
return fallback;
|
|
503
520
|
return Math.max(1, Math.round(value));
|
|
504
521
|
}
|
|
522
|
+
/** @internal */
|
|
505
523
|
sanitizeDuration(value, fallback) {
|
|
506
524
|
if (!Number.isFinite(value))
|
|
507
525
|
return fallback;
|
|
508
526
|
return Math.max(1, value);
|
|
509
527
|
}
|
|
528
|
+
/** @internal */
|
|
510
529
|
sanitizeNonNegative(value, fallback) {
|
|
511
530
|
if (!Number.isFinite(value))
|
|
512
531
|
return fallback;
|
|
513
532
|
return Math.max(0, value);
|
|
514
533
|
}
|
|
534
|
+
/** @internal */
|
|
515
535
|
sanitizeNumber(value, fallback) {
|
|
516
536
|
if (!Number.isFinite(value))
|
|
517
537
|
return fallback;
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class InputSystem {
|
|
3
|
-
constructor(
|
|
3
|
+
constructor(
|
|
4
|
+
/** @internal */ game,
|
|
5
|
+
/** @internal */ canvas) {
|
|
4
6
|
this.game = game;
|
|
5
7
|
this.canvas = canvas;
|
|
8
|
+
/** @internal */
|
|
6
9
|
this._keysDown = new Set();
|
|
10
|
+
/** @internal */
|
|
7
11
|
this._keysPressed = new Set();
|
|
12
|
+
/** @internal */
|
|
8
13
|
this._pointerDown = false;
|
|
14
|
+
/** @internal */
|
|
9
15
|
this._pointerPressed = false;
|
|
16
|
+
/** @internal */
|
|
10
17
|
this._pointerX = 0;
|
|
18
|
+
/** @internal */
|
|
11
19
|
this._pointerY = 0;
|
|
20
|
+
/** @internal */
|
|
12
21
|
this._mouseDown = false;
|
|
22
|
+
/** @internal */
|
|
13
23
|
this._mousePressed = false;
|
|
24
|
+
/** @internal */
|
|
14
25
|
this._mouseX = 0;
|
|
26
|
+
/** @internal */
|
|
15
27
|
this._mouseY = 0;
|
|
28
|
+
/** @internal */
|
|
16
29
|
this._touchPointers = new Map();
|
|
30
|
+
/** @internal */
|
|
17
31
|
this._primaryTouchId = null;
|
|
18
32
|
}
|
|
19
33
|
get pointerX() {
|
|
@@ -176,6 +190,7 @@ export class InputSystem {
|
|
|
176
190
|
resetPressedState() {
|
|
177
191
|
this.clearFramePressedState();
|
|
178
192
|
}
|
|
193
|
+
/** @internal */
|
|
179
194
|
getCanvasPoint(clientX, clientY) {
|
|
180
195
|
const rect = this.canvas.getBoundingClientRect();
|
|
181
196
|
const scaleX = this.canvas.width / rect.width;
|
|
@@ -185,6 +200,7 @@ export class InputSystem {
|
|
|
185
200
|
y: (clientY - rect.top) * scaleY,
|
|
186
201
|
};
|
|
187
202
|
}
|
|
203
|
+
/** @internal */
|
|
188
204
|
isScreenPointOverSprite(x, y, sprite) {
|
|
189
205
|
const halfWidth = sprite.displayWidth / 2;
|
|
190
206
|
const halfHeight = sprite.displayHeight / 2;
|
|
@@ -192,6 +208,7 @@ export class InputSystem {
|
|
|
192
208
|
const drawY = sprite.ignoreScroll ? sprite.renderY : sprite.renderY - this.game.scrollY;
|
|
193
209
|
return Math.abs(x - drawX) <= halfWidth && Math.abs(y - drawY) <= halfHeight;
|
|
194
210
|
}
|
|
211
|
+
/** @internal */
|
|
195
212
|
collectPointers() {
|
|
196
213
|
const pointers = [];
|
|
197
214
|
if (this._mouseDown) {
|
|
@@ -214,6 +231,7 @@ export class InputSystem {
|
|
|
214
231
|
}
|
|
215
232
|
return pointers;
|
|
216
233
|
}
|
|
234
|
+
/** @internal */
|
|
217
235
|
syncPrimaryPointer() {
|
|
218
236
|
if (this._primaryTouchId !== null) {
|
|
219
237
|
const primaryTouch = this._touchPointers.get(this._primaryTouchId);
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class LoopSystem {
|
|
3
|
-
constructor(onFrame) {
|
|
3
|
+
constructor(/** @internal */ onFrame) {
|
|
4
4
|
this.onFrame = onFrame;
|
|
5
|
+
/** @internal */
|
|
5
6
|
this._rafId = null;
|
|
7
|
+
/** @internal */
|
|
6
8
|
this._lastTimestamp = null;
|
|
9
|
+
/** @internal */
|
|
7
10
|
this._running = false;
|
|
11
|
+
/** @internal */
|
|
8
12
|
this._hasCreated = false;
|
|
13
|
+
/** @internal */
|
|
9
14
|
this._onCreate = null;
|
|
15
|
+
/** @internal */
|
|
10
16
|
this.loop = (timestamp) => {
|
|
11
17
|
if (!this._running)
|
|
12
18
|
return;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class PhysicsSystem {
|
|
3
3
|
constructor() {
|
|
4
|
+
/** @internal */
|
|
4
5
|
this._gravityX = 0;
|
|
6
|
+
/** @internal */
|
|
5
7
|
this._gravityY = 0;
|
|
8
|
+
/** @internal */
|
|
6
9
|
this._enabled = false;
|
|
7
10
|
}
|
|
8
11
|
get gravityX() {
|
|
@@ -94,11 +97,13 @@ export class PhysicsSystem {
|
|
|
94
97
|
}
|
|
95
98
|
return null;
|
|
96
99
|
}
|
|
100
|
+
/** @internal */
|
|
97
101
|
assertEnabled() {
|
|
98
102
|
if (!this._enabled) {
|
|
99
103
|
throw new Error("MinimoJS: collide() and collideAny() require game.physics = true.");
|
|
100
104
|
}
|
|
101
105
|
}
|
|
106
|
+
/** @internal */
|
|
102
107
|
getCollisionResolution(a, b) {
|
|
103
108
|
const halfWidthA = a.bodyDisplayWidth / 2;
|
|
104
109
|
const halfHeightA = a.bodyDisplayHeight / 2;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class RenderSystem {
|
|
3
3
|
constructor() {
|
|
4
|
+
/** @internal */
|
|
4
5
|
this._surfaceCache = new Map();
|
|
6
|
+
/** @internal */
|
|
5
7
|
this._lastAppliedPageBackground = undefined;
|
|
8
|
+
/** @internal */
|
|
6
9
|
this._transitionScratchCanvas = null;
|
|
10
|
+
/** @internal */
|
|
7
11
|
this._transitionScratchContext = null;
|
|
12
|
+
/** @internal */
|
|
8
13
|
this._dynamicSurfacePassId = 0;
|
|
9
14
|
}
|
|
10
15
|
clearSpriteCache() {
|
|
@@ -95,6 +100,7 @@ export class RenderSystem {
|
|
|
95
100
|
return;
|
|
96
101
|
}
|
|
97
102
|
}
|
|
103
|
+
/** @internal */
|
|
98
104
|
renderScene(options) {
|
|
99
105
|
const ctx = options.context;
|
|
100
106
|
const W = options.canvas.width;
|
|
@@ -207,6 +213,7 @@ export class RenderSystem {
|
|
|
207
213
|
this.drawInputDebugOverlay(ctx, sorted, options.scrollX, options.scrollY);
|
|
208
214
|
}
|
|
209
215
|
}
|
|
216
|
+
/** @internal */
|
|
210
217
|
drawBodyDebugOverlay(ctx, sprites, scrollX, scrollY) {
|
|
211
218
|
ctx.save();
|
|
212
219
|
ctx.setLineDash([6, 4]);
|
|
@@ -233,6 +240,7 @@ export class RenderSystem {
|
|
|
233
240
|
}
|
|
234
241
|
ctx.restore();
|
|
235
242
|
}
|
|
243
|
+
/** @internal */
|
|
236
244
|
drawInputDebugOverlay(ctx, sprites, scrollX, scrollY) {
|
|
237
245
|
ctx.save();
|
|
238
246
|
ctx.setLineDash([3, 5]);
|
|
@@ -284,6 +292,7 @@ export class RenderSystem {
|
|
|
284
292
|
ctx.strokeRect(barX, barY, barWidth, barHeight);
|
|
285
293
|
ctx.restore();
|
|
286
294
|
}
|
|
295
|
+
/** @internal */
|
|
287
296
|
drawFadeTransition(ctx, transition, width, height, progress) {
|
|
288
297
|
const firstHalf = progress < 0.5;
|
|
289
298
|
const localT = firstHalf ? progress / 0.5 : (progress - 0.5) / 0.5;
|
|
@@ -294,6 +303,7 @@ export class RenderSystem {
|
|
|
294
303
|
ctx.fillRect(0, 0, width, height);
|
|
295
304
|
ctx.restore();
|
|
296
305
|
}
|
|
306
|
+
/** @internal */
|
|
297
307
|
drawWipeTransition(ctx, transition, width, height, progress) {
|
|
298
308
|
ctx.drawImage(transition.fromCanvas, 0, 0, width, height);
|
|
299
309
|
const clip = this.getScreenWipeClipRect(width, height, transition.direction, progress);
|
|
@@ -306,11 +316,13 @@ export class RenderSystem {
|
|
|
306
316
|
ctx.drawImage(transition.toCanvas, 0, 0, width, height);
|
|
307
317
|
ctx.restore();
|
|
308
318
|
}
|
|
319
|
+
/** @internal */
|
|
309
320
|
drawSlideTransition(ctx, transition, width, height, progress) {
|
|
310
321
|
const offset = this.getSlideOffset(transition.direction, width, height, progress);
|
|
311
322
|
ctx.drawImage(transition.fromCanvas, offset.fromX, offset.fromY, width, height);
|
|
312
323
|
ctx.drawImage(transition.toCanvas, offset.toX, offset.toY, width, height);
|
|
313
324
|
}
|
|
325
|
+
/** @internal */
|
|
314
326
|
drawIrisTransition(ctx, transition, width, height, progress) {
|
|
315
327
|
ctx.drawImage(transition.fromCanvas, 0, 0, width, height);
|
|
316
328
|
const maxRadius = Math.max(Math.hypot(transition.centerX, transition.centerY), Math.hypot(width - transition.centerX, transition.centerY), Math.hypot(transition.centerX, height - transition.centerY), Math.hypot(width - transition.centerX, height - transition.centerY));
|
|
@@ -321,6 +333,7 @@ export class RenderSystem {
|
|
|
321
333
|
ctx.drawImage(transition.toCanvas, 0, 0, width, height);
|
|
322
334
|
ctx.restore();
|
|
323
335
|
}
|
|
336
|
+
/** @internal */
|
|
324
337
|
drawPixelateTransition(ctx, transition, width, height, progress) {
|
|
325
338
|
const firstHalf = progress < 0.5;
|
|
326
339
|
const localT = firstHalf ? progress / 0.5 : (progress - 0.5) / 0.5;
|
|
@@ -330,6 +343,7 @@ export class RenderSystem {
|
|
|
330
343
|
: this.lerp(transition.pixelSize, 1, localT);
|
|
331
344
|
this.drawPixelatedCanvas(ctx, source, width, height, pixelSize);
|
|
332
345
|
}
|
|
346
|
+
/** @internal */
|
|
333
347
|
drawFlashTransition(ctx, transition, width, height, progress) {
|
|
334
348
|
const swapPoint = 0.28;
|
|
335
349
|
const overlayPeak = 0.52;
|
|
@@ -351,22 +365,38 @@ export class RenderSystem {
|
|
|
351
365
|
ctx.fillRect(0, 0, width, height);
|
|
352
366
|
ctx.restore();
|
|
353
367
|
}
|
|
368
|
+
/** @internal */
|
|
354
369
|
getRenderSurface(sprite) {
|
|
355
370
|
if (this.isDrawSprite(sprite)) {
|
|
356
371
|
return this.getDrawSurface(sprite);
|
|
357
372
|
}
|
|
358
373
|
const cacheKey = sprite.getRenderCacheKey();
|
|
359
374
|
const cached = this._surfaceCache.get(cacheKey);
|
|
360
|
-
if (cached)
|
|
375
|
+
if (cached) {
|
|
376
|
+
this._surfaceCache.delete(cacheKey);
|
|
377
|
+
this._surfaceCache.set(cacheKey, cached);
|
|
361
378
|
return cached;
|
|
379
|
+
}
|
|
362
380
|
const surface = this.isTextSprite(sprite)
|
|
363
381
|
? this.createTextSurface(sprite)
|
|
364
382
|
: this.isImageSprite(sprite)
|
|
365
383
|
? this.createImageSurface(sprite)
|
|
366
384
|
: this.createEmojiSurface(this.asEmojiSprite(sprite));
|
|
367
385
|
this._surfaceCache.set(cacheKey, surface);
|
|
386
|
+
this.trimSurfaceCache();
|
|
368
387
|
return surface;
|
|
369
388
|
}
|
|
389
|
+
/** @internal */
|
|
390
|
+
trimSurfaceCache() {
|
|
391
|
+
while (this._surfaceCache.size > RenderSystem.MAX_SURFACE_CACHE_ENTRIES) {
|
|
392
|
+
const oldestKey = this._surfaceCache.keys().next().value;
|
|
393
|
+
if (oldestKey === undefined) {
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
this._surfaceCache.delete(oldestKey);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/** @internal */
|
|
370
400
|
createEmojiSurface(sprite) {
|
|
371
401
|
const size = Math.max(1, Math.round(sprite.size));
|
|
372
402
|
const color = sprite.color;
|
|
@@ -390,6 +420,7 @@ export class RenderSystem {
|
|
|
390
420
|
glyphCtx.fillText(sprite.sprite, boxSize / 2, boxSize / 2);
|
|
391
421
|
return glyphCanvas;
|
|
392
422
|
}
|
|
423
|
+
/** @internal */
|
|
393
424
|
createTextSurface(sprite) {
|
|
394
425
|
const canvas = document.createElement("canvas");
|
|
395
426
|
const ctx = canvas.getContext("2d");
|
|
@@ -458,6 +489,7 @@ export class RenderSystem {
|
|
|
458
489
|
}
|
|
459
490
|
return canvas;
|
|
460
491
|
}
|
|
492
|
+
/** @internal */
|
|
461
493
|
createImageSurface(sprite) {
|
|
462
494
|
const image = sprite.game?.getImage(sprite.imageKey);
|
|
463
495
|
if (!image) {
|
|
@@ -474,6 +506,7 @@ export class RenderSystem {
|
|
|
474
506
|
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
475
507
|
return canvas;
|
|
476
508
|
}
|
|
509
|
+
/** @internal */
|
|
477
510
|
getDrawSurface(sprite) {
|
|
478
511
|
const width = Math.max(1, Math.round(sprite.width));
|
|
479
512
|
const height = Math.max(1, Math.round(sprite.height));
|
|
@@ -509,15 +542,19 @@ export class RenderSystem {
|
|
|
509
542
|
sprite._lastRedrawPassId = this._dynamicSurfacePassId;
|
|
510
543
|
return surface;
|
|
511
544
|
}
|
|
545
|
+
/** @internal */
|
|
512
546
|
isTextSprite(sprite) {
|
|
513
547
|
return "text" in sprite && "fontFamily" in sprite && "fontSize" in sprite;
|
|
514
548
|
}
|
|
549
|
+
/** @internal */
|
|
515
550
|
isDrawSprite(sprite) {
|
|
516
551
|
return "redraw" in sprite && "_surface" in sprite && "_surfaceCtx" in sprite;
|
|
517
552
|
}
|
|
553
|
+
/** @internal */
|
|
518
554
|
isImageSprite(sprite) {
|
|
519
555
|
return "imageKey" in sprite && "setTexture" in sprite;
|
|
520
556
|
}
|
|
557
|
+
/** @internal */
|
|
521
558
|
getWipeClipRect(drawWidth, drawHeight, direction, progress) {
|
|
522
559
|
const halfWidth = drawWidth / 2;
|
|
523
560
|
const halfHeight = drawHeight / 2;
|
|
@@ -556,6 +593,7 @@ export class RenderSystem {
|
|
|
556
593
|
}
|
|
557
594
|
}
|
|
558
595
|
}
|
|
596
|
+
/** @internal */
|
|
559
597
|
getScreenWipeClipRect(width, height, direction, progress) {
|
|
560
598
|
switch (direction) {
|
|
561
599
|
case "left-to-right":
|
|
@@ -592,12 +630,14 @@ export class RenderSystem {
|
|
|
592
630
|
}
|
|
593
631
|
}
|
|
594
632
|
}
|
|
633
|
+
/** @internal */
|
|
595
634
|
asEmojiSprite(sprite) {
|
|
596
635
|
if ("sprite" in sprite && "size" in sprite) {
|
|
597
636
|
return sprite;
|
|
598
637
|
}
|
|
599
638
|
throw new Error("MinimoJS: Unsupported sprite type for emoji rendering.");
|
|
600
639
|
}
|
|
640
|
+
/** @internal */
|
|
601
641
|
layoutTextLines(ctx, sprite) {
|
|
602
642
|
this.applyTextStyle(ctx, sprite);
|
|
603
643
|
const rawLines = sprite.text.split("\n");
|
|
@@ -627,6 +667,7 @@ export class RenderSystem {
|
|
|
627
667
|
}
|
|
628
668
|
return wrappedLines.length > 0 ? wrappedLines : [""];
|
|
629
669
|
}
|
|
670
|
+
/** @internal */
|
|
630
671
|
getAvailableTextWidth(sprite) {
|
|
631
672
|
const borderPad = Math.max(0, sprite.borderWidth) * 2;
|
|
632
673
|
const strokePad = Math.max(0, sprite.strokeWidth) * 2;
|
|
@@ -642,6 +683,7 @@ export class RenderSystem {
|
|
|
642
683
|
}
|
|
643
684
|
return Math.max(1, Math.floor(Math.min(...limits)));
|
|
644
685
|
}
|
|
686
|
+
/** @internal */
|
|
645
687
|
applyTextStyle(ctx, sprite) {
|
|
646
688
|
ctx.font = `${sprite.fontWeight} ${sprite.fontSize}px ${sprite.fontFamily}`;
|
|
647
689
|
ctx.shadowColor = "transparent";
|
|
@@ -649,14 +691,17 @@ export class RenderSystem {
|
|
|
649
691
|
ctx.shadowOffsetX = 0;
|
|
650
692
|
ctx.shadowOffsetY = 0;
|
|
651
693
|
}
|
|
694
|
+
/** @internal */
|
|
652
695
|
fillRoundedRect(ctx, x, y, width, height, radius) {
|
|
653
696
|
this.buildRoundedRectPath(ctx, x, y, width, height, radius);
|
|
654
697
|
ctx.fill();
|
|
655
698
|
}
|
|
699
|
+
/** @internal */
|
|
656
700
|
strokeRoundedRect(ctx, x, y, width, height, radius) {
|
|
657
701
|
this.buildRoundedRectPath(ctx, x, y, width, height, radius);
|
|
658
702
|
ctx.stroke();
|
|
659
703
|
}
|
|
704
|
+
/** @internal */
|
|
660
705
|
buildRoundedRectPath(ctx, x, y, width, height, radius) {
|
|
661
706
|
const safeRadius = Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
662
707
|
ctx.beginPath();
|
|
@@ -675,23 +720,27 @@ export class RenderSystem {
|
|
|
675
720
|
ctx.quadraticCurveTo(x, y, x + safeRadius, y);
|
|
676
721
|
ctx.closePath();
|
|
677
722
|
}
|
|
723
|
+
/** @internal */
|
|
678
724
|
getSafeRenderScale(value) {
|
|
679
725
|
if (!Number.isFinite(value))
|
|
680
726
|
return 1;
|
|
681
727
|
const safeValue = value;
|
|
682
728
|
return Math.max(0, safeValue);
|
|
683
729
|
}
|
|
730
|
+
/** @internal */
|
|
684
731
|
getSafePivot(value) {
|
|
685
732
|
if (!Number.isFinite(value))
|
|
686
733
|
return 0.5;
|
|
687
734
|
const safeValue = value;
|
|
688
735
|
return Math.max(0, Math.min(1, safeValue));
|
|
689
736
|
}
|
|
737
|
+
/** @internal */
|
|
690
738
|
getSafeNumber(value, fallback) {
|
|
691
739
|
if (!Number.isFinite(value))
|
|
692
740
|
return fallback;
|
|
693
741
|
return value;
|
|
694
742
|
}
|
|
743
|
+
/** @internal */
|
|
695
744
|
paintCanvasBackground(ctx, width, height, background, backgroundGradient) {
|
|
696
745
|
ctx.clearRect(0, 0, width, height);
|
|
697
746
|
if (backgroundGradient !== null) {
|
|
@@ -706,6 +755,7 @@ export class RenderSystem {
|
|
|
706
755
|
ctx.fillRect(0, 0, width, height);
|
|
707
756
|
}
|
|
708
757
|
}
|
|
758
|
+
/** @internal */
|
|
709
759
|
drawBackgroundLayer(ctx, canvas, layer, image) {
|
|
710
760
|
const destX = Math.round(layer.x);
|
|
711
761
|
const destY = Math.round(layer.y);
|
|
@@ -739,11 +789,13 @@ export class RenderSystem {
|
|
|
739
789
|
ctx.drawImage(image, drawX, drawY, drawW, drawH);
|
|
740
790
|
ctx.restore();
|
|
741
791
|
}
|
|
792
|
+
/** @internal */
|
|
742
793
|
getPositiveLength(value, fallback) {
|
|
743
794
|
if (!Number.isFinite(value) || value <= 0)
|
|
744
795
|
return fallback;
|
|
745
796
|
return Math.round(value);
|
|
746
797
|
}
|
|
798
|
+
/** @internal */
|
|
747
799
|
getBackgroundScale(fit, destW, destH, imageW, imageH) {
|
|
748
800
|
const scaleX = destW / imageW;
|
|
749
801
|
const scaleY = destH / imageH;
|
|
@@ -752,10 +804,12 @@ export class RenderSystem {
|
|
|
752
804
|
}
|
|
753
805
|
return Math.max(scaleX, scaleY);
|
|
754
806
|
}
|
|
807
|
+
/** @internal */
|
|
755
808
|
easeTransitionProgress(t) {
|
|
756
809
|
const clamped = Math.max(0, Math.min(1, t));
|
|
757
810
|
return clamped * clamped * (3 - 2 * clamped);
|
|
758
811
|
}
|
|
812
|
+
/** @internal */
|
|
759
813
|
getSlideOffset(direction, width, height, progress) {
|
|
760
814
|
switch (direction) {
|
|
761
815
|
case "left-to-right":
|
|
@@ -788,6 +842,7 @@ export class RenderSystem {
|
|
|
788
842
|
};
|
|
789
843
|
}
|
|
790
844
|
}
|
|
845
|
+
/** @internal */
|
|
791
846
|
drawPixelatedCanvas(ctx, source, width, height, pixelSize) {
|
|
792
847
|
const roundedPixelSize = Math.max(1, Math.round(pixelSize));
|
|
793
848
|
if (roundedPixelSize <= 1) {
|
|
@@ -812,6 +867,7 @@ export class RenderSystem {
|
|
|
812
867
|
ctx.drawImage(scratch, 0, 0, scaledWidth, scaledHeight, 0, 0, width, height);
|
|
813
868
|
ctx.restore();
|
|
814
869
|
}
|
|
870
|
+
/** @internal */
|
|
815
871
|
getTransitionScratchSurface(width, height) {
|
|
816
872
|
if (!this._transitionScratchCanvas) {
|
|
817
873
|
this._transitionScratchCanvas = document.createElement("canvas");
|
|
@@ -831,9 +887,11 @@ export class RenderSystem {
|
|
|
831
887
|
}
|
|
832
888
|
return this._transitionScratchCanvas;
|
|
833
889
|
}
|
|
890
|
+
/** @internal */
|
|
834
891
|
lerp(from, to, t) {
|
|
835
892
|
return from + (to - from) * t;
|
|
836
893
|
}
|
|
894
|
+
/** @internal */
|
|
837
895
|
applyPageBackground(pageBackground) {
|
|
838
896
|
if (!document.body)
|
|
839
897
|
return;
|
|
@@ -848,3 +906,5 @@ export class RenderSystem {
|
|
|
848
906
|
this._lastAppliedPageBackground = pageBackground;
|
|
849
907
|
}
|
|
850
908
|
}
|
|
909
|
+
/** @internal */
|
|
910
|
+
RenderSystem.MAX_SURFACE_CACHE_ENTRIES = 256;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class SoundSystem {
|
|
3
3
|
constructor() {
|
|
4
|
+
/** @internal */
|
|
4
5
|
this._audioCtx = null;
|
|
5
6
|
}
|
|
6
7
|
sound(freq, durationMs) {
|
|
@@ -40,6 +41,7 @@ export class SoundSystem {
|
|
|
40
41
|
startTime += durationSec;
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
/** @internal */
|
|
43
45
|
getAudioContext() {
|
|
44
46
|
if (!this._audioCtx) {
|
|
45
47
|
this._audioCtx = new AudioContext();
|