@skewedaspect/sage 0.3.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 (86) hide show
  1. package/LICENSE +21 -0
  2. package/Readme.md +53 -0
  3. package/dist/classes/bindings/toggle.d.ts +122 -0
  4. package/dist/classes/bindings/trigger.d.ts +79 -0
  5. package/dist/classes/bindings/value.d.ts +104 -0
  6. package/dist/classes/entity.d.ts +83 -0
  7. package/dist/classes/eventBus.d.ts +94 -0
  8. package/dist/classes/gameEngine.d.ts +57 -0
  9. package/dist/classes/input/gamepad.d.ts +94 -0
  10. package/dist/classes/input/keyboard.d.ts +66 -0
  11. package/dist/classes/input/mouse.d.ts +80 -0
  12. package/dist/classes/input/readers/gamepad.d.ts +77 -0
  13. package/dist/classes/input/readers/keyboard.d.ts +60 -0
  14. package/dist/classes/input/readers/mouse.d.ts +45 -0
  15. package/dist/classes/loggers/consoleBackend.d.ts +29 -0
  16. package/dist/classes/loggers/nullBackend.d.ts +14 -0
  17. package/dist/engines/scene.d.ts +11 -0
  18. package/dist/interfaces/action.d.ts +20 -0
  19. package/dist/interfaces/binding.d.ts +144 -0
  20. package/dist/interfaces/entity.d.ts +9 -0
  21. package/dist/interfaces/game.d.ts +26 -0
  22. package/dist/interfaces/input.d.ts +181 -0
  23. package/dist/interfaces/logger.d.ts +88 -0
  24. package/dist/managers/binding.d.ts +185 -0
  25. package/dist/managers/entity.d.ts +70 -0
  26. package/dist/managers/game.d.ts +20 -0
  27. package/dist/managers/input.d.ts +56 -0
  28. package/dist/managers/level.d.ts +55 -0
  29. package/dist/sage.d.ts +20 -0
  30. package/dist/sage.es.js +2208 -0
  31. package/dist/sage.es.js.map +1 -0
  32. package/dist/sage.umd.js +2 -0
  33. package/dist/sage.umd.js.map +1 -0
  34. package/dist/utils/capabilities.d.ts +2 -0
  35. package/dist/utils/graphics.d.ts +10 -0
  36. package/dist/utils/logger.d.ts +66 -0
  37. package/dist/utils/physics.d.ts +2 -0
  38. package/dist/utils/version.d.ts +5 -0
  39. package/docs/architecture.md +129 -0
  40. package/docs/behaviors.md +706 -0
  41. package/docs/binding_system.md +820 -0
  42. package/docs/design/input.md +86 -0
  43. package/docs/entity_system.md +538 -0
  44. package/docs/eventbus.md +225 -0
  45. package/docs/getting_started.md +264 -0
  46. package/docs/images/sage_logo.png +0 -0
  47. package/docs/images/sage_logo_shape.png +0 -0
  48. package/docs/overview.md +38 -0
  49. package/docs/physics_system.md +686 -0
  50. package/docs/scene_system.md +513 -0
  51. package/package.json +69 -0
  52. package/src/classes/bindings/toggle.ts +261 -0
  53. package/src/classes/bindings/trigger.ts +211 -0
  54. package/src/classes/bindings/value.ts +227 -0
  55. package/src/classes/entity.ts +256 -0
  56. package/src/classes/eventBus.ts +259 -0
  57. package/src/classes/gameEngine.ts +125 -0
  58. package/src/classes/input/gamepad.ts +388 -0
  59. package/src/classes/input/keyboard.ts +189 -0
  60. package/src/classes/input/mouse.ts +276 -0
  61. package/src/classes/input/readers/gamepad.ts +179 -0
  62. package/src/classes/input/readers/keyboard.ts +123 -0
  63. package/src/classes/input/readers/mouse.ts +133 -0
  64. package/src/classes/loggers/consoleBackend.ts +135 -0
  65. package/src/classes/loggers/nullBackend.ts +51 -0
  66. package/src/engines/scene.ts +112 -0
  67. package/src/images/sage_logo.svg +172 -0
  68. package/src/images/sage_logo_shape.svg +146 -0
  69. package/src/interfaces/action.ts +30 -0
  70. package/src/interfaces/binding.ts +191 -0
  71. package/src/interfaces/entity.ts +21 -0
  72. package/src/interfaces/game.ts +44 -0
  73. package/src/interfaces/input.ts +221 -0
  74. package/src/interfaces/logger.ts +118 -0
  75. package/src/managers/binding.ts +729 -0
  76. package/src/managers/entity.ts +252 -0
  77. package/src/managers/game.ts +111 -0
  78. package/src/managers/input.ts +233 -0
  79. package/src/managers/level.ts +261 -0
  80. package/src/sage.ts +119 -0
  81. package/src/types/global.d.ts +11 -0
  82. package/src/utils/capabilities.ts +16 -0
  83. package/src/utils/graphics.ts +148 -0
  84. package/src/utils/logger.ts +225 -0
  85. package/src/utils/physics.ts +16 -0
  86. package/src/utils/version.ts +11 -0
@@ -0,0 +1,388 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Gamepad Input
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ // Interfaces
6
+ import type { ButtonState, GamepadDevice, GamepadInputState } from '../../interfaces/input.ts';
7
+
8
+ //----------------------------------------------------------------------------------------------------------------------
9
+
10
+ /**
11
+ * Callback type for gamepad device connection events
12
+ */
13
+ export type GamepadDeviceCallback = (device : GamepadDevice) => void;
14
+
15
+ /**
16
+ * Callback type for gamepad input events
17
+ */
18
+ export type GamepadInputCallback = (device : GamepadDevice, state : GamepadInputState) => void;
19
+
20
+ /**
21
+ * Responsible for tracking gamepad state and notifying subscribers of changes.
22
+ */
23
+ export class GamepadInputPlugin
24
+ {
25
+ private _gamepadDevices : Record<number, GamepadDevice> = {};
26
+ private _buttonStates : Record<number, Record<string, ButtonState>> = {};
27
+ private _axesStates : Record<number, Record<string, number>> = {};
28
+
29
+ // Callbacks
30
+ private _onDeviceConnected ?: GamepadDeviceCallback;
31
+ private _onDeviceDisconnected ?: GamepadDeviceCallback;
32
+ private _onInputChanged ?: GamepadInputCallback;
33
+
34
+ //------------------------------------------------------------------------------------------------------------------
35
+
36
+ /**
37
+ * Create a new GamepadResourceAccess
38
+ */
39
+ constructor()
40
+ {
41
+ // Set up event listeners for gamepad connection and disconnection
42
+ this._setupGamepadEvents();
43
+ }
44
+
45
+ //------------------------------------------------------------------------------------------------------------------
46
+ // Public API
47
+ //------------------------------------------------------------------------------------------------------------------
48
+
49
+ /**
50
+ * Register a callback for device connected events
51
+ *
52
+ * @param callback - The callback to register
53
+ */
54
+ public onDeviceConnected(callback : GamepadDeviceCallback) : void
55
+ {
56
+ this._onDeviceConnected = callback;
57
+ }
58
+
59
+ /**
60
+ * Register a callback for device disconnected events
61
+ *
62
+ * @param callback - The callback to register
63
+ */
64
+ public onDeviceDisconnected(callback : GamepadDeviceCallback) : void
65
+ {
66
+ this._onDeviceDisconnected = callback;
67
+ }
68
+
69
+ /**
70
+ * Register a callback for input changed events
71
+ *
72
+ * @param callback - The callback to register
73
+ */
74
+ public onInputChanged(callback : GamepadInputCallback) : void
75
+ {
76
+ this._onInputChanged = callback;
77
+ }
78
+
79
+ /**
80
+ * Get all connected gamepad devices
81
+ */
82
+ public getDevices() : GamepadDevice[]
83
+ {
84
+ return Object.values(this._gamepadDevices).map((device) => ({ ...device }));
85
+ }
86
+
87
+ /**
88
+ * Get all connected gamepad states
89
+ */
90
+ public getStates() : Record<number, GamepadInputState>
91
+ {
92
+ const states : Record<number, GamepadInputState> = {};
93
+
94
+ Object.keys(this._buttonStates).forEach((indexStr) =>
95
+ {
96
+ const index = Number(indexStr);
97
+ const buttons = this._buttonStates[index] || {};
98
+ const axes = this._axesStates[index] || {};
99
+
100
+ states[index] = {
101
+ type: 'gamepad',
102
+ buttons: { ...buttons },
103
+ axes: { ...axes },
104
+ };
105
+ });
106
+
107
+ return states;
108
+ }
109
+
110
+ /**
111
+ * Get a specific gamepad device by index
112
+ *
113
+ * @param index - The index of the gamepad to get
114
+ */
115
+ public getDevice(index : number) : GamepadDevice | null
116
+ {
117
+ const device = this._gamepadDevices[index];
118
+ return device ? { ...device } : null;
119
+ }
120
+
121
+ /**
122
+ * Get a specific gamepad state by index
123
+ *
124
+ * @param index - The index of the gamepad state to get
125
+ */
126
+ public getState(index : number) : GamepadInputState | null
127
+ {
128
+ const buttons = this._buttonStates[index];
129
+ const axes = this._axesStates[index];
130
+
131
+ if(!buttons && !axes) { return null; }
132
+
133
+ return {
134
+ type: 'gamepad',
135
+ buttons: buttons ? { ...buttons } : {},
136
+ axes: axes ? { ...axes } : {},
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Poll for gamepad state updates - call this in your game loop
142
+ */
143
+ public pollGamepads() : void
144
+ {
145
+ /* eslint-disable no-continue */
146
+
147
+ if(!navigator.getGamepads) { return; }
148
+
149
+ const gamepads = navigator.getGamepads();
150
+ for(const gamepad of gamepads)
151
+ {
152
+ if(!gamepad) { continue; }
153
+
154
+ const index = gamepad.index;
155
+
156
+ if(!this._gamepadDevices[index])
157
+ {
158
+ this._handleGamepadConnected(gamepad);
159
+ continue;
160
+ }
161
+
162
+ // Get device
163
+ const device = this._gamepadDevices[index];
164
+ if(!device) { continue; }
165
+
166
+ // Get current button and axis states or initialize them
167
+ const currentButtons = this._buttonStates[index] || {};
168
+ const currentAxes = this._axesStates[index] || {};
169
+
170
+ // Build new button state object
171
+ const newButtons : Record<string, ButtonState> = {};
172
+ let buttonsChanged = false;
173
+
174
+ gamepad.buttons.forEach((btn, i) =>
175
+ {
176
+ const buttonKey = `button-${ i }`;
177
+ const buttonState : ButtonState = {
178
+ pressed: btn.pressed,
179
+ touched: btn.touched,
180
+ value: btn.value,
181
+ };
182
+
183
+ newButtons[buttonKey] = buttonState;
184
+
185
+ const prevButton = currentButtons[buttonKey];
186
+ if(!prevButton
187
+ || prevButton.pressed !== buttonState.pressed
188
+ || prevButton.touched !== buttonState.touched
189
+ || prevButton.value !== buttonState.value)
190
+ {
191
+ buttonsChanged = true;
192
+ }
193
+ });
194
+
195
+ // Build new axis state object
196
+ const newAxes : Record<string, number> = {};
197
+ let axesChanged = false;
198
+
199
+ gamepad.axes.forEach((axisValue, i) =>
200
+ {
201
+ const axisKey = `axis-${ i }`;
202
+ newAxes[axisKey] = axisValue;
203
+
204
+ const prevAxis = currentAxes[axisKey];
205
+ if(prevAxis !== axisValue)
206
+ {
207
+ axesChanged = true;
208
+ }
209
+ });
210
+
211
+ // Update state objects
212
+ this._buttonStates[index] = newButtons;
213
+ this._axesStates[index] = newAxes;
214
+
215
+ // Notify if state changed
216
+ if(buttonsChanged || axesChanged)
217
+ {
218
+ const state : GamepadInputState = {
219
+ type: 'gamepad',
220
+ buttons: { ...newButtons },
221
+ axes: { ...newAxes },
222
+ };
223
+
224
+ this._notifyInputChanged(device, state);
225
+ }
226
+ }
227
+
228
+ /* eslint-enable no-continue */
229
+ }
230
+
231
+ /**
232
+ * Destroy the gamepad resource access and clean up event listeners
233
+ */
234
+ public destroy() : void
235
+ {
236
+ // Remove gamepad event listeners
237
+ window.removeEventListener('gamepadconnected', this._handleGamepadConnected);
238
+ window.removeEventListener('gamepaddisconnected', this._handleGamepadDisconnected);
239
+ }
240
+
241
+ //------------------------------------------------------------------------------------------------------------------
242
+ // Private Methods
243
+ //------------------------------------------------------------------------------------------------------------------
244
+
245
+ /**
246
+ * Set up gamepad event listeners
247
+ */
248
+ private _setupGamepadEvents() : void
249
+ {
250
+ // Bind handlers to this instance
251
+ this._handleGamepadConnected = this._handleGamepadConnected.bind(this);
252
+ this._handleGamepadDisconnected = this._handleGamepadDisconnected.bind(this);
253
+
254
+ // Add event listeners
255
+ window.addEventListener('gamepadconnected', this._handleGamepadConnected);
256
+ window.addEventListener('gamepaddisconnected', this._handleGamepadDisconnected);
257
+
258
+ // Check for already connected gamepads (browsers sometimes miss the connected event)
259
+ if(navigator.getGamepads)
260
+ {
261
+ const gamepads = navigator.getGamepads();
262
+ for(const gamepad of gamepads)
263
+ {
264
+ if(gamepad)
265
+ {
266
+ this._handleGamepadConnected(gamepad);
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Handle gamepad connected event
274
+ */
275
+ private _handleGamepadConnected(event : GamepadEvent | Gamepad) : void
276
+ {
277
+ const gamepad = event instanceof GamepadEvent ? event.gamepad : event;
278
+ const index = gamepad.index;
279
+
280
+ // Create gamepad device object
281
+ const gamepadDevice : GamepadDevice = {
282
+ id: `gamepad-${ index }`,
283
+ name: gamepad.id,
284
+ type: 'gamepad',
285
+ connected: true,
286
+ index,
287
+ mapping: gamepad.mapping,
288
+ axes: Array.from(gamepad.axes),
289
+ buttons: Array.from(gamepad.buttons),
290
+ };
291
+
292
+ // Store the device
293
+ this._gamepadDevices[index] = gamepadDevice;
294
+
295
+ // Initialize button state object
296
+ const buttonStateObj : Record<string, ButtonState> = {};
297
+ Array.from(gamepad.buttons).forEach((btn, i) =>
298
+ {
299
+ buttonStateObj[`button-${ i }`] = {
300
+ pressed: btn.pressed,
301
+ touched: btn.touched,
302
+ value: btn.value,
303
+ };
304
+ });
305
+ this._buttonStates[index] = buttonStateObj;
306
+
307
+ // Initialize axis state object
308
+ const axesStateObj : Record<string, number> = {};
309
+ Array.from(gamepad.axes).forEach((axisValue, i) =>
310
+ {
311
+ axesStateObj[`axis-${ i }`] = axisValue;
312
+ });
313
+ this._axesStates[index] = axesStateObj;
314
+
315
+ // Notify device connected with initial state
316
+ this._notifyDeviceConnected(gamepadDevice);
317
+
318
+ // Emit input changed event with initial state
319
+ this._notifyInputChanged(gamepadDevice, {
320
+ type: 'gamepad',
321
+ buttons: { ...buttonStateObj },
322
+ axes: { ...axesStateObj },
323
+ event: event instanceof GamepadEvent ? event : undefined,
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Handle gamepad disconnected event
329
+ */
330
+ private _handleGamepadDisconnected(event : GamepadEvent) : void
331
+ {
332
+ const gamepad = event.gamepad;
333
+ const index = gamepad.index;
334
+
335
+ // Get the device
336
+ const gamepadDevice = this._gamepadDevices[index];
337
+
338
+ if(gamepadDevice)
339
+ {
340
+ // Update connection status
341
+ gamepadDevice.connected = false;
342
+
343
+ // Notify device disconnected
344
+ this._notifyDeviceDisconnected(gamepadDevice);
345
+
346
+ // Remove from objects
347
+ // Using object property assignment with undefined instead of delete
348
+ this._gamepadDevices[index] = undefined as unknown as GamepadDevice;
349
+ this._buttonStates[index] = undefined as unknown as Record<string, ButtonState>;
350
+ this._axesStates[index] = undefined as unknown as Record<string, number>;
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Notify subscribers of device connected event
356
+ */
357
+ private _notifyDeviceConnected(device : GamepadDevice) : void
358
+ {
359
+ if(this._onDeviceConnected)
360
+ {
361
+ this._onDeviceConnected(device);
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Notify subscribers of device disconnected event
367
+ */
368
+ private _notifyDeviceDisconnected(device : GamepadDevice) : void
369
+ {
370
+ if(this._onDeviceDisconnected)
371
+ {
372
+ this._onDeviceDisconnected(device);
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Notify subscribers of input changed event
378
+ */
379
+ private _notifyInputChanged(device : GamepadDevice, state : GamepadInputState) : void
380
+ {
381
+ if(this._onInputChanged)
382
+ {
383
+ this._onInputChanged(device, state);
384
+ }
385
+ }
386
+ }
387
+
388
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,189 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Keyboard Input Resource Access
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ // Interfaces
6
+ import type { KeyboardDevice, KeyboardInputState } from '../../interfaces/input.ts';
7
+
8
+ //----------------------------------------------------------------------------------------------------------------------
9
+
10
+ /**
11
+ * Callback type for keyboard device connection events
12
+ */
13
+ export type KeyboardDeviceCallback = (device : KeyboardDevice) => void;
14
+
15
+ /**
16
+ * Callback type for keyboard input events
17
+ */
18
+ export type KeyboardInputCallback = (device : KeyboardDevice, state : KeyboardInputState) => void;
19
+
20
+ /**
21
+ * Responsible for tracking keyboard state and notifying subscribers of changes.
22
+ */
23
+ export class KeyboardInputPlugin
24
+ {
25
+ private _keyboardDevice : KeyboardDevice;
26
+ private _keysState : Record<string, boolean> = {};
27
+
28
+ // Callbacks
29
+ private _onDeviceConnected ?: KeyboardDeviceCallback;
30
+ private _onInputChanged ?: KeyboardInputCallback;
31
+
32
+ //------------------------------------------------------------------------------------------------------------------
33
+
34
+ /**
35
+ * Create a new KeyboardResourceAccess
36
+ */
37
+ constructor()
38
+ {
39
+ // Initialize keyboard device
40
+ this._keyboardDevice = {
41
+ id: 'keyboard-0',
42
+ name: 'Keyboard',
43
+ type: 'keyboard',
44
+ connected: true,
45
+ };
46
+
47
+ // Set up event listeners
48
+ this._setupKeyboardEvents();
49
+
50
+ // Notify that device is connected (on next tick to allow callback registration)
51
+ setTimeout(() => this._notifyDeviceConnected(), 0);
52
+ }
53
+
54
+ //------------------------------------------------------------------------------------------------------------------
55
+ // Public API
56
+ //------------------------------------------------------------------------------------------------------------------
57
+
58
+ /**
59
+ * Register a callback for device connected events
60
+ *
61
+ * @param callback - The callback to register
62
+ */
63
+ public onDeviceConnected(callback : KeyboardDeviceCallback) : void
64
+ {
65
+ this._onDeviceConnected = callback;
66
+ }
67
+
68
+ /**
69
+ * Register a callback for input changed events
70
+ *
71
+ * @param callback - The callback to register
72
+ */
73
+ public onInputChanged(callback : KeyboardInputCallback) : void
74
+ {
75
+ this._onInputChanged = callback;
76
+ }
77
+
78
+ /**
79
+ * Get the current keyboard state
80
+ */
81
+ public getState() : KeyboardInputState
82
+ {
83
+ return {
84
+ type: 'keyboard',
85
+ keys: { ...this._keysState },
86
+ delta: {},
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Get the keyboard device
92
+ */
93
+ public getDevice() : KeyboardDevice
94
+ {
95
+ return { ...this._keyboardDevice };
96
+ }
97
+
98
+ /**
99
+ * Destroy the keyboard resource access and clean up event listeners
100
+ */
101
+ public destroy() : void
102
+ {
103
+ // Remove keyboard event listeners
104
+ window.removeEventListener('keydown', this._handleKeyDown);
105
+ window.removeEventListener('keyup', this._handleKeyUp);
106
+ }
107
+
108
+ //------------------------------------------------------------------------------------------------------------------
109
+ // Private Methods
110
+ //------------------------------------------------------------------------------------------------------------------
111
+
112
+ /**
113
+ * Set up keyboard event listeners
114
+ */
115
+ private _setupKeyboardEvents() : void
116
+ {
117
+ // Bind handlers to this instance
118
+ this._handleKeyDown = this._handleKeyDown.bind(this);
119
+ this._handleKeyUp = this._handleKeyUp.bind(this);
120
+
121
+ // Add event listeners
122
+ window.addEventListener('keydown', this._handleKeyDown);
123
+ window.addEventListener('keyup', this._handleKeyUp);
124
+ }
125
+
126
+ /**
127
+ * Handle keyboard key down events
128
+ */
129
+ private _handleKeyDown(event : KeyboardEvent) : void
130
+ {
131
+ this._keysState[event.code] = true;
132
+
133
+ const delta : Record<string, boolean> = {};
134
+ delta[event.code] = true;
135
+
136
+ const state : KeyboardInputState = {
137
+ type: 'keyboard',
138
+ keys: { ...this._keysState },
139
+ delta,
140
+ event,
141
+ };
142
+
143
+ this._notifyInputChanged(state);
144
+ }
145
+
146
+ /**
147
+ * Handle keyboard key up events
148
+ */
149
+ private _handleKeyUp(event : KeyboardEvent) : void
150
+ {
151
+ this._keysState[event.code] = false;
152
+
153
+ const delta : Record<string, boolean> = {};
154
+ delta[event.code] = false;
155
+
156
+ const state : KeyboardInputState = {
157
+ type: 'keyboard',
158
+ keys: { ...this._keysState },
159
+ delta,
160
+ event,
161
+ };
162
+
163
+ this._notifyInputChanged(state);
164
+ }
165
+
166
+ /**
167
+ * Notify subscribers of device connected event
168
+ */
169
+ private _notifyDeviceConnected() : void
170
+ {
171
+ if(this._onDeviceConnected)
172
+ {
173
+ this._onDeviceConnected(this._keyboardDevice);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Notify subscribers of input changed event
179
+ */
180
+ private _notifyInputChanged(state : KeyboardInputState) : void
181
+ {
182
+ if(this._onInputChanged)
183
+ {
184
+ this._onInputChanged(this._keyboardDevice, state);
185
+ }
186
+ }
187
+ }
188
+
189
+ //----------------------------------------------------------------------------------------------------------------------