canvasengine 2.0.0-beta.34 → 2.0.0-beta.35
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/dist/{DebugRenderer-BYa_lwD-.js → DebugRenderer-DMqhoMfE.js} +2 -2
- package/dist/{DebugRenderer-BYa_lwD-.js.map → DebugRenderer-DMqhoMfE.js.map} +1 -1
- package/dist/components/Sprite.d.ts +44 -0
- package/dist/components/Sprite.d.ts.map +1 -1
- package/dist/directives/Controls.d.ts +102 -0
- package/dist/directives/Controls.d.ts.map +1 -0
- package/dist/directives/ControlsBase.d.ts +198 -0
- package/dist/directives/ControlsBase.d.ts.map +1 -0
- package/dist/directives/GamepadControls.d.ts +223 -0
- package/dist/directives/GamepadControls.d.ts.map +1 -0
- package/dist/directives/KeyboardControls.d.ts +55 -366
- package/dist/directives/KeyboardControls.d.ts.map +1 -1
- package/dist/directives/index.d.ts +9 -1
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/{index-BLbc2zG5.js → index-B9ATEmLe.js} +3926 -3564
- package/dist/index-B9ATEmLe.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.js +6 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +68 -57
- package/package.json +2 -1
- package/src/components/Sprite.ts +87 -3
- package/src/directives/Controls.ts +182 -0
- package/src/directives/ControlsBase.ts +266 -0
- package/src/directives/GamepadControls.ts +515 -0
- package/src/directives/KeyboardControls.ts +66 -426
- package/src/directives/index.ts +9 -6
- package/src/index.ts +1 -1
- package/dist/index-BLbc2zG5.js.map +0 -1
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { ControlsBase, Controls } from "./ControlsBase";
|
|
2
|
+
import { WritableSignal } from "@signe/reactive";
|
|
3
|
+
import 'joypad.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gamepad configuration interface
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const gamepadConfig: GamepadConfig = {
|
|
11
|
+
* enabled: true,
|
|
12
|
+
* buttonMapping: {
|
|
13
|
+
* 'button_0': 'action',
|
|
14
|
+
* 'button_1': 'back'
|
|
15
|
+
* },
|
|
16
|
+
* axisMapping: {
|
|
17
|
+
* 'top': 'up',
|
|
18
|
+
* 'bottom': 'down',
|
|
19
|
+
* 'left': 'left',
|
|
20
|
+
* 'right': 'right'
|
|
21
|
+
* },
|
|
22
|
+
* moveInterval: 400,
|
|
23
|
+
* onConnect: () => console.log('Gamepad connected!'),
|
|
24
|
+
* onDisconnect: () => console.log('Gamepad disconnected!')
|
|
25
|
+
* };
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export interface GamepadConfig {
|
|
29
|
+
/** Whether gamepad is enabled (default: true) */
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
/** Mapping of gamepad button names to control names */
|
|
32
|
+
buttonMapping?: {
|
|
33
|
+
[buttonName: string]: string; // e.g., 'button_0' -> 'action'
|
|
34
|
+
};
|
|
35
|
+
/** Mapping of axis directions to control directions */
|
|
36
|
+
axisMapping?: {
|
|
37
|
+
[axisDirection: string]: string; // e.g., 'top' -> 'up'
|
|
38
|
+
};
|
|
39
|
+
/** Threshold for axis movement detection (default: 0.5) */
|
|
40
|
+
axisThreshold?: number;
|
|
41
|
+
/** Interval in milliseconds for repeating movement actions (default: 400) */
|
|
42
|
+
moveInterval?: number;
|
|
43
|
+
/** Callback called when a gamepad is connected */
|
|
44
|
+
onConnect?: () => void;
|
|
45
|
+
/** Callback called when a gamepad is disconnected */
|
|
46
|
+
onDisconnect?: () => void;
|
|
47
|
+
/** Signal that tracks gamepad connection status (optional) */
|
|
48
|
+
gamepadConnected?: WritableSignal<boolean>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default button mapping
|
|
53
|
+
*/
|
|
54
|
+
const DEFAULT_BUTTON_MAPPING: { [buttonName: string]: string } = {
|
|
55
|
+
'button_0': 'action',
|
|
56
|
+
'button_1': 'back',
|
|
57
|
+
'button_9': 'back'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Default axis mapping
|
|
62
|
+
*/
|
|
63
|
+
const DEFAULT_AXIS_MAPPING: { [axisDirection: string]: string } = {
|
|
64
|
+
'top': 'up',
|
|
65
|
+
'bottom': 'down',
|
|
66
|
+
'left': 'left',
|
|
67
|
+
'right': 'right'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gamepad input controls implementation
|
|
72
|
+
*
|
|
73
|
+
* Handles gamepad input events using joypad.js library and maps them to control actions.
|
|
74
|
+
* Supports button presses and analog stick movement with configurable mappings.
|
|
75
|
+
*
|
|
76
|
+
* The gamepad controls are automatically activated when joypad.js is available and enabled.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const gamepadControls = new GamepadControls();
|
|
81
|
+
* gamepadControls.setInputs({
|
|
82
|
+
* up: {
|
|
83
|
+
* repeat: true,
|
|
84
|
+
* bind: 'up',
|
|
85
|
+
* keyDown() {
|
|
86
|
+
* console.log('Up pressed');
|
|
87
|
+
* }
|
|
88
|
+
* }
|
|
89
|
+
* });
|
|
90
|
+
* gamepadControls.updateGamepadConfig({
|
|
91
|
+
* enabled: true,
|
|
92
|
+
* buttonMapping: {
|
|
93
|
+
* 'button_0': 'action'
|
|
94
|
+
* }
|
|
95
|
+
* });
|
|
96
|
+
* gamepadControls.start();
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export class GamepadControls extends ControlsBase {
|
|
100
|
+
private gamepadEnabled: boolean = true;
|
|
101
|
+
private gamepadConfig: GamepadConfig = {
|
|
102
|
+
enabled: true,
|
|
103
|
+
buttonMapping: DEFAULT_BUTTON_MAPPING,
|
|
104
|
+
axisMapping: DEFAULT_AXIS_MAPPING,
|
|
105
|
+
axisThreshold: 0.5,
|
|
106
|
+
moveInterval: 400
|
|
107
|
+
};
|
|
108
|
+
private gamepadMoving: boolean = false;
|
|
109
|
+
private gamepadDirections: { [direction: string]: boolean } = {};
|
|
110
|
+
private gamepadAxisDate: number = 0;
|
|
111
|
+
private gamepadMoveInterval: any = null;
|
|
112
|
+
private joypad: any = null;
|
|
113
|
+
private connectCallbacks: Array<() => void> = [];
|
|
114
|
+
private disconnectCallbacks: Array<() => void> = [];
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Setup gamepad event listeners
|
|
118
|
+
* Initializes joypad.js if available
|
|
119
|
+
*/
|
|
120
|
+
protected setupListeners(): void {
|
|
121
|
+
this.initGamepad();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Cleanup gamepad event listeners and intervals
|
|
126
|
+
*/
|
|
127
|
+
protected cleanup(): void {
|
|
128
|
+
if (this.gamepadMoveInterval) {
|
|
129
|
+
clearInterval(this.gamepadMoveInterval);
|
|
130
|
+
this.gamepadMoveInterval = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (this.joypad) {
|
|
134
|
+
this.joypad.off('connect');
|
|
135
|
+
this.joypad.off('disconnect');
|
|
136
|
+
this.joypad.off('button_press');
|
|
137
|
+
this.joypad.off('axis_move');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Initialize joypad.js library if available
|
|
143
|
+
*/
|
|
144
|
+
private initGamepad(): void {
|
|
145
|
+
if (typeof window === 'undefined') return;
|
|
146
|
+
|
|
147
|
+
const joypadLib = (window as any)['joypad'];
|
|
148
|
+
|
|
149
|
+
if (!joypadLib) {
|
|
150
|
+
// joypad.js not available
|
|
151
|
+
// Set initial state if signal is provided
|
|
152
|
+
if (this.gamepadConfig.gamepadConnected) {
|
|
153
|
+
this.gamepadConfig.gamepadConnected.set(false);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.joypad = joypadLib;
|
|
159
|
+
|
|
160
|
+
// Setup event listeners
|
|
161
|
+
this.joypad.on('connect', () => this.handleGamepadConnect());
|
|
162
|
+
this.joypad.on('disconnect', () => this.handleGamepadDisconnect());
|
|
163
|
+
this.joypad.on('button_press', (e: any) => this.handleGamepadButtonPress(e));
|
|
164
|
+
this.joypad.on('axis_move', (e: any) => this.handleGamepadAxisMove(e));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Handle gamepad connection event
|
|
169
|
+
*/
|
|
170
|
+
private handleGamepadConnect(): void {
|
|
171
|
+
if (!this.gamepadEnabled || !this.gamepadConfig.enabled) return;
|
|
172
|
+
|
|
173
|
+
// Start movement processing interval
|
|
174
|
+
this.gamepadMoveInterval = setInterval(() => {
|
|
175
|
+
this.processGamepadMovement();
|
|
176
|
+
}, this.gamepadConfig.moveInterval || 400);
|
|
177
|
+
|
|
178
|
+
// Update gamepadConnected signal if provided
|
|
179
|
+
if (this.gamepadConfig.gamepadConnected) {
|
|
180
|
+
this.gamepadConfig.gamepadConnected.set(true);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Call all registered connect callbacks
|
|
184
|
+
this.connectCallbacks.forEach(callback => callback());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Handle gamepad disconnection event
|
|
189
|
+
*/
|
|
190
|
+
private handleGamepadDisconnect(): void {
|
|
191
|
+
// Stop movement interval
|
|
192
|
+
if (this.gamepadMoveInterval) {
|
|
193
|
+
clearInterval(this.gamepadMoveInterval);
|
|
194
|
+
this.gamepadMoveInterval = null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Reset states
|
|
198
|
+
this.gamepadMoving = false;
|
|
199
|
+
this.gamepadDirections = {};
|
|
200
|
+
this.gamepadAxisDate = 0;
|
|
201
|
+
|
|
202
|
+
// Update gamepadConnected signal if provided
|
|
203
|
+
if (this.gamepadConfig.gamepadConnected) {
|
|
204
|
+
this.gamepadConfig.gamepadConnected.set(false);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Call all registered disconnect callbacks
|
|
208
|
+
this.disconnectCallbacks.forEach(callback => callback());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Handle gamepad button press event
|
|
213
|
+
*
|
|
214
|
+
* @param e - Button press event from joypad.js
|
|
215
|
+
*/
|
|
216
|
+
private handleGamepadButtonPress(e: any): void {
|
|
217
|
+
if (!this.gamepadEnabled || !this.gamepadConfig.enabled) return;
|
|
218
|
+
if (this.stop) return;
|
|
219
|
+
|
|
220
|
+
const { buttonName } = e.detail;
|
|
221
|
+
const buttonMapping = this.gamepadConfig.buttonMapping || DEFAULT_BUTTON_MAPPING;
|
|
222
|
+
const controlName = buttonMapping[buttonName];
|
|
223
|
+
|
|
224
|
+
if (controlName) {
|
|
225
|
+
// Apply the control action (press and release)
|
|
226
|
+
this.applyControl(controlName).catch(() => {
|
|
227
|
+
// Ignore errors
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Handle gamepad axis movement event
|
|
234
|
+
*
|
|
235
|
+
* @param e - Axis move event from joypad.js
|
|
236
|
+
*/
|
|
237
|
+
private handleGamepadAxisMove(e: any): void {
|
|
238
|
+
if (!this.gamepadEnabled || !this.gamepadConfig.enabled) return;
|
|
239
|
+
if (this.stop) return;
|
|
240
|
+
|
|
241
|
+
this.gamepadMoving = true;
|
|
242
|
+
this.gamepadAxisDate = Date.now();
|
|
243
|
+
|
|
244
|
+
let direction = e.detail.directionOfMovement;
|
|
245
|
+
const axisMapping = this.gamepadConfig.axisMapping || DEFAULT_AXIS_MAPPING;
|
|
246
|
+
|
|
247
|
+
// Map joypad direction to control direction
|
|
248
|
+
if (direction === 'top') direction = axisMapping['top'] || 'up';
|
|
249
|
+
else if (direction === 'bottom') direction = axisMapping['bottom'] || 'down';
|
|
250
|
+
else if (direction === 'left') direction = axisMapping['left'] || 'left';
|
|
251
|
+
else if (direction === 'right') direction = axisMapping['right'] || 'right';
|
|
252
|
+
|
|
253
|
+
// Update active directions
|
|
254
|
+
this.gamepadDirections = {
|
|
255
|
+
[direction]: true
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Release other directions
|
|
259
|
+
const allDirections = ['up', 'down', 'left', 'right'];
|
|
260
|
+
for (const dir of allDirections) {
|
|
261
|
+
if (!this.gamepadDirections[dir]) {
|
|
262
|
+
this.applyControl(dir, false).catch(() => {
|
|
263
|
+
// Ignore errors
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Trigger movement
|
|
269
|
+
this.processGamepadMovement();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Process continuous gamepad movement
|
|
274
|
+
* Called at intervals to repeat movement actions while axes are active
|
|
275
|
+
*/
|
|
276
|
+
private processGamepadMovement(): void {
|
|
277
|
+
if (!this.gamepadMoving) return;
|
|
278
|
+
if (this.stop) return;
|
|
279
|
+
|
|
280
|
+
for (const direction in this.gamepadDirections) {
|
|
281
|
+
if (this.gamepadDirections[direction]) {
|
|
282
|
+
this.applyControl(direction, true).catch(() => {
|
|
283
|
+
// Ignore errors
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Process gamepad inputs each step
|
|
291
|
+
* Handles timeout for stopping movements after axis inactivity
|
|
292
|
+
*/
|
|
293
|
+
protected preStep(): void {
|
|
294
|
+
if (this.stop) return;
|
|
295
|
+
|
|
296
|
+
// Stop movements if no axis input for 100ms
|
|
297
|
+
const now = Date.now();
|
|
298
|
+
if (now - this.gamepadAxisDate > 100 && this.gamepadMoving) {
|
|
299
|
+
const allDirections = ['up', 'down', 'left', 'right'];
|
|
300
|
+
for (const dir of allDirections) {
|
|
301
|
+
this.gamepadDirections = {};
|
|
302
|
+
this.gamepadMoving = false;
|
|
303
|
+
this.applyControl(dir, false).catch(() => {
|
|
304
|
+
// Ignore errors
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Update gamepad configuration
|
|
312
|
+
* Merges provided config with defaults
|
|
313
|
+
* Automatically registers callbacks from config
|
|
314
|
+
*
|
|
315
|
+
* @param config - Partial gamepad configuration
|
|
316
|
+
*/
|
|
317
|
+
updateGamepadConfig(config: Partial<GamepadConfig>): void {
|
|
318
|
+
// Remove old callbacks if they were registered
|
|
319
|
+
if (this.gamepadConfig.onConnect) {
|
|
320
|
+
this.offConnect(this.gamepadConfig.onConnect);
|
|
321
|
+
}
|
|
322
|
+
if (this.gamepadConfig.onDisconnect) {
|
|
323
|
+
this.offDisconnect(this.gamepadConfig.onDisconnect);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.gamepadConfig = {
|
|
327
|
+
enabled: config.enabled !== undefined ? config.enabled : true,
|
|
328
|
+
buttonMapping: config.buttonMapping || DEFAULT_BUTTON_MAPPING,
|
|
329
|
+
axisMapping: config.axisMapping || DEFAULT_AXIS_MAPPING,
|
|
330
|
+
axisThreshold: config.axisThreshold || 0.5,
|
|
331
|
+
moveInterval: config.moveInterval || 400,
|
|
332
|
+
onConnect: config.onConnect,
|
|
333
|
+
onDisconnect: config.onDisconnect,
|
|
334
|
+
gamepadConnected: config.gamepadConnected
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Register new callbacks if provided
|
|
338
|
+
if (this.gamepadConfig.onConnect) {
|
|
339
|
+
this.onConnect(this.gamepadConfig.onConnect);
|
|
340
|
+
}
|
|
341
|
+
if (this.gamepadConfig.onDisconnect) {
|
|
342
|
+
this.onDisconnect(this.gamepadConfig.onDisconnect);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Extract gamepad config from controls configuration and update
|
|
348
|
+
* Note: Callbacks are stored but not automatically registered, they should be registered in mount()
|
|
349
|
+
*
|
|
350
|
+
* @param inputs - Controls configuration that may contain a 'gamepad' property
|
|
351
|
+
*/
|
|
352
|
+
extractGamepadConfig(inputs: Controls & { gamepad?: GamepadConfig }): void {
|
|
353
|
+
if (inputs.gamepad) {
|
|
354
|
+
this.updateGamepadConfig(inputs.gamepad);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get the current gamepad configuration
|
|
360
|
+
*
|
|
361
|
+
* @returns The gamepad configuration object
|
|
362
|
+
*/
|
|
363
|
+
getGamepadConfig(): GamepadConfig {
|
|
364
|
+
return this.gamepadConfig;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Apply a control action programmatically
|
|
369
|
+
* Uses the bound controls to trigger actions
|
|
370
|
+
*
|
|
371
|
+
* @param controlName - Name of the control
|
|
372
|
+
* @param isDown - Whether the control is pressed (true) or released (false)
|
|
373
|
+
* @returns Promise that resolves when the action is complete
|
|
374
|
+
*/
|
|
375
|
+
async applyControl(controlName: string | number, isDown?: boolean): Promise<void> {
|
|
376
|
+
const control = this._controlsOptions[controlName];
|
|
377
|
+
if (!control) return;
|
|
378
|
+
|
|
379
|
+
// Find the bound key for this control
|
|
380
|
+
const boundKeys = Object.keys(this.boundKeys);
|
|
381
|
+
for (const keyName of boundKeys) {
|
|
382
|
+
const boundKey = this.boundKeys[keyName];
|
|
383
|
+
if (boundKey.actionName === String(controlName)) {
|
|
384
|
+
// Execute the control callback
|
|
385
|
+
if (isDown === undefined) {
|
|
386
|
+
// Press and release (simulate button press)
|
|
387
|
+
if (boundKey.options.keyDown) {
|
|
388
|
+
let parameters = boundKey.parameters;
|
|
389
|
+
if (typeof parameters === "function") {
|
|
390
|
+
parameters = parameters();
|
|
391
|
+
}
|
|
392
|
+
boundKey.options.keyDown(boundKey);
|
|
393
|
+
}
|
|
394
|
+
// Release after a short delay (similar to keyboard)
|
|
395
|
+
return new Promise((resolve) => {
|
|
396
|
+
setTimeout(() => {
|
|
397
|
+
if (boundKey.options.keyUp) {
|
|
398
|
+
let parameters = boundKey.parameters;
|
|
399
|
+
if (typeof parameters === "function") {
|
|
400
|
+
parameters = parameters();
|
|
401
|
+
}
|
|
402
|
+
boundKey.options.keyUp(boundKey);
|
|
403
|
+
}
|
|
404
|
+
resolve();
|
|
405
|
+
}, 200);
|
|
406
|
+
});
|
|
407
|
+
} else if (isDown) {
|
|
408
|
+
if (boundKey.options.keyDown) {
|
|
409
|
+
let parameters = boundKey.parameters;
|
|
410
|
+
if (typeof parameters === "function") {
|
|
411
|
+
parameters = parameters();
|
|
412
|
+
}
|
|
413
|
+
boundKey.options.keyDown(boundKey);
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
if (boundKey.options.keyUp) {
|
|
417
|
+
let parameters = boundKey.parameters;
|
|
418
|
+
if (typeof parameters === "function") {
|
|
419
|
+
parameters = parameters();
|
|
420
|
+
}
|
|
421
|
+
boundKey.options.keyUp(boundKey);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Override setInputs to extract gamepad config
|
|
431
|
+
*/
|
|
432
|
+
setInputs(inputs: Controls & { gamepad?: GamepadConfig }): void {
|
|
433
|
+
super.setInputs(inputs);
|
|
434
|
+
this.extractGamepadConfig(inputs);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Register a callback to be called when a gamepad is connected
|
|
439
|
+
*
|
|
440
|
+
* @param callback - Function to call when gamepad connects
|
|
441
|
+
* @example
|
|
442
|
+
* ```ts
|
|
443
|
+
* gamepadControls.onConnect(() => {
|
|
444
|
+
* console.log('Gamepad connected!');
|
|
445
|
+
* });
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
onConnect(callback: () => void): void {
|
|
449
|
+
this.connectCallbacks.push(callback);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Register a callback to be called when a gamepad is disconnected
|
|
454
|
+
*
|
|
455
|
+
* @param callback - Function to call when gamepad disconnects
|
|
456
|
+
* @example
|
|
457
|
+
* ```ts
|
|
458
|
+
* gamepadControls.onDisconnect(() => {
|
|
459
|
+
* console.log('Gamepad disconnected!');
|
|
460
|
+
* });
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
onDisconnect(callback: () => void): void {
|
|
464
|
+
this.disconnectCallbacks.push(callback);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Remove a connect callback
|
|
469
|
+
*
|
|
470
|
+
* @param callback - Callback to remove
|
|
471
|
+
*/
|
|
472
|
+
offConnect(callback: () => void): void {
|
|
473
|
+
const index = this.connectCallbacks.indexOf(callback);
|
|
474
|
+
if (index > -1) {
|
|
475
|
+
this.connectCallbacks.splice(index, 1);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Remove a disconnect callback
|
|
481
|
+
*
|
|
482
|
+
* @param callback - Callback to remove
|
|
483
|
+
*/
|
|
484
|
+
offDisconnect(callback: () => void): void {
|
|
485
|
+
const index = this.disconnectCallbacks.indexOf(callback);
|
|
486
|
+
if (index > -1) {
|
|
487
|
+
this.disconnectCallbacks.splice(index, 1);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Check if gamepad is currently connected
|
|
493
|
+
*
|
|
494
|
+
* @returns true if gamepad is connected, false otherwise
|
|
495
|
+
*/
|
|
496
|
+
isConnected(): boolean {
|
|
497
|
+
return this.gamepadMoveInterval !== null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Reinitialize gamepad listeners
|
|
502
|
+
* Useful if joypad.js becomes available after initialization
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* ```ts
|
|
506
|
+
* // If joypad.js loads later
|
|
507
|
+
* gamepadControls.reinit();
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
510
|
+
reinit(): void {
|
|
511
|
+
if (!this.joypad) {
|
|
512
|
+
this.initGamepad();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|