minimojs 1.0.0-alpha.10 → 1.0.0-alpha.12
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 +24 -15
- package/dist/internal/RenderSystem.js +22 -1
- package/dist/internal/SpriteSystem.js +3 -1
- package/dist/minimo.d.ts +147 -54
- package/dist/minimo.js +204 -62
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,10 +21,11 @@ This README is intentionally high-level. It explains what the project is and how
|
|
|
21
21
|
- Runtime update delta (`dt`) is seconds
|
|
22
22
|
- Coordinate system is center-based world space
|
|
23
23
|
- Positive Y goes downward
|
|
24
|
+
- For new mobile-first games, prefer a portrait canvas of `720x1280`
|
|
24
25
|
|
|
25
26
|
## What It Intentionally Avoids
|
|
26
27
|
|
|
27
|
-
-
|
|
28
|
+
- Heavy scene-manager/ECS architecture
|
|
28
29
|
- Heavy physics engine features
|
|
29
30
|
- Image spritesheets and asset pipelines
|
|
30
31
|
- Nested subsystem APIs
|
|
@@ -39,26 +40,32 @@ npm install minimojs
|
|
|
39
40
|
## Quick Start
|
|
40
41
|
|
|
41
42
|
```ts
|
|
42
|
-
import { Game, Sprite } from "minimojs";
|
|
43
|
+
import { Game, Sprite, type IScene } from "minimojs";
|
|
43
44
|
|
|
44
|
-
const game = new Game(
|
|
45
|
+
const game = new Game(720, 1280);
|
|
45
46
|
game.gravityY = 980;
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
player
|
|
49
|
-
player.y = 300;
|
|
50
|
-
player.gravityScale = 1;
|
|
48
|
+
class DemoScene implements IScene {
|
|
49
|
+
private player: Sprite | null = null;
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
onCreate() {
|
|
52
|
+
this.player = game.add(new Sprite("🐢", 360, 640, 48));
|
|
53
|
+
this.player.gravityScale = 1;
|
|
54
|
+
}
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
56
|
+
onUpdate(dt: number) {
|
|
57
|
+
if (!this.player) return;
|
|
60
58
|
|
|
61
|
-
game.
|
|
59
|
+
if (game.isKeyDown("ArrowLeft")) this.player.vx = -200;
|
|
60
|
+
else if (game.isKeyDown("ArrowRight")) this.player.vx = 200;
|
|
61
|
+
else this.player.vx = 0;
|
|
62
|
+
|
|
63
|
+
if (game.isKeyPressed(" ")) this.player.vy = -600;
|
|
64
|
+
game.drawText("MinimoJS", 10, 10, 16);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
game.start(new DemoScene());
|
|
62
69
|
```
|
|
63
70
|
|
|
64
71
|
Note: `drawText()` uses `"Press Start 2P", monospace`. Load the font in your HTML if you want pixel-font styling.
|
|
@@ -71,6 +78,8 @@ Note: `drawText()` uses `"Press Start 2P", monospace`. Load the font in your HTM
|
|
|
71
78
|
- `examples/super-minimo-bros/`
|
|
72
79
|
- `examples/scale-shift/`
|
|
73
80
|
- `examples/background-desert/`
|
|
81
|
+
- `examples/scene-lab/`
|
|
82
|
+
- `examples/image-sprite-sad-plush/`
|
|
74
83
|
- `examples/animations/`
|
|
75
84
|
|
|
76
85
|
Run locally from the `minimojs` directory:
|
|
@@ -159,7 +159,9 @@ export class RenderSystem {
|
|
|
159
159
|
return cached;
|
|
160
160
|
const surface = this.isTextSprite(sprite)
|
|
161
161
|
? this.createTextSurface(sprite)
|
|
162
|
-
: this.
|
|
162
|
+
: this.isImageSprite(sprite)
|
|
163
|
+
? this.createImageSurface(sprite)
|
|
164
|
+
: this.createEmojiSurface(this.asEmojiSprite(sprite));
|
|
163
165
|
this._surfaceCache.set(cacheKey, surface);
|
|
164
166
|
return surface;
|
|
165
167
|
}
|
|
@@ -254,9 +256,28 @@ export class RenderSystem {
|
|
|
254
256
|
}
|
|
255
257
|
return canvas;
|
|
256
258
|
}
|
|
259
|
+
createImageSurface(sprite) {
|
|
260
|
+
const image = sprite.game?.getImage(sprite.imageKey);
|
|
261
|
+
if (!image) {
|
|
262
|
+
throw new Error(`MinimoJS: Image '${sprite.imageKey}' is not loaded.`);
|
|
263
|
+
}
|
|
264
|
+
const canvas = document.createElement("canvas");
|
|
265
|
+
canvas.width = Math.max(1, image.naturalWidth || image.width);
|
|
266
|
+
canvas.height = Math.max(1, image.naturalHeight || image.height);
|
|
267
|
+
const ctx = canvas.getContext("2d");
|
|
268
|
+
if (!ctx) {
|
|
269
|
+
throw new Error("MinimoJS: Could not acquire an image rendering context.");
|
|
270
|
+
}
|
|
271
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
272
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
273
|
+
return canvas;
|
|
274
|
+
}
|
|
257
275
|
isTextSprite(sprite) {
|
|
258
276
|
return "text" in sprite && "fontFamily" in sprite && "fontSize" in sprite;
|
|
259
277
|
}
|
|
278
|
+
isImageSprite(sprite) {
|
|
279
|
+
return "imageKey" in sprite && "setTexture" in sprite;
|
|
280
|
+
}
|
|
260
281
|
asEmojiSprite(sprite) {
|
|
261
282
|
if ("sprite" in sprite && "size" in sprite) {
|
|
262
283
|
return sprite;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export class SpriteSystem {
|
|
3
|
-
constructor(animationSystem) {
|
|
3
|
+
constructor(animationSystem, game) {
|
|
4
4
|
this.animationSystem = animationSystem;
|
|
5
|
+
this.game = game;
|
|
5
6
|
this._sprites = [];
|
|
6
7
|
}
|
|
7
8
|
add(sprite) {
|
|
9
|
+
sprite._setGame(this.game);
|
|
8
10
|
this._sprites.push(sprite);
|
|
9
11
|
return sprite;
|
|
10
12
|
}
|
package/dist/minimo.d.ts
CHANGED
|
@@ -56,6 +56,25 @@ export interface TrailOptions {
|
|
|
56
56
|
* - `"cover"`: preserves aspect ratio and fully covers the destination, cropping if needed.
|
|
57
57
|
*/
|
|
58
58
|
export type BackgroundFit = "none" | "stretch" | "contain" | "cover";
|
|
59
|
+
/**
|
|
60
|
+
* Minimal scene contract understood by {@link Game.start} and {@link Game.reset}.
|
|
61
|
+
*
|
|
62
|
+
* Scene methods are invoked directly on the scene instance, so class-based
|
|
63
|
+
* scenes keep their expected `this` value.
|
|
64
|
+
*/
|
|
65
|
+
export interface IScene {
|
|
66
|
+
/**
|
|
67
|
+
* Called when the scene is created, before the first frame and again after
|
|
68
|
+
* each {@link Game.reset} that targets this scene.
|
|
69
|
+
*/
|
|
70
|
+
onCreate?(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Called once per frame after timers, animation, and physics updates.
|
|
73
|
+
*
|
|
74
|
+
* @param dt - Delta time in seconds since the previous frame.
|
|
75
|
+
*/
|
|
76
|
+
onUpdate?(dt: number): void;
|
|
77
|
+
}
|
|
59
78
|
/**
|
|
60
79
|
* Base class for all renderable MinimoJS actors.
|
|
61
80
|
*
|
|
@@ -64,6 +83,11 @@ export type BackgroundFit = "none" | "stretch" | "contain" | "cover";
|
|
|
64
83
|
* engine.
|
|
65
84
|
*/
|
|
66
85
|
export declare abstract class BaseSprite {
|
|
86
|
+
protected constructor(game?: Game | null);
|
|
87
|
+
/**
|
|
88
|
+
* Game instance associated with this sprite, if any.
|
|
89
|
+
*/
|
|
90
|
+
get game(): Game | null;
|
|
67
91
|
/**
|
|
68
92
|
* X position in world space (horizontal center of the sprite), in pixels.
|
|
69
93
|
* Positive X points right. Updated each frame by: `x += vx * dt`.
|
|
@@ -233,7 +257,7 @@ export declare class Sprite extends BaseSprite {
|
|
|
233
257
|
* All other properties use their defaults and can be set after construction.
|
|
234
258
|
*
|
|
235
259
|
* @param sprite - The emoji character to render. Must be a single emoji.
|
|
236
|
-
*
|
|
260
|
+
* Use {@link ImageSprite} for preloaded bitmap textures.
|
|
237
261
|
* @example "🔥", "⭐", "🐢", "💣", "👾"
|
|
238
262
|
* @param x - Initial X position in world space (center), in pixels. Default: `0`.
|
|
239
263
|
* @param y - Initial Y position in world space (center), in pixels. Default: `0`.
|
|
@@ -248,9 +272,47 @@ export declare class Sprite extends BaseSprite {
|
|
|
248
272
|
constructor(sprite: string, x?: number, y?: number, size?: number);
|
|
249
273
|
getRenderCacheKey(): string;
|
|
250
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* A renderable sprite backed by a preloaded image asset.
|
|
277
|
+
*
|
|
278
|
+
* Width and height are always resolved from the current texture. To resize the
|
|
279
|
+
* sprite visually, use {@link BaseSprite.scale}.
|
|
280
|
+
*/
|
|
281
|
+
export declare class ImageSprite extends BaseSprite {
|
|
282
|
+
private _imageKey;
|
|
283
|
+
get imageKey(): string;
|
|
284
|
+
get width(): number;
|
|
285
|
+
get height(): number;
|
|
286
|
+
/**
|
|
287
|
+
* Creates a new image-backed sprite.
|
|
288
|
+
*
|
|
289
|
+
* @param game - Game instance used to resolve the texture key.
|
|
290
|
+
* @param imageKey - Preloaded image key previously registered with {@link Game.loadImage}.
|
|
291
|
+
* @param x - Initial X position in world space (center), in pixels. Default: `0`.
|
|
292
|
+
* @param y - Initial Y position in world space (center), in pixels. Default: `0`.
|
|
293
|
+
*/
|
|
294
|
+
constructor(game: Game, imageKey: string, x?: number, y?: number);
|
|
295
|
+
/**
|
|
296
|
+
* Replaces the current texture with another preloaded image.
|
|
297
|
+
*
|
|
298
|
+
* @param imageKey - New preloaded image key to render.
|
|
299
|
+
*/
|
|
300
|
+
setTexture(imageKey: string): void;
|
|
301
|
+
getRenderCacheKey(): string;
|
|
302
|
+
private getResolvedImage;
|
|
303
|
+
private assertTextureAvailable;
|
|
304
|
+
}
|
|
251
305
|
/**
|
|
252
306
|
* A renderable text actor that participates in the same animation/effects
|
|
253
307
|
* pipeline as regular sprites.
|
|
308
|
+
*
|
|
309
|
+
* Use `TextSprite` when the content is primarily text, or when you need text
|
|
310
|
+
* layout features such as wrapping, fixed button sizes, background/border, or
|
|
311
|
+
* text stroke.
|
|
312
|
+
*
|
|
313
|
+
* For controls that are only a single emoji, prefer {@link Sprite}. Emoji-only
|
|
314
|
+
* buttons usually behave better as sprites because they do not need text
|
|
315
|
+
* padding/layout and their bounds match the rendered emoji more directly.
|
|
254
316
|
*/
|
|
255
317
|
export declare class TextSprite extends BaseSprite {
|
|
256
318
|
private static _measurementCanvas;
|
|
@@ -491,6 +553,11 @@ export interface CollisionInfo {
|
|
|
491
553
|
* If you use additional families, register them with {@link Game.requireFont}
|
|
492
554
|
* before calling {@link Game.start}.
|
|
493
555
|
*
|
|
556
|
+
* Prefer {@link TextSprite} for persistent UI text, interactive labels,
|
|
557
|
+
* buttons, fixed-size text boxes, and styled text elements. Keep
|
|
558
|
+
* `drawText()` for simple screen-space overlays such as HUD counters, debug
|
|
559
|
+
* text, or other text that is redrawn every frame.
|
|
560
|
+
*
|
|
494
561
|
* You MUST still declare the fonts yourself in your `index.html`. MinimoJS
|
|
495
562
|
* does NOT download, inject, or manage web fonts for you. If a font is missing,
|
|
496
563
|
* the browser falls back to `monospace`.
|
|
@@ -509,7 +576,7 @@ export interface CollisionInfo {
|
|
|
509
576
|
* Example custom font registration:
|
|
510
577
|
*
|
|
511
578
|
* ```ts
|
|
512
|
-
* const game = new Game(
|
|
579
|
+
* const game = new Game(720, 1280);
|
|
513
580
|
* game.requireFont('"Bangers"', { weight: "400" });
|
|
514
581
|
* game.start();
|
|
515
582
|
* ```
|
|
@@ -521,7 +588,7 @@ export interface CollisionInfo {
|
|
|
521
588
|
* ```ts
|
|
522
589
|
* import { Game, Sprite } from "https://cdn.jsdelivr.net/npm/minimojs@<version>/dist/minimo.js";
|
|
523
590
|
*
|
|
524
|
-
* const game = new Game(
|
|
591
|
+
* const game = new Game(720, 1280);
|
|
525
592
|
*
|
|
526
593
|
* const player = new Sprite("🐢", 400, 500, 48);
|
|
527
594
|
* game.add(player);
|
|
@@ -636,10 +703,10 @@ export interface CollisionInfo {
|
|
|
636
703
|
*
|
|
637
704
|
* ---
|
|
638
705
|
*
|
|
639
|
-
* ## Scene Initialization with `
|
|
706
|
+
* ## Scene Initialization with `IScene`
|
|
640
707
|
*
|
|
641
|
-
* MinimoJS
|
|
642
|
-
*
|
|
708
|
+
* MinimoJS supports simple scene objects via {@link IScene}. The engine calls
|
|
709
|
+
* a scene's `onCreate()`:
|
|
643
710
|
* - Once before the first frame (when {@link Game.start} is called).
|
|
644
711
|
* - Again after each {@link Game.reset}.
|
|
645
712
|
*
|
|
@@ -651,25 +718,30 @@ export interface CollisionInfo {
|
|
|
651
718
|
* - Scroll position (scrollX and scrollY reset to 0)
|
|
652
719
|
* - Per-frame input state (pressed keys/pointer)
|
|
653
720
|
*
|
|
654
|
-
* After clearing, `reset()` calls `onCreate()` so
|
|
655
|
-
*
|
|
721
|
+
* After clearing, `reset()` calls the active scene's `onCreate()` so it can
|
|
722
|
+
* rebuild immediately. Simply re-add sprites and re-register timers.
|
|
656
723
|
*
|
|
657
724
|
* ```ts
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
725
|
+
* class SkullScene implements IScene {
|
|
726
|
+
* onCreate() {
|
|
727
|
+
* const skull = new Sprite("💀", 400, 300, 96);
|
|
728
|
+
* game.add(skull);
|
|
729
|
+
* }
|
|
730
|
+
*
|
|
731
|
+
* onUpdate(dt: number) {
|
|
732
|
+
* // normal per-frame update
|
|
733
|
+
* }
|
|
734
|
+
* }
|
|
735
|
+
*
|
|
736
|
+
* const scene = new SkullScene();
|
|
737
|
+
* game.start(scene); // calls onCreate() once before first frame
|
|
669
738
|
* // later:
|
|
670
739
|
* game.reset(); // clears + calls onCreate() again
|
|
671
740
|
* ```
|
|
672
741
|
*
|
|
742
|
+
* Legacy `game.onCreate` / `game.onUpdate` callbacks still work when no active
|
|
743
|
+
* {@link IScene} is set.
|
|
744
|
+
*
|
|
673
745
|
* ---
|
|
674
746
|
*
|
|
675
747
|
* ## Sprite Lifecycle Ownership
|
|
@@ -686,7 +758,7 @@ export interface CollisionInfo {
|
|
|
686
758
|
* ## Forbidden Features
|
|
687
759
|
*
|
|
688
760
|
* The following do NOT exist in MinimoJS v1. Do NOT attempt to use them:
|
|
689
|
-
* -
|
|
761
|
+
* - Full scene manager architecture (scene stacks, transitions, loaders, etc.)
|
|
690
762
|
* - Entity Component System (ECS)
|
|
691
763
|
* - Physics engine (no Box2D, Matter.js, etc.)
|
|
692
764
|
* - Camera zoom or scale
|
|
@@ -695,7 +767,6 @@ export interface CollisionInfo {
|
|
|
695
767
|
* - Parallax layers
|
|
696
768
|
* - Multiple cameras
|
|
697
769
|
* - Full physics engine (only basic explicit AABB collision helpers are provided)
|
|
698
|
-
* - Image sprites (PNG, SVG, canvas, etc.) as gameplay actors
|
|
699
770
|
* - `setTimeout` or `setInterval`
|
|
700
771
|
*/
|
|
701
772
|
export declare class Game {
|
|
@@ -819,10 +890,10 @@ export declare class Game {
|
|
|
819
890
|
*/
|
|
820
891
|
onPreload: (() => void) | null;
|
|
821
892
|
/**
|
|
822
|
-
*
|
|
893
|
+
* Legacy scene creation callback.
|
|
823
894
|
*
|
|
824
895
|
* Called once before the first frame on {@link Game.start}, and again after
|
|
825
|
-
* each {@link Game.reset}
|
|
896
|
+
* each {@link Game.reset} when no active {@link IScene} is set.
|
|
826
897
|
*
|
|
827
898
|
* @example
|
|
828
899
|
* ```ts
|
|
@@ -837,7 +908,9 @@ export declare class Game {
|
|
|
837
908
|
get onCreate(): (() => void) | null;
|
|
838
909
|
set onCreate(callback: (() => void) | null);
|
|
839
910
|
/**
|
|
840
|
-
*
|
|
911
|
+
* Legacy per-frame callback invoked after physics and timer updates.
|
|
912
|
+
*
|
|
913
|
+
* This is used only when no active {@link IScene} is set.
|
|
841
914
|
*
|
|
842
915
|
* @param dt - Delta time in **seconds** since the last frame.
|
|
843
916
|
* Use this for velocity-based movement: `sprite.x += speed * dt`.
|
|
@@ -852,21 +925,26 @@ export declare class Game {
|
|
|
852
925
|
* ```
|
|
853
926
|
*/
|
|
854
927
|
onUpdate: ((dt: number) => void) | null;
|
|
928
|
+
/**
|
|
929
|
+
* Currently active scene object, if any.
|
|
930
|
+
*/
|
|
931
|
+
get currentScene(): IScene | null;
|
|
855
932
|
/**
|
|
856
933
|
* Creates a new MinimoJS game instance.
|
|
857
934
|
*
|
|
858
935
|
* The engine creates its own `<canvas>`, sets its dimensions, and appends it
|
|
859
936
|
* to `document.body`. The canvas is automatically centered and responsively
|
|
860
937
|
* scaled to use the maximum available viewport space while preserving aspect ratio.
|
|
938
|
+
* For new mobile-first Minimo Games, prefer a portrait canvas such as `720x1280`.
|
|
861
939
|
*
|
|
862
|
-
* @param width - Canvas width in pixels. Default: `
|
|
863
|
-
* @param height - Canvas height in pixels. Default: `
|
|
940
|
+
* @param width - Canvas width in pixels. Default: `720`.
|
|
941
|
+
* @param height - Canvas height in pixels. Default: `1280`.
|
|
864
942
|
*
|
|
865
943
|
* @throws Error if a 2D context cannot be obtained.
|
|
866
944
|
*
|
|
867
945
|
* @example
|
|
868
946
|
* ```ts
|
|
869
|
-
* const game = new Game(
|
|
947
|
+
* const game = new Game(720, 1280);
|
|
870
948
|
* game.physics = true;
|
|
871
949
|
* ```
|
|
872
950
|
*/
|
|
@@ -894,7 +972,7 @@ export declare class Game {
|
|
|
894
972
|
*/
|
|
895
973
|
get pointerY(): number;
|
|
896
974
|
/**
|
|
897
|
-
* Registers a {@link
|
|
975
|
+
* Registers a {@link BaseSprite} (or subclass instance) with the engine.
|
|
898
976
|
*
|
|
899
977
|
* After calling `add`, the sprite is rendered and, if dynamic
|
|
900
978
|
* (`isStatic = false`), receives built-in velocity/gravity integration every
|
|
@@ -903,11 +981,11 @@ export declare class Game {
|
|
|
903
981
|
* **Ownership:** The game instance takes ownership of the sprite from this
|
|
904
982
|
* point forward. It will appear in {@link Game.getSprites} on the same frame.
|
|
905
983
|
*
|
|
906
|
-
* **Subclasses:** Any class that extends {@link
|
|
907
|
-
* The engine stores and processes it as a
|
|
984
|
+
* **Subclasses:** Any class that extends {@link BaseSprite} can be passed here.
|
|
985
|
+
* The engine stores and processes it as a live sprite; your custom properties
|
|
908
986
|
* are preserved on the instance.
|
|
909
987
|
*
|
|
910
|
-
* @param sprite - A {@link
|
|
988
|
+
* @param sprite - A {@link BaseSprite} instance (or subclass) to add.
|
|
911
989
|
* @returns The same sprite instance, for chaining or inline assignment.
|
|
912
990
|
*
|
|
913
991
|
* @example
|
|
@@ -922,7 +1000,7 @@ export declare class Game {
|
|
|
922
1000
|
* constructor(x: number, y: number) {
|
|
923
1001
|
* super("👾", x, y, 40);
|
|
924
1002
|
* }
|
|
925
|
-
*
|
|
1003
|
+
* }
|
|
926
1004
|
* const enemy = game.add(new Enemy(600, 100));
|
|
927
1005
|
* ```
|
|
928
1006
|
*/
|
|
@@ -1650,11 +1728,11 @@ export declare class Game {
|
|
|
1650
1728
|
*/
|
|
1651
1729
|
clearTimer(id: number): void;
|
|
1652
1730
|
/**
|
|
1653
|
-
* Draws text on screen as a **screen-space overlay** this frame.
|
|
1731
|
+
* Draws text on screen as a **simple screen-space overlay** this frame.
|
|
1654
1732
|
*
|
|
1655
1733
|
* **Overlay behavior:** Text is drawn in canvas/screen space — it ignores
|
|
1656
1734
|
* `scrollX` / `scrollY`. Position `(0, 0)` is always the top-left of the canvas.
|
|
1657
|
-
* Use this for HUD elements: score, lives, timer, debug info.
|
|
1735
|
+
* Use this for lightweight HUD elements: score, lives, timer, debug info.
|
|
1658
1736
|
*
|
|
1659
1737
|
* **Per-frame:** `drawText` must be called every frame to keep text visible.
|
|
1660
1738
|
* The text overlay list is cleared after each render. Call this inside `onUpdate`.
|
|
@@ -1669,6 +1747,14 @@ export declare class Game {
|
|
|
1669
1747
|
* registered with {@link Game.requireFont}. If a font is unavailable, the
|
|
1670
1748
|
* browser falls back to `monospace`.
|
|
1671
1749
|
*
|
|
1750
|
+
* Prefer {@link TextSprite} for UI buttons or labels that need persistent
|
|
1751
|
+
* bounds, hit testing, fixed sizes, backgrounds, borders, or text stroke.
|
|
1752
|
+
* `drawText()` is the lightweight overlay API, not the primary UI API.
|
|
1753
|
+
*
|
|
1754
|
+
* For controls that are only a single emoji, prefer {@link Sprite} over
|
|
1755
|
+
* {@link TextSprite}. Emoji-only buttons do not benefit from text padding and
|
|
1756
|
+
* usually fit more naturally in the sprite pipeline.
|
|
1757
|
+
*
|
|
1672
1758
|
* @param text - The string to render. Supports emoji and Unicode.
|
|
1673
1759
|
* @param x - X position in **screen space** (pixels from canvas left edge).
|
|
1674
1760
|
* @param y - Y position in **screen space** (pixels from canvas top edge).
|
|
@@ -1762,25 +1848,26 @@ export declare class Game {
|
|
|
1762
1848
|
* - Canvas dimensions
|
|
1763
1849
|
* - AudioContext
|
|
1764
1850
|
*
|
|
1765
|
-
* After clearing, `reset()` immediately calls
|
|
1766
|
-
*
|
|
1851
|
+
* After clearing, `reset()` immediately calls the active scene's
|
|
1852
|
+
* `onCreate()` (or legacy {@link Game.onCreate} if no scene is active) so it
|
|
1853
|
+
* can rebuild synchronously.
|
|
1854
|
+
*
|
|
1855
|
+
* @param scene - Optional new scene to make active before rebuilding.
|
|
1767
1856
|
*
|
|
1768
1857
|
* @example
|
|
1769
1858
|
* ```ts
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
*
|
|
1775
|
-
*
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
*
|
|
1779
|
-
* game.addTimer(3000, false, () => game.reset()); // auto-restart
|
|
1780
|
-
* };
|
|
1859
|
+
* class GameOverScene {
|
|
1860
|
+
* onCreate() {
|
|
1861
|
+
* const skull = new Sprite("💀", 400, 300, 96);
|
|
1862
|
+
* game.add(skull);
|
|
1863
|
+
* game.addTimer(3000, false, () => game.reset());
|
|
1864
|
+
* }
|
|
1865
|
+
* }
|
|
1866
|
+
*
|
|
1867
|
+
* game.reset(new GameOverScene());
|
|
1781
1868
|
* ```
|
|
1782
1869
|
*/
|
|
1783
|
-
reset(): void;
|
|
1870
|
+
reset(scene?: IScene): void;
|
|
1784
1871
|
/**
|
|
1785
1872
|
* Starts the `requestAnimationFrame` game loop.
|
|
1786
1873
|
* Safe to call multiple times — does nothing if already running.
|
|
@@ -1788,10 +1875,12 @@ export declare class Game {
|
|
|
1788
1875
|
* asset registration, loads queued images, waits for all fonts registered via
|
|
1789
1876
|
* {@link Game.requireFont}, and only then, if images were queued, shows a
|
|
1790
1877
|
* default loading screen while those image assets are still loading.
|
|
1791
|
-
* After preload completes, `onCreate()` is called before
|
|
1878
|
+
* After preload completes, the active scene's `onCreate()` is called before
|
|
1879
|
+
* the first frame.
|
|
1792
1880
|
*
|
|
1793
|
-
* The loop calls `onUpdate`
|
|
1794
|
-
*
|
|
1881
|
+
* The loop calls the active scene's `onUpdate(dt)` (or legacy
|
|
1882
|
+
* {@link Game.onUpdate}) once per frame, then renders all sprites and text
|
|
1883
|
+
* overlays. Order per frame:
|
|
1795
1884
|
* 1. Accumulate timer elapsed time; fire ready callbacks.
|
|
1796
1885
|
* 2. Advance animations (linear interpolation).
|
|
1797
1886
|
* 3. Advance active explosion effects.
|
|
@@ -1805,11 +1894,15 @@ export declare class Game {
|
|
|
1805
1894
|
*
|
|
1806
1895
|
* @example
|
|
1807
1896
|
* ```ts
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
1897
|
+
* class DemoScene {
|
|
1898
|
+
* onCreate() {}
|
|
1899
|
+
* onUpdate(dt: number) {}
|
|
1900
|
+
* }
|
|
1901
|
+
*
|
|
1902
|
+
* game.start(new DemoScene());
|
|
1810
1903
|
* ```
|
|
1811
1904
|
*/
|
|
1812
|
-
start(): void;
|
|
1905
|
+
start(scene?: IScene): void;
|
|
1813
1906
|
/**
|
|
1814
1907
|
* Stops the game loop. The canvas retains its last rendered frame.
|
|
1815
1908
|
* Call {@link Game.start} to resume.
|
package/dist/minimo.js
CHANGED
|
@@ -32,7 +32,7 @@ import { TimerSystem } from "./internal/TimerSystem.js";
|
|
|
32
32
|
* engine.
|
|
33
33
|
*/
|
|
34
34
|
export class BaseSprite {
|
|
35
|
-
constructor() {
|
|
35
|
+
constructor(game = null) {
|
|
36
36
|
/**
|
|
37
37
|
* X position in world space (horizontal center of the sprite), in pixels.
|
|
38
38
|
* Positive X points right. Updated each frame by: `x += vx * dt`.
|
|
@@ -137,6 +137,20 @@ export class BaseSprite {
|
|
|
137
137
|
* Values > 1 amplify gravity; negative values invert it.
|
|
138
138
|
*/
|
|
139
139
|
this.gravityScale = 0;
|
|
140
|
+
this._game = game;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Game instance associated with this sprite, if any.
|
|
144
|
+
*/
|
|
145
|
+
get game() {
|
|
146
|
+
return this._game;
|
|
147
|
+
}
|
|
148
|
+
/** @internal */
|
|
149
|
+
_setGame(game) {
|
|
150
|
+
if (this._game !== null && this._game !== game) {
|
|
151
|
+
throw new Error("MinimoJS: A sprite cannot be attached to multiple Game instances.");
|
|
152
|
+
}
|
|
153
|
+
this._game = game;
|
|
140
154
|
}
|
|
141
155
|
/**
|
|
142
156
|
* Resolved center X used internally by rendering, hit testing, and collisions.
|
|
@@ -207,7 +221,7 @@ export class Sprite extends BaseSprite {
|
|
|
207
221
|
* All other properties use their defaults and can be set after construction.
|
|
208
222
|
*
|
|
209
223
|
* @param sprite - The emoji character to render. Must be a single emoji.
|
|
210
|
-
*
|
|
224
|
+
* Use {@link ImageSprite} for preloaded bitmap textures.
|
|
211
225
|
* @example "🔥", "⭐", "🐢", "💣", "👾"
|
|
212
226
|
* @param x - Initial X position in world space (center), in pixels. Default: `0`.
|
|
213
227
|
* @param y - Initial Y position in world space (center), in pixels. Default: `0`.
|
|
@@ -231,9 +245,86 @@ export class Sprite extends BaseSprite {
|
|
|
231
245
|
return `emoji:${this.sprite}|size:${this._size}|color:${this.color}`;
|
|
232
246
|
}
|
|
233
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* A renderable sprite backed by a preloaded image asset.
|
|
250
|
+
*
|
|
251
|
+
* Width and height are always resolved from the current texture. To resize the
|
|
252
|
+
* sprite visually, use {@link BaseSprite.scale}.
|
|
253
|
+
*/
|
|
254
|
+
export class ImageSprite extends BaseSprite {
|
|
255
|
+
get imageKey() {
|
|
256
|
+
return this._imageKey;
|
|
257
|
+
}
|
|
258
|
+
get width() {
|
|
259
|
+
const image = this.getResolvedImage();
|
|
260
|
+
return image.naturalWidth || image.width;
|
|
261
|
+
}
|
|
262
|
+
get height() {
|
|
263
|
+
const image = this.getResolvedImage();
|
|
264
|
+
return image.naturalHeight || image.height;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Creates a new image-backed sprite.
|
|
268
|
+
*
|
|
269
|
+
* @param game - Game instance used to resolve the texture key.
|
|
270
|
+
* @param imageKey - Preloaded image key previously registered with {@link Game.loadImage}.
|
|
271
|
+
* @param x - Initial X position in world space (center), in pixels. Default: `0`.
|
|
272
|
+
* @param y - Initial Y position in world space (center), in pixels. Default: `0`.
|
|
273
|
+
*/
|
|
274
|
+
constructor(game, imageKey, x = 0, y = 0) {
|
|
275
|
+
super(game);
|
|
276
|
+
this._imageKey = imageKey;
|
|
277
|
+
this.x = x;
|
|
278
|
+
this.y = y;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Replaces the current texture with another preloaded image.
|
|
282
|
+
*
|
|
283
|
+
* @param imageKey - New preloaded image key to render.
|
|
284
|
+
*/
|
|
285
|
+
setTexture(imageKey) {
|
|
286
|
+
if (imageKey === this._imageKey)
|
|
287
|
+
return;
|
|
288
|
+
this.assertTextureAvailable(imageKey);
|
|
289
|
+
this._imageKey = imageKey;
|
|
290
|
+
}
|
|
291
|
+
getRenderCacheKey() {
|
|
292
|
+
const image = this.getResolvedImage();
|
|
293
|
+
return [
|
|
294
|
+
"image",
|
|
295
|
+
this._imageKey,
|
|
296
|
+
image.naturalWidth || image.width,
|
|
297
|
+
image.naturalHeight || image.height,
|
|
298
|
+
].join("|");
|
|
299
|
+
}
|
|
300
|
+
getResolvedImage() {
|
|
301
|
+
this.assertTextureAvailable(this._imageKey);
|
|
302
|
+
const image = this.game?.getImage(this._imageKey);
|
|
303
|
+
if (!image) {
|
|
304
|
+
throw new Error(`MinimoJS: Image '${this._imageKey}' is not loaded.`);
|
|
305
|
+
}
|
|
306
|
+
return image;
|
|
307
|
+
}
|
|
308
|
+
assertTextureAvailable(imageKey) {
|
|
309
|
+
if (!this.game) {
|
|
310
|
+
throw new Error("MinimoJS: ImageSprite requires an associated Game instance.");
|
|
311
|
+
}
|
|
312
|
+
if (!this.game.hasImage(imageKey)) {
|
|
313
|
+
throw new Error(`MinimoJS: Image '${imageKey}' is not loaded.`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
234
317
|
/**
|
|
235
318
|
* A renderable text actor that participates in the same animation/effects
|
|
236
319
|
* pipeline as regular sprites.
|
|
320
|
+
*
|
|
321
|
+
* Use `TextSprite` when the content is primarily text, or when you need text
|
|
322
|
+
* layout features such as wrapping, fixed button sizes, background/border, or
|
|
323
|
+
* text stroke.
|
|
324
|
+
*
|
|
325
|
+
* For controls that are only a single emoji, prefer {@link Sprite}. Emoji-only
|
|
326
|
+
* buttons usually behave better as sprites because they do not need text
|
|
327
|
+
* padding/layout and their bounds match the rendered emoji more directly.
|
|
237
328
|
*/
|
|
238
329
|
export class TextSprite extends BaseSprite {
|
|
239
330
|
get width() {
|
|
@@ -254,8 +345,8 @@ export class TextSprite extends BaseSprite {
|
|
|
254
345
|
this.maxWidth = 0;
|
|
255
346
|
this.fixedWidth = 0;
|
|
256
347
|
this.fixedHeight = 0;
|
|
257
|
-
this.paddingX =
|
|
258
|
-
this.paddingY =
|
|
348
|
+
this.paddingX = 2;
|
|
349
|
+
this.paddingY = 2;
|
|
259
350
|
this.backgroundColor = null;
|
|
260
351
|
this.borderColor = null;
|
|
261
352
|
this.borderWidth = 0;
|
|
@@ -579,6 +670,11 @@ export class BackgroundLayer {
|
|
|
579
670
|
* If you use additional families, register them with {@link Game.requireFont}
|
|
580
671
|
* before calling {@link Game.start}.
|
|
581
672
|
*
|
|
673
|
+
* Prefer {@link TextSprite} for persistent UI text, interactive labels,
|
|
674
|
+
* buttons, fixed-size text boxes, and styled text elements. Keep
|
|
675
|
+
* `drawText()` for simple screen-space overlays such as HUD counters, debug
|
|
676
|
+
* text, or other text that is redrawn every frame.
|
|
677
|
+
*
|
|
582
678
|
* You MUST still declare the fonts yourself in your `index.html`. MinimoJS
|
|
583
679
|
* does NOT download, inject, or manage web fonts for you. If a font is missing,
|
|
584
680
|
* the browser falls back to `monospace`.
|
|
@@ -597,7 +693,7 @@ export class BackgroundLayer {
|
|
|
597
693
|
* Example custom font registration:
|
|
598
694
|
*
|
|
599
695
|
* ```ts
|
|
600
|
-
* const game = new Game(
|
|
696
|
+
* const game = new Game(720, 1280);
|
|
601
697
|
* game.requireFont('"Bangers"', { weight: "400" });
|
|
602
698
|
* game.start();
|
|
603
699
|
* ```
|
|
@@ -609,7 +705,7 @@ export class BackgroundLayer {
|
|
|
609
705
|
* ```ts
|
|
610
706
|
* import { Game, Sprite } from "https://cdn.jsdelivr.net/npm/minimojs@<version>/dist/minimo.js";
|
|
611
707
|
*
|
|
612
|
-
* const game = new Game(
|
|
708
|
+
* const game = new Game(720, 1280);
|
|
613
709
|
*
|
|
614
710
|
* const player = new Sprite("🐢", 400, 500, 48);
|
|
615
711
|
* game.add(player);
|
|
@@ -724,10 +820,10 @@ export class BackgroundLayer {
|
|
|
724
820
|
*
|
|
725
821
|
* ---
|
|
726
822
|
*
|
|
727
|
-
* ## Scene Initialization with `
|
|
823
|
+
* ## Scene Initialization with `IScene`
|
|
728
824
|
*
|
|
729
|
-
* MinimoJS
|
|
730
|
-
*
|
|
825
|
+
* MinimoJS supports simple scene objects via {@link IScene}. The engine calls
|
|
826
|
+
* a scene's `onCreate()`:
|
|
731
827
|
* - Once before the first frame (when {@link Game.start} is called).
|
|
732
828
|
* - Again after each {@link Game.reset}.
|
|
733
829
|
*
|
|
@@ -739,25 +835,30 @@ export class BackgroundLayer {
|
|
|
739
835
|
* - Scroll position (scrollX and scrollY reset to 0)
|
|
740
836
|
* - Per-frame input state (pressed keys/pointer)
|
|
741
837
|
*
|
|
742
|
-
* After clearing, `reset()` calls `onCreate()` so
|
|
743
|
-
*
|
|
838
|
+
* After clearing, `reset()` calls the active scene's `onCreate()` so it can
|
|
839
|
+
* rebuild immediately. Simply re-add sprites and re-register timers.
|
|
744
840
|
*
|
|
745
841
|
* ```ts
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
842
|
+
* class SkullScene implements IScene {
|
|
843
|
+
* onCreate() {
|
|
844
|
+
* const skull = new Sprite("💀", 400, 300, 96);
|
|
845
|
+
* game.add(skull);
|
|
846
|
+
* }
|
|
847
|
+
*
|
|
848
|
+
* onUpdate(dt: number) {
|
|
849
|
+
* // normal per-frame update
|
|
850
|
+
* }
|
|
851
|
+
* }
|
|
852
|
+
*
|
|
853
|
+
* const scene = new SkullScene();
|
|
854
|
+
* game.start(scene); // calls onCreate() once before first frame
|
|
757
855
|
* // later:
|
|
758
856
|
* game.reset(); // clears + calls onCreate() again
|
|
759
857
|
* ```
|
|
760
858
|
*
|
|
859
|
+
* Legacy `game.onCreate` / `game.onUpdate` callbacks still work when no active
|
|
860
|
+
* {@link IScene} is set.
|
|
861
|
+
*
|
|
761
862
|
* ---
|
|
762
863
|
*
|
|
763
864
|
* ## Sprite Lifecycle Ownership
|
|
@@ -774,7 +875,7 @@ export class BackgroundLayer {
|
|
|
774
875
|
* ## Forbidden Features
|
|
775
876
|
*
|
|
776
877
|
* The following do NOT exist in MinimoJS v1. Do NOT attempt to use them:
|
|
777
|
-
* -
|
|
878
|
+
* - Full scene manager architecture (scene stacks, transitions, loaders, etc.)
|
|
778
879
|
* - Entity Component System (ECS)
|
|
779
880
|
* - Physics engine (no Box2D, Matter.js, etc.)
|
|
780
881
|
* - Camera zoom or scale
|
|
@@ -783,7 +884,6 @@ export class BackgroundLayer {
|
|
|
783
884
|
* - Parallax layers
|
|
784
885
|
* - Multiple cameras
|
|
785
886
|
* - Full physics engine (only basic explicit AABB collision helpers are provided)
|
|
786
|
-
* - Image sprites (PNG, SVG, canvas, etc.) as gameplay actors
|
|
787
887
|
* - `setTimeout` or `setInterval`
|
|
788
888
|
*/
|
|
789
889
|
export class Game {
|
|
@@ -845,10 +945,10 @@ export class Game {
|
|
|
845
945
|
this._physicsSystem.enabled = value;
|
|
846
946
|
}
|
|
847
947
|
/**
|
|
848
|
-
*
|
|
948
|
+
* Legacy scene creation callback.
|
|
849
949
|
*
|
|
850
950
|
* Called once before the first frame on {@link Game.start}, and again after
|
|
851
|
-
* each {@link Game.reset}
|
|
951
|
+
* each {@link Game.reset} when no active {@link IScene} is set.
|
|
852
952
|
*
|
|
853
953
|
* @example
|
|
854
954
|
* ```ts
|
|
@@ -861,10 +961,16 @@ export class Game {
|
|
|
861
961
|
* ```
|
|
862
962
|
*/
|
|
863
963
|
get onCreate() {
|
|
864
|
-
return this.
|
|
964
|
+
return this._onCreate;
|
|
865
965
|
}
|
|
866
966
|
set onCreate(callback) {
|
|
867
|
-
this.
|
|
967
|
+
this._onCreate = callback;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Currently active scene object, if any.
|
|
971
|
+
*/
|
|
972
|
+
get currentScene() {
|
|
973
|
+
return this._currentScene;
|
|
868
974
|
}
|
|
869
975
|
// -------------------------------------------------------------------------
|
|
870
976
|
// Constructor
|
|
@@ -875,24 +981,27 @@ export class Game {
|
|
|
875
981
|
* The engine creates its own `<canvas>`, sets its dimensions, and appends it
|
|
876
982
|
* to `document.body`. The canvas is automatically centered and responsively
|
|
877
983
|
* scaled to use the maximum available viewport space while preserving aspect ratio.
|
|
984
|
+
* For new mobile-first Minimo Games, prefer a portrait canvas such as `720x1280`.
|
|
878
985
|
*
|
|
879
|
-
* @param width - Canvas width in pixels. Default: `
|
|
880
|
-
* @param height - Canvas height in pixels. Default: `
|
|
986
|
+
* @param width - Canvas width in pixels. Default: `720`.
|
|
987
|
+
* @param height - Canvas height in pixels. Default: `1280`.
|
|
881
988
|
*
|
|
882
989
|
* @throws Error if a 2D context cannot be obtained.
|
|
883
990
|
*
|
|
884
991
|
* @example
|
|
885
992
|
* ```ts
|
|
886
|
-
* const game = new Game(
|
|
993
|
+
* const game = new Game(720, 1280);
|
|
887
994
|
* game.physics = true;
|
|
888
995
|
* ```
|
|
889
996
|
*/
|
|
890
|
-
constructor(width =
|
|
997
|
+
constructor(width = 720, height = 1280) {
|
|
891
998
|
/** @internal */ this._requiredFonts = new Map();
|
|
892
999
|
/** @internal */ this._hasAppliedGlobalFontRequirements = false;
|
|
893
1000
|
/** @internal */ this._isRegisteringPreloadAssets = false;
|
|
894
1001
|
/** @internal */ this._hasCompletedPreload = false;
|
|
895
1002
|
/** @internal */ this._preloadPromise = null;
|
|
1003
|
+
/** @internal */ this._onCreate = null;
|
|
1004
|
+
/** @internal */ this._currentScene = null;
|
|
896
1005
|
/**
|
|
897
1006
|
* Horizontal scroll offset of the world camera, in pixels.
|
|
898
1007
|
* The canvas viewport is shifted left by `scrollX` — sprites with higher `x`
|
|
@@ -968,7 +1077,9 @@ export class Game {
|
|
|
968
1077
|
*/
|
|
969
1078
|
this.onPreload = null;
|
|
970
1079
|
/**
|
|
971
|
-
*
|
|
1080
|
+
* Legacy per-frame callback invoked after physics and timer updates.
|
|
1081
|
+
*
|
|
1082
|
+
* This is used only when no active {@link IScene} is set.
|
|
972
1083
|
*
|
|
973
1084
|
* @param dt - Delta time in **seconds** since the last frame.
|
|
974
1085
|
* Use this for velocity-based movement: `sprite.x += speed * dt`.
|
|
@@ -1000,7 +1111,7 @@ export class Game {
|
|
|
1000
1111
|
this._timerSystem = new TimerSystem();
|
|
1001
1112
|
this._animationSystem = new AnimationSystem();
|
|
1002
1113
|
this._physicsSystem = new PhysicsSystem();
|
|
1003
|
-
this._spriteSystem = new SpriteSystem(this._animationSystem);
|
|
1114
|
+
this._spriteSystem = new SpriteSystem(this._animationSystem, this);
|
|
1004
1115
|
this._backgroundSystem = new BackgroundSystem();
|
|
1005
1116
|
this._assetSystem = new AssetSystem();
|
|
1006
1117
|
this._inputSystem = new InputSystem(this, this._canvas);
|
|
@@ -1010,6 +1121,7 @@ export class Game {
|
|
|
1010
1121
|
this._explosionSystem = new ExplosionSystem();
|
|
1011
1122
|
this._trailSystem = new TrailSystem();
|
|
1012
1123
|
this._loopSystem = new LoopSystem(this._onLoopFrameCallback.bind(this));
|
|
1124
|
+
this._loopSystem.onCreate = this._invokeCreate.bind(this);
|
|
1013
1125
|
this._inputSystem.bindInputEvents();
|
|
1014
1126
|
this.requireFont('"Press Start 2P"', {
|
|
1015
1127
|
sampleText: "Loading... SCORE LIVES GAME OVER YOU WIN",
|
|
@@ -1053,7 +1165,7 @@ export class Game {
|
|
|
1053
1165
|
// Sprite management
|
|
1054
1166
|
// -------------------------------------------------------------------------
|
|
1055
1167
|
/**
|
|
1056
|
-
* Registers a {@link
|
|
1168
|
+
* Registers a {@link BaseSprite} (or subclass instance) with the engine.
|
|
1057
1169
|
*
|
|
1058
1170
|
* After calling `add`, the sprite is rendered and, if dynamic
|
|
1059
1171
|
* (`isStatic = false`), receives built-in velocity/gravity integration every
|
|
@@ -1062,11 +1174,11 @@ export class Game {
|
|
|
1062
1174
|
* **Ownership:** The game instance takes ownership of the sprite from this
|
|
1063
1175
|
* point forward. It will appear in {@link Game.getSprites} on the same frame.
|
|
1064
1176
|
*
|
|
1065
|
-
* **Subclasses:** Any class that extends {@link
|
|
1066
|
-
* The engine stores and processes it as a
|
|
1177
|
+
* **Subclasses:** Any class that extends {@link BaseSprite} can be passed here.
|
|
1178
|
+
* The engine stores and processes it as a live sprite; your custom properties
|
|
1067
1179
|
* are preserved on the instance.
|
|
1068
1180
|
*
|
|
1069
|
-
* @param sprite - A {@link
|
|
1181
|
+
* @param sprite - A {@link BaseSprite} instance (or subclass) to add.
|
|
1070
1182
|
* @returns The same sprite instance, for chaining or inline assignment.
|
|
1071
1183
|
*
|
|
1072
1184
|
* @example
|
|
@@ -1081,7 +1193,7 @@ export class Game {
|
|
|
1081
1193
|
* constructor(x: number, y: number) {
|
|
1082
1194
|
* super("👾", x, y, 40);
|
|
1083
1195
|
* }
|
|
1084
|
-
*
|
|
1196
|
+
* }
|
|
1085
1197
|
* const enemy = game.add(new Enemy(600, 100));
|
|
1086
1198
|
* ```
|
|
1087
1199
|
*/
|
|
@@ -1926,11 +2038,11 @@ export class Game {
|
|
|
1926
2038
|
// Text
|
|
1927
2039
|
// -------------------------------------------------------------------------
|
|
1928
2040
|
/**
|
|
1929
|
-
* Draws text on screen as a **screen-space overlay** this frame.
|
|
2041
|
+
* Draws text on screen as a **simple screen-space overlay** this frame.
|
|
1930
2042
|
*
|
|
1931
2043
|
* **Overlay behavior:** Text is drawn in canvas/screen space — it ignores
|
|
1932
2044
|
* `scrollX` / `scrollY`. Position `(0, 0)` is always the top-left of the canvas.
|
|
1933
|
-
* Use this for HUD elements: score, lives, timer, debug info.
|
|
2045
|
+
* Use this for lightweight HUD elements: score, lives, timer, debug info.
|
|
1934
2046
|
*
|
|
1935
2047
|
* **Per-frame:** `drawText` must be called every frame to keep text visible.
|
|
1936
2048
|
* The text overlay list is cleared after each render. Call this inside `onUpdate`.
|
|
@@ -1945,6 +2057,14 @@ export class Game {
|
|
|
1945
2057
|
* registered with {@link Game.requireFont}. If a font is unavailable, the
|
|
1946
2058
|
* browser falls back to `monospace`.
|
|
1947
2059
|
*
|
|
2060
|
+
* Prefer {@link TextSprite} for UI buttons or labels that need persistent
|
|
2061
|
+
* bounds, hit testing, fixed sizes, backgrounds, borders, or text stroke.
|
|
2062
|
+
* `drawText()` is the lightweight overlay API, not the primary UI API.
|
|
2063
|
+
*
|
|
2064
|
+
* For controls that are only a single emoji, prefer {@link Sprite} over
|
|
2065
|
+
* {@link TextSprite}. Emoji-only buttons do not benefit from text padding and
|
|
2066
|
+
* usually fit more naturally in the sprite pipeline.
|
|
2067
|
+
*
|
|
1948
2068
|
* @param text - The string to render. Supports emoji and Unicode.
|
|
1949
2069
|
* @param x - X position in **screen space** (pixels from canvas left edge).
|
|
1950
2070
|
* @param y - Y position in **screen space** (pixels from canvas top edge).
|
|
@@ -2067,25 +2187,29 @@ export class Game {
|
|
|
2067
2187
|
* - Canvas dimensions
|
|
2068
2188
|
* - AudioContext
|
|
2069
2189
|
*
|
|
2070
|
-
* After clearing, `reset()` immediately calls
|
|
2071
|
-
*
|
|
2190
|
+
* After clearing, `reset()` immediately calls the active scene's
|
|
2191
|
+
* `onCreate()` (or legacy {@link Game.onCreate} if no scene is active) so it
|
|
2192
|
+
* can rebuild synchronously.
|
|
2193
|
+
*
|
|
2194
|
+
* @param scene - Optional new scene to make active before rebuilding.
|
|
2072
2195
|
*
|
|
2073
2196
|
* @example
|
|
2074
2197
|
* ```ts
|
|
2075
|
-
*
|
|
2076
|
-
*
|
|
2077
|
-
*
|
|
2078
|
-
*
|
|
2079
|
-
*
|
|
2080
|
-
*
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
*
|
|
2084
|
-
* game.addTimer(3000, false, () => game.reset()); // auto-restart
|
|
2085
|
-
* };
|
|
2198
|
+
* class GameOverScene {
|
|
2199
|
+
* onCreate() {
|
|
2200
|
+
* const skull = new Sprite("💀", 400, 300, 96);
|
|
2201
|
+
* game.add(skull);
|
|
2202
|
+
* game.addTimer(3000, false, () => game.reset());
|
|
2203
|
+
* }
|
|
2204
|
+
* }
|
|
2205
|
+
*
|
|
2206
|
+
* game.reset(new GameOverScene());
|
|
2086
2207
|
* ```
|
|
2087
2208
|
*/
|
|
2088
|
-
reset() {
|
|
2209
|
+
reset(scene) {
|
|
2210
|
+
if (scene !== undefined) {
|
|
2211
|
+
this._currentScene = scene;
|
|
2212
|
+
}
|
|
2089
2213
|
this._backgroundSystem.clearAll();
|
|
2090
2214
|
this._spriteSystem.clearAll();
|
|
2091
2215
|
this._timerSystem.clearAll();
|
|
@@ -2108,10 +2232,12 @@ export class Game {
|
|
|
2108
2232
|
* asset registration, loads queued images, waits for all fonts registered via
|
|
2109
2233
|
* {@link Game.requireFont}, and only then, if images were queued, shows a
|
|
2110
2234
|
* default loading screen while those image assets are still loading.
|
|
2111
|
-
* After preload completes, `onCreate()` is called before
|
|
2235
|
+
* After preload completes, the active scene's `onCreate()` is called before
|
|
2236
|
+
* the first frame.
|
|
2112
2237
|
*
|
|
2113
|
-
* The loop calls `onUpdate`
|
|
2114
|
-
*
|
|
2238
|
+
* The loop calls the active scene's `onUpdate(dt)` (or legacy
|
|
2239
|
+
* {@link Game.onUpdate}) once per frame, then renders all sprites and text
|
|
2240
|
+
* overlays. Order per frame:
|
|
2115
2241
|
* 1. Accumulate timer elapsed time; fire ready callbacks.
|
|
2116
2242
|
* 2. Advance animations (linear interpolation).
|
|
2117
2243
|
* 3. Advance active explosion effects.
|
|
@@ -2125,11 +2251,18 @@ export class Game {
|
|
|
2125
2251
|
*
|
|
2126
2252
|
* @example
|
|
2127
2253
|
* ```ts
|
|
2128
|
-
*
|
|
2129
|
-
*
|
|
2254
|
+
* class DemoScene {
|
|
2255
|
+
* onCreate() {}
|
|
2256
|
+
* onUpdate(dt: number) {}
|
|
2257
|
+
* }
|
|
2258
|
+
*
|
|
2259
|
+
* game.start(new DemoScene());
|
|
2130
2260
|
* ```
|
|
2131
2261
|
*/
|
|
2132
|
-
start() {
|
|
2262
|
+
start(scene) {
|
|
2263
|
+
if (scene !== undefined) {
|
|
2264
|
+
this._currentScene = scene;
|
|
2265
|
+
}
|
|
2133
2266
|
this.applyGlobalFontRequirements();
|
|
2134
2267
|
if (this._hasCompletedPreload) {
|
|
2135
2268
|
this._loopSystem.start();
|
|
@@ -2207,7 +2340,9 @@ export class Game {
|
|
|
2207
2340
|
this._animationSystem.update(dtMs);
|
|
2208
2341
|
this._explosionSystem.update(dt, dtMs);
|
|
2209
2342
|
this._physicsSystem.update(this._spriteSystem.getMutableSprites(), dt);
|
|
2210
|
-
if (this.onUpdate)
|
|
2343
|
+
if (this._currentScene?.onUpdate)
|
|
2344
|
+
this._currentScene.onUpdate(dt);
|
|
2345
|
+
else if (this.onUpdate)
|
|
2211
2346
|
this.onUpdate(dt);
|
|
2212
2347
|
this._trailSystem.update(dtMs, this._renderSystem.getSpriteRenderSnapshot.bind(this._renderSystem));
|
|
2213
2348
|
this._render();
|
|
@@ -2215,6 +2350,13 @@ export class Game {
|
|
|
2215
2350
|
this._textSystem.clear();
|
|
2216
2351
|
}
|
|
2217
2352
|
/** @internal */
|
|
2353
|
+
_invokeCreate() {
|
|
2354
|
+
if (this._currentScene?.onCreate)
|
|
2355
|
+
this._currentScene.onCreate();
|
|
2356
|
+
else
|
|
2357
|
+
this._onCreate?.();
|
|
2358
|
+
}
|
|
2359
|
+
/** @internal */
|
|
2218
2360
|
waitForDocumentFonts() {
|
|
2219
2361
|
if (typeof document === "undefined") {
|
|
2220
2362
|
return Promise.resolve();
|
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.12",
|
|
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",
|