canvasengine 2.0.0-beta.5 → 2.0.0-beta.50

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 (172) hide show
  1. package/dist/components/Button.d.ts +185 -0
  2. package/dist/components/Button.d.ts.map +1 -0
  3. package/dist/components/Canvas.d.ts +17 -0
  4. package/dist/components/Canvas.d.ts.map +1 -0
  5. package/dist/components/Container.d.ts +86 -0
  6. package/dist/components/Container.d.ts.map +1 -0
  7. package/dist/components/DOMContainer.d.ts +98 -0
  8. package/dist/components/DOMContainer.d.ts.map +1 -0
  9. package/dist/components/DOMElement.d.ts +54 -0
  10. package/dist/components/DOMElement.d.ts.map +1 -0
  11. package/dist/components/DOMSprite.d.ts +127 -0
  12. package/dist/components/DOMSprite.d.ts.map +1 -0
  13. package/dist/components/DisplayObject.d.ts +94 -0
  14. package/dist/components/DisplayObject.d.ts.map +1 -0
  15. package/dist/components/FocusContainer.d.ts +129 -0
  16. package/dist/components/FocusContainer.d.ts.map +1 -0
  17. package/dist/components/Graphic.d.ts +64 -0
  18. package/dist/components/Graphic.d.ts.map +1 -0
  19. package/dist/components/Joystick.d.ts +36 -0
  20. package/dist/components/Joystick.d.ts.map +1 -0
  21. package/dist/components/Mesh.d.ts +208 -0
  22. package/dist/components/Mesh.d.ts.map +1 -0
  23. package/dist/components/NineSliceSprite.d.ts +16 -0
  24. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  25. package/dist/components/ParticleEmitter.d.ts +4 -0
  26. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  27. package/dist/components/Scene.d.ts +2 -0
  28. package/dist/components/Scene.d.ts.map +1 -0
  29. package/dist/components/Sprite.d.ts +242 -0
  30. package/dist/components/Sprite.d.ts.map +1 -0
  31. package/dist/components/Text.d.ts +25 -0
  32. package/dist/components/Text.d.ts.map +1 -0
  33. package/dist/components/TilingSprite.d.ts +17 -0
  34. package/dist/components/TilingSprite.d.ts.map +1 -0
  35. package/dist/components/Video.d.ts +14 -0
  36. package/dist/components/Video.d.ts.map +1 -0
  37. package/dist/components/Viewport.d.ts +121 -0
  38. package/dist/components/Viewport.d.ts.map +1 -0
  39. package/dist/components/index.d.ts +20 -0
  40. package/dist/components/index.d.ts.map +1 -0
  41. package/dist/components/types/DisplayObject.d.ts +106 -0
  42. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  43. package/dist/components/types/MouseEvent.d.ts +4 -0
  44. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  45. package/dist/components/types/Spritesheet.d.ts +248 -0
  46. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  47. package/dist/components/types/index.d.ts +4 -0
  48. package/dist/components/types/index.d.ts.map +1 -0
  49. package/dist/directives/Controls.d.ts +112 -0
  50. package/dist/directives/Controls.d.ts.map +1 -0
  51. package/dist/directives/ControlsBase.d.ts +199 -0
  52. package/dist/directives/ControlsBase.d.ts.map +1 -0
  53. package/dist/directives/Drag.d.ts +69 -0
  54. package/dist/directives/Drag.d.ts.map +1 -0
  55. package/dist/directives/Flash.d.ts +116 -0
  56. package/dist/directives/Flash.d.ts.map +1 -0
  57. package/dist/directives/FocusNavigation.d.ts +52 -0
  58. package/dist/directives/FocusNavigation.d.ts.map +1 -0
  59. package/dist/directives/GamepadControls.d.ts +224 -0
  60. package/dist/directives/GamepadControls.d.ts.map +1 -0
  61. package/dist/directives/JoystickControls.d.ts +171 -0
  62. package/dist/directives/JoystickControls.d.ts.map +1 -0
  63. package/dist/directives/KeyboardControls.d.ts +219 -0
  64. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  65. package/dist/directives/Scheduler.d.ts +35 -0
  66. package/dist/directives/Scheduler.d.ts.map +1 -0
  67. package/dist/directives/Shake.d.ts +98 -0
  68. package/dist/directives/Shake.d.ts.map +1 -0
  69. package/dist/directives/Sound.d.ts +25 -0
  70. package/dist/directives/Sound.d.ts.map +1 -0
  71. package/dist/directives/Transition.d.ts +10 -0
  72. package/dist/directives/Transition.d.ts.map +1 -0
  73. package/dist/directives/ViewportCull.d.ts +11 -0
  74. package/dist/directives/ViewportCull.d.ts.map +1 -0
  75. package/dist/directives/ViewportFollow.d.ts +18 -0
  76. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  77. package/dist/directives/index.d.ts +13 -0
  78. package/dist/directives/index.d.ts.map +1 -0
  79. package/dist/engine/FocusManager.d.ts +174 -0
  80. package/dist/engine/FocusManager.d.ts.map +1 -0
  81. package/dist/engine/animation.d.ts +72 -0
  82. package/dist/engine/animation.d.ts.map +1 -0
  83. package/dist/engine/bootstrap.d.ts +48 -0
  84. package/dist/engine/bootstrap.d.ts.map +1 -0
  85. package/dist/engine/directive.d.ts +13 -0
  86. package/dist/engine/directive.d.ts.map +1 -0
  87. package/dist/engine/reactive.d.ts +134 -0
  88. package/dist/engine/reactive.d.ts.map +1 -0
  89. package/dist/engine/signal.d.ts +71 -0
  90. package/dist/engine/signal.d.ts.map +1 -0
  91. package/dist/engine/trigger.d.ts +54 -0
  92. package/dist/engine/trigger.d.ts.map +1 -0
  93. package/dist/engine/utils.d.ts +89 -0
  94. package/dist/engine/utils.d.ts.map +1 -0
  95. package/dist/hooks/addContext.d.ts +2 -0
  96. package/dist/hooks/addContext.d.ts.map +1 -0
  97. package/dist/hooks/useFocus.d.ts +60 -0
  98. package/dist/hooks/useFocus.d.ts.map +1 -0
  99. package/dist/hooks/useProps.d.ts +42 -0
  100. package/dist/hooks/useProps.d.ts.map +1 -0
  101. package/dist/hooks/useRef.d.ts +4 -0
  102. package/dist/hooks/useRef.d.ts.map +1 -0
  103. package/dist/index-DaGekQUW.js +2218 -0
  104. package/dist/index-DaGekQUW.js.map +1 -0
  105. package/dist/index.d.ts +19 -1099
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.global.js +5 -0
  108. package/dist/index.global.js.map +1 -0
  109. package/dist/index.js +11749 -2901
  110. package/dist/index.js.map +1 -1
  111. package/dist/utils/Ease.d.ts +17 -0
  112. package/dist/utils/Ease.d.ts.map +1 -0
  113. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  114. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  115. package/dist/utils/RadialGradient.d.ts +57 -0
  116. package/dist/utils/RadialGradient.d.ts.map +1 -0
  117. package/dist/utils/functions.d.ts +2 -0
  118. package/dist/utils/functions.d.ts.map +1 -0
  119. package/dist/utils/tabindex.d.ts +16 -0
  120. package/dist/utils/tabindex.d.ts.map +1 -0
  121. package/package.json +13 -7
  122. package/src/components/Button.ts +399 -0
  123. package/src/components/Canvas.ts +62 -46
  124. package/src/components/Container.ts +21 -2
  125. package/src/components/DOMContainer.ts +379 -0
  126. package/src/components/DOMElement.ts +556 -0
  127. package/src/components/DOMSprite.ts +1040 -0
  128. package/src/components/DisplayObject.ts +392 -201
  129. package/src/components/FocusContainer.ts +368 -0
  130. package/src/components/Graphic.ts +227 -66
  131. package/src/components/Joystick.ts +363 -0
  132. package/src/components/Mesh.ts +222 -0
  133. package/src/components/NineSliceSprite.ts +4 -1
  134. package/src/components/ParticleEmitter.ts +12 -8
  135. package/src/components/Sprite.ts +297 -31
  136. package/src/components/Text.ts +125 -18
  137. package/src/components/Video.ts +2 -2
  138. package/src/components/Viewport.ts +118 -63
  139. package/src/components/index.ts +9 -2
  140. package/src/components/types/DisplayObject.ts +41 -4
  141. package/src/components/types/Spritesheet.ts +0 -118
  142. package/src/directives/Controls.ts +254 -0
  143. package/src/directives/ControlsBase.ts +267 -0
  144. package/src/directives/Drag.ts +357 -52
  145. package/src/directives/Flash.ts +419 -0
  146. package/src/directives/FocusNavigation.ts +113 -0
  147. package/src/directives/GamepadControls.ts +537 -0
  148. package/src/directives/JoystickControls.ts +396 -0
  149. package/src/directives/KeyboardControls.ts +85 -430
  150. package/src/directives/Scheduler.ts +12 -4
  151. package/src/directives/Shake.ts +298 -0
  152. package/src/directives/Sound.ts +94 -31
  153. package/src/directives/ViewportFollow.ts +40 -9
  154. package/src/directives/index.ts +12 -6
  155. package/src/engine/FocusManager.ts +510 -0
  156. package/src/engine/animation.ts +175 -21
  157. package/src/engine/bootstrap.ts +93 -3
  158. package/src/engine/directive.ts +4 -4
  159. package/src/engine/reactive.ts +901 -161
  160. package/src/engine/signal.ts +113 -25
  161. package/src/engine/trigger.ts +34 -7
  162. package/src/engine/utils.ts +19 -3
  163. package/src/hooks/useFocus.ts +91 -0
  164. package/src/hooks/useProps.ts +1 -1
  165. package/src/index.ts +8 -2
  166. package/src/types/pixi-cull.d.ts +7 -0
  167. package/src/utils/GlobalAssetLoader.ts +257 -0
  168. package/src/utils/functions.ts +7 -0
  169. package/src/utils/tabindex.ts +70 -0
  170. package/testing/index.ts +35 -4
  171. package/tsconfig.json +18 -0
  172. package/vite.config.ts +39 -0
@@ -208,124 +208,6 @@ export interface TexturesOptions extends TextureOptions, TransformOptions {
208
208
  }
209
209
 
210
210
  export interface SpritesheetOptions extends TransformOptions, TextureOptions {
211
- /**
212
- * Object containing all animations.
213
- * The key to the object is the name of the animation. The value is a two-dimensional array
214
- *
215
- * ```ts
216
- * textures: {
217
- * myanim: {
218
- * animations: [
219
- * [ { time: 0, frameX: 0, frameY: 0 } ]
220
- * ]
221
- * }
222
- * }
223
- * ```
224
- *
225
- * The first array represents an animation group. You can put several of them together to create an animation cluster. For example, several explosions with the same spritesheet
226
- * The second array represents the animation itself which will animate over time. The object indicates, over a period of time (in frame), which part of the spritesheet will be taken (`frameX`, `frameY`)
227
- *
228
- * Here are the properties:
229
- *
230
- * * `time`: Time in frame
231
- * * `frameX`: Retrieve a frame from the spritesheet on the X-axis
232
- * * `frameY`: Retrieve a frame from the spritesheet on the Y-axis
233
- * * `opacity`
234
- * * `pivot`
235
- * * `anchor`
236
- * * `rotation`
237
- * * `angle`
238
- * * `scale`
239
- * * `skew`
240
- * * `x`
241
- * * `y`
242
- * * `visible`
243
- * * `sound`: The sound that will be played during the frame
244
- *
245
- * ---
246
- * **Extract Animation of Spritesheet**
247
- *
248
- * Sometimes the animation is part of the image
249
- *
250
- * ```ts
251
- * textures: {
252
- * myanim: {
253
- * rectWidth: 64,
254
- * rectHeight: 64,
255
- * framesWidth: 10,
256
- * framesHeight: 2,
257
- * offset: {x: 0, y: 230},
258
- * sound: 'my-sound-id', // You can put a sound just for the animation
259
- * animations: [
260
- * [ { time: 0, frameX: 0, frameY: 0 } ]
261
- * ]
262
- * }
263
- * }
264
- * ```
265
- *
266
- * Above, we can specify which part we want to recover
267
- *
268
- * 1. We go to the position {0, 230} of the image (`offset`)
269
- * 2. We recover cells of 64px (`rectWidth` and `rectHeight`)
270
- * 3. And we get 20 cells (10 on the width, 2 on the height) (`frameX` and `frameY`)
271
- *
272
- * ---
273
- *
274
- * **Advanced**
275
- *
276
- * You can create an animation that will be linked to a data. For example, different animation according to a direction of the character.
277
- *
278
- * Full example:
279
- *
280
- * ```ts
281
- * import { Spritesheet, Animation, Direction } from '@rpgjs/client'
282
- *
283
- * @Spritesheet({
284
- * id: 'chest',
285
- * image: require('./assets/chest.png'),
286
- * width: 124,
287
- * height: 61,
288
- * framesHeight: 2,
289
- * framesWidth: 4,
290
- * textures: {
291
- * [Animation.Stand]: {
292
- * animations: direction => [[ {time: 0, frameX: 3, frameY: direction == Direction.Up ? 0 : 1 } ]]
293
- * }
294
- * })
295
- * })
296
- * export class Chest { }
297
- * ```
298
- *
299
- * > It is important to know that `Animation.Stand` animation is called if it exists. it only works in the case of an event that doesn't move. The direction is then sent
300
- *
301
- * As you can see, the property contains a function that returns the array for the animation. Here, it is the direction but the parameters depend on the call of the animation. Example:
302
- *
303
- * ```ts
304
- * import { Spritesheet, Animation, Direction, RpgSprite, ISpriteCharacter } from '@rpgjs/client'
305
- *
306
- * @Spritesheet({
307
- * id: 'chest',
308
- * image: require('./assets/chest.png'),
309
- * width: 124,
310
- * height: 61,
311
- * framesHeight: 2,
312
- * framesWidth: 4,
313
- * textures: {
314
- * [Animation.Stand]: {
315
- * animations: str => [[ {time: 0, frameX: 3, frameY: str == 'hello' ? 0 : 1 } ]]
316
- * }
317
- * }
318
- * })
319
- * export class Chest implements ISpriteCharacter {
320
- * onCharacterStand(sprite: RpgSprite) {
321
- * sprite.animation.play(Animation.Stand, ['hello'])
322
- * }
323
- * }
324
- * ```
325
- *
326
- * @prop { { [animName: string]: { animations: Array<Array<FrameOptions>> | Function, ...other } } } [textures]
327
- * @memberof Spritesheet
328
- * */
329
211
  textures?: {
330
212
  [animationName: string]: Partial<TexturesOptions> & Pick<TexturesOptions, 'animations'>
331
213
  }
@@ -0,0 +1,254 @@
1
+ import { Directive, registerDirective } from "../engine/directive";
2
+ import { Element, isElementFrozen } from "../engine/reactive";
3
+ import { ControlsBase, Controls } from "./ControlsBase";
4
+ import { KeyboardControls } from "./KeyboardControls";
5
+ import { GamepadControls, GamepadConfig } from "./GamepadControls";
6
+ import { JoystickControls, JoystickConfig } from "./JoystickControls";
7
+ import { Signal, isSignal } from "@signe/reactive";
8
+ import { Subscription } from "rxjs";
9
+
10
+ /**
11
+ * Controls directive that coordinates keyboard, gamepad, and joystick input systems
12
+ *
13
+ * This directive automatically activates keyboard, gamepad, and joystick controls when available.
14
+ * The gamepad is automatically enabled if joypad.js is detected in the environment.
15
+ *
16
+ * All systems share the same control configuration and can work simultaneously.
17
+ *
18
+ * @example
19
+ * ```html
20
+ * <Sprite
21
+ * image="path/to/image.png"
22
+ * controls={controlsConfig}
23
+ * x={x}
24
+ * y={y}
25
+ * />
26
+ * ```
27
+ */
28
+ export class ControlsDirective extends Directive {
29
+ private keyboardControls: KeyboardControls | null = null;
30
+ private gamepadControls: GamepadControls | null = null;
31
+ private joystickControls: JoystickControls | null = null;
32
+ private freezeSubscription: Subscription | null = null;
33
+ private element: Element | null = null;
34
+
35
+ /**
36
+ * Initialize the controls directive
37
+ * Sets up keyboard, gamepad, and joystick controls if available
38
+ */
39
+ onInit(element: Element<any>) {
40
+ this.element = element;
41
+ const value = element.props.controls?.value ?? element.props.controls;
42
+ if (!value) return;
43
+
44
+ // Initialize keyboard controls (always available)
45
+ this.keyboardControls = new KeyboardControls();
46
+ this.keyboardControls.setInputs(value as Controls);
47
+ this.keyboardControls.start();
48
+
49
+ // Initialize gamepad controls if gamepad config is present
50
+ // GamepadControls will handle joypad.js availability internally
51
+ const gamepadConfig = (value as Controls & { gamepad?: GamepadConfig }).gamepad;
52
+ if (gamepadConfig !== undefined && gamepadConfig.enabled !== false) {
53
+ this.gamepadControls = new GamepadControls();
54
+ this.gamepadControls.setInputs(value as Controls & { gamepad?: GamepadConfig });
55
+ this.gamepadControls.start();
56
+ }
57
+
58
+ // Initialize joystick controls if joystick config is present
59
+ const joystickConfig = (value as Controls & { joystick?: JoystickConfig }).joystick;
60
+ if (joystickConfig !== undefined && joystickConfig.enabled !== false) {
61
+ this.joystickControls = new JoystickControls();
62
+ this.joystickControls.setInputs(value as Controls & { joystick?: JoystickConfig });
63
+ this.joystickControls.start();
64
+ }
65
+
66
+ // Check initial freeze state
67
+ if (isElementFrozen(element)) {
68
+ this.stopInputs();
69
+ }
70
+
71
+ // Subscribe to freeze prop if it's a signal
72
+ const freezeProp = element.propObservables?.freeze ?? element.props?.freeze;
73
+ if (isSignal(freezeProp)) {
74
+ this.freezeSubscription = ((freezeProp as Signal<boolean>).observable as any).subscribe((isFrozen) => {
75
+ if (isFrozen) {
76
+ this.stopInputs();
77
+ } else {
78
+ this.listenInputs();
79
+ }
80
+ });
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Mount hook (no specific action needed)
86
+ */
87
+ onMount(element: Element<any>) { }
88
+
89
+ /**
90
+ * Update controls configuration
91
+ * Updates both keyboard and gamepad controls
92
+ */
93
+ onUpdate(props: any, element: Element<any>) {
94
+ const value = props.controls?.value ?? props.controls;
95
+ if (value) {
96
+ if (this.keyboardControls) {
97
+ this.keyboardControls.setInputs(value as Controls);
98
+ }
99
+
100
+ if (this.gamepadControls) {
101
+ this.gamepadControls.setInputs(value as Controls & { gamepad?: GamepadConfig });
102
+ }
103
+ }
104
+
105
+ // Handle freeze prop update
106
+ if (props.freeze !== undefined && this.element) {
107
+ if (isElementFrozen(this.element)) {
108
+ this.stopInputs();
109
+ } else {
110
+ this.listenInputs();
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Cleanup and destroy all control systems
117
+ */
118
+ onDestroy(element: Element<any>) {
119
+ if (this.freezeSubscription) {
120
+ this.freezeSubscription.unsubscribe();
121
+ this.freezeSubscription = null;
122
+ }
123
+
124
+ if (this.keyboardControls) {
125
+ this.keyboardControls.destroy();
126
+ this.keyboardControls = null;
127
+ }
128
+
129
+ if (this.gamepadControls) {
130
+ this.gamepadControls.destroy();
131
+ this.gamepadControls = null;
132
+ }
133
+
134
+ if (this.joystickControls) {
135
+ this.joystickControls.destroy();
136
+ this.joystickControls = null;
137
+ }
138
+
139
+ this.element = null;
140
+ }
141
+
142
+ /**
143
+ * Get a control by input name
144
+ * Delegates to keyboard controls (primary system)
145
+ *
146
+ * @param inputName - Name of the input/key
147
+ * @returns BoundKey if found, undefined otherwise
148
+ */
149
+ getControl(inputName: string) {
150
+ return this.keyboardControls?.getControl(inputName);
151
+ }
152
+
153
+ /**
154
+ * Get all bound controls
155
+ * Delegates to keyboard controls (primary system)
156
+ *
157
+ * @returns Object mapping input names to BoundKey objects
158
+ */
159
+ getControls() {
160
+ return this.keyboardControls?.getControls() || {};
161
+ }
162
+
163
+ /**
164
+ * Apply a control action programmatically
165
+ * Applies to both keyboard and gamepad if available
166
+ *
167
+ * @param controlName - Name of the control
168
+ * @param isDown - Whether the control is pressed (true) or released (false)
169
+ * @param payload - Optional payload to pass to keyDown/keyUp callbacks (e.g., { power: 0.8 })
170
+ * @returns Promise that resolves when the action is complete
171
+ */
172
+ async applyControl(controlName: string | number, isDown?: boolean, payload?: any): Promise<void> {
173
+ if (this.keyboardControls) {
174
+ await this.keyboardControls.applyControl(controlName, isDown);
175
+ }
176
+ if (this.gamepadControls) {
177
+ await this.gamepadControls.applyControl(controlName, isDown, payload);
178
+ }
179
+ if (this.joystickControls) {
180
+ await this.joystickControls.applyControl(controlName, isDown, payload);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Stop listening to inputs
186
+ * Stops keyboard, gamepad, and joystick input processing
187
+ */
188
+ stopInputs() {
189
+ if (this.keyboardControls) {
190
+ this.keyboardControls.stopInputs();
191
+ }
192
+ if (this.gamepadControls) {
193
+ this.gamepadControls.stopInputs();
194
+ }
195
+ if (this.joystickControls) {
196
+ this.joystickControls.stopInputs();
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Resume listening to inputs
202
+ * Resumes keyboard, gamepad, and joystick input processing
203
+ */
204
+ listenInputs() {
205
+ if (this.keyboardControls) {
206
+ this.keyboardControls.listenInputs();
207
+ }
208
+ if (this.gamepadControls) {
209
+ this.gamepadControls.listenInputs();
210
+ }
211
+ if (this.joystickControls) {
212
+ this.joystickControls.listenInputs();
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Get the current controls configuration
218
+ * Returns keyboard controls options (both systems share the same config)
219
+ *
220
+ * @returns The controls options object
221
+ */
222
+ get options(): Controls {
223
+ return this.keyboardControls?.options || {};
224
+ }
225
+
226
+ /**
227
+ * Get the keyboard controls instance
228
+ *
229
+ * @returns KeyboardControls instance or null
230
+ */
231
+ get keyboard(): KeyboardControls | null {
232
+ return this.keyboardControls;
233
+ }
234
+
235
+ /**
236
+ * Get the gamepad controls instance
237
+ *
238
+ * @returns GamepadControls instance or null
239
+ */
240
+ get gamepad(): GamepadControls | null {
241
+ return this.gamepadControls;
242
+ }
243
+
244
+ /**
245
+ * Get the joystick controls instance
246
+ *
247
+ * @returns JoystickControls instance or null
248
+ */
249
+ get joystick(): JoystickControls | null {
250
+ return this.joystickControls;
251
+ }
252
+ }
253
+
254
+ registerDirective('controls', ControlsDirective);
@@ -0,0 +1,267 @@
1
+ import { fps2ms } from "../engine/utils";
2
+
3
+ export interface ControlOptions {
4
+ repeat?: boolean;
5
+ bind: string | string[];
6
+ keyUp?: Function;
7
+ keyDown?: Function;
8
+ throttle?: number;
9
+ delay?: number | {
10
+ duration: number;
11
+ otherControls?: (string)[];
12
+ };
13
+ }
14
+
15
+ export interface Controls {
16
+ [controlName: string]: ControlOptions;
17
+ }
18
+
19
+ export type BoundKey = { actionName: string, options: ControlOptions, parameters?: any };
20
+
21
+ /**
22
+ * Abstract base class for control systems (keyboard, gamepad, etc.)
23
+ *
24
+ * This class provides common functionality shared across all control implementations:
25
+ * - Input binding and management
26
+ * - Control configuration
27
+ * - Input state management
28
+ * - Common methods for querying and triggering controls
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * class MyControls extends ControlsBase {
33
+ * protected setupListeners() {
34
+ * // Setup specific input listeners
35
+ * }
36
+ *
37
+ * protected cleanup() {
38
+ * // Cleanup specific resources
39
+ * }
40
+ *
41
+ * protected preStep() {
42
+ * // Process inputs each frame
43
+ * }
44
+ * }
45
+ * ```
46
+ */
47
+ export abstract class ControlsBase {
48
+ protected boundKeys: {
49
+ [keyName: string]: BoundKey
50
+ } = {}
51
+ protected stop: boolean = false
52
+ protected _controlsOptions: Controls = {}
53
+ protected interval: any
54
+ protected serverFps: number = 60
55
+
56
+ /**
57
+ * Setup input listeners specific to this control implementation
58
+ * Must be implemented by subclasses
59
+ */
60
+ protected abstract setupListeners(): void;
61
+
62
+ /**
63
+ * Cleanup resources specific to this control implementation
64
+ * Must be implemented by subclasses
65
+ */
66
+ protected abstract cleanup(): void;
67
+
68
+ /**
69
+ * Process inputs each step/frame
70
+ * Must be implemented by subclasses
71
+ */
72
+ protected abstract preStep(): void;
73
+
74
+ /**
75
+ * Start the control processing loop
76
+ * Initializes listeners and starts the interval
77
+ */
78
+ start() {
79
+ this.setupListeners();
80
+ this.interval = setInterval(() => {
81
+ this.preStep()
82
+ }, fps2ms(this.serverFps ?? 60))
83
+ }
84
+
85
+ /**
86
+ * Stop the control processing and cleanup resources
87
+ */
88
+ destroy() {
89
+ if (this.interval) {
90
+ clearInterval(this.interval)
91
+ }
92
+ this.cleanup();
93
+ }
94
+
95
+ /**
96
+ * Bind a key/input to a control action
97
+ *
98
+ * @param keys - Single key or array of keys to bind
99
+ * @param actionName - Name of the control action
100
+ * @param options - Control options (repeat, keyDown, keyUp, etc.)
101
+ * @param parameters - Optional parameters to pass to the control callbacks
102
+ */
103
+ protected bindKey(keys: string | string[], actionName: string, options: ControlOptions, parameters?: object) {
104
+ if (!Array.isArray(keys)) keys = [keys]
105
+ const keyOptions = Object.assign({
106
+ repeat: false
107
+ }, options);
108
+ keys.forEach(keyName => {
109
+ this.boundKeys[keyName] = { actionName, options: keyOptions, parameters }
110
+ })
111
+ }
112
+
113
+ /**
114
+ * Apply an input action for a bound key
115
+ * Can be overridden by subclasses for custom behavior
116
+ *
117
+ * @param keyName - Name of the key/input to process
118
+ */
119
+ protected applyInput(keyName: string) {
120
+ const boundKey = this.boundKeys[keyName];
121
+ if (!boundKey) return;
122
+
123
+ const { repeat, keyDown } = boundKey.options;
124
+ // Default implementation - subclasses may override for state tracking
125
+ if (keyDown) {
126
+ let parameters = boundKey.parameters;
127
+ if (typeof parameters === "function") {
128
+ parameters = parameters();
129
+ }
130
+ keyDown(boundKey);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get a control by input name
136
+ *
137
+ * @param inputName - Name of the input/key
138
+ * @returns BoundKey if found, undefined otherwise
139
+ * @example
140
+ * ```ts
141
+ * const control = controls.getControl('up');
142
+ * if (control) {
143
+ * console.log(control.actionName); // 'up'
144
+ * }
145
+ * ```
146
+ */
147
+ getControl(inputName: string): BoundKey | undefined {
148
+ return this.boundKeys[inputName]
149
+ }
150
+
151
+ /**
152
+ * Get all bound controls
153
+ *
154
+ * @returns Object mapping input names to BoundKey objects
155
+ * @example
156
+ * ```ts
157
+ * const allControls = controls.getControls();
158
+ * console.log(Object.keys(allControls)); // ['up', 'down', 'left', 'right', ...]
159
+ * ```
160
+ */
161
+ getControls(): { [key: string]: BoundKey } {
162
+ return this.boundKeys
163
+ }
164
+
165
+ /**
166
+ * Apply a control action programmatically
167
+ *
168
+ * Must be implemented by subclasses to provide input-specific behavior
169
+ *
170
+ * @param controlName - Name or identifier of the control
171
+ * @param isDown - Whether the control is pressed down (true) or released (false)
172
+ * @returns Promise that resolves when the control action is complete
173
+ * @example
174
+ * ```ts
175
+ * // Press a control
176
+ * await controls.applyControl('action', true);
177
+ *
178
+ * // Release a control
179
+ * await controls.applyControl('action', false);
180
+ *
181
+ * // Press and release (default)
182
+ * await controls.applyControl('action');
183
+ * ```
184
+ */
185
+ abstract applyControl(controlName: string | number, isDown?: boolean): Promise<void>;
186
+
187
+ /**
188
+ * Stop listening to inputs
189
+ * Input events will be ignored until listenInputs() is called
190
+ *
191
+ * @example
192
+ * ```ts
193
+ * controls.stopInputs();
194
+ * // ... later
195
+ * controls.listenInputs();
196
+ * ```
197
+ */
198
+ stopInputs() {
199
+ this.stop = true
200
+ }
201
+
202
+ /**
203
+ * Resume listening to inputs after stopInputs() was called
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * controls.stopInputs();
208
+ * // ... later
209
+ * controls.listenInputs();
210
+ * ```
211
+ */
212
+ listenInputs() {
213
+ this.stop = false
214
+ }
215
+
216
+ /**
217
+ * Configure controls with input mappings
218
+ *
219
+ * This method sets up the binding between input keys/buttons and control actions.
220
+ * It clears existing bindings and creates new ones based on the provided configuration.
221
+ *
222
+ * @param inputs - Control configuration object
223
+ * @example
224
+ * ```ts
225
+ * controls.setInputs({
226
+ * up: {
227
+ * repeat: true,
228
+ * bind: 'up',
229
+ * keyDown() {
230
+ * console.log('Up pressed');
231
+ * }
232
+ * },
233
+ * action: {
234
+ * bind: ['space', 'enter'],
235
+ * keyDown() {
236
+ * console.log('Action triggered');
237
+ * }
238
+ * }
239
+ * });
240
+ * ```
241
+ */
242
+ setInputs(inputs: Controls) {
243
+ if (!inputs) return
244
+ this.boundKeys = {}
245
+ for (let control in inputs) {
246
+ const option = inputs[control]
247
+ const { bind } = option
248
+ let inputsKey: any = bind
249
+ if (!Array.isArray(inputsKey)) {
250
+ inputsKey = [bind]
251
+ }
252
+ for (let input of inputsKey) {
253
+ this.bindKey(input, control, option)
254
+ }
255
+ }
256
+ this._controlsOptions = inputs
257
+ }
258
+
259
+ /**
260
+ * Get the current controls configuration
261
+ *
262
+ * @returns The controls options object
263
+ */
264
+ get options(): Controls {
265
+ return this._controlsOptions
266
+ }
267
+ }