like2d 1.0.0 → 2.0.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 (96) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +67 -43
  3. package/dist/adapters/callback/index.d.ts +43 -0
  4. package/dist/adapters/callback/index.d.ts.map +1 -0
  5. package/dist/adapters/callback/index.js +80 -0
  6. package/dist/adapters/scene/index.d.ts +42 -0
  7. package/dist/adapters/scene/index.d.ts.map +1 -0
  8. package/dist/adapters/scene/index.js +112 -0
  9. package/dist/adapters/scene/scene.d.ts +18 -0
  10. package/dist/adapters/scene/scene.d.ts.map +1 -0
  11. package/dist/adapters/scene/startup-scene.d.ts +17 -0
  12. package/dist/adapters/scene/startup-scene.d.ts.map +1 -0
  13. package/dist/adapters/scene/startup-scene.js +41 -0
  14. package/dist/core/audio.d.ts +61 -0
  15. package/dist/core/audio.d.ts.map +1 -0
  16. package/dist/core/audio.js +226 -0
  17. package/dist/core/canvas-config.d.ts +22 -0
  18. package/dist/core/canvas-config.d.ts.map +1 -0
  19. package/dist/core/canvas-config.js +14 -0
  20. package/dist/core/canvas-manager.d.ts +26 -0
  21. package/dist/core/canvas-manager.d.ts.map +1 -0
  22. package/dist/core/canvas-manager.js +197 -0
  23. package/dist/core/events.d.ts +52 -0
  24. package/dist/core/events.d.ts.map +1 -0
  25. package/dist/core/gamepad-button-map.d.ts.map +1 -0
  26. package/dist/core/gamepad-buttons.d.ts +23 -0
  27. package/dist/core/gamepad-buttons.d.ts.map +1 -0
  28. package/dist/core/gamepad-buttons.js +36 -0
  29. package/dist/core/gamepad-db.d.ts.map +1 -0
  30. package/dist/{gamepad-mapping.d.ts → core/gamepad-mapping.d.ts} +3 -15
  31. package/dist/core/gamepad-mapping.d.ts.map +1 -0
  32. package/dist/core/gamepad-mapping.js +223 -0
  33. package/dist/{gamepad.d.ts → core/gamepad.d.ts} +22 -17
  34. package/dist/core/gamepad.d.ts.map +1 -0
  35. package/dist/{gamepad.js → core/gamepad.js} +91 -70
  36. package/dist/{graphics.d.ts → core/graphics.d.ts} +2 -8
  37. package/dist/core/graphics.d.ts.map +1 -0
  38. package/dist/{graphics.js → core/graphics.js} +4 -41
  39. package/dist/core/input-state.d.ts.map +1 -0
  40. package/dist/{input.d.ts → core/input.d.ts} +11 -14
  41. package/dist/core/input.d.ts.map +1 -0
  42. package/dist/{input.js → core/input.js} +31 -41
  43. package/dist/core/keyboard.d.ts +15 -0
  44. package/dist/core/keyboard.d.ts.map +1 -0
  45. package/dist/core/keyboard.js +70 -0
  46. package/dist/core/mouse.d.ts +29 -0
  47. package/dist/core/mouse.d.ts.map +1 -0
  48. package/dist/core/mouse.js +130 -0
  49. package/dist/{rect.d.ts → core/rect.d.ts} +1 -2
  50. package/dist/core/rect.d.ts.map +1 -0
  51. package/dist/{rect.js → core/rect.js} +24 -28
  52. package/dist/{timer.d.ts → core/timer.d.ts} +0 -1
  53. package/dist/core/timer.d.ts.map +1 -0
  54. package/dist/{timer.js → core/timer.js} +0 -1
  55. package/dist/{vector2.d.ts → core/vector2.d.ts} +4 -10
  56. package/dist/core/vector2.d.ts.map +1 -0
  57. package/dist/{vector2.js → core/vector2.js} +40 -40
  58. package/dist/engine.d.ts +42 -0
  59. package/dist/engine.d.ts.map +1 -0
  60. package/dist/engine.js +154 -0
  61. package/dist/index.d.ts +38 -44
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +24 -250
  64. package/package.json +8 -23
  65. package/dist/audio.d.ts +0 -52
  66. package/dist/audio.d.ts.map +0 -1
  67. package/dist/audio.js +0 -250
  68. package/dist/events.d.ts +0 -36
  69. package/dist/events.d.ts.map +0 -1
  70. package/dist/gamepad-button-map.d.ts.map +0 -1
  71. package/dist/gamepad-db.d.ts.map +0 -1
  72. package/dist/gamepad-mapping.d.ts.map +0 -1
  73. package/dist/gamepad-mapping.js +0 -191
  74. package/dist/gamepad.d.ts.map +0 -1
  75. package/dist/graphics.d.ts.map +0 -1
  76. package/dist/input-state.d.ts.map +0 -1
  77. package/dist/input.d.ts.map +0 -1
  78. package/dist/keyboard.d.ts +0 -9
  79. package/dist/keyboard.d.ts.map +0 -1
  80. package/dist/keyboard.js +0 -33
  81. package/dist/mouse.d.ts +0 -20
  82. package/dist/mouse.d.ts.map +0 -1
  83. package/dist/mouse.js +0 -84
  84. package/dist/rect.d.ts.map +0 -1
  85. package/dist/scene.d.ts +0 -10
  86. package/dist/scene.d.ts.map +0 -1
  87. package/dist/timer.d.ts.map +0 -1
  88. package/dist/vector2.d.ts.map +0 -1
  89. /package/dist/{scene.js → adapters/scene/scene.js} +0 -0
  90. /package/dist/{events.js → core/events.js} +0 -0
  91. /package/dist/{gamepad-button-map.d.ts → core/gamepad-button-map.d.ts} +0 -0
  92. /package/dist/{gamepad-button-map.js → core/gamepad-button-map.js} +0 -0
  93. /package/dist/{gamepad-db.d.ts → core/gamepad-db.d.ts} +0 -0
  94. /package/dist/{gamepad-db.js → core/gamepad-db.js} +0 -0
  95. /package/dist/{input-state.d.ts → core/input-state.d.ts} +0 -0
  96. /package/dist/{input-state.js → core/input-state.js} +0 -0
@@ -0,0 +1,223 @@
1
+ // Gamepad button mapping layer
2
+ // Bridges SDL database mappings with our internal button naming system
3
+ import { GP } from './gamepad-buttons';
4
+ // Map SDL button names to our standard button indices
5
+ const SDL_TO_GP = {
6
+ 'a': GP.Bottom,
7
+ 'b': GP.Right,
8
+ 'x': GP.Left,
9
+ 'y': GP.Top,
10
+ 'leftshoulder': GP.LB,
11
+ 'rightshoulder': GP.RB,
12
+ 'lefttrigger': GP.LT,
13
+ 'righttrigger': GP.RT,
14
+ 'back': GP.Back,
15
+ 'start': GP.Start,
16
+ 'guide': GP.Guide,
17
+ 'leftstick': GP.LS,
18
+ 'rightstick': GP.RS,
19
+ 'dpup': GP.DUp,
20
+ 'dpdown': GP.DDown,
21
+ 'dpleft': GP.DLeft,
22
+ 'dpright': GP.DRight,
23
+ };
24
+ // Internal database for storing pre-built controller mappings
25
+ class GamepadDatabase {
26
+ constructor() {
27
+ // vendorProductKey -> mapping
28
+ Object.defineProperty(this, "mappings", {
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true,
32
+ value: new Map()
33
+ });
34
+ Object.defineProperty(this, "loaded", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: false
39
+ });
40
+ }
41
+ load(dbContent) {
42
+ this.mappings.clear();
43
+ const lines = dbContent.split('\n');
44
+ for (const line of lines) {
45
+ const trimmed = line.trim();
46
+ if (!trimmed || trimmed.startsWith('#')) {
47
+ continue;
48
+ }
49
+ const mapping = this.parseLine(trimmed);
50
+ if (mapping) {
51
+ // Extract vendor/product from GUID and store mapping
52
+ const guid = line.split(',')[0].toLowerCase().trim();
53
+ const vpKey = this.extractVendorProductKey(guid);
54
+ if (vpKey !== null && !this.mappings.has(vpKey)) {
55
+ this.mappings.set(vpKey, mapping);
56
+ }
57
+ }
58
+ }
59
+ this.loaded = true;
60
+ }
61
+ extractVendorProductKey(guid) {
62
+ if (guid.length < 20)
63
+ return null;
64
+ const vendorHex = guid.substring(8, 12);
65
+ const productHex = guid.substring(16, 20);
66
+ const vendor = parseInt(vendorHex.substring(2, 4) + vendorHex.substring(0, 2), 16);
67
+ const product = parseInt(productHex.substring(2, 4) + productHex.substring(0, 2), 16);
68
+ if (isNaN(vendor) || isNaN(product))
69
+ return null;
70
+ return 0x10000 * vendor + product;
71
+ }
72
+ isLoaded() {
73
+ return this.loaded;
74
+ }
75
+ getMappingCount() {
76
+ return this.mappings.size;
77
+ }
78
+ getMapping(vendor, product) {
79
+ const key = 0x10000 * vendor + product;
80
+ return this.mappings.get(key);
81
+ }
82
+ parseLine(line) {
83
+ const parts = line.split(',');
84
+ if (parts.length < 3) {
85
+ return null;
86
+ }
87
+ const name = parts[1].trim();
88
+ const toStandard = new Map();
89
+ // Parse mapping pairs (key:value)
90
+ for (let i = 2; i < parts.length; i++) {
91
+ const part = parts[i].trim();
92
+ if (!part || part.startsWith('platform:'))
93
+ continue;
94
+ const colonIndex = part.indexOf(':');
95
+ if (colonIndex === -1)
96
+ continue;
97
+ const sdlName = part.substring(0, colonIndex).trim();
98
+ const value = part.substring(colonIndex + 1).trim();
99
+ if (!sdlName || !value)
100
+ continue;
101
+ // Only handle button mappings (b0, b1, etc.)
102
+ if (value.startsWith('b')) {
103
+ const controllerIndex = parseInt(value.substring(1), 10);
104
+ const gpIndex = SDL_TO_GP[sdlName];
105
+ if (!isNaN(controllerIndex) && gpIndex !== undefined) {
106
+ toStandard.set(controllerIndex, gpIndex);
107
+ }
108
+ }
109
+ }
110
+ return { name, toStandard };
111
+ }
112
+ }
113
+ // Singleton instance
114
+ const gamepadDatabase = new GamepadDatabase();
115
+ export class GamepadMapping {
116
+ constructor() {
117
+ Object.defineProperty(this, "dbLoaded", {
118
+ enumerable: true,
119
+ configurable: true,
120
+ writable: true,
121
+ value: false
122
+ });
123
+ }
124
+ async loadDatabase() {
125
+ if (this.dbLoaded)
126
+ return;
127
+ try {
128
+ const res = await fetch('./gamecontrollerdb.txt');
129
+ if (res.ok) {
130
+ const text = await res.text();
131
+ // Validate it's actually the DB file, not HTML error page
132
+ if (text.startsWith('# Game Controller DB') || text.includes('03000000')) {
133
+ gamepadDatabase.load(text);
134
+ this.dbLoaded = true;
135
+ }
136
+ }
137
+ }
138
+ catch { }
139
+ if (!this.dbLoaded) {
140
+ try {
141
+ // @ts-ignore - Vite handles ?raw imports
142
+ const module = await import('../gamecontrollerdb.txt?raw');
143
+ if (typeof module.default === 'string') {
144
+ gamepadDatabase.load(module.default);
145
+ this.dbLoaded = true;
146
+ }
147
+ }
148
+ catch { }
149
+ }
150
+ if (this.dbLoaded) {
151
+ console.log(`[Gamepad] Loaded ${gamepadDatabase.getMappingCount()} controller mappings`);
152
+ }
153
+ }
154
+ loadDatabaseFromText(content) {
155
+ gamepadDatabase.load(content);
156
+ this.dbLoaded = true;
157
+ }
158
+ /**
159
+ * Get button mapping for a specific gamepad
160
+ */
161
+ getMapping(gamepad) {
162
+ const vp = this.extractVendorProduct(gamepad);
163
+ // If browser provides "standard" mapping, use identity mapping
164
+ if (gamepad.mapping === 'standard') {
165
+ return {
166
+ toStandard: IDENTITY_MAP,
167
+ controllerName: gamepad.id,
168
+ hasMapping: true,
169
+ vendor: vp?.vendor ?? null,
170
+ product: vp?.product ?? null,
171
+ };
172
+ }
173
+ // Look up in database
174
+ if (vp && this.dbLoaded) {
175
+ const dbMapping = gamepadDatabase.getMapping(vp.vendor, vp.product);
176
+ if (dbMapping) {
177
+ return {
178
+ toStandard: dbMapping.toStandard,
179
+ controllerName: dbMapping.name,
180
+ hasMapping: true,
181
+ vendor: vp.vendor,
182
+ product: vp.product,
183
+ };
184
+ }
185
+ }
186
+ // No mapping found - use identity
187
+ return {
188
+ toStandard: IDENTITY_MAP,
189
+ controllerName: gamepad.id,
190
+ hasMapping: false,
191
+ vendor: vp?.vendor ?? null,
192
+ product: vp?.product ?? null,
193
+ };
194
+ }
195
+ extractVendorProduct(gamepad) {
196
+ const id = gamepad.id;
197
+ const vendorProductMatch = id.match(/Vendor:\s*([0-9a-fA-F]+)\s+Product:\s*([0-9a-fA-F]+)/i);
198
+ if (vendorProductMatch) {
199
+ const vendor = parseInt(vendorProductMatch[1], 16);
200
+ const product = parseInt(vendorProductMatch[2], 16);
201
+ if (!isNaN(vendor) && !isNaN(product)) {
202
+ return { vendor, product };
203
+ }
204
+ }
205
+ const hexMatch = id.match(/^([0-9a-fA-F]{4})[\s-]+([0-9a-fA-F]{4})/);
206
+ if (hexMatch) {
207
+ const vendor = parseInt(hexMatch[1], 16);
208
+ const product = parseInt(hexMatch[2], 16);
209
+ if (!isNaN(vendor) && !isNaN(product)) {
210
+ return { vendor, product };
211
+ }
212
+ }
213
+ return null;
214
+ }
215
+ }
216
+ // Reusable identity map for standard/unmapped controllers
217
+ const IDENTITY_MAP = new Map([
218
+ [0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6], [7, 7],
219
+ [8, 8], [9, 9], [10, 10], [11, 11], [12, 12], [13, 13], [14, 14], [15, 15],
220
+ [16, 16], [17, 17], [18, 18], [19, 19],
221
+ ]);
222
+ // Singleton instance
223
+ export const gamepadMapping = new GamepadMapping();
@@ -1,35 +1,41 @@
1
- import { getButtonName, getButtonIndex } from './gamepad-button-map';
1
+ import { getGPName, GP } from './gamepad-buttons';
2
2
  import { ButtonMapping } from './gamepad-mapping';
3
- export { getButtonName, getButtonIndex };
4
- export interface GamepadButtonEvent {
5
- gamepadIndex: number;
6
- buttonIndex: number;
7
- buttonName: string;
8
- rawButtonIndex: number;
9
- }
3
+ export { GP, getGPName };
10
4
  export interface StickPosition {
11
5
  x: number;
12
6
  y: number;
13
7
  }
8
+ export type ButtonCallback = (gamepadIndex: number, buttonIndex: number, buttonName: string, pressed: boolean) => void;
14
9
  export declare class Gamepad {
15
10
  private buttonTrackers;
16
11
  private connectedGamepads;
17
12
  private buttonMappings;
13
+ onButtonEvent?: (gamepadIndex: number, buttonIndex: number, buttonName: string, pressed: boolean) => void;
14
+ private onConnected?;
15
+ private onDisconnected?;
16
+ private gamepadConnectedHandler;
17
+ private gamepadDisconnectedHandler;
18
+ private blurHandler;
18
19
  constructor();
20
+ private handleGamepadConnected;
21
+ private handleGamepadDisconnected;
22
+ private handleBlur;
23
+ setCallbacks(callbacks: {
24
+ onConnected?: (gamepad: globalThis.Gamepad) => void;
25
+ onDisconnected?: (gamepadIndex: number) => void;
26
+ }): void;
27
+ dispose(): void;
19
28
  init(): Promise<void>;
20
- private extractVendorProduct;
21
- private setupEventListeners;
22
- update(): {
23
- pressed: GamepadButtonEvent[];
24
- released: GamepadButtonEvent[];
25
- };
29
+ private onGamepadConnectedInternal;
30
+ private onGamepadDisconnectedInternal;
31
+ update(): void;
26
32
  isConnected(gamepadIndex: number): boolean;
27
33
  /**
28
34
  * Check if a button is currently pressed on a specific gamepad
29
35
  * Uses mapped button indices (standard layout)
30
36
  */
31
- isButtonDown(gamepadIndex: number, button: number | string): boolean;
32
- isButtonDownOnAny(button: number | string): boolean;
37
+ isButtonDown(gamepadIndex: number, buttonIndex: number): boolean;
38
+ isButtonDownOnAny(buttonIndex: number): boolean;
33
39
  getPressedButtons(gamepadIndex: number): Set<number>;
34
40
  getConnectedGamepads(): number[];
35
41
  /**
@@ -52,5 +58,4 @@ export declare class Gamepad {
52
58
  getLeftStick(gamepadIndex: number): StickPosition;
53
59
  getRightStick(gamepadIndex: number): StickPosition;
54
60
  }
55
- export declare const gamepad: Gamepad;
56
61
  //# sourceMappingURL=gamepad.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gamepad.d.ts","sourceRoot":"","sources":["../../src/core/gamepad.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AAElD,OAAO,EAAkB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;AAEzB,MAAM,WAAW,aAAa;IAC5B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAkBD,MAAM,MAAM,cAAc,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;AAEvH,qBAAa,OAAO;IAClB,OAAO,CAAC,cAAc,CAAgD;IACtE,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,cAAc,CAAoC;IACnD,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjH,OAAO,CAAC,WAAW,CAAC,CAAwC;IAC5D,OAAO,CAAC,cAAc,CAAC,CAAiC;IAGxD,OAAO,CAAC,uBAAuB,CAAuC;IACtE,OAAO,CAAC,0BAA0B,CAAuC;IACzE,OAAO,CAAC,WAAW,CAAa;;IAchC,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,yBAAyB;IAKjC,OAAO,CAAC,UAAU;IAMlB,YAAY,CAAC,SAAS,EAAE;QACtB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,KAAK,IAAI,CAAC;QACpD,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;KACjD,GAAG,IAAI;IAKR,OAAO,IAAI,IAAI;IAST,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,OAAO,CAAC,0BAA0B;IAclC,OAAO,CAAC,6BAA6B;IAMrC,MAAM,IAAI,IAAI;IA4Cd,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAI1C;;;OAGG;IACH,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO;IAKhE,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAO/C,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAKpD,oBAAoB,IAAI,MAAM,EAAE;IAIhC;;OAEG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,CAAC,OAAO,GAAG,SAAS;IAIhE;;OAEG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIjE;;OAEG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAKzC;;OAEG;IACH,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAK3D,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAMxD,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa;IAMjD,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa;CAKnD"}
@@ -1,7 +1,7 @@
1
- import { getButtonName, getButtonIndex } from './gamepad-button-map';
1
+ import { getGPName, GP } from './gamepad-buttons';
2
2
  import { InputStateTracker } from './input-state';
3
3
  import { gamepadMapping } from './gamepad-mapping';
4
- export { getButtonName, getButtonIndex };
4
+ export { GP, getGPName };
5
5
  const AXIS_DEADZONE = 0.15;
6
6
  function applyDeadzone(value, deadzone = AXIS_DEADZONE) {
7
7
  if (Math.abs(value) < deadzone)
@@ -37,60 +37,99 @@ export class Gamepad {
37
37
  writable: true,
38
38
  value: new Map()
39
39
  });
40
- this.setupEventListeners();
40
+ Object.defineProperty(this, "onButtonEvent", {
41
+ enumerable: true,
42
+ configurable: true,
43
+ writable: true,
44
+ value: void 0
45
+ });
46
+ Object.defineProperty(this, "onConnected", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: void 0
51
+ });
52
+ Object.defineProperty(this, "onDisconnected", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: void 0
57
+ });
58
+ // Event handler references for cleanup
59
+ Object.defineProperty(this, "gamepadConnectedHandler", {
60
+ enumerable: true,
61
+ configurable: true,
62
+ writable: true,
63
+ value: void 0
64
+ });
65
+ Object.defineProperty(this, "gamepadDisconnectedHandler", {
66
+ enumerable: true,
67
+ configurable: true,
68
+ writable: true,
69
+ value: void 0
70
+ });
71
+ Object.defineProperty(this, "blurHandler", {
72
+ enumerable: true,
73
+ configurable: true,
74
+ writable: true,
75
+ value: void 0
76
+ });
77
+ // Bind event handlers
78
+ this.gamepadConnectedHandler = this.handleGamepadConnected.bind(this);
79
+ this.gamepadDisconnectedHandler = this.handleGamepadDisconnected.bind(this);
80
+ this.blurHandler = this.handleBlur.bind(this);
81
+ // Register event listeners
82
+ window.addEventListener('gamepadconnected', this.gamepadConnectedHandler);
83
+ window.addEventListener('gamepaddisconnected', this.gamepadDisconnectedHandler);
84
+ window.addEventListener('blur', this.blurHandler);
85
+ }
86
+ handleGamepadConnected(e) {
87
+ this.onGamepadConnectedInternal(e.gamepad);
88
+ this.onConnected?.(e.gamepad);
89
+ }
90
+ handleGamepadDisconnected(e) {
91
+ this.onGamepadDisconnectedInternal(e.gamepad.index);
92
+ this.onDisconnected?.(e.gamepad.index);
93
+ }
94
+ handleBlur() {
95
+ for (const tracker of this.buttonTrackers.values()) {
96
+ tracker.clear();
97
+ }
98
+ }
99
+ setCallbacks(callbacks) {
100
+ this.onConnected = callbacks.onConnected;
101
+ this.onDisconnected = callbacks.onDisconnected;
102
+ }
103
+ dispose() {
104
+ window.removeEventListener('gamepadconnected', this.gamepadConnectedHandler);
105
+ window.removeEventListener('gamepaddisconnected', this.gamepadDisconnectedHandler);
106
+ window.removeEventListener('blur', this.blurHandler);
107
+ this.connectedGamepads.clear();
108
+ this.buttonTrackers.clear();
109
+ this.buttonMappings.clear();
41
110
  }
42
111
  async init() {
43
112
  await gamepadMapping.loadDatabase();
44
113
  }
45
- extractVendorProduct(gamepad) {
46
- const id = gamepad.id;
47
- const vendorProductMatch = id.match(/Vendor:\s*([0-9a-fA-F]+)\s+Product:\s*([0-9a-fA-F]+)/i);
48
- if (vendorProductMatch) {
49
- const vendor = parseInt(vendorProductMatch[1], 16);
50
- const product = parseInt(vendorProductMatch[2], 16);
51
- if (!isNaN(vendor) && !isNaN(product)) {
52
- return { vendor, product };
53
- }
54
- }
55
- const hexMatch = id.match(/^([0-9a-fA-F]{4})[\s-]+([0-9a-fA-F]{4})/);
56
- if (hexMatch) {
57
- const vendor = parseInt(hexMatch[1], 16);
58
- const product = parseInt(hexMatch[2], 16);
59
- if (!isNaN(vendor) && !isNaN(product)) {
60
- return { vendor, product };
61
- }
114
+ onGamepadConnectedInternal(gamepad) {
115
+ this.connectedGamepads.set(gamepad.index, gamepad);
116
+ this.buttonTrackers.set(gamepad.index, new InputStateTracker());
117
+ const mapping = gamepadMapping.getMapping(gamepad);
118
+ this.buttonMappings.set(gamepad.index, mapping);
119
+ console.log(`[Gamepad] Connected: "${gamepad.id}"`);
120
+ if (mapping.vendor !== null && mapping.product !== null) {
121
+ console.log(`[Gamepad] Vendor: 0x${mapping.vendor.toString(16).padStart(4, '0')}, Product: 0x${mapping.product.toString(16).padStart(4, '0')}`);
62
122
  }
63
- return null;
64
- }
65
- setupEventListeners() {
66
- window.addEventListener('gamepadconnected', (e) => {
67
- this.connectedGamepads.set(e.gamepad.index, e.gamepad);
68
- this.buttonTrackers.set(e.gamepad.index, new InputStateTracker());
69
- const mapping = gamepadMapping.getMapping(e.gamepad);
70
- this.buttonMappings.set(e.gamepad.index, mapping);
71
- console.log(`[Gamepad] Connected: "${e.gamepad.id}"`);
72
- const vp = this.extractVendorProduct(e.gamepad);
73
- if (vp) {
74
- console.log(`[Gamepad] Vendor: 0x${vp.vendor.toString(16).padStart(4, '0')}, Product: 0x${vp.product.toString(16).padStart(4, '0')}`);
75
- }
76
- const mappingType = e.gamepad.mapping === 'standard' ? 'browser standard' : (mapping.hasMapping ? 'SDL DB' : 'unmapped');
77
- console.log(`[Gamepad] Mapped as: "${mapping.controllerName}" (${mappingType})`);
78
- });
79
- window.addEventListener('gamepaddisconnected', (e) => {
80
- this.connectedGamepads.delete(e.gamepad.index);
81
- this.buttonTrackers.delete(e.gamepad.index);
82
- this.buttonMappings.delete(e.gamepad.index);
83
- });
84
- window.addEventListener('blur', () => {
85
- for (const tracker of this.buttonTrackers.values()) {
86
- tracker.clear();
87
- }
88
- });
123
+ const mappingType = gamepad.mapping === 'standard' ? 'browser standard' : (mapping.hasMapping ? 'SDL DB' : 'unmapped');
124
+ console.log(`[Gamepad] Mapped as: "${mapping.controllerName}" (${mappingType})`);
125
+ }
126
+ onGamepadDisconnectedInternal(gamepadIndex) {
127
+ this.connectedGamepads.delete(gamepadIndex);
128
+ this.buttonTrackers.delete(gamepadIndex);
129
+ this.buttonMappings.delete(gamepadIndex);
89
130
  }
90
131
  update() {
91
132
  const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
92
- const pressed = [];
93
- const released = [];
94
133
  for (let i = 0; i < gamepads.length; i++) {
95
134
  const gamepad = gamepads[i];
96
135
  if (gamepad) {
@@ -118,24 +157,13 @@ export class Gamepad {
118
157
  }
119
158
  const changes = tracker.update(pressedButtons);
120
159
  for (const buttonIndex of changes.justPressed) {
121
- pressed.push({
122
- gamepadIndex: i,
123
- buttonIndex,
124
- buttonName: getButtonName(buttonIndex),
125
- rawButtonIndex: mapping.fromStandard.get(buttonIndex) ?? buttonIndex,
126
- });
160
+ this.onButtonEvent?.(i, buttonIndex, getGPName(buttonIndex), true);
127
161
  }
128
162
  for (const buttonIndex of changes.justReleased) {
129
- released.push({
130
- gamepadIndex: i,
131
- buttonIndex,
132
- buttonName: getButtonName(buttonIndex),
133
- rawButtonIndex: mapping.fromStandard.get(buttonIndex) ?? buttonIndex,
134
- });
163
+ this.onButtonEvent?.(i, buttonIndex, getGPName(buttonIndex), false);
135
164
  }
136
165
  }
137
166
  }
138
- return { pressed, released };
139
167
  }
140
168
  isConnected(gamepadIndex) {
141
169
  return this.connectedGamepads.has(gamepadIndex);
@@ -144,17 +172,11 @@ export class Gamepad {
144
172
  * Check if a button is currently pressed on a specific gamepad
145
173
  * Uses mapped button indices (standard layout)
146
174
  */
147
- isButtonDown(gamepadIndex, button) {
148
- const buttonIndex = typeof button === 'string' ? getButtonIndex(button) : button;
149
- if (buttonIndex === undefined)
150
- return false;
175
+ isButtonDown(gamepadIndex, buttonIndex) {
151
176
  const tracker = this.buttonTrackers.get(gamepadIndex);
152
177
  return tracker ? tracker.isDown(buttonIndex) : false;
153
178
  }
154
- isButtonDownOnAny(button) {
155
- const buttonIndex = typeof button === 'string' ? getButtonIndex(button) : button;
156
- if (buttonIndex === undefined)
157
- return false;
179
+ isButtonDownOnAny(buttonIndex) {
158
180
  for (const tracker of this.buttonTrackers.values()) {
159
181
  if (tracker.isDown(buttonIndex))
160
182
  return true;
@@ -213,4 +235,3 @@ export class Gamepad {
213
235
  return applyRadialDeadzone(gamepad.axes[2], gamepad.axes[3]);
214
236
  }
215
237
  }
216
- export const gamepad = new Gamepad();
@@ -40,12 +40,12 @@ export declare class ImageHandle {
40
40
  }
41
41
  export declare class Graphics {
42
42
  private ctx;
43
- private screenCtx;
43
+ private readonly screenCtx;
44
44
  private canvases;
45
45
  private backgroundColor;
46
46
  private images;
47
47
  private defaultFont;
48
- setContext(ctx: CanvasRenderingContext2D | null): void;
48
+ constructor(ctx: CanvasRenderingContext2D);
49
49
  private applyColor;
50
50
  private setStrokeProps;
51
51
  clear(): void;
@@ -63,11 +63,6 @@ export declare class Graphics {
63
63
  getFont(): string;
64
64
  newImage(path: string): ImageHandle;
65
65
  draw(handle: ImageHandle, position: Vector2, props?: DrawProps): void;
66
- push(): void;
67
- pop(): void;
68
- translate(delta: Vector2): void;
69
- rotate(angle: number): void;
70
- scale(s: number | Vector2): void;
71
66
  getCanvasSize(): Vector2;
72
67
  newCanvas(size: Vector2): Canvas;
73
68
  setCanvas(canvas?: Canvas | null): void;
@@ -76,5 +71,4 @@ export declare class Graphics {
76
71
  arc(mode: DrawMode, x: number, y: number, radius: number, angle1: number, angle2: number, props?: ShapeProps): void;
77
72
  points(color: Color, points: Vector2[]): void;
78
73
  }
79
- export declare const graphics: Graphics;
80
74
  //# sourceMappingURL=graphics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphics.d.ts","sourceRoot":"","sources":["../../src/core/graphics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhC,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC;AAC/D,MAAM,MAAM,IAAI,GAAG,IAAI,CAAC;AAExB,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAE9B,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,iBAAiB,CAAC;IAC3B,GAAG,EAAE,wBAAwB,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACtC,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG;IACnC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;CACrC,CAAC;AAEF,qBAAa,WAAW;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,EAAE,MAAM;IAiBxB,OAAO,IAAI,OAAO;IAIlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,UAAU,IAAI,gBAAgB,GAAG,IAAI;CAGtC;AAWD,qBAAa,QAAQ;IACnB,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2B;IAErD,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,WAAW,CAAqB;gBAE5B,GAAG,EAAE,wBAAwB;IAMzC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,cAAc;IAStB,KAAK,IAAI,IAAI;IAMb,kBAAkB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAKtC,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI;IAgB7E,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI;IA4BvJ,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI;IAe/D,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI;IAuB9E,OAAO,CAAC,QAAQ;IAqBhB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAqB,GAAG,IAAI;IAMxD,OAAO,IAAI,MAAM;IAIjB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW;IASnC,IAAI,CACF,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,SAAS,GAChB,IAAI;IA6BP,aAAa,IAAI,OAAO;IAMxB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM;IAchC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAQvC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI;IAevB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI;IAqBlF,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI;IAkBnH,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI;CAO9C"}
@@ -59,18 +59,18 @@ function parseColor(color) {
59
59
  return `rgba(${r * 255}, ${g * 255}, ${b * 255}, ${a})`;
60
60
  }
61
61
  export class Graphics {
62
- constructor() {
62
+ constructor(ctx) {
63
63
  Object.defineProperty(this, "ctx", {
64
64
  enumerable: true,
65
65
  configurable: true,
66
66
  writable: true,
67
- value: null
67
+ value: void 0
68
68
  });
69
69
  Object.defineProperty(this, "screenCtx", {
70
70
  enumerable: true,
71
71
  configurable: true,
72
72
  writable: true,
73
- value: null
73
+ value: void 0
74
74
  });
75
75
  Object.defineProperty(this, "canvases", {
76
76
  enumerable: true,
@@ -96,13 +96,9 @@ export class Graphics {
96
96
  writable: true,
97
97
  value: '16px sans-serif'
98
98
  });
99
- }
100
- setContext(ctx) {
101
99
  this.screenCtx = ctx;
102
100
  this.ctx = ctx;
103
- if (ctx) {
104
- ctx.font = this.defaultFont;
105
- }
101
+ ctx.font = this.defaultFont;
106
102
  }
107
103
  applyColor(color) {
108
104
  return parseColor(color ?? [1, 1, 1, 1]);
@@ -269,38 +265,6 @@ export class Graphics {
269
265
  }
270
266
  this.ctx.restore();
271
267
  }
272
- push() {
273
- if (!this.ctx)
274
- return;
275
- this.ctx.save();
276
- }
277
- pop() {
278
- if (!this.ctx)
279
- return;
280
- this.ctx.restore();
281
- }
282
- translate(delta) {
283
- if (!this.ctx)
284
- return;
285
- const [x, y] = delta;
286
- this.ctx.translate(x, y);
287
- }
288
- rotate(angle) {
289
- if (!this.ctx)
290
- return;
291
- this.ctx.rotate(angle);
292
- }
293
- scale(s) {
294
- if (!this.ctx)
295
- return;
296
- if (typeof s === 'number') {
297
- this.ctx.scale(s, s);
298
- }
299
- else {
300
- const [sx, sy] = s;
301
- this.ctx.scale(sx, sy);
302
- }
303
- }
304
268
  getCanvasSize() {
305
269
  const width = this.ctx?.canvas.width ?? 800;
306
270
  const height = this.ctx?.canvas.height ?? 600;
@@ -385,4 +349,3 @@ export class Graphics {
385
349
  points.forEach(([x, y]) => this.ctx.fillRect(x, y, 1, 1));
386
350
  }
387
351
  }
388
- export const graphics = new Graphics();
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-state.d.ts","sourceRoot":"","sources":["../../src/core/input-state.ts"],"names":[],"mappings":"AAAA,qBAAa,iBAAiB,CAAC,CAAC;IAC9B,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,SAAS,CAAgB;IAEjC,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG;QAAE,WAAW,EAAE,CAAC,EAAE,CAAC;QAAC,YAAY,EAAE,CAAC,EAAE,CAAA;KAAE;IAuBpE,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAIvB,WAAW,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAI5B,YAAY,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAI7B,eAAe,IAAI,GAAG,CAAC,CAAC,CAAC;IAIzB,KAAK,IAAI,IAAI;CAId"}
@@ -1,12 +1,22 @@
1
+ import type { Keyboard } from './keyboard';
2
+ import type { Mouse } from './mouse';
3
+ import type { Gamepad } from './gamepad';
1
4
  export type InputType = 'keyboard' | 'mouse' | 'gamepad';
2
5
  export interface InputBinding {
3
6
  type: InputType;
4
7
  code: string;
5
- gamepadIndex?: number;
6
8
  }
7
9
  export declare class Input {
8
10
  private actionMap;
9
11
  private actionStateTracker;
12
+ private keyboard;
13
+ private mouse;
14
+ private gamepad;
15
+ constructor(deps: {
16
+ keyboard: Keyboard;
17
+ mouse: Mouse;
18
+ gamepad: Gamepad;
19
+ });
10
20
  map(action: string, inputs: string[]): void;
11
21
  unmap(action: string): void;
12
22
  isDown(action: string): boolean;
@@ -15,22 +25,9 @@ export declare class Input {
15
25
  update(): {
16
26
  pressed: string[];
17
27
  released: string[];
18
- gamepadPressed: Array<{
19
- gamepadIndex: number;
20
- buttonIndex: number;
21
- buttonName: string;
22
- rawButtonIndex: number;
23
- }>;
24
- gamepadReleased: Array<{
25
- gamepadIndex: number;
26
- buttonIndex: number;
27
- buttonName: string;
28
- rawButtonIndex: number;
29
- }>;
30
28
  };
31
29
  private parseInput;
32
30
  private isBindingActive;
33
31
  clear(): void;
34
32
  }
35
- export declare const input: Input;
36
33
  //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/core/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAaD,qBAAa,KAAK;IAChB,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,OAAO,CAAU;gBAEb,IAAI,EAAE;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IAMxE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;IAK3C,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK3B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAO/B,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIpC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIrC,MAAM,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE;IAgBnD,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,eAAe;IAuBvB,KAAK,IAAI,IAAI;CAId"}