canvasengine 2.0.0-beta.33 → 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.
@@ -0,0 +1,266 @@
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
+ delay?: number | {
9
+ duration: number;
10
+ otherControls?: (string)[];
11
+ };
12
+ }
13
+
14
+ export interface Controls {
15
+ [controlName: string]: ControlOptions;
16
+ }
17
+
18
+ export type BoundKey = { actionName: string, options: ControlOptions, parameters?: any };
19
+
20
+ /**
21
+ * Abstract base class for control systems (keyboard, gamepad, etc.)
22
+ *
23
+ * This class provides common functionality shared across all control implementations:
24
+ * - Input binding and management
25
+ * - Control configuration
26
+ * - Input state management
27
+ * - Common methods for querying and triggering controls
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * class MyControls extends ControlsBase {
32
+ * protected setupListeners() {
33
+ * // Setup specific input listeners
34
+ * }
35
+ *
36
+ * protected cleanup() {
37
+ * // Cleanup specific resources
38
+ * }
39
+ *
40
+ * protected preStep() {
41
+ * // Process inputs each frame
42
+ * }
43
+ * }
44
+ * ```
45
+ */
46
+ export abstract class ControlsBase {
47
+ protected boundKeys: {
48
+ [keyName: string]: BoundKey
49
+ } = {}
50
+ protected stop: boolean = false
51
+ protected _controlsOptions: Controls = {}
52
+ protected interval: any
53
+ protected serverFps: number = 60
54
+
55
+ /**
56
+ * Setup input listeners specific to this control implementation
57
+ * Must be implemented by subclasses
58
+ */
59
+ protected abstract setupListeners(): void;
60
+
61
+ /**
62
+ * Cleanup resources specific to this control implementation
63
+ * Must be implemented by subclasses
64
+ */
65
+ protected abstract cleanup(): void;
66
+
67
+ /**
68
+ * Process inputs each step/frame
69
+ * Must be implemented by subclasses
70
+ */
71
+ protected abstract preStep(): void;
72
+
73
+ /**
74
+ * Start the control processing loop
75
+ * Initializes listeners and starts the interval
76
+ */
77
+ start() {
78
+ this.setupListeners();
79
+ this.interval = setInterval(() => {
80
+ this.preStep()
81
+ }, fps2ms(this.serverFps ?? 60))
82
+ }
83
+
84
+ /**
85
+ * Stop the control processing and cleanup resources
86
+ */
87
+ destroy() {
88
+ if (this.interval) {
89
+ clearInterval(this.interval)
90
+ }
91
+ this.cleanup();
92
+ }
93
+
94
+ /**
95
+ * Bind a key/input to a control action
96
+ *
97
+ * @param keys - Single key or array of keys to bind
98
+ * @param actionName - Name of the control action
99
+ * @param options - Control options (repeat, keyDown, keyUp, etc.)
100
+ * @param parameters - Optional parameters to pass to the control callbacks
101
+ */
102
+ protected bindKey(keys: string | string[], actionName: string, options: ControlOptions, parameters?: object) {
103
+ if (!Array.isArray(keys)) keys = [keys]
104
+ const keyOptions = Object.assign({
105
+ repeat: false
106
+ }, options);
107
+ keys.forEach(keyName => {
108
+ this.boundKeys[keyName] = { actionName, options: keyOptions, parameters }
109
+ })
110
+ }
111
+
112
+ /**
113
+ * Apply an input action for a bound key
114
+ * Can be overridden by subclasses for custom behavior
115
+ *
116
+ * @param keyName - Name of the key/input to process
117
+ */
118
+ protected applyInput(keyName: string) {
119
+ const boundKey = this.boundKeys[keyName];
120
+ if (!boundKey) return;
121
+
122
+ const { repeat, keyDown } = boundKey.options;
123
+ // Default implementation - subclasses may override for state tracking
124
+ if (keyDown) {
125
+ let parameters = boundKey.parameters;
126
+ if (typeof parameters === "function") {
127
+ parameters = parameters();
128
+ }
129
+ keyDown(boundKey);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Get a control by input name
135
+ *
136
+ * @param inputName - Name of the input/key
137
+ * @returns BoundKey if found, undefined otherwise
138
+ * @example
139
+ * ```ts
140
+ * const control = controls.getControl('up');
141
+ * if (control) {
142
+ * console.log(control.actionName); // 'up'
143
+ * }
144
+ * ```
145
+ */
146
+ getControl(inputName: string): BoundKey | undefined {
147
+ return this.boundKeys[inputName]
148
+ }
149
+
150
+ /**
151
+ * Get all bound controls
152
+ *
153
+ * @returns Object mapping input names to BoundKey objects
154
+ * @example
155
+ * ```ts
156
+ * const allControls = controls.getControls();
157
+ * console.log(Object.keys(allControls)); // ['up', 'down', 'left', 'right', ...]
158
+ * ```
159
+ */
160
+ getControls(): { [key: string]: BoundKey } {
161
+ return this.boundKeys
162
+ }
163
+
164
+ /**
165
+ * Apply a control action programmatically
166
+ *
167
+ * Must be implemented by subclasses to provide input-specific behavior
168
+ *
169
+ * @param controlName - Name or identifier of the control
170
+ * @param isDown - Whether the control is pressed down (true) or released (false)
171
+ * @returns Promise that resolves when the control action is complete
172
+ * @example
173
+ * ```ts
174
+ * // Press a control
175
+ * await controls.applyControl('action', true);
176
+ *
177
+ * // Release a control
178
+ * await controls.applyControl('action', false);
179
+ *
180
+ * // Press and release (default)
181
+ * await controls.applyControl('action');
182
+ * ```
183
+ */
184
+ abstract applyControl(controlName: string | number, isDown?: boolean): Promise<void>;
185
+
186
+ /**
187
+ * Stop listening to inputs
188
+ * Input events will be ignored until listenInputs() is called
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * controls.stopInputs();
193
+ * // ... later
194
+ * controls.listenInputs();
195
+ * ```
196
+ */
197
+ stopInputs() {
198
+ this.stop = true
199
+ }
200
+
201
+ /**
202
+ * Resume listening to inputs after stopInputs() was called
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * controls.stopInputs();
207
+ * // ... later
208
+ * controls.listenInputs();
209
+ * ```
210
+ */
211
+ listenInputs() {
212
+ this.stop = false
213
+ }
214
+
215
+ /**
216
+ * Configure controls with input mappings
217
+ *
218
+ * This method sets up the binding between input keys/buttons and control actions.
219
+ * It clears existing bindings and creates new ones based on the provided configuration.
220
+ *
221
+ * @param inputs - Control configuration object
222
+ * @example
223
+ * ```ts
224
+ * controls.setInputs({
225
+ * up: {
226
+ * repeat: true,
227
+ * bind: 'up',
228
+ * keyDown() {
229
+ * console.log('Up pressed');
230
+ * }
231
+ * },
232
+ * action: {
233
+ * bind: ['space', 'enter'],
234
+ * keyDown() {
235
+ * console.log('Action triggered');
236
+ * }
237
+ * }
238
+ * });
239
+ * ```
240
+ */
241
+ setInputs(inputs: Controls) {
242
+ if (!inputs) return
243
+ this.boundKeys = {}
244
+ for (let control in inputs) {
245
+ const option = inputs[control]
246
+ const { bind } = option
247
+ let inputsKey: any = bind
248
+ if (!Array.isArray(inputsKey)) {
249
+ inputsKey = [bind]
250
+ }
251
+ for (let input of inputsKey) {
252
+ this.bindKey(input, control, option)
253
+ }
254
+ }
255
+ this._controlsOptions = inputs
256
+ }
257
+
258
+ /**
259
+ * Get the current controls configuration
260
+ *
261
+ * @returns The controls options object
262
+ */
263
+ get options(): Controls {
264
+ return this._controlsOptions
265
+ }
266
+ }