@dryanovski/gamefoo 0.2.3 → 0.2.5

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 (84) hide show
  1. package/dist/core/animate.js +147 -0
  2. package/dist/core/asset.js +74 -0
  3. package/dist/core/behaviour.js +88 -0
  4. package/dist/core/behaviours/collidable.js +186 -0
  5. package/dist/core/behaviours/control.js +75 -0
  6. package/dist/core/behaviours/healtkit.js +153 -0
  7. package/dist/core/behaviours/sprite_render.js +193 -0
  8. package/dist/core/camera.js +134 -0
  9. package/dist/core/engine.d.ts +1 -1
  10. package/dist/core/engine.d.ts.map +1 -1
  11. package/dist/core/engine.js +527 -0
  12. package/dist/core/fonts/font_bitmap.js +205 -0
  13. package/dist/core/fonts/font_bitmap_prebuild.js +137 -0
  14. package/dist/core/fonts/internal/font_3x5.js +169 -0
  15. package/dist/core/fonts/internal/font_4x6.js +171 -0
  16. package/dist/core/fonts/internal/font_5x5.js +129 -0
  17. package/dist/core/fonts/internal/font_6x8.js +171 -0
  18. package/dist/core/fonts/internal/font_8x13.js +171 -0
  19. package/dist/core/fonts/internal/font_8x8.js +171 -0
  20. package/dist/core/game_object_register.js +134 -0
  21. package/dist/core/input.js +170 -0
  22. package/dist/core/sprite.js +222 -0
  23. package/dist/core/utils/perlin_noise.js +183 -0
  24. package/dist/core/world.js +304 -0
  25. package/dist/debug/monitor.js +47 -0
  26. package/dist/decorators/index.js +1 -0
  27. package/dist/decorators/log.js +42 -0
  28. package/dist/entities/dynamic_entity.js +99 -0
  29. package/dist/entities/entity.js +283 -0
  30. package/dist/entities/player.js +93 -0
  31. package/dist/entities/text.js +62 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +47 -29
  35. package/dist/subsystems/camera_system.d.ts +2 -2
  36. package/dist/subsystems/camera_system.d.ts.map +1 -1
  37. package/dist/subsystems/camera_system.js +41 -0
  38. package/dist/subsystems/collision_system.js +17 -0
  39. package/dist/subsystems/monitor_system.js +20 -0
  40. package/dist/subsystems/object_system.d.ts +1 -1
  41. package/dist/subsystems/object_system.d.ts.map +1 -1
  42. package/dist/subsystems/object_system.js +29 -0
  43. package/dist/subsystems/types.d.ts +1 -1
  44. package/dist/subsystems/types.d.ts.map +1 -1
  45. package/dist/subsystems/types.js +0 -0
  46. package/dist/types.js +10 -0
  47. package/package.json +17 -6
  48. package/src/core/animate.ts +159 -0
  49. package/src/core/asset.ts +76 -0
  50. package/src/core/behaviour.ts +145 -0
  51. package/src/core/behaviours/collidable.ts +296 -0
  52. package/src/core/behaviours/control.ts +80 -0
  53. package/src/core/behaviours/healtkit.ts +166 -0
  54. package/src/core/behaviours/sprite_render.ts +216 -0
  55. package/src/core/camera.ts +145 -0
  56. package/src/core/engine.ts +607 -0
  57. package/src/core/fonts/font_bitmap.ts +232 -0
  58. package/src/core/fonts/font_bitmap_prebuild.ts +141 -0
  59. package/src/core/fonts/internal/font_3x5.ts +178 -0
  60. package/src/core/fonts/internal/font_4x6.ts +180 -0
  61. package/src/core/fonts/internal/font_5x5.ts +137 -0
  62. package/src/core/fonts/internal/font_6x8.ts +180 -0
  63. package/src/core/fonts/internal/font_8x13.ts +180 -0
  64. package/src/core/fonts/internal/font_8x8.ts +180 -0
  65. package/src/core/game_object_register.ts +146 -0
  66. package/src/core/input.ts +182 -0
  67. package/src/core/sprite.ts +339 -0
  68. package/src/core/utils/perlin_noise.ts +196 -0
  69. package/src/core/world.ts +331 -0
  70. package/src/debug/monitor.ts +60 -0
  71. package/src/decorators/index.ts +1 -0
  72. package/src/decorators/log.ts +45 -0
  73. package/src/entities/dynamic_entity.ts +106 -0
  74. package/src/entities/entity.ts +322 -0
  75. package/src/entities/player.ts +99 -0
  76. package/src/entities/text.ts +72 -0
  77. package/src/index.ts +51 -0
  78. package/src/subsystems/camera_system.ts +52 -0
  79. package/src/subsystems/collision_system.ts +21 -0
  80. package/src/subsystems/monitor_system.ts +26 -0
  81. package/src/subsystems/object_system.ts +37 -0
  82. package/src/subsystems/types.ts +46 -0
  83. package/src/types.ts +178 -0
  84. package/dist/index.js.map +0 -9
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Built-in 8x8 pixel bitmap font.
3
+ *
4
+ * Each character is a 8-pixel-wide, 8-pixel-tall glyph stored as an
5
+ * array of 8 integers. Each integer is a bitmask where bit 7
6
+ * (MSB) corresponds to the leftmost pixel and bit 0 to the rightmost.
7
+ *
8
+ * Chunky square glyphs, ideal at 2× or 4× scale.
9
+ *
10
+ * Supported characters: uppercase A–Z, lowercase a–z, digits 0–9,
11
+ * and common punctuation / special characters.
12
+ *
13
+ * @category Fonts
14
+ * @since 0.2.0
15
+ * @internal
16
+ *
17
+ * @example Reading a glyph
18
+ * ```ts
19
+ * import FONT_8x8 from "./font_8x8";
20
+ *
21
+ * const letterA = FONT_8x8["A"];
22
+ * // 24, 36, 66, 66, 126, 66, 66, 0
23
+ * ```
24
+ */
25
+
26
+ /** Catalogue name used by {@link FontBitmap} to look up this font. */
27
+ export const FONT_8x8_NAME = "8x8";
28
+
29
+ /**
30
+ * Glyph width in pixels (excluding spacing).
31
+ * @defaultValue `8`
32
+ */
33
+ export const FONT_8x8_WIDTH = 8;
34
+
35
+ /**
36
+ * Glyph height in pixels.
37
+ * @defaultValue `8`
38
+ */
39
+ export const FONT_8x8_HEIGHT = 8;
40
+
41
+ /**
42
+ * Horizontal spacing between glyphs in pixels.
43
+ * @defaultValue `1`
44
+ */
45
+ export const FONT_8x8_SPACING = 1;
46
+
47
+ /**
48
+ * Complete set of supported characters as a single string.
49
+ */
50
+ export const FONT_8x8_CHARS =
51
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz !?.,:;-+*/\\()[]{}<>=#%&@^_'\"`~|$";
52
+
53
+ /**
54
+ * Glyph data keyed by character.
55
+ *
56
+ * Each value is a 8-element `number[]` where each entry is a 8-bit
57
+ * row bitmask (bit 7 = leftmost pixel, bit 0 = rightmost pixel).
58
+ *
59
+ * @see {@link FontBitmap} — consumes this data for rendering
60
+ */
61
+ export const FONT_8x8: Record<string, number[]> = {
62
+ "0": [60, 66, 70, 74, 82, 98, 60, 0],
63
+ "1": [24, 56, 24, 24, 24, 24, 126, 0],
64
+ "2": [60, 66, 2, 12, 48, 64, 126, 0],
65
+ "3": [126, 2, 4, 28, 2, 2, 124, 0],
66
+ "4": [4, 12, 20, 36, 68, 254, 4, 0],
67
+ "5": [254, 128, 252, 2, 2, 66, 60, 0],
68
+ "6": [62, 64, 128, 252, 130, 130, 124, 0],
69
+ "7": [254, 4, 8, 16, 32, 64, 128, 0],
70
+ "8": [60, 66, 66, 60, 66, 66, 60, 0],
71
+ "9": [60, 66, 66, 62, 2, 2, 60, 0],
72
+ A: [24, 36, 66, 66, 126, 66, 66, 0],
73
+ B: [252, 66, 66, 124, 66, 66, 252, 0],
74
+ C: [62, 64, 128, 128, 128, 64, 62, 0],
75
+ D: [248, 68, 66, 66, 66, 68, 248, 0],
76
+ E: [254, 128, 128, 252, 128, 128, 254, 0],
77
+ F: [254, 128, 128, 252, 128, 128, 128, 0],
78
+ G: [62, 64, 128, 142, 130, 66, 62, 0],
79
+ H: [66, 66, 66, 126, 66, 66, 66, 0],
80
+ I: [126, 24, 24, 24, 24, 24, 126, 0],
81
+ J: [62, 4, 4, 4, 132, 136, 112, 0],
82
+ K: [130, 132, 136, 240, 136, 132, 130, 0],
83
+ L: [128, 128, 128, 128, 128, 128, 254, 0],
84
+ M: [130, 198, 170, 146, 130, 130, 130, 0],
85
+ N: [130, 194, 162, 146, 138, 134, 130, 0],
86
+ O: [60, 66, 129, 129, 129, 66, 60, 0],
87
+ P: [252, 130, 130, 252, 128, 128, 128, 0],
88
+ Q: [60, 66, 129, 129, 133, 66, 61, 0],
89
+ R: [252, 130, 130, 252, 136, 132, 130, 0],
90
+ S: [62, 64, 128, 124, 2, 2, 252, 0],
91
+ T: [254, 16, 16, 16, 16, 16, 16, 0],
92
+ U: [130, 130, 130, 130, 130, 68, 56, 0],
93
+ V: [130, 130, 68, 68, 40, 40, 16, 0],
94
+ W: [130, 130, 130, 146, 170, 198, 130, 0],
95
+ X: [130, 68, 40, 16, 40, 68, 130, 0],
96
+ Y: [130, 68, 40, 16, 16, 16, 16, 0],
97
+ Z: [254, 4, 8, 16, 32, 64, 254, 0],
98
+ a: [0, 0, 60, 2, 62, 66, 62, 0],
99
+ b: [64, 64, 124, 66, 66, 66, 124, 0],
100
+ c: [0, 0, 62, 64, 64, 64, 62, 0],
101
+ d: [2, 2, 62, 66, 66, 66, 62, 0],
102
+ e: [0, 0, 60, 66, 126, 64, 62, 0],
103
+ f: [14, 16, 16, 62, 16, 16, 16, 0],
104
+ g: [0, 62, 66, 66, 62, 2, 60, 0],
105
+ h: [64, 64, 124, 66, 66, 66, 66, 0],
106
+ i: [16, 0, 48, 16, 16, 16, 62, 0],
107
+ j: [8, 0, 8, 8, 8, 72, 48, 0],
108
+ k: [64, 68, 72, 112, 72, 68, 66, 0],
109
+ l: [96, 32, 32, 32, 32, 32, 126, 0],
110
+ m: [0, 0, 218, 170, 170, 130, 130, 0],
111
+ n: [0, 0, 124, 66, 66, 66, 66, 0],
112
+ o: [0, 0, 60, 66, 66, 66, 60, 0],
113
+ p: [0, 124, 66, 66, 124, 64, 64, 0],
114
+ q: [0, 62, 66, 66, 62, 2, 2, 0],
115
+ r: [0, 0, 94, 96, 64, 64, 64, 0],
116
+ s: [0, 0, 62, 64, 60, 2, 124, 0],
117
+ t: [32, 32, 126, 32, 32, 34, 28, 0],
118
+ u: [0, 0, 66, 66, 66, 70, 58, 0],
119
+ v: [0, 0, 66, 66, 36, 40, 16, 0],
120
+ w: [0, 0, 130, 130, 146, 170, 68, 0],
121
+ x: [0, 0, 66, 36, 24, 36, 66, 0],
122
+ y: [0, 66, 66, 66, 62, 2, 60, 0],
123
+ z: [0, 0, 254, 4, 24, 96, 254, 0],
124
+ " ": [0, 0, 0, 0, 0, 0, 0, 0],
125
+ "!": [24, 24, 24, 24, 24, 0, 24, 0],
126
+ "?": [60, 66, 2, 4, 8, 0, 8, 0],
127
+ ".": [0, 0, 0, 0, 0, 48, 48, 0],
128
+ ",": [0, 0, 0, 0, 48, 48, 96, 0],
129
+ ":": [0, 24, 24, 0, 24, 24, 0, 0],
130
+ ";": [0, 24, 24, 0, 24, 24, 48, 0],
131
+ "-": [0, 0, 0, 254, 0, 0, 0, 0],
132
+ "+": [0, 16, 16, 254, 16, 16, 0, 0],
133
+ "*": [0, 130, 68, 56, 68, 130, 0, 0],
134
+ "/": [2, 4, 8, 16, 32, 64, 128, 0],
135
+ "\\": [128, 64, 32, 16, 8, 4, 2, 0],
136
+ "(": [12, 16, 32, 32, 32, 16, 12, 0],
137
+ ")": [48, 8, 4, 4, 4, 8, 48, 0],
138
+ "[": [120, 64, 64, 64, 64, 64, 120, 0],
139
+ "]": [30, 2, 2, 2, 2, 2, 30, 0],
140
+ "{": [30, 48, 16, 96, 16, 48, 30, 0],
141
+ "}": [120, 12, 8, 6, 8, 12, 120, 0],
142
+ "<": [4, 8, 16, 32, 16, 8, 4, 0],
143
+ ">": [32, 16, 8, 4, 8, 16, 32, 0],
144
+ "=": [0, 0, 254, 0, 254, 0, 0, 0],
145
+ "#": [36, 36, 254, 36, 254, 36, 36, 0],
146
+ "%": [194, 196, 8, 16, 32, 67, 131, 0],
147
+ "&": [56, 68, 72, 48, 74, 68, 58, 0],
148
+ "@": [60, 66, 157, 165, 158, 64, 60, 0],
149
+ "^": [16, 40, 68, 130, 0, 0, 0, 0],
150
+ _: [0, 0, 0, 0, 0, 0, 254, 0],
151
+ "'": [24, 24, 48, 0, 0, 0, 0, 0],
152
+ '"': [72, 72, 0, 0, 0, 0, 0, 0],
153
+ "`": [48, 24, 8, 0, 0, 0, 0, 0],
154
+ "~": [0, 70, 137, 144, 0, 0, 0, 0],
155
+ "|": [24, 24, 24, 24, 24, 24, 24, 0],
156
+ $: [24, 62, 96, 60, 6, 124, 24, 0],
157
+ };
158
+
159
+ /**
160
+ * Complete metadata object for the 8x8 font, used by the
161
+ * {@link FontBitmap} catalogue at module load time.
162
+ *
163
+ * @internal
164
+ */
165
+ export const metadata = {
166
+ /** Catalogue name. */
167
+ name: FONT_8x8_NAME,
168
+ /** Cell width including spacing (8 + 1 = 9). */
169
+ width: FONT_8x8_WIDTH + FONT_8x8_SPACING,
170
+ /** Cell height (8). */
171
+ height: FONT_8x8_HEIGHT,
172
+ /** Inter-glyph spacing (1). */
173
+ spacing: FONT_8x8_SPACING,
174
+ /** Supported character string. */
175
+ chars: FONT_8x8_CHARS,
176
+ /** Glyph bitmask data. */
177
+ data: FONT_8x8,
178
+ };
179
+
180
+ export default FONT_8x8;
@@ -0,0 +1,146 @@
1
+ import type { GameObject } from "../types";
2
+
3
+ /**
4
+ * Central registry that stores and manages all non-player
5
+ * {@link GameObject | game objects} within the engine.
6
+ *
7
+ * Objects are keyed by their `id` property, so each ID must be unique.
8
+ * The {@link Engine} delegates per-frame `update` and `render` calls to
9
+ * this register.
10
+ *
11
+ * @category Core
12
+ * @since 0.1.0
13
+ *
14
+ * @example Registering and retrieving objects
15
+ * ```ts
16
+ * const register = new GameObjectRegister();
17
+ *
18
+ * register.register(tree);
19
+ * register.register(rock);
20
+ *
21
+ * const found = register.get("tree"); // Entity | undefined
22
+ * console.log(register.has("rock")); // true
23
+ * ```
24
+ *
25
+ * @example Bulk update / render
26
+ * ```ts
27
+ * // Called internally by Engine each frame:
28
+ * register.updateAll(deltaTime);
29
+ * register.renderAll(ctx);
30
+ * ```
31
+ *
32
+ * @see {@link Engine.attachObjects} — convenience method that delegates here
33
+ */
34
+ export default class GameObjectRegister {
35
+ /**
36
+ * Internal map from entity ID to its {@link GameObject} instance.
37
+ */
38
+ private objects: Map<string, GameObject> = new Map();
39
+
40
+ private _cache: GameObject[] | null = null;
41
+
42
+ /**
43
+ * Adds a game object to the registry.
44
+ *
45
+ * If an object with the same `id` already exists it will be
46
+ * silently overwritten.
47
+ *
48
+ * @param object - The game object to register.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * register.register(new Crate("crate_1", 200, 150, 32, 32));
53
+ * ```
54
+ */
55
+ register(object: GameObject) {
56
+ this.objects.set(object.id, object);
57
+ this._cache = null;
58
+ }
59
+
60
+ /**
61
+ * Retrieves a registered object by its unique ID.
62
+ *
63
+ * @param id - The ID of the object to find.
64
+ * @returns The matching {@link GameObject}, or `undefined` if not found.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const crate = register.get("crate_1");
69
+ * if (crate) crate.x += 10;
70
+ * ```
71
+ */
72
+ get(id: string): GameObject | undefined {
73
+ return this.objects.get(id);
74
+ }
75
+
76
+ /**
77
+ * Checks whether an object with the given ID is registered.
78
+ *
79
+ * @param id - The ID to look up.
80
+ * @returns `true` if the registry contains the object.
81
+ */
82
+ has(id: string): boolean {
83
+ return this.objects.has(id);
84
+ }
85
+
86
+ /**
87
+ * Returns all registered objects as an array.
88
+ *
89
+ * Make sure to also cache the objects
90
+ *
91
+ * @since 0.2.0
92
+ *
93
+ * @returns An array of all {@link GameObject} instances in the registry.
94
+ */
95
+ toArray(): GameObject[] {
96
+ if (!this._cache) {
97
+ this._cache = Array.from(this.objects.values());
98
+ }
99
+
100
+ return this._cache;
101
+ }
102
+
103
+ /**
104
+ * Returns all registered objects that pass the supplied filter.
105
+ *
106
+ * @param filter - (optional) A predicate function. Return `true` to include the
107
+ * object in the result.
108
+ * @returns An array of matching {@link GameObject} instances.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const enemies = register.getAll(() => true);
113
+ * ```
114
+ */
115
+ getAll(filter?: () => true): GameObject[] {
116
+ if (typeof filter === "function") {
117
+ return this.toArray().filter(filter);
118
+ }
119
+
120
+ return this.toArray();
121
+ }
122
+
123
+ /**
124
+ * Calls {@link GameObject.update | update(deltaTime)} on every
125
+ * registered object.
126
+ *
127
+ * @param deltaTime - Seconds elapsed since the previous frame.
128
+ */
129
+ updateAll(deltaTime: number): void {
130
+ for (const obj of this.getAll()) {
131
+ obj.update(deltaTime);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Calls {@link GameObject.render | render(ctx)} on every registered
137
+ * object.
138
+ *
139
+ * @param ctx - The canvas 2-D rendering context.
140
+ */
141
+ renderAll(ctx: CanvasRenderingContext2D): void {
142
+ for (const obj of this.getAll()) {
143
+ obj.render(ctx);
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Unified keyboard and mouse input manager.
3
+ *
4
+ * `Input` listens to `keydown`, `keyup`, `mousedown`, `mouseup`, and
5
+ * `mousemove` events on the `window` and exposes a polling API so game
6
+ * logic can query the current state at any point during a frame rather
7
+ * than relying on event callbacks.
8
+ *
9
+ * All keyboard keys are stored **lowercased** for case-insensitive
10
+ * look-ups.
11
+ *
12
+ * @category Core
13
+ * @since 0.1.0
14
+ *
15
+ * @example Polling keys
16
+ * ```ts
17
+ * const input = new Input();
18
+ *
19
+ * function update() {
20
+ * if (input.isKeyDown("w")) {
21
+ * player.y -= speed;
22
+ * }
23
+ * }
24
+ * ```
25
+ *
26
+ * @example Checking mouse state
27
+ * ```ts
28
+ * const input = new Input();
29
+ *
30
+ * if (input.isMouseButtonDown(0)) { // left-click
31
+ * const { x, y } = input.getMousePosition();
32
+ * shoot(x, y);
33
+ * }
34
+ * ```
35
+ *
36
+ * @see {@link Control} — behaviour that consumes `Input` for player movement
37
+ */
38
+ export default class Input {
39
+ /**
40
+ * Set of currently-pressed keyboard keys (lowercased).
41
+ *
42
+ * Populated on `keydown`, cleared on `keyup`.
43
+ */
44
+ private keys: Set<string> = new Set();
45
+
46
+ /**
47
+ * Set of currently-pressed mouse button indices.
48
+ *
49
+ * Standard mapping: `0` = left, `1` = middle, `2` = right.
50
+ */
51
+ private mouseButtons: Set<number> = new Set();
52
+
53
+ /**
54
+ * Last known mouse position in **client** (viewport) coordinates.
55
+ *
56
+ * @defaultValue `{ x: 0, y: 0 }`
57
+ */
58
+ private mousePosition: { x: number; y: number } = { x: 0, y: 0 };
59
+
60
+ /**
61
+ * Creates a new `Input` instance and attaches global event listeners
62
+ * to the `window`.
63
+ *
64
+ * @remarks
65
+ * Only one `Input` instance should exist at a time to avoid
66
+ * duplicate listeners. If you need to tear down, call {@link Input.reset}
67
+ * to clear tracked state.
68
+ */
69
+ constructor() {
70
+ window.addEventListener("keydown", (e) => {
71
+ this.keys.add(e.key.toLowerCase());
72
+ });
73
+
74
+ window.addEventListener("keyup", (e) => {
75
+ this.keys.delete(e.key.toLowerCase());
76
+ });
77
+
78
+ window.addEventListener("mousedown", (e) => {
79
+ this.mouseButtons.add(e.button);
80
+ });
81
+
82
+ window.addEventListener("mouseup", (e) => {
83
+ this.mouseButtons.delete(e.button);
84
+ });
85
+
86
+ window.addEventListener("mousemove", (e) => {
87
+ this.mousePosition.x = e.clientX;
88
+ this.mousePosition.y = e.clientY;
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Checks whether a specific key is currently held down.
94
+ *
95
+ * @param key - The key name to check (case-insensitive).
96
+ * Uses the standard {@link KeyboardEvent.key} values (e.g. `"a"`,
97
+ * `"ArrowLeft"`, `"Shift"`).
98
+ * @returns `true` if the key is currently pressed.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * if (input.isKeyDown("space")) {
103
+ * player.jump();
104
+ * }
105
+ * ```
106
+ */
107
+ isKeyDown(key: string): boolean {
108
+ return this.keys.has(key.toLowerCase());
109
+ }
110
+
111
+ /**
112
+ * Returns a snapshot of all keys that are currently held down.
113
+ *
114
+ * The returned `Set` is a **copy** — mutating it does not affect
115
+ * the internal state.
116
+ *
117
+ * @returns A new `Set<string>` of pressed key names (lowercased).
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const pressed = input.getPressedKeys();
122
+ * console.log([...pressed]); // e.g. ["w", "shift"]
123
+ * ```
124
+ */
125
+ getPressedKeys(): Set<string> {
126
+ return new Set(this.keys);
127
+ }
128
+
129
+ /**
130
+ * Checks whether a specific mouse button is currently held down.
131
+ *
132
+ * @param button - The mouse button index (`0` = left, `1` = middle,
133
+ * `2` = right).
134
+ * @returns `true` if the button is currently pressed.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * if (input.isMouseButtonDown(2)) {
139
+ * openContextMenu();
140
+ * }
141
+ * ```
142
+ */
143
+ isMouseButtonDown(button: number): boolean {
144
+ return this.mouseButtons.has(button);
145
+ }
146
+
147
+ /**
148
+ * Returns the last known mouse position in client (viewport)
149
+ * coordinates.
150
+ *
151
+ * The returned object is a **copy** — mutating it does not affect
152
+ * the internal state.
153
+ *
154
+ * @returns An `{ x, y }` object with the mouse coordinates.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * const pos = input.getMousePosition();
159
+ * ctx.fillRect(pos.x, pos.y, 4, 4); // draw cursor dot
160
+ * ```
161
+ */
162
+ getMousePosition(): { x: number; y: number } {
163
+ return { ...this.mousePosition };
164
+ }
165
+
166
+ /**
167
+ * Clears all tracked key and mouse-button state.
168
+ *
169
+ * Useful when pausing the game or switching scenes to prevent stale
170
+ * input from carrying over.
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * engine.pause();
175
+ * input.reset();
176
+ * ```
177
+ */
178
+ reset(): void {
179
+ this.keys.clear();
180
+ this.mouseButtons.clear();
181
+ }
182
+ }