minimojs 1.0.0-alpha.16 → 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/README.md +22 -1
- 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 +106 -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 +86 -9
- package/dist/minimo.js +117 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,17 +40,35 @@ npm install minimojs
|
|
|
40
40
|
## Quick Start
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
-
import { Game, Sprite, type IScene } from "minimojs";
|
|
43
|
+
import { DrawSprite, Game, Sprite, type IScene } from "minimojs";
|
|
44
44
|
|
|
45
45
|
const game = new Game(720, 1280);
|
|
46
46
|
game.gravityY = 980;
|
|
47
47
|
|
|
48
|
+
class MeterSprite extends DrawSprite {
|
|
49
|
+
public value = 0.5;
|
|
50
|
+
|
|
51
|
+
constructor(x: number, y: number) {
|
|
52
|
+
super(160, 28, x, y);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
redraw(ctx: CanvasRenderingContext2D) {
|
|
56
|
+
ctx.fillStyle = "#1a2236";
|
|
57
|
+
ctx.fillRect(0, 0, this.width, this.height);
|
|
58
|
+
ctx.fillStyle = "#7ce7ff";
|
|
59
|
+
ctx.fillRect(0, 0, this.width * this.value, this.height);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
class DemoScene implements IScene {
|
|
49
64
|
private player: Sprite | null = null;
|
|
65
|
+
private meter: MeterSprite | null = null;
|
|
50
66
|
|
|
51
67
|
onCreate() {
|
|
52
68
|
this.player = game.add(new Sprite("🐢", 360, 640, 48));
|
|
53
69
|
this.player.gravityScale = 1;
|
|
70
|
+
this.meter = game.add(new MeterSprite(360, 80));
|
|
71
|
+
this.meter.ignoreScroll = true;
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
onUpdate(dt: number) {
|
|
@@ -61,6 +79,7 @@ class DemoScene implements IScene {
|
|
|
61
79
|
else this.player.vx = 0;
|
|
62
80
|
|
|
63
81
|
if (game.isKeyPressed(" ")) this.player.vy = -600;
|
|
82
|
+
if (this.meter) this.meter.value = Math.abs(this.player.vx) / 200;
|
|
64
83
|
game.drawText("MinimoJS", 10, 10, 16);
|
|
65
84
|
}
|
|
66
85
|
}
|
|
@@ -80,6 +99,8 @@ Note: `drawText()` uses `"Press Start 2P", monospace`. Load the font in your HTM
|
|
|
80
99
|
- `examples/background-desert/`
|
|
81
100
|
- `examples/scene-lab/`
|
|
82
101
|
- `examples/image-sprite-sad-plush/`
|
|
102
|
+
- `examples/draw-sprite/`
|
|
103
|
+
- `examples/pseudo-3d-racer/`
|
|
83
104
|
- `examples/animations/`
|
|
84
105
|
|
|
85
106
|
Run locally from the `minimojs` directory:
|
|
@@ -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;
|