like2d 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +34 -35
  2. package/dist/core/audio.d.ts +12 -9
  3. package/dist/core/audio.d.ts.map +1 -1
  4. package/dist/core/audio.js +7 -4
  5. package/dist/core/canvas.d.ts +58 -0
  6. package/dist/core/canvas.d.ts.map +1 -0
  7. package/dist/core/canvas.js +209 -0
  8. package/dist/core/events.d.ts +91 -13
  9. package/dist/core/events.d.ts.map +1 -1
  10. package/dist/core/events.js +20 -0
  11. package/dist/core/gamepad-mapping.d.ts +57 -18
  12. package/dist/core/gamepad-mapping.d.ts.map +1 -1
  13. package/dist/core/gamepad-mapping.js +23 -223
  14. package/dist/core/gamepad.d.ts +34 -58
  15. package/dist/core/gamepad.d.ts.map +1 -1
  16. package/dist/core/gamepad.js +79 -213
  17. package/dist/core/graphics.d.ts +175 -65
  18. package/dist/core/graphics.d.ts.map +1 -1
  19. package/dist/core/graphics.js +294 -202
  20. package/dist/core/input-state.d.ts +2 -2
  21. package/dist/core/input-state.d.ts.map +1 -1
  22. package/dist/core/input.d.ts +22 -15
  23. package/dist/core/input.d.ts.map +1 -1
  24. package/dist/core/input.js +25 -37
  25. package/dist/core/keyboard.d.ts +6 -7
  26. package/dist/core/keyboard.d.ts.map +1 -1
  27. package/dist/core/keyboard.js +15 -35
  28. package/dist/core/like.d.ts +98 -44
  29. package/dist/core/like.d.ts.map +1 -1
  30. package/dist/core/like.js +8 -0
  31. package/dist/core/mouse.d.ts +31 -24
  32. package/dist/core/mouse.d.ts.map +1 -1
  33. package/dist/core/mouse.js +59 -99
  34. package/dist/core/timer.d.ts +2 -5
  35. package/dist/core/timer.d.ts.map +1 -1
  36. package/dist/core/timer.js +2 -14
  37. package/dist/engine.d.ts +61 -16
  38. package/dist/engine.d.ts.map +1 -1
  39. package/dist/engine.js +121 -160
  40. package/dist/index.d.ts +42 -21
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +35 -14
  43. package/dist/math/index.d.ts +2 -0
  44. package/dist/math/index.d.ts.map +1 -0
  45. package/dist/math/index.js +1 -0
  46. package/dist/math/rect.d.ts +71 -0
  47. package/dist/math/rect.d.ts.map +1 -0
  48. package/dist/{core → math}/rect.js +8 -0
  49. package/dist/math/vector2.d.ts +78 -0
  50. package/dist/math/vector2.d.ts.map +1 -0
  51. package/dist/{core → math}/vector2.js +24 -0
  52. package/dist/prefab-scenes/index.d.ts +7 -0
  53. package/dist/prefab-scenes/index.d.ts.map +1 -0
  54. package/dist/prefab-scenes/index.js +6 -0
  55. package/dist/prefab-scenes/startScreen.d.ts +59 -0
  56. package/dist/prefab-scenes/startScreen.d.ts.map +1 -0
  57. package/dist/{scenes/startup.js → prefab-scenes/startScreen.js} +48 -8
  58. package/dist/scene.d.ts +103 -5
  59. package/dist/scene.d.ts.map +1 -1
  60. package/dist/scene.js +28 -1
  61. package/package.json +18 -2
  62. package/dist/core/canvas-config.d.ts +0 -22
  63. package/dist/core/canvas-config.d.ts.map +0 -1
  64. package/dist/core/canvas-config.js +0 -14
  65. package/dist/core/canvas-manager.d.ts +0 -32
  66. package/dist/core/canvas-manager.d.ts.map +0 -1
  67. package/dist/core/canvas-manager.js +0 -179
  68. package/dist/core/gamepad-buttons.d.ts +0 -23
  69. package/dist/core/gamepad-buttons.d.ts.map +0 -1
  70. package/dist/core/gamepad-buttons.js +0 -36
  71. package/dist/core/rect.d.ts +0 -26
  72. package/dist/core/rect.d.ts.map +0 -1
  73. package/dist/core/vector2.d.ts +0 -26
  74. package/dist/core/vector2.d.ts.map +0 -1
  75. package/dist/gamecontrollerdb.txt +0 -2221
  76. package/dist/scenes/startup.d.ts +0 -18
  77. package/dist/scenes/startup.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # LÏKE2D
2
2
 
3
- <svg width="300mm" height="105mm" version="1.1" viewBox="0 0 300 105" xmlns="http://www.w3.org/2000/svg">
3
+ <svg version="1.1" viewBox="0 0 300 105" xmlns="http://www.w3.org/2000/svg">
4
4
  <rect x="10" y="11.23" width="280" height="83.544" fill="#e48080" stroke="#000" stroke-linejoin="round" stroke-width="2"/>
5
5
  <g fill="none" stroke="#000" stroke-linejoin="round">
6
6
  <rect x="97.484" y="11.23" width="52.516" height="46.237"/>
@@ -31,37 +31,44 @@
31
31
  </g>
32
32
  </svg>
33
33
 
34
- A web-native 2D game framework inspired by [LÖVE](https://love2d.org/), built for simplicity and the modern web.
34
+ Lightweight Web framework inspired by [LÖVE](https://love2d.org/).
35
35
 
36
+ ## <div style="color:red">During v2.x.x, LIKE's API will change.</div>
36
37
  ## What it is
37
38
 
38
- LIKE is a **curated toolkit** around browser APIs.
39
+ LIKE is a cozy way to make 2d games for browser.
39
40
 
40
- It does less, because it **does the right thing**. And when that's not the right thing for you, we hand you the wrench.
41
+ ## What LIKE does
41
42
 
42
- - **Stateless Graphics:** Forget to reset native Canvas state (like LineCap) between calls and things break mysteriously. We make drawing explicit: what you see is what you set.
43
-
44
- - **Fire-and-forget Assets:** Async asset loading directly on realtime web games is annoying. We let you pretend it's instant and synchronous.
45
-
46
- - **Physical Joypad:** Our gamepad module auto-maps to physical buttons like "bottom" and "top". Because A isn't always in the same spot.
47
-
48
- - **Actions System:** Of course you can use device input callbacks just like love2d -- but you can also map inputs to actions and get callbacks on that.
49
-
50
- - **Scaling Modes:** Pixel art games need pixel-perfect scaling. So we do that: integer nearest -> linear. Or not; turn off pixelart mode to have a canvas that stays at native resolution.
51
-
52
- - **Sane Architecture:** Everything is built around a centralized event handler for browser-native events. We won't reinvent the wheel.
43
+ - **🔥 Fire-and-forget Assets:** Graphics and audio that pretend to be synchronous.
44
+ - **🎯 DWIM graphics:** Turns repetitive draw calls into one while removing state bleed for properties like `lineCap`.
45
+ - **↔️ Two Canvas Modes:**
46
+ - 🖊️ Audio-resize the canvas; sharp at any resolution.
47
+ - 👾 For retro-style developers, pixels stay crisp but smooth via prescaling.
48
+ - **⭕ Easier Geometry:** `Vector2` and `Rect` are just number tuples (arrays), but a pure-functional library makes them easy to work with and plays nice with `map` and `reduce`.
49
+ - **🚲 Easy Input:** Keyboard, Mouse, and Gamepad all are given both event-based and tracking-based options. Choose what fits your architecture.
50
+ - **👟 Consistent APIs:** Colors 0-1, not 0-255. Seconds, not milliseconds.
51
+ - **👉 Actions System:** An input layer maps inputs to actions, which fire usable events.
52
+ - **🌎 Global control:** Choose how to handle LIKE events, and manage resources with centralized trackers. LIKE is a great foundation for your own engine.
53
+ - **🐦 Light and Elegant:** Zero dependencies and less than 5000 lines of code -- focused entirely on what matters.
53
54
 
54
55
  ## Installation
55
56
 
57
+ Most package managers will work.
56
58
  ```bash
57
59
  npm install like2d
58
- # or
59
- pnpm add like2d
60
+ # or ...
61
+ deno add jsr:@like2d/like
62
+ # or...
60
63
  ```
61
64
 
62
65
  ## Quick Start
63
66
 
64
- [Like2D Starter Template](https://github.com/44100hertz/Like2D-starter)
67
+ To try Like2D quickly, use this starter with
68
+ hot reloading and a basic webpage.
69
+ ```bash
70
+ npx degit 44100hertz/Like2D/examples/starter my-game
71
+ ```
65
72
 
66
73
  ## Usage Example
67
74
 
@@ -71,8 +78,8 @@ import { createLike } from 'like2d';
71
78
  const like = createLike(document.body);
72
79
 
73
80
  like.load = () => {
74
- like.setMode({ pixelResolution: [800, 600] });
75
- like.input.map('jump', ['Space', 'ButtonBottom']);
81
+ like.setMode([800, 600]);
82
+ like.input.setAction('jump', ['Space', 'ButtonBottom']);
76
83
  };
77
84
 
78
85
  like.update = (dt) => {
@@ -93,27 +100,19 @@ await like.start();
93
100
  ## For Love2D Developers
94
101
 
95
102
  LIKE's API is not the same as LOVE, but similar in spirit. Notable differences:
96
- - Graphics have less state, there is no setColor getColor etc.
97
- - Our APIs are, in general, a bit different. You'll have to learn some.
98
- - You manage your own state; like is not global, but you can share it around for similar results. This allows multiple LIKE instances per webpage.
99
- - We use Vector2 and Rect tuples (as in `[number, number]`) instead of loose coordinates.
100
- - Theres an actions system -- `input.map` / `actionpressed` and `actionreleased` callbacks.
103
+ - Draw your graphics in one call, that's all. No setup or state bleed.
104
+ - You manage your own instance of like in a big friendly object. This allows us to have multiple games on one page.
105
+ - We use Vector2 and Rect tuples (like `[x, y]`) instead of loose coordinates.
106
+ - Theres an actions system -- `input.setAction` / `actionpressed` and `actionreleased` callbacks.
101
107
  - Some things are missing either due to browser limitations or smaller scope.
102
108
 
103
109
  ## Feedback welcome
104
110
 
105
- Before you report a bug:
106
- 1. Make sure you're on the latest release.
107
- 2. If the issue exists already, just comment on that one.
108
- 3. See if it happens in other web browsers.
111
+ [LIKE is on GitHub.](https://github.com/44100hertz/Like2D)
109
112
 
110
- Before you request a feature:
111
- 1. ask: Would it make sense as a core feature, or should it be an external library?
112
- 2. See if the feature exists already.
113
- 3. Think about the demand for it overall. Make your case why game developers want it.
114
- 4. Consider just making a PR yourself, or sending a prompt/spec that an AI can hack on.
113
+ [Check out the docs for our long-term vision.](https://github.com/44100hertz/Like2D/tree/master/docs)
115
114
 
116
- [Then, put your feedback on GitHub.](https://github.com/44100hertz/Like2D/issues)
115
+ [Feature requests welcome. PRs discouraged for now.](https://github.com/44100hertz/Like2D/issues/)
117
116
 
118
117
  ## License
119
118
 
@@ -7,26 +7,26 @@
7
7
  * Start, stop, or set global volume for every currently playing sound.
8
8
  *
9
9
  */
10
- export type SourceOptions = {
10
+ export type AudioSourceOptions = {
11
11
  volume?: number;
12
12
  };
13
13
  /**
14
- * Handle to a loaded audio resource.
14
+ * Handle to a loaded audio resource, which pretends to be synchronous.
15
15
  * Use `play()`, `stop()`, `pause()`, `resume()` for playback control.
16
16
  * Access the underlying HTMLAudioElement via `source.audio` for looping,
17
17
  * pitch, etc. Note: Use `source.setVolume()` instead of setting
18
18
  * `source.audio.volume` directly to ensure global volume scaling works correctly.
19
19
  */
20
- export declare class Source {
20
+ export declare class AudioSource {
21
21
  readonly path: string;
22
22
  /** Underlying HTMLAudioElement. Modify directly for looping, pitch, etc. Use methods for playback control. Avoid setting volume directly. */
23
23
  readonly audio: HTMLAudioElement;
24
- readonly options: Required<SourceOptions>;
24
+ readonly options: Required<AudioSourceOptions>;
25
25
  /** Resolves when the audio is loaded and ready to play. */
26
26
  readonly ready: Promise<void>;
27
27
  private loadState;
28
28
  private audioRef;
29
- constructor(path: string, audioRef: Audio, options?: SourceOptions);
29
+ constructor(path: string, audioRef: AudioInternal, options?: AudioSourceOptions);
30
30
  isReady(): boolean;
31
31
  play(): void;
32
32
  stop(): void;
@@ -42,20 +42,23 @@ export declare class Source {
42
42
  getVolume(): number;
43
43
  getDuration(): number;
44
44
  }
45
- export declare class Audio {
45
+ export declare class AudioInternal {
46
46
  private sources;
47
47
  private globalVolume;
48
- newSource(path: string, options?: SourceOptions): Source;
48
+ /**
49
+ * Get a {@link AudioSource}
50
+ */
51
+ newSource(path: string, options?: AudioSourceOptions): AudioSource;
49
52
  /** Get all audio sources -- note that sources are tracked
50
53
  * using weak references, and storing this list can cause
51
54
  * a memory leak.
52
55
  */
53
- private getAllSources;
56
+ getAllSources(): AudioSource[];
54
57
  stopAll(): void;
55
58
  pauseAll(): void;
56
59
  resumeAll(): void;
57
60
  setVolume(volume: number): void;
58
61
  getVolume(): number;
59
- clone(source: Source): Source;
62
+ clone(source: AudioSource): AudioSource;
60
63
  }
61
64
  //# sourceMappingURL=audio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../../src/core/audio.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAA;AAMD;;;;;;GAMG;AACH,qBAAa,MAAM;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6IAA6I;IAC7I,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC1C,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,SAAS,CAAoE;IACrF,OAAO,CAAC,QAAQ,CAAQ;gBAEZ,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,GAAE,aAAkB;IAmCtE,OAAO,IAAI,OAAO;IAIlB,IAAI,IAAI,IAAI;IAUZ,IAAI,IAAI,IAAI;IAUZ,KAAK,IAAI,IAAI;IAQb,MAAM,IAAI,IAAI;IAYd,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQ5B,IAAI,IAAI,MAAM;IAKd,SAAS,IAAI,OAAO;IAKpB,QAAQ,IAAI,OAAO;IAKnB,SAAS,IAAI,OAAO;IAKpB,+FAA+F;IAC/F,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK/B,SAAS,IAAI,MAAM;IAInB,WAAW,IAAI,MAAM;CAItB;AAED,qBAAa,KAAK;IAChB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,YAAY,CAAK;IAEzB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM;IAMxD;;;QAGI;IACJ,OAAO,CAAC,aAAa;IASrB,OAAO,IAAI,IAAI;IAIf,QAAQ,IAAI,IAAI;IAIhB,SAAS,IAAI,IAAI;IAIjB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAO/B,SAAS,IAAI,MAAM;IAInB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAG9B"}
1
+ {"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../../src/core/audio.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAA;AAMD;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6IAA6I;IAC7I,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC/C,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,SAAS,CAAoE;IACrF,OAAO,CAAC,QAAQ,CAAgB;gBAEpB,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,GAAE,kBAAuB;IAmCnF,OAAO,IAAI,OAAO;IAIlB,IAAI,IAAI,IAAI;IAUZ,IAAI,IAAI,IAAI;IAUZ,KAAK,IAAI,IAAI;IAQb,MAAM,IAAI,IAAI;IAYd,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQ5B,IAAI,IAAI,MAAM;IAKd,SAAS,IAAI,OAAO;IAKpB,QAAQ,IAAI,OAAO;IAKnB,SAAS,IAAI,OAAO;IAKpB,+FAA+F;IAC/F,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK/B,SAAS,IAAI,MAAM;IAInB,WAAW,IAAI,MAAM;CAItB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,YAAY,CAAK;IAEzB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,WAAW;IAMlE;;;QAGI;IACJ,aAAa,IAAI,WAAW,EAAE;IAS9B,OAAO,IAAI,IAAI;IAIf,QAAQ,IAAI,IAAI;IAIhB,SAAS,IAAI,IAAI;IAIjB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAO/B,SAAS,IAAI,MAAM;IAInB,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW;CAGxC"}
@@ -8,13 +8,13 @@
8
8
  *
9
9
  */
10
10
  /**
11
- * Handle to a loaded audio resource.
11
+ * Handle to a loaded audio resource, which pretends to be synchronous.
12
12
  * Use `play()`, `stop()`, `pause()`, `resume()` for playback control.
13
13
  * Access the underlying HTMLAudioElement via `source.audio` for looping,
14
14
  * pitch, etc. Note: Use `source.setVolume()` instead of setting
15
15
  * `source.audio.volume` directly to ensure global volume scaling works correctly.
16
16
  */
17
- export class Source {
17
+ export class AudioSource {
18
18
  constructor(path, audioRef, options = {}) {
19
19
  Object.defineProperty(this, "path", {
20
20
  enumerable: true,
@@ -169,7 +169,7 @@ export class Source {
169
169
  return 0;
170
170
  }
171
171
  }
172
- export class Audio {
172
+ export class AudioInternal {
173
173
  constructor() {
174
174
  Object.defineProperty(this, "sources", {
175
175
  enumerable: true,
@@ -184,8 +184,11 @@ export class Audio {
184
184
  value: 1
185
185
  });
186
186
  }
187
+ /**
188
+ * Get a {@link AudioSource}
189
+ */
187
190
  newSource(path, options) {
188
- const source = new Source(path, this, options);
191
+ const source = new AudioSource(path, this, options);
189
192
  this.sources.push(new WeakRef(source));
190
193
  return source;
191
194
  }
@@ -0,0 +1,58 @@
1
+ import { EngineDispatch } from "../engine";
2
+ import { Rectangle } from "../math/rect";
3
+ import { type Vector2 } from "../math/vector2";
4
+ export type CanvasModeOptions = {
5
+ fullscreen: boolean;
6
+ };
7
+ export type CanvasSize = Vector2 | 'native';
8
+ export declare class CanvasInternal {
9
+ dispatch: EngineDispatch;
10
+ /** The ultimately visible canvas in the browser */
11
+ _displayCanvas: HTMLCanvasElement;
12
+ /** The canvas that we're drawing to with `like.gfx` functions.
13
+ * If it's the same as _displayCanvas, we're in native mode.
14
+ * Otherwise, we're in pixelart mode, consisting of nearest -> linear scaling.
15
+ */
16
+ _renderCanvas: HTMLCanvasElement;
17
+ private resizeTimeoutId;
18
+ private abort;
19
+ constructor(dispatch: EngineDispatch);
20
+ /** Get a unified canvas info object. */
21
+ getMode(): {
22
+ size: Vector2;
23
+ flags: CanvasModeOptions;
24
+ };
25
+ /** Set the game's apparent resolution, fullscreen, etc.
26
+ *
27
+ * ### `'native'` mode
28
+ * Keeps the canvas pixel resolution
29
+ * the same as the physical pixel resolution of the
30
+ * device.
31
+ *
32
+ * ### Pixel art mode `[width, height]`
33
+ * The canvas will use prescaling to keep your pixel
34
+ * games looking sharp, but without the uneven pixels
35
+ * caused by the naive approach.
36
+ *
37
+ * @param size 'native' for native mode, otherwise [width, height]
38
+ * @param flags optional options.
39
+ */
40
+ setMode(size: CanvasSize, flags?: Partial<CanvasModeOptions>): void;
41
+ /** Get the apparent (in-game) canvas size. */
42
+ getSize(): Vector2;
43
+ /** Sometimes you want a screen rect! */
44
+ getRect(): Rectangle;
45
+ /** Get the actual (physical) canvas size on screen. */
46
+ _getDisplayPixelSize(): Vector2;
47
+ /** Are we fullscreen? */
48
+ getFullscreen(): boolean;
49
+ /** Set fullscreen. */
50
+ setFullscreen(fullscreen: boolean): void;
51
+ /** Called every frame by the engine after drawing */
52
+ _present(): void;
53
+ /** @returns if size was changed. */
54
+ static setCanvasElemSize(canvas: HTMLCanvasElement, newSize: Vector2): boolean;
55
+ static getCanvasElemSize(canvas: HTMLCanvasElement): Vector2;
56
+ _dispose(): void;
57
+ }
58
+ //# sourceMappingURL=canvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/core/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAQ,SAAS,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC;AACxD,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE5C,qBAAa,cAAc;IAYJ,QAAQ,EAAE,cAAc;IAX3C,mDAAmD;IAC5C,cAAc,EAAE,iBAAiB,CAAC;IACzC;;;MAGE;IACK,aAAa,EAAE,iBAAiB,CAAC;IAExC,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,KAAK,CAAyB;gBAEnB,QAAQ,EAAE,cAAc;IA0D3C,wCAAwC;IACxC,OAAO,IAAI;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,iBAAiB,CAAA;KAAE;IAStD;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,GAAE,OAAO,CAAC,iBAAiB,CAAM;IA2BhE,8CAA8C;IAC9C,OAAO,IAAI,OAAO;IAIlB,wCAAwC;IACxC,OAAO,IAAI,SAAS;IAIpB,uDAAuD;IACvD,oBAAoB,IAAI,OAAO;IAO/B,yBAAyB;IACzB,aAAa,IAAI,OAAO;IAIxB,sBAAsB;IACtB,aAAa,CAAC,UAAU,EAAE,OAAO;IASjC,qDAAqD;IACrD,QAAQ;IAyCR,qCAAqC;IACrC,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;IAO9E,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO;IAI5D,QAAQ,IAAI,IAAI;CAGnB"}
@@ -0,0 +1,209 @@
1
+ import { Rect } from "../math/rect";
2
+ import { Vec2 } from "../math/vector2";
3
+ export class CanvasInternal {
4
+ constructor(dispatch) {
5
+ Object.defineProperty(this, "dispatch", {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value: dispatch
10
+ });
11
+ /** The ultimately visible canvas in the browser */
12
+ Object.defineProperty(this, "_displayCanvas", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: void 0
17
+ });
18
+ /** The canvas that we're drawing to with `like.gfx` functions.
19
+ * If it's the same as _displayCanvas, we're in native mode.
20
+ * Otherwise, we're in pixelart mode, consisting of nearest -> linear scaling.
21
+ */
22
+ Object.defineProperty(this, "_renderCanvas", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: void 0
27
+ });
28
+ Object.defineProperty(this, "resizeTimeoutId", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: 0
33
+ });
34
+ Object.defineProperty(this, "abort", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: new AbortController()
39
+ });
40
+ this._displayCanvas = document.createElement('canvas');
41
+ this._displayCanvas.tabIndex = 0;
42
+ this._displayCanvas.style.width = '100%';
43
+ this._displayCanvas.style.height = '100%';
44
+ this._renderCanvas = this._displayCanvas;
45
+ this.setMode('native');
46
+ /** Only the canvas can really transform the mouse to the game size.
47
+ * This hack sends an event for the mouse module to listen to.
48
+ */
49
+ this._displayCanvas.addEventListener('mousemove', (ev) => {
50
+ let pos;
51
+ let delta;
52
+ const rawPos = [ev.offsetX, ev.offsetY];
53
+ const rawDelta = [ev.movementX, ev.movementY];
54
+ if (this._renderCanvas == this._displayCanvas) {
55
+ /* Native mode. */
56
+ pos = Vec2.mul(rawPos, window.devicePixelRatio ?? 1);
57
+ delta = Vec2.mul(rawDelta, window.devicePixelRatio ?? 1);
58
+ }
59
+ else {
60
+ /* Pixelart mode. This math simulates object-fit: contain,
61
+ * which preserves aspect ratio.
62
+ */
63
+ const csize = [
64
+ this._displayCanvas.clientWidth,
65
+ this._displayCanvas.clientHeight
66
+ ];
67
+ /* Scale of both dimensions */
68
+ const scale = calcAspectFriendlyScale(this.getSize(), csize);
69
+ /* Upper-left corner */
70
+ const offset = Vec2.div(Vec2.sub(csize, Vec2.mul(this.getSize(), scale)), 2);
71
+ pos = Vec2.div(Vec2.sub(rawPos, offset), scale);
72
+ delta = Vec2.div(rawDelta, scale);
73
+ /* Only handle mousemove events that are in bounds. */
74
+ if (!Rect.containsPoint(this.getRect(), pos)) {
75
+ return;
76
+ }
77
+ }
78
+ this._displayCanvas.dispatchEvent(new CustomEvent('like:mousemoved', {
79
+ detail: {
80
+ pos,
81
+ delta,
82
+ renderSize: this.getSize(),
83
+ }
84
+ }));
85
+ }, { signal: this.abort.signal });
86
+ }
87
+ /** Get a unified canvas info object. */
88
+ getMode() {
89
+ return {
90
+ size: this.getSize(),
91
+ flags: {
92
+ fullscreen: this.getFullscreen(),
93
+ }
94
+ };
95
+ }
96
+ /** Set the game's apparent resolution, fullscreen, etc.
97
+ *
98
+ * ### `'native'` mode
99
+ * Keeps the canvas pixel resolution
100
+ * the same as the physical pixel resolution of the
101
+ * device.
102
+ *
103
+ * ### Pixel art mode `[width, height]`
104
+ * The canvas will use prescaling to keep your pixel
105
+ * games looking sharp, but without the uneven pixels
106
+ * caused by the naive approach.
107
+ *
108
+ * @param size 'native' for native mode, otherwise [width, height]
109
+ * @param flags optional options.
110
+ */
111
+ setMode(size, flags = {}) {
112
+ // set up sizing / render target
113
+ const prevRenderCanvas = this._renderCanvas;
114
+ if (size == 'native') {
115
+ this._displayCanvas.style.objectFit = 'fill';
116
+ this._renderCanvas = this._displayCanvas;
117
+ }
118
+ else {
119
+ this._displayCanvas.style.objectFit = 'contain';
120
+ this._renderCanvas = document.createElement('canvas');
121
+ const changed = CanvasInternal.setCanvasElemSize(this._renderCanvas, size);
122
+ if (changed) {
123
+ this.dispatch('resize', [size]);
124
+ }
125
+ }
126
+ if (prevRenderCanvas != this._renderCanvas) {
127
+ this._displayCanvas.dispatchEvent(new CustomEvent('like:updateRenderTarget', {
128
+ detail: {
129
+ target: this._renderCanvas,
130
+ }
131
+ }));
132
+ }
133
+ if ('fullscreen' in flags) {
134
+ this.setFullscreen(flags.fullscreen);
135
+ }
136
+ }
137
+ /** Get the apparent (in-game) canvas size. */
138
+ getSize() {
139
+ return [this._renderCanvas.width, this._renderCanvas.height];
140
+ }
141
+ /** Sometimes you want a screen rect! */
142
+ getRect() {
143
+ return [0, 0, ...this.getSize()];
144
+ }
145
+ /** Get the actual (physical) canvas size on screen. */
146
+ _getDisplayPixelSize() {
147
+ return Vec2.round(Vec2.mul([this._displayCanvas.clientWidth, this._displayCanvas.clientHeight], window.devicePixelRatio ?? 1));
148
+ }
149
+ /** Are we fullscreen? */
150
+ getFullscreen() {
151
+ return this._displayCanvas === document.fullscreenElement;
152
+ }
153
+ /** Set fullscreen. */
154
+ setFullscreen(fullscreen) {
155
+ if (fullscreen) {
156
+ this._displayCanvas.requestFullscreen();
157
+ this._displayCanvas.focus();
158
+ }
159
+ else {
160
+ document.exitFullscreen();
161
+ }
162
+ }
163
+ /** Called every frame by the engine after drawing */
164
+ _present() {
165
+ if (this._renderCanvas == this._displayCanvas) {
166
+ const realSize = this._getDisplayPixelSize();
167
+ if ((realSize[0] != this._displayCanvas.width ||
168
+ realSize[1] != this._displayCanvas.height) &&
169
+ !this.resizeTimeoutId) {
170
+ /** In native scaling mode, zooming and resizing the window cause us
171
+ * to set canvas width and height every frame, which could cause
172
+ * tons of canvas bitmap reallocations. So wait 1/4 second..
173
+ */
174
+ CanvasInternal.setCanvasElemSize(this._displayCanvas, realSize);
175
+ this.dispatch('resize', [realSize]);
176
+ this.resizeTimeoutId = setTimeout(() => { this.resizeTimeoutId = 0; }, 250);
177
+ }
178
+ }
179
+ else if (this._renderCanvas != this._displayCanvas) {
180
+ /* We're in pixelart mode,
181
+ * so set output canvas size to an ideal integer scale.
182
+ * No debounce: changes to integer ratio are infrequent.
183
+ */
184
+ CanvasInternal.setCanvasElemSize(this._displayCanvas, Vec2.mul(this.getSize(), Math.round(calcAspectFriendlyScale(this.getSize(), this._getDisplayPixelSize()))));
185
+ // Copy the internal canvas to the visible one.
186
+ const ctx = this._displayCanvas.getContext('2d');
187
+ ctx.imageSmoothingEnabled = false;
188
+ ctx.drawImage(this._renderCanvas, 0, 0, this._renderCanvas.width, this._renderCanvas.height, 0, 0, this._displayCanvas.width, this._displayCanvas.height);
189
+ }
190
+ }
191
+ /** @returns if size was changed. */
192
+ static setCanvasElemSize(canvas, newSize) {
193
+ if (canvas.width != newSize[0] || canvas.height != newSize[1]) {
194
+ [canvas.width, canvas.height] = newSize;
195
+ return true;
196
+ }
197
+ return false;
198
+ }
199
+ static getCanvasElemSize(canvas) {
200
+ return [canvas.width, canvas.height];
201
+ }
202
+ _dispose() {
203
+ this.abort.abort();
204
+ }
205
+ }
206
+ /** How much could this image be scaled, preserving aspect? */
207
+ function calcAspectFriendlyScale(imageSize, containerSize) {
208
+ return Math.min(...Vec2.div(containerSize, imageSize));
209
+ }
@@ -1,27 +1,105 @@
1
- import type { Vector2 } from './vector2';
2
- /** Game events. All events flow through handleEvent; these are also available as individual callbacks. */
1
+ /**
2
+ * @module events
3
+ * @description All events that flow through the engine.
4
+ *
5
+ * ## Overview
6
+ *
7
+ * LIKE uses events at its core.
8
+ * These pass through the engine and down to your
9
+ * callbacks or scene.
10
+ *
11
+ * This module is the single source of truth for what
12
+ * events are possible.
13
+ *
14
+ * Use it as a reference.
15
+ *
16
+ * @see {@link EventMap} lists every event.
17
+ * @see {@link Scene} for implementing callbacks in a class
18
+ * @see {@link Like} for global callback assignment
19
+ * @see {@link Input} for action mapping
20
+ */
21
+ import type { Vector2 } from '../math/vector2';
22
+ import { LikeButton } from './gamepad-mapping';
23
+ export type MouseButton = 'left' | 'middle' | 'right';
24
+ declare global {
25
+ interface HTMLElementEventMap {
26
+ ['like:mousemoved']: CustomEvent<{
27
+ pos: Vector2;
28
+ delta: Vector2;
29
+ renderSize: Vector2;
30
+ }>;
31
+ ['like:updateRenderTarget']: CustomEvent<{
32
+ target: HTMLCanvasElement;
33
+ }>;
34
+ }
35
+ }
36
+ /**
37
+ * The master type will all events on it.
38
+ *
39
+ * Each frame:
40
+ * 1. `update(dt)` - Game logic with delta time in seconds
41
+ * 2. `draw` - Render the frame
42
+ *
43
+ * Input events fire immediately when they occur:
44
+ * - `keypressed`/`keyreleased` - Keyboard input
45
+ * - `mousemoved`/`mousepressed`/`mousereleased` - Mouse input
46
+ * - `gamepadpressed`/`gamepadreleased` - Controller input
47
+ * - `actionpressed`/`actionreleased` - Mapped actions (see {@link Input})
48
+ *
49
+ * Window events:
50
+ * - `focus`/`blur` - Tab/window focus changes
51
+ * - `resize` - Canvas size changes
52
+ *
53
+ * Lifecycle:
54
+ * - `load` - Called once when the game starts
55
+ */
3
56
  export type EventMap = {
57
+ /** Game initialization. Called once before the first frame. */
4
58
  load: [];
59
+ /** Frame update. dt is delta time in seconds (time since last frame). */
5
60
  update: [dt: number];
61
+ /** Render frame. Clear the screen and draw your game here. */
6
62
  draw: [];
7
- resize: [size: Vector2, pixelSize: Vector2, fullscreen: boolean];
63
+ /** Canvas was resized. Used mostly in native mode, though setMode may send it too. */
64
+ resize: [size: Vector2];
65
+ /** Physical key pressed. scancode is the physical key, keycode is the character. */
8
66
  keypressed: [scancode: string, keycode: string];
67
+ /** Physical key released. */
9
68
  keyreleased: [scancode: string, keycode: string];
10
- focus: [];
11
- blur: [];
12
- /** Mouse moved. relative=true means pos is delta [dx, dy] (pointer lock). */
13
- mousemoved: [pos: Vector2, relative: boolean];
14
- /** Mouse button pressed. pos in canvas pixels. Button 1 = left, 2 = middle, 3 = right. */
15
- mousepressed: [pos: Vector2, button: number];
69
+ /** Canvas or tab gained focus. Game may resume audio/updates. */
70
+ focus: [source: 'canvas' | 'tab'];
71
+ /** Canvas or tab lost focus. Game may pause audio/updates. */
72
+ blur: [source: 'canvas' | 'tab'];
73
+ /** Mouse moved event. `pos` is absolute, `delta` is relative. */
74
+ mousemoved: [pos: Vector2, delta: Vector2];
75
+ /** Mouse button pressed. pos in canvas pixels. Button: 1=left, 2=middle, 3=right. */
76
+ mousepressed: [pos: Vector2, button: MouseButton];
16
77
  /** Mouse button released. */
17
- mousereleased: [pos: Vector2, button: number];
18
- gamepadpressed: [gamepadIndex: number, buttonIndex: number, buttonName: string];
19
- gamepadreleased: [gamepadIndex: number, buttonIndex: number, buttonName: string];
78
+ mousereleased: [pos: Vector2, button: MouseButton];
79
+ /** Gamepad button pressed. index is controller number (0-3). */
80
+ gamepadpressed: [source: number, num: number, name: LikeButton];
81
+ /** Gamepad button released. */
82
+ gamepadreleased: [source: number, num: number, name: LikeButton];
83
+ /** Mapped action triggered. See {@link Input} for action mapping. */
20
84
  actionpressed: [action: string];
85
+ /** Mapped action released. */
21
86
  actionreleased: [action: string];
22
87
  };
23
88
  export type EventType = keyof EventMap;
24
- export type Like2DEvent = {
89
+ /**
90
+ * Discriminated union of all event objects.
91
+ * Use this with `handleEvent` to receive all events generically.
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * handleEvent(like: Like, event: Like2DEvent) {
96
+ * if (event.type === 'update') {
97
+ * const dt = event.args[0];
98
+ * }
99
+ * }
100
+ * ```
101
+ */
102
+ export type LikeEvent = {
25
103
  [K in EventType]: {
26
104
  type: K;
27
105
  args: EventMap[K];
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/core/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,0GAA0G;AAC1G,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,EAAE,CAAC;IACT,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACrB,IAAI,EAAE,EAAE,CAAC;IACT,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACjE,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAChD,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACjD,KAAK,EAAE,EAAE,CAAC;IACV,IAAI,EAAE,EAAE,CAAC;IACT,6EAA6E;IAC7E,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,0FAA0F;IAC1F,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,6BAA6B;IAC7B,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAChF,eAAe,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IACjF,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC;AAGvC,MAAM,MAAM,WAAW,GAAG;KACvB,CAAC,IAAI,SAAS,GAAG;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;CACpE,CAAC,SAAS,CAAC,CAAC"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/core/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,mBAAmB;QAC3B,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;YAAC,GAAG,EAAE,OAAO,CAAC;YAAC,KAAK,EAAE,OAAO,CAAC;YAAC,UAAU,EAAE,OAAO,CAAA;SAAC,CAAC,CAAC;QACtF,CAAC,yBAAyB,CAAC,EAAE,WAAW,CAAC;YAAC,MAAM,EAAE,iBAAiB,CAAA;SAAC,CAAC,CAAC;KACvE;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,+DAA+D;IAC/D,IAAI,EAAE,EAAE,CAAC;IAET,yEAAyE;IACzE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAErB,8DAA8D;IAC9D,IAAI,EAAE,EAAE,CAAC;IAET,sFAAsF;IACtF,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAExB,oFAAoF;IACpF,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAEhD,6BAA6B;IAC7B,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAEjD,iEAAiE;IACjE,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,KAAK,CAAC,CAAC;IAElC,8DAA8D;IAC9D,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,KAAK,CAAC,CAAC;IAEjC,iEAAiE;IACjE,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAE3C,qFAAqF;IACrF,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAElD,6BAA6B;IAC7B,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAEnD,gEAAgE;IAChE,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAEhE,+BAA+B;IAC/B,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAEjE,qEAAqE;IACrE,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhC,8BAA8B;IAC9B,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC;AAEvC;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,SAAS,GAAG;KACrB,CAAC,IAAI,SAAS,GAAG;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;CACpE,CAAC,SAAS,CAAC,CAAC"}
@@ -1 +1,21 @@
1
+ /**
2
+ * @module events
3
+ * @description All events that flow through the engine.
4
+ *
5
+ * ## Overview
6
+ *
7
+ * LIKE uses events at its core.
8
+ * These pass through the engine and down to your
9
+ * callbacks or scene.
10
+ *
11
+ * This module is the single source of truth for what
12
+ * events are possible.
13
+ *
14
+ * Use it as a reference.
15
+ *
16
+ * @see {@link EventMap} lists every event.
17
+ * @see {@link Scene} for implementing callbacks in a class
18
+ * @see {@link Like} for global callback assignment
19
+ * @see {@link Input} for action mapping
20
+ */
1
21
  export {};