canvasengine 2.0.0-beta.38 → 2.0.0-beta.39

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 (32) hide show
  1. package/dist/{DebugRenderer-DxJSMb9B.js → DebugRenderer-Rrw9FlTd.js} +2 -2
  2. package/dist/{DebugRenderer-DxJSMb9B.js.map → DebugRenderer-Rrw9FlTd.js.map} +1 -1
  3. package/dist/components/Button.d.ts +50 -3
  4. package/dist/components/Button.d.ts.map +1 -1
  5. package/dist/components/Joystick.d.ts +36 -0
  6. package/dist/components/Joystick.d.ts.map +1 -0
  7. package/dist/components/index.d.ts +1 -0
  8. package/dist/components/index.d.ts.map +1 -1
  9. package/dist/directives/Controls.d.ts +16 -7
  10. package/dist/directives/Controls.d.ts.map +1 -1
  11. package/dist/directives/GamepadControls.d.ts +3 -1
  12. package/dist/directives/GamepadControls.d.ts.map +1 -1
  13. package/dist/directives/JoystickControls.d.ts +172 -0
  14. package/dist/directives/JoystickControls.d.ts.map +1 -0
  15. package/dist/directives/index.d.ts +1 -0
  16. package/dist/directives/index.d.ts.map +1 -1
  17. package/dist/engine/reactive.d.ts.map +1 -1
  18. package/dist/{index-BgNWflRE.js → index-BQ99FClW.js} +5543 -5141
  19. package/dist/index-BQ99FClW.js.map +1 -0
  20. package/dist/index.global.js +7 -7
  21. package/dist/index.global.js.map +1 -1
  22. package/dist/index.js +59 -57
  23. package/package.json +1 -1
  24. package/src/components/Button.ts +168 -41
  25. package/src/components/Joystick.ts +361 -0
  26. package/src/components/index.ts +2 -1
  27. package/src/directives/Controls.ts +42 -8
  28. package/src/directives/GamepadControls.ts +40 -11
  29. package/src/directives/JoystickControls.ts +396 -0
  30. package/src/directives/index.ts +1 -0
  31. package/src/engine/reactive.ts +41 -14
  32. package/dist/index-BgNWflRE.js.map +0 -1
@@ -0,0 +1,361 @@
1
+ /*
2
+ * Joystick
3
+ *
4
+ * Inspired by https://github.com/endel/pixi-virtual-joystick
5
+ */
6
+
7
+ import * as PIXI from "pixi.js";
8
+ import { Container, Graphics, Sprite, h, signal, isSignal } from "../";
9
+
10
+ export interface JoystickChangeEvent {
11
+ angle: number;
12
+ direction: Direction;
13
+ power: number;
14
+ }
15
+
16
+ export enum Direction {
17
+ LEFT = "left",
18
+ TOP = "top",
19
+ BOTTOM = "bottom",
20
+ RIGHT = "right",
21
+ TOP_LEFT = "top_left",
22
+ TOP_RIGHT = "top_right",
23
+ BOTTOM_LEFT = "bottom_left",
24
+ BOTTOM_RIGHT = "bottom_right",
25
+ }
26
+
27
+ export interface JoystickSettings {
28
+ outer?: string;
29
+ inner?: string;
30
+ outerScale?: { x: number; y: number };
31
+ innerScale?: { x: number; y: number };
32
+ innerColor?: string;
33
+ outerColor?: string;
34
+ onChange?: (data: JoystickChangeEvent) => void;
35
+ onStart?: () => void;
36
+ onEnd?: () => void;
37
+ /** Controls instance to automatically apply joystick events to (e.g., JoystickControls or ControlsDirective) */
38
+ controls?: any;
39
+ }
40
+
41
+ export function Joystick(opts: JoystickSettings = {}) {
42
+ const settings = Object.assign(
43
+ {
44
+ outerScale: { x: 1, y: 1 },
45
+ innerScale: { x: 1, y: 1 },
46
+ innerColor: "black",
47
+ outerColor: "black",
48
+ },
49
+ opts
50
+ );
51
+
52
+ // Unwrap controls if it's a signal
53
+ const getControls = () => {
54
+ if (isSignal(settings.controls)) {
55
+ return settings.controls();
56
+ }
57
+ return settings.controls;
58
+ };
59
+
60
+ let outerRadius = 70;
61
+ let innerRadius = 10;
62
+ const innerAlphaStandby = 0.5;
63
+
64
+ let dragging = false;
65
+ let startPosition: PIXI.PointData | null = null;
66
+ let power = 0;
67
+
68
+ const innerPositionX = signal(0);
69
+ const innerPositionY = signal(0);
70
+ const innerAlpha = signal(innerAlphaStandby);
71
+
72
+ function getPower(centerPoint: PIXI.Point) {
73
+ const a = centerPoint.x - 0;
74
+ const b = centerPoint.y - 0;
75
+ return Math.min(1, Math.sqrt(a * a + b * b) / outerRadius);
76
+ }
77
+
78
+ function getDirection(center: PIXI.Point) {
79
+ let rad = Math.atan2(center.y, center.x); // [-PI, PI]
80
+ if ((rad >= -Math.PI / 8 && rad < 0) || (rad >= 0 && rad < Math.PI / 8)) {
81
+ return Direction.RIGHT;
82
+ } else if (rad >= Math.PI / 8 && rad < (3 * Math.PI) / 8) {
83
+ return Direction.BOTTOM_RIGHT;
84
+ } else if (rad >= (3 * Math.PI) / 8 && rad < (5 * Math.PI) / 8) {
85
+ return Direction.BOTTOM;
86
+ } else if (rad >= (5 * Math.PI) / 8 && rad < (7 * Math.PI) / 8) {
87
+ return Direction.BOTTOM_LEFT;
88
+ } else if (
89
+ (rad >= (7 * Math.PI) / 8 && rad < Math.PI) ||
90
+ (rad >= -Math.PI && rad < (-7 * Math.PI) / 8)
91
+ ) {
92
+ return Direction.LEFT;
93
+ } else if (rad >= (-7 * Math.PI) / 8 && rad < (-5 * Math.PI) / 8) {
94
+ return Direction.TOP_LEFT;
95
+ } else if (rad >= (-5 * Math.PI) / 8 && rad < (-3 * Math.PI) / 8) {
96
+ return Direction.TOP;
97
+ } else {
98
+ return Direction.TOP_RIGHT;
99
+ }
100
+ }
101
+
102
+ function handleDragStart(event: any) {
103
+ startPosition = event.getLocalPosition(this);
104
+ dragging = true;
105
+ innerAlpha.set(1);
106
+ settings.onStart?.();
107
+
108
+ // Notify controls if provided
109
+ const controls = getControls();
110
+ if (controls) {
111
+ // Check if it's JoystickControls instance
112
+ if (controls.handleJoystickStart) {
113
+ controls.handleJoystickStart();
114
+ }
115
+ // Check if it's ControlsDirective with joystick getter
116
+ else if (controls.joystick && controls.joystick.handleJoystickStart) {
117
+ controls.joystick.handleJoystickStart();
118
+ }
119
+ }
120
+ }
121
+
122
+ function handleDragEnd() {
123
+ if (!dragging) return;
124
+ innerPositionX.set(0);
125
+ innerPositionY.set(0);
126
+ dragging = false;
127
+ innerAlpha.set(innerAlphaStandby);
128
+ settings.onEnd?.();
129
+
130
+ // Notify controls if provided
131
+ const controls = getControls();
132
+ if (controls) {
133
+ // Check if it's JoystickControls instance
134
+ if (controls.handleJoystickEnd) {
135
+ controls.handleJoystickEnd();
136
+ }
137
+ // Check if it's ControlsDirective with joystick getter
138
+ else if (controls.joystick && controls.joystick.handleJoystickEnd) {
139
+ controls.joystick.handleJoystickEnd();
140
+ }
141
+ }
142
+ }
143
+
144
+ function handleDragMove(event: any) {
145
+ if (dragging == false) {
146
+ return;
147
+ }
148
+
149
+ let newPosition = event.getLocalPosition(this);
150
+
151
+ let sideX = newPosition.x - (startPosition?.x ?? 0);
152
+ let sideY = newPosition.y - (startPosition?.y ?? 0);
153
+
154
+ let centerPoint = new PIXI.Point(0, 0);
155
+ let angle = 0;
156
+
157
+ if (sideX == 0 && sideY == 0) {
158
+ return;
159
+ }
160
+
161
+ let calRadius = 0;
162
+
163
+ if (sideX * sideX + sideY * sideY >= outerRadius * outerRadius) {
164
+ calRadius = outerRadius;
165
+ } else {
166
+ calRadius = outerRadius - innerRadius;
167
+ }
168
+
169
+ /**
170
+ * x: -1 <-> 1
171
+ * y: -1 <-> 1
172
+ * Y
173
+ * ^
174
+ * |
175
+ * 180 | 90
176
+ * ------------> X
177
+ * 270 | 360
178
+ * |
179
+ * |
180
+ */
181
+
182
+ let direction = Direction.LEFT;
183
+
184
+ if (sideX == 0) {
185
+ if (sideY > 0) {
186
+ centerPoint.set(0, sideY > outerRadius ? outerRadius : sideY);
187
+ angle = 270;
188
+ direction = Direction.BOTTOM;
189
+ } else {
190
+ centerPoint.set(
191
+ 0,
192
+ -(Math.abs(sideY) > outerRadius ? outerRadius : Math.abs(sideY))
193
+ );
194
+ angle = 90;
195
+ direction = Direction.TOP;
196
+ }
197
+ innerPositionX.set(centerPoint.x);
198
+ innerPositionY.set(centerPoint.y);
199
+ power = getPower(centerPoint);
200
+ const changeEvent = { angle, direction, power };
201
+ settings.onChange?.(changeEvent);
202
+
203
+ // Notify controls if provided
204
+ const controls = getControls();
205
+ if (controls) {
206
+ // Check if it's JoystickControls instance
207
+ if (controls.handleJoystickChange) {
208
+ controls.handleJoystickChange(changeEvent);
209
+ }
210
+ // Check if it's ControlsDirective with joystick getter
211
+ else if (controls.joystick && controls.joystick.handleJoystickChange) {
212
+ controls.joystick.handleJoystickChange(changeEvent);
213
+ }
214
+ }
215
+ return;
216
+ }
217
+
218
+ if (sideY == 0) {
219
+ if (sideX > 0) {
220
+ centerPoint.set(
221
+ Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX),
222
+ 0
223
+ );
224
+ angle = 0;
225
+ direction = Direction.RIGHT;
226
+ } else {
227
+ centerPoint.set(
228
+ -(Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX)),
229
+ 0
230
+ );
231
+ angle = 180;
232
+ direction = Direction.LEFT;
233
+ }
234
+
235
+ innerPositionX.set(centerPoint.x);
236
+ innerPositionY.set(centerPoint.y);
237
+ power = getPower(centerPoint);
238
+ const changeEvent = { angle, direction, power };
239
+ settings.onChange?.(changeEvent);
240
+
241
+ // Notify controls if provided
242
+ const controls = getControls();
243
+ if (controls) {
244
+ // Check if it's JoystickControls instance
245
+ if (controls.handleJoystickChange) {
246
+ controls.handleJoystickChange(changeEvent);
247
+ }
248
+ // Check if it's ControlsDirective with joystick getter
249
+ else if (controls.joystick && controls.joystick.handleJoystickChange) {
250
+ controls.joystick.handleJoystickChange(changeEvent);
251
+ }
252
+ }
253
+ return;
254
+ }
255
+
256
+ let tanVal = Math.abs(sideY / sideX);
257
+ let radian = Math.atan(tanVal);
258
+ angle = (radian * 180) / Math.PI;
259
+
260
+ let centerX = 0;
261
+ let centerY = 0;
262
+
263
+ if (sideX * sideX + sideY * sideY >= outerRadius * outerRadius) {
264
+ centerX = outerRadius * Math.cos(radian);
265
+ centerY = outerRadius * Math.sin(radian);
266
+ } else {
267
+ centerX = Math.abs(sideX) > outerRadius ? outerRadius : Math.abs(sideX);
268
+ centerY = Math.abs(sideY) > outerRadius ? outerRadius : Math.abs(sideY);
269
+ }
270
+
271
+ if (sideY < 0) {
272
+ centerY = -Math.abs(centerY);
273
+ }
274
+ if (sideX < 0) {
275
+ centerX = -Math.abs(centerX);
276
+ }
277
+
278
+ if (sideX > 0 && sideY < 0) {
279
+ // < 90
280
+ } else if (sideX < 0 && sideY < 0) {
281
+ // 90 ~ 180
282
+ angle = 180 - angle;
283
+ } else if (sideX < 0 && sideY > 0) {
284
+ // 180 ~ 270
285
+ angle = angle + 180;
286
+ } else if (sideX > 0 && sideY > 0) {
287
+ // 270 ~ 369
288
+ angle = 360 - angle;
289
+ }
290
+ centerPoint.set(centerX, centerY);
291
+ power = getPower(centerPoint);
292
+
293
+ direction = getDirection(centerPoint);
294
+ innerPositionX.set(centerPoint.x);
295
+ innerPositionY.set(centerPoint.y);
296
+ const changeEvent = { angle, direction, power };
297
+ settings.onChange?.(changeEvent);
298
+
299
+ // Notify controls if provided
300
+ const controls = getControls();
301
+ if (controls) {
302
+ // Check if it's JoystickControls instance
303
+ if (controls.handleJoystickChange) {
304
+ controls.handleJoystickChange(changeEvent);
305
+ }
306
+ // Check if it's ControlsDirective with joystick getter
307
+ else if (controls.joystick && controls.joystick.handleJoystickChange) {
308
+ controls.joystick.handleJoystickChange(changeEvent);
309
+ }
310
+ }
311
+ }
312
+
313
+ let innerElement;
314
+ let outerElement;
315
+
316
+ if (!settings.outer) {
317
+ outerElement = h(Graphics, {
318
+ draw: (g) => {
319
+ g.circle(0, 0, outerRadius).fill(settings.outerColor);
320
+ },
321
+ alpha: 0.5,
322
+ });
323
+ } else {
324
+ outerElement = h(Sprite, {
325
+ image: settings.outer,
326
+ anchor: { x: 0.5, y: 0.5 },
327
+ scale: settings.outerScale,
328
+ });
329
+ }
330
+
331
+ const innerOptions: any = {
332
+ scale: settings.innerScale,
333
+ x: innerPositionX,
334
+ y: innerPositionY,
335
+ alpha: innerAlpha,
336
+ };
337
+
338
+ if (!settings.inner) {
339
+ innerElement = h(Graphics, {
340
+ draw: (g) => {
341
+ g.circle(0, 0, innerRadius * 2.5).fill(settings.innerColor);
342
+ },
343
+ ...innerOptions,
344
+ });
345
+ } else {
346
+ innerElement = settings.inner
347
+ }
348
+
349
+ return h(
350
+ Container,
351
+ {
352
+ ...opts,
353
+ pointerdown: handleDragStart,
354
+ pointerup: handleDragEnd,
355
+ pointerupoutside: handleDragEnd,
356
+ pointermove: handleDragMove,
357
+ },
358
+ outerElement,
359
+ innerElement,
360
+ );
361
+ }
@@ -13,4 +13,5 @@ export { NineSliceSprite } from './NineSliceSprite'
13
13
  export { type ComponentInstance } from './DisplayObject'
14
14
  export { DOMContainer } from './DOMContainer'
15
15
  export { DOMElement } from './DOMElement'
16
- export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'
16
+ export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'
17
+ export { Joystick, type JoystickSettings, type JoystickChangeEvent } from './Joystick'
@@ -3,14 +3,15 @@ import { Element } from "../engine/reactive";
3
3
  import { ControlsBase, Controls } from "./ControlsBase";
4
4
  import { KeyboardControls } from "./KeyboardControls";
5
5
  import { GamepadControls, GamepadConfig } from "./GamepadControls";
6
+ import { JoystickControls, JoystickConfig } from "./JoystickControls";
6
7
 
7
8
  /**
8
- * Controls directive that coordinates keyboard and gamepad input systems
9
+ * Controls directive that coordinates keyboard, gamepad, and joystick input systems
9
10
  *
10
- * This directive automatically activates both keyboard and gamepad controls when available.
11
+ * This directive automatically activates keyboard, gamepad, and joystick controls when available.
11
12
  * The gamepad is automatically enabled if joypad.js is detected in the environment.
12
13
  *
13
- * Both systems share the same control configuration and can work simultaneously.
14
+ * All systems share the same control configuration and can work simultaneously.
14
15
  *
15
16
  * @example
16
17
  * ```html
@@ -25,10 +26,11 @@ import { GamepadControls, GamepadConfig } from "./GamepadControls";
25
26
  export class ControlsDirective extends Directive {
26
27
  private keyboardControls: KeyboardControls | null = null;
27
28
  private gamepadControls: GamepadControls | null = null;
29
+ private joystickControls: JoystickControls | null = null;
28
30
 
29
31
  /**
30
32
  * Initialize the controls directive
31
- * Sets up keyboard and gamepad controls if available
33
+ * Sets up keyboard, gamepad, and joystick controls if available
32
34
  */
33
35
  onInit(element: Element) {
34
36
  const value = element.props.controls?.value ?? element.props.controls;
@@ -47,6 +49,14 @@ export class ControlsDirective extends Directive {
47
49
  this.gamepadControls.setInputs(value as Controls & { gamepad?: GamepadConfig });
48
50
  this.gamepadControls.start();
49
51
  }
52
+
53
+ // Initialize joystick controls if joystick config is present
54
+ const joystickConfig = (value as Controls & { joystick?: JoystickConfig }).joystick;
55
+ if (joystickConfig !== undefined && joystickConfig.enabled !== false) {
56
+ this.joystickControls = new JoystickControls();
57
+ this.joystickControls.setInputs(value as Controls & { joystick?: JoystickConfig });
58
+ this.joystickControls.start();
59
+ }
50
60
  }
51
61
 
52
62
  /**
@@ -84,6 +94,11 @@ export class ControlsDirective extends Directive {
84
94
  this.gamepadControls.destroy();
85
95
  this.gamepadControls = null;
86
96
  }
97
+
98
+ if (this.joystickControls) {
99
+ this.joystickControls.destroy();
100
+ this.joystickControls = null;
101
+ }
87
102
  }
88
103
 
89
104
  /**
@@ -113,20 +128,24 @@ export class ControlsDirective extends Directive {
113
128
  *
114
129
  * @param controlName - Name of the control
115
130
  * @param isDown - Whether the control is pressed (true) or released (false)
131
+ * @param payload - Optional payload to pass to keyDown/keyUp callbacks (e.g., { power: 0.8 })
116
132
  * @returns Promise that resolves when the action is complete
117
133
  */
118
- async applyControl(controlName: string | number, isDown?: boolean): Promise<void> {
134
+ async applyControl(controlName: string | number, isDown?: boolean, payload?: any): Promise<void> {
119
135
  if (this.keyboardControls) {
120
136
  await this.keyboardControls.applyControl(controlName, isDown);
121
137
  }
122
138
  if (this.gamepadControls) {
123
- await this.gamepadControls.applyControl(controlName, isDown);
139
+ await this.gamepadControls.applyControl(controlName, isDown, payload);
140
+ }
141
+ if (this.joystickControls) {
142
+ await this.joystickControls.applyControl(controlName, isDown, payload);
124
143
  }
125
144
  }
126
145
 
127
146
  /**
128
147
  * Stop listening to inputs
129
- * Stops both keyboard and gamepad input processing
148
+ * Stops keyboard, gamepad, and joystick input processing
130
149
  */
131
150
  stopInputs() {
132
151
  if (this.keyboardControls) {
@@ -135,11 +154,14 @@ export class ControlsDirective extends Directive {
135
154
  if (this.gamepadControls) {
136
155
  this.gamepadControls.stopInputs();
137
156
  }
157
+ if (this.joystickControls) {
158
+ this.joystickControls.stopInputs();
159
+ }
138
160
  }
139
161
 
140
162
  /**
141
163
  * Resume listening to inputs
142
- * Resumes both keyboard and gamepad input processing
164
+ * Resumes keyboard, gamepad, and joystick input processing
143
165
  */
144
166
  listenInputs() {
145
167
  if (this.keyboardControls) {
@@ -148,6 +170,9 @@ export class ControlsDirective extends Directive {
148
170
  if (this.gamepadControls) {
149
171
  this.gamepadControls.listenInputs();
150
172
  }
173
+ if (this.joystickControls) {
174
+ this.joystickControls.listenInputs();
175
+ }
151
176
  }
152
177
 
153
178
  /**
@@ -177,6 +202,15 @@ export class ControlsDirective extends Directive {
177
202
  get gamepad(): GamepadControls | null {
178
203
  return this.gamepadControls;
179
204
  }
205
+
206
+ /**
207
+ * Get the joystick controls instance
208
+ *
209
+ * @returns JoystickControls instance or null
210
+ */
211
+ get joystick(): JoystickControls | null {
212
+ return this.joystickControls;
213
+ }
180
214
  }
181
215
 
182
216
  registerDirective('controls', ControlsDirective);
@@ -112,6 +112,7 @@ export class GamepadControls extends ControlsBase {
112
112
  private joypad: any = null;
113
113
  private connectCallbacks: Array<() => void> = [];
114
114
  private disconnectCallbacks: Array<() => void> = [];
115
+ private currentPower: number = 0;
115
116
 
116
117
  /**
117
118
  * Setup gamepad event listeners
@@ -191,6 +192,7 @@ export class GamepadControls extends ControlsBase {
191
192
  this.gamepadMoving = false;
192
193
  this.gamepadDirections = {};
193
194
  this.gamepadAxisDate = 0;
195
+ this.currentPower = 0;
194
196
 
195
197
  // Update gamepadConnected signal if provided
196
198
  if (this.gamepadConfig.gamepadConnected) {
@@ -243,6 +245,21 @@ export class GamepadControls extends ControlsBase {
243
245
  else if (direction === 'left') direction = axisMapping['left'] || 'left';
244
246
  else if (direction === 'right') direction = axisMapping['right'] || 'right';
245
247
 
248
+ // Calculate power/intensity from axis values
249
+ // Get the first connected gamepad instance
250
+ if (this.joypad && this.joypad.instances) {
251
+ const gamepadInstances = Object.values(this.joypad.instances) as any[];
252
+ if (gamepadInstances.length > 0) {
253
+ const gamepad = gamepadInstances[0];
254
+ // Get axes values (axes 0-1 for left stick, 2-3 for right stick)
255
+ // We'll use the left stick by default (axes 0 and 1)
256
+ const axisX = gamepad.axes?.[0] || 0;
257
+ const axisY = gamepad.axes?.[1] || 0;
258
+ // Calculate power as magnitude of the vector
259
+ this.currentPower = Math.min(1, Math.sqrt(axisX * axisX + axisY * axisY));
260
+ }
261
+ }
262
+
246
263
  // Update active directions
247
264
  this.gamepadDirections = {
248
265
  [direction]: true
@@ -258,7 +275,7 @@ export class GamepadControls extends ControlsBase {
258
275
  }
259
276
  }
260
277
 
261
- // Trigger movement
278
+ // Trigger movement with power
262
279
  this.processGamepadMovement();
263
280
  }
264
281
 
@@ -270,9 +287,20 @@ export class GamepadControls extends ControlsBase {
270
287
  if (!this.gamepadMoving) return;
271
288
  if (this.stop) return;
272
289
 
290
+ // Update current power from gamepad axes if available
291
+ if (this.joypad && this.joypad.instances) {
292
+ const gamepadInstances = Object.values(this.joypad.instances) as any[];
293
+ if (gamepadInstances.length > 0) {
294
+ const gamepad = gamepadInstances[0];
295
+ const axisX = gamepad.axes?.[0] || 0;
296
+ const axisY = gamepad.axes?.[1] || 0;
297
+ this.currentPower = Math.min(1, Math.sqrt(axisX * axisX + axisY * axisY));
298
+ }
299
+ }
300
+
273
301
  for (const direction in this.gamepadDirections) {
274
302
  if (this.gamepadDirections[direction]) {
275
- this.applyControl(direction, true).catch(() => {
303
+ this.applyControl(direction, true, { power: this.currentPower }).catch(() => {
276
304
  // Ignore errors
277
305
  });
278
306
  }
@@ -363,9 +391,10 @@ export class GamepadControls extends ControlsBase {
363
391
  *
364
392
  * @param controlName - Name of the control
365
393
  * @param isDown - Whether the control is pressed (true) or released (false)
394
+ * @param payload - Optional payload to pass to keyDown/keyUp callbacks (e.g., { power: 0.8 })
366
395
  * @returns Promise that resolves when the action is complete
367
396
  */
368
- async applyControl(controlName: string | number, isDown?: boolean): Promise<void> {
397
+ async applyControl(controlName: string | number, isDown?: boolean, payload?: any): Promise<void> {
369
398
  const control = this._controlsOptions[controlName];
370
399
  if (!control) return;
371
400
 
@@ -378,40 +407,40 @@ export class GamepadControls extends ControlsBase {
378
407
  if (isDown === undefined) {
379
408
  // Press and release (simulate button press)
380
409
  if (boundKey.options.keyDown) {
381
- let parameters = boundKey.parameters;
410
+ let parameters = payload ?? boundKey.parameters;
382
411
  if (typeof parameters === "function") {
383
412
  parameters = parameters();
384
413
  }
385
- boundKey.options.keyDown(boundKey);
414
+ boundKey.options.keyDown(boundKey, parameters);
386
415
  }
387
416
  // Release after a short delay (similar to keyboard)
388
417
  return new Promise((resolve) => {
389
418
  setTimeout(() => {
390
419
  if (boundKey.options.keyUp) {
391
- let parameters = boundKey.parameters;
420
+ let parameters = payload ?? boundKey.parameters;
392
421
  if (typeof parameters === "function") {
393
422
  parameters = parameters();
394
423
  }
395
- boundKey.options.keyUp(boundKey);
424
+ boundKey.options.keyUp(boundKey, parameters);
396
425
  }
397
426
  resolve();
398
427
  }, 200);
399
428
  });
400
429
  } else if (isDown) {
401
430
  if (boundKey.options.keyDown) {
402
- let parameters = boundKey.parameters;
431
+ let parameters = payload ?? boundKey.parameters;
403
432
  if (typeof parameters === "function") {
404
433
  parameters = parameters();
405
434
  }
406
- boundKey.options.keyDown(boundKey);
435
+ boundKey.options.keyDown(boundKey, parameters);
407
436
  }
408
437
  } else {
409
438
  if (boundKey.options.keyUp) {
410
- let parameters = boundKey.parameters;
439
+ let parameters = payload ?? boundKey.parameters;
411
440
  if (typeof parameters === "function") {
412
441
  parameters = parameters();
413
442
  }
414
- boundKey.options.keyUp(boundKey);
443
+ boundKey.options.keyUp(boundKey, parameters);
415
444
  }
416
445
  }
417
446
  break;