minimojs 1.0.0-alpha.16 → 1.0.0-alpha.17
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/RenderSystem.js +45 -0
- package/dist/minimo.d.ts +90 -0
- package/dist/minimo.js +108 -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:
|
|
@@ -5,10 +5,14 @@ export class RenderSystem {
|
|
|
5
5
|
this._lastAppliedPageBackground = undefined;
|
|
6
6
|
this._transitionScratchCanvas = null;
|
|
7
7
|
this._transitionScratchContext = null;
|
|
8
|
+
this._dynamicSurfacePassId = 0;
|
|
8
9
|
}
|
|
9
10
|
clearSpriteCache() {
|
|
10
11
|
this._surfaceCache.clear();
|
|
11
12
|
}
|
|
13
|
+
beginDynamicSurfacePass() {
|
|
14
|
+
this._dynamicSurfacePassId += 1;
|
|
15
|
+
}
|
|
12
16
|
getSpriteGlyphCanvasForEffects(sprite) {
|
|
13
17
|
return this.getRenderSurface(sprite);
|
|
14
18
|
}
|
|
@@ -348,6 +352,9 @@ export class RenderSystem {
|
|
|
348
352
|
ctx.restore();
|
|
349
353
|
}
|
|
350
354
|
getRenderSurface(sprite) {
|
|
355
|
+
if (this.isDrawSprite(sprite)) {
|
|
356
|
+
return this.getDrawSurface(sprite);
|
|
357
|
+
}
|
|
351
358
|
const cacheKey = sprite.getRenderCacheKey();
|
|
352
359
|
const cached = this._surfaceCache.get(cacheKey);
|
|
353
360
|
if (cached)
|
|
@@ -467,9 +474,47 @@ export class RenderSystem {
|
|
|
467
474
|
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
468
475
|
return canvas;
|
|
469
476
|
}
|
|
477
|
+
getDrawSurface(sprite) {
|
|
478
|
+
const width = Math.max(1, Math.round(sprite.width));
|
|
479
|
+
const height = Math.max(1, Math.round(sprite.height));
|
|
480
|
+
let surface = sprite._surface;
|
|
481
|
+
if (!surface) {
|
|
482
|
+
surface = document.createElement("canvas");
|
|
483
|
+
sprite._surface = surface;
|
|
484
|
+
}
|
|
485
|
+
const sizeChanged = surface.width !== width || surface.height !== height;
|
|
486
|
+
if (sprite.frozen && !sizeChanged && surface.width > 0 && surface.height > 0) {
|
|
487
|
+
return surface;
|
|
488
|
+
}
|
|
489
|
+
if (!sprite.frozen &&
|
|
490
|
+
sprite._lastRedrawPassId === this._dynamicSurfacePassId &&
|
|
491
|
+
surface.width === width &&
|
|
492
|
+
surface.height === height) {
|
|
493
|
+
return surface;
|
|
494
|
+
}
|
|
495
|
+
if (sizeChanged) {
|
|
496
|
+
surface.width = width;
|
|
497
|
+
surface.height = height;
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
// Resetting width clears pixels and restores the default 2D context state.
|
|
501
|
+
surface.width = width;
|
|
502
|
+
}
|
|
503
|
+
const ctx = surface.getContext("2d");
|
|
504
|
+
if (!ctx) {
|
|
505
|
+
throw new Error("MinimoJS: Could not acquire a DrawSprite rendering context.");
|
|
506
|
+
}
|
|
507
|
+
sprite._surfaceCtx = ctx;
|
|
508
|
+
sprite.redraw(ctx);
|
|
509
|
+
sprite._lastRedrawPassId = this._dynamicSurfacePassId;
|
|
510
|
+
return surface;
|
|
511
|
+
}
|
|
470
512
|
isTextSprite(sprite) {
|
|
471
513
|
return "text" in sprite && "fontFamily" in sprite && "fontSize" in sprite;
|
|
472
514
|
}
|
|
515
|
+
isDrawSprite(sprite) {
|
|
516
|
+
return "redraw" in sprite && "_surface" in sprite && "_surfaceCtx" in sprite;
|
|
517
|
+
}
|
|
473
518
|
isImageSprite(sprite) {
|
|
474
519
|
return "imageKey" in sprite && "setTexture" in sprite;
|
|
475
520
|
}
|
package/dist/minimo.d.ts
CHANGED
|
@@ -471,6 +471,96 @@ export declare class ImageSprite extends BaseSprite {
|
|
|
471
471
|
private getResolvedImage;
|
|
472
472
|
private assertTextureAvailable;
|
|
473
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* A renderable sprite backed by a per-instance canvas that is repainted on demand.
|
|
476
|
+
*
|
|
477
|
+
* `DrawSprite` is useful for procedural shapes, gauges, charts, minimaps, and
|
|
478
|
+
* HUD widgets whose appearance changes often and is easier to express with
|
|
479
|
+
* Canvas 2D drawing commands than with emoji, text, or image swaps.
|
|
480
|
+
*
|
|
481
|
+
* Treat `DrawSprite` as a specialized tool, not the default sprite type.
|
|
482
|
+
* Because it may execute custom Canvas 2D drawing code repeatedly, overusing it
|
|
483
|
+
* can affect game performance much more than regular {@link Sprite},
|
|
484
|
+
* {@link ImageSprite}, or {@link TextSprite} instances.
|
|
485
|
+
*
|
|
486
|
+
* Prefer the other sprite types whenever they can express the same result more
|
|
487
|
+
* simply. Reach for `DrawSprite` only when you truly need procedural drawing or
|
|
488
|
+
* custom per-sprite canvas rendering that the built-in sprite types cannot
|
|
489
|
+
* provide cleanly.
|
|
490
|
+
*
|
|
491
|
+
* MinimoJS creates and owns an internal canvas for each `DrawSprite` instance.
|
|
492
|
+
* Before every engine render that needs this sprite's surface, the engine:
|
|
493
|
+
*
|
|
494
|
+
* 1. Resolves the sprite's internal canvas size
|
|
495
|
+
* 2. Optionally clears and repaints that internal canvas
|
|
496
|
+
* 3. Draws the resulting canvas like any other sprite surface
|
|
497
|
+
*
|
|
498
|
+
* By default, `DrawSprite` is live-rendered: the engine clears the internal
|
|
499
|
+
* canvas and calls {@link DrawSprite.redraw} every frame.
|
|
500
|
+
*
|
|
501
|
+
* Set {@link DrawSprite.frozen} to `true` if you want to keep and reuse the
|
|
502
|
+
* last rendered canvas contents without repainting on each frame. This is
|
|
503
|
+
* useful for shapes or procedural art that you only want to draw once.
|
|
504
|
+
*
|
|
505
|
+
* While `frozen` is `true`, MinimoJS reuses the existing surface exactly as-is:
|
|
506
|
+
* it does not clear the canvas and does not call {@link DrawSprite.redraw}
|
|
507
|
+
* again, unless the surface does not exist yet or its size had to be rebuilt.
|
|
508
|
+
*
|
|
509
|
+
* This means freezing is a rendering optimization and content-preservation
|
|
510
|
+
* flag, not a separate caching system. You can switch `frozen` on or off at
|
|
511
|
+
* runtime whenever it makes sense for your sprite.
|
|
512
|
+
*
|
|
513
|
+
* The local drawing coordinate system uses the sprite surface itself:
|
|
514
|
+
* - `(0, 0)` is the top-left corner of the internal canvas
|
|
515
|
+
* - `width` / `height` match the sprite's logical size before `scale`
|
|
516
|
+
* - draw centered content yourself if you want the visual origin in the middle
|
|
517
|
+
*
|
|
518
|
+
* Override {@link DrawSprite.redraw} in a subclass, or assign your own method
|
|
519
|
+
* on an instance if you prefer an inline style in JavaScript.
|
|
520
|
+
*/
|
|
521
|
+
export declare class DrawSprite extends BaseSprite {
|
|
522
|
+
private static _nextSurfaceId;
|
|
523
|
+
/**
|
|
524
|
+
* When `false` (default), MinimoJS clears this sprite's internal canvas and
|
|
525
|
+
* calls {@link DrawSprite.redraw} on every render pass.
|
|
526
|
+
*
|
|
527
|
+
* When `true`, MinimoJS keeps and reuses the existing canvas contents without
|
|
528
|
+
* clearing or repainting them again, unless the internal surface does not yet
|
|
529
|
+
* exist or had to be resized.
|
|
530
|
+
*
|
|
531
|
+
* Use this when your procedural drawing becomes static after its first paint,
|
|
532
|
+
* or when you want explicit manual control over when the sprite is redrawn by
|
|
533
|
+
* toggling `frozen` at runtime.
|
|
534
|
+
*/
|
|
535
|
+
frozen: boolean;
|
|
536
|
+
private readonly _surfaceId;
|
|
537
|
+
private readonly _width;
|
|
538
|
+
private readonly _height;
|
|
539
|
+
get width(): number;
|
|
540
|
+
get height(): number;
|
|
541
|
+
/**
|
|
542
|
+
* Creates a new dynamic canvas-backed sprite.
|
|
543
|
+
*
|
|
544
|
+
* @param width - Internal canvas width in pixels. Minimum `1`.
|
|
545
|
+
* @param height - Internal canvas height in pixels. Minimum `1`.
|
|
546
|
+
* @param x - Initial X position in world space (center), in pixels. Default: `0`.
|
|
547
|
+
* @param y - Initial Y position in world space (center), in pixels. Default: `0`.
|
|
548
|
+
*/
|
|
549
|
+
constructor(width: number, height: number, x?: number, y?: number);
|
|
550
|
+
/**
|
|
551
|
+
* Called by the engine whenever the sprite's internal surface must be repainted.
|
|
552
|
+
*
|
|
553
|
+
* The provided context is already cleared and reset to the default 2D canvas
|
|
554
|
+
* state for the current surface size. MinimoJS repaints each `DrawSprite` at
|
|
555
|
+
* most once per render pass while it is not {@link DrawSprite.frozen}, so
|
|
556
|
+
* repeated snapshot reads during the same frame reuse the already-redrawn
|
|
557
|
+
* surface.
|
|
558
|
+
*
|
|
559
|
+
* @param ctx - The sprite's internal 2D drawing context.
|
|
560
|
+
*/
|
|
561
|
+
redraw(_ctx: CanvasRenderingContext2D): void;
|
|
562
|
+
getRenderCacheKey(): string;
|
|
563
|
+
}
|
|
474
564
|
/**
|
|
475
565
|
* A renderable text actor that participates in the same animation/effects
|
|
476
566
|
* pipeline as regular sprites.
|
package/dist/minimo.js
CHANGED
|
@@ -383,6 +383,112 @@ export class ImageSprite extends BaseSprite {
|
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* A renderable sprite backed by a per-instance canvas that is repainted on demand.
|
|
388
|
+
*
|
|
389
|
+
* `DrawSprite` is useful for procedural shapes, gauges, charts, minimaps, and
|
|
390
|
+
* HUD widgets whose appearance changes often and is easier to express with
|
|
391
|
+
* Canvas 2D drawing commands than with emoji, text, or image swaps.
|
|
392
|
+
*
|
|
393
|
+
* Treat `DrawSprite` as a specialized tool, not the default sprite type.
|
|
394
|
+
* Because it may execute custom Canvas 2D drawing code repeatedly, overusing it
|
|
395
|
+
* can affect game performance much more than regular {@link Sprite},
|
|
396
|
+
* {@link ImageSprite}, or {@link TextSprite} instances.
|
|
397
|
+
*
|
|
398
|
+
* Prefer the other sprite types whenever they can express the same result more
|
|
399
|
+
* simply. Reach for `DrawSprite` only when you truly need procedural drawing or
|
|
400
|
+
* custom per-sprite canvas rendering that the built-in sprite types cannot
|
|
401
|
+
* provide cleanly.
|
|
402
|
+
*
|
|
403
|
+
* MinimoJS creates and owns an internal canvas for each `DrawSprite` instance.
|
|
404
|
+
* Before every engine render that needs this sprite's surface, the engine:
|
|
405
|
+
*
|
|
406
|
+
* 1. Resolves the sprite's internal canvas size
|
|
407
|
+
* 2. Optionally clears and repaints that internal canvas
|
|
408
|
+
* 3. Draws the resulting canvas like any other sprite surface
|
|
409
|
+
*
|
|
410
|
+
* By default, `DrawSprite` is live-rendered: the engine clears the internal
|
|
411
|
+
* canvas and calls {@link DrawSprite.redraw} every frame.
|
|
412
|
+
*
|
|
413
|
+
* Set {@link DrawSprite.frozen} to `true` if you want to keep and reuse the
|
|
414
|
+
* last rendered canvas contents without repainting on each frame. This is
|
|
415
|
+
* useful for shapes or procedural art that you only want to draw once.
|
|
416
|
+
*
|
|
417
|
+
* While `frozen` is `true`, MinimoJS reuses the existing surface exactly as-is:
|
|
418
|
+
* it does not clear the canvas and does not call {@link DrawSprite.redraw}
|
|
419
|
+
* again, unless the surface does not exist yet or its size had to be rebuilt.
|
|
420
|
+
*
|
|
421
|
+
* This means freezing is a rendering optimization and content-preservation
|
|
422
|
+
* flag, not a separate caching system. You can switch `frozen` on or off at
|
|
423
|
+
* runtime whenever it makes sense for your sprite.
|
|
424
|
+
*
|
|
425
|
+
* The local drawing coordinate system uses the sprite surface itself:
|
|
426
|
+
* - `(0, 0)` is the top-left corner of the internal canvas
|
|
427
|
+
* - `width` / `height` match the sprite's logical size before `scale`
|
|
428
|
+
* - draw centered content yourself if you want the visual origin in the middle
|
|
429
|
+
*
|
|
430
|
+
* Override {@link DrawSprite.redraw} in a subclass, or assign your own method
|
|
431
|
+
* on an instance if you prefer an inline style in JavaScript.
|
|
432
|
+
*/
|
|
433
|
+
export class DrawSprite extends BaseSprite {
|
|
434
|
+
get width() {
|
|
435
|
+
return this._width;
|
|
436
|
+
}
|
|
437
|
+
get height() {
|
|
438
|
+
return this._height;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Creates a new dynamic canvas-backed sprite.
|
|
442
|
+
*
|
|
443
|
+
* @param width - Internal canvas width in pixels. Minimum `1`.
|
|
444
|
+
* @param height - Internal canvas height in pixels. Minimum `1`.
|
|
445
|
+
* @param x - Initial X position in world space (center), in pixels. Default: `0`.
|
|
446
|
+
* @param y - Initial Y position in world space (center), in pixels. Default: `0`.
|
|
447
|
+
*/
|
|
448
|
+
constructor(width, height, x = 0, y = 0) {
|
|
449
|
+
super();
|
|
450
|
+
/** @internal */
|
|
451
|
+
this._surface = null;
|
|
452
|
+
/** @internal */
|
|
453
|
+
this._surfaceCtx = null;
|
|
454
|
+
/** @internal */
|
|
455
|
+
this._lastRedrawPassId = -1;
|
|
456
|
+
/**
|
|
457
|
+
* When `false` (default), MinimoJS clears this sprite's internal canvas and
|
|
458
|
+
* calls {@link DrawSprite.redraw} on every render pass.
|
|
459
|
+
*
|
|
460
|
+
* When `true`, MinimoJS keeps and reuses the existing canvas contents without
|
|
461
|
+
* clearing or repainting them again, unless the internal surface does not yet
|
|
462
|
+
* exist or had to be resized.
|
|
463
|
+
*
|
|
464
|
+
* Use this when your procedural drawing becomes static after its first paint,
|
|
465
|
+
* or when you want explicit manual control over when the sprite is redrawn by
|
|
466
|
+
* toggling `frozen` at runtime.
|
|
467
|
+
*/
|
|
468
|
+
this.frozen = false;
|
|
469
|
+
this._surfaceId = DrawSprite._nextSurfaceId++;
|
|
470
|
+
this._width = Math.max(1, Math.round(Number.isFinite(width) ? width : 1));
|
|
471
|
+
this._height = Math.max(1, Math.round(Number.isFinite(height) ? height : 1));
|
|
472
|
+
this.x = x;
|
|
473
|
+
this.y = y;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Called by the engine whenever the sprite's internal surface must be repainted.
|
|
477
|
+
*
|
|
478
|
+
* The provided context is already cleared and reset to the default 2D canvas
|
|
479
|
+
* state for the current surface size. MinimoJS repaints each `DrawSprite` at
|
|
480
|
+
* most once per render pass while it is not {@link DrawSprite.frozen}, so
|
|
481
|
+
* repeated snapshot reads during the same frame reuse the already-redrawn
|
|
482
|
+
* surface.
|
|
483
|
+
*
|
|
484
|
+
* @param ctx - The sprite's internal 2D drawing context.
|
|
485
|
+
*/
|
|
486
|
+
redraw(_ctx) { }
|
|
487
|
+
getRenderCacheKey() {
|
|
488
|
+
return `draw:${this._surfaceId}`;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
DrawSprite._nextSurfaceId = 1;
|
|
386
492
|
/**
|
|
387
493
|
* A renderable text actor that participates in the same animation/effects
|
|
388
494
|
* pipeline as regular sprites.
|
|
@@ -2540,6 +2646,7 @@ export class Game {
|
|
|
2540
2646
|
// Private — rendering
|
|
2541
2647
|
// -------------------------------------------------------------------------
|
|
2542
2648
|
/** @internal */ _onLoopFrameCallback(dt, dtMs) {
|
|
2649
|
+
this._renderSystem.beginDynamicSurfacePass();
|
|
2543
2650
|
if (this._transitionSystem.isActive) {
|
|
2544
2651
|
this._transitionSystem.update(dtMs);
|
|
2545
2652
|
if (this._transitionSystem.isActive) {
|
|
@@ -2684,6 +2791,7 @@ export class Game {
|
|
|
2684
2791
|
});
|
|
2685
2792
|
}
|
|
2686
2793
|
/** @internal */ _captureFrameSnapshot() {
|
|
2794
|
+
this._renderSystem.beginDynamicSurfacePass();
|
|
2687
2795
|
return this._renderSystem.captureFrame({
|
|
2688
2796
|
canvas: this._canvas,
|
|
2689
2797
|
context: this._ctx,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minimojs",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.17",
|
|
4
4
|
"description": "MinimoJS v1 — ultra-minimal, flat, deterministic 2D web game engine. Emoji-only sprites, rAF loop, TypeScript-first, LLM-friendly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/minimo.js",
|