@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,252 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Entity Manager
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ import { GameEntity } from '../classes/entity.ts';
6
+ import { GameEventBus } from '../classes/eventBus.ts';
7
+
8
+ // Interfaces
9
+ import type { Action } from '../interfaces/action.ts';
10
+ import type { GameEntityDefinition } from '../interfaces/entity.ts';
11
+ import type { LoggerInterface } from '../interfaces/logger.ts';
12
+ import type { BindingManager } from '../managers/binding.ts';
13
+
14
+ // Utils
15
+ import { LoggingUtility, SAGELogger } from '../utils/logger.ts';
16
+
17
+ //----------------------------------------------------------------------------------------------------------------------
18
+
19
+ export class GameEntityManager
20
+ {
21
+ /** The event bus for the entity manager. */
22
+ private eventBus : GameEventBus;
23
+
24
+ /** A map of entities managed by the entity manager. */
25
+ private entities : Map<string, GameEntity> = new Map<string, GameEntity>();
26
+
27
+ /** A map of entity definitions registered with the entity manager. */
28
+ private entityDefinitions : Map<string, GameEntityDefinition> = new Map<string, GameEntityDefinition>();
29
+
30
+ /** Reference to the binding manager for registering actions */
31
+ private bindingManager : BindingManager;
32
+
33
+ /** Logger instance */
34
+ private _log : LoggerInterface;
35
+
36
+ /**
37
+ * Creates an instance of EntityManager.
38
+ * @param eventBus - The event bus for the entity manager.
39
+ * @param logger - The logging utility to use
40
+ * @param bindingManager - The binding manager for registering actions
41
+ */
42
+ constructor(eventBus : GameEventBus, logger : LoggingUtility | undefined, bindingManager : BindingManager)
43
+ {
44
+ this.eventBus = eventBus;
45
+ this.bindingManager = bindingManager;
46
+ this._log = logger?.getLogger('EntityManager') || new SAGELogger('EntityManager');
47
+ this._log.info('EntityManager initialized');
48
+ }
49
+
50
+ //------------------------------------------------------------------------------------------------------------------
51
+ // Private Methods
52
+ //------------------------------------------------------------------------------------------------------------------
53
+
54
+ /**
55
+ * Checks if two actions are compatible
56
+ * @param existingAction - The action already registered
57
+ * @param newAction - The action being registered
58
+ * @returns true if the actions are compatible, false if they have conflicting options
59
+ */
60
+ private areActionsCompatible(existingAction : Action, newAction : Action) : boolean
61
+ {
62
+ // If types don't match, they're incompatible
63
+ if(existingAction.type !== newAction.type)
64
+ {
65
+ return false;
66
+ }
67
+
68
+ // For analog actions, check min and max values
69
+ if(existingAction.type === 'analog' && newAction.type === 'analog')
70
+ {
71
+ // If minValue or maxValue is specified and differs, they're incompatible
72
+ if((newAction.minValue !== undefined && existingAction.minValue !== newAction.minValue)
73
+ || (newAction.maxValue !== undefined && existingAction.maxValue !== newAction.maxValue))
74
+ {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ // Digital actions are always compatible if they have the same name
80
+ return true;
81
+ }
82
+
83
+ /**
84
+ * Registers actions defined in the entity definition with the binding manager
85
+ * @param entityDef - The entity definition containing actions to register
86
+ */
87
+ private registerEntityActions(entityDef : GameEntityDefinition) : void
88
+ {
89
+ if(!entityDef.actions)
90
+ {
91
+ return;
92
+ }
93
+
94
+ for(const action of entityDef.actions)
95
+ {
96
+ try
97
+ {
98
+ // Check if the action is already registered
99
+ const existingAction = this.bindingManager.getAction(action.name);
100
+
101
+ if(!existingAction)
102
+ {
103
+ // Action doesn't exist yet, register it
104
+ this._log.debug(`Registering action "${ action.name }" from entity type "${ entityDef.type }"`);
105
+ this.bindingManager.registerAction(action);
106
+ }
107
+ else if(!this.areActionsCompatible(existingAction, action))
108
+ {
109
+ this._log.warn(
110
+ `Action "${ action.name }" already registered with different options. `
111
+ + `Entity "${ entityDef.type }" requires: ${ JSON.stringify(action) }, `
112
+ + `but found: ${ JSON.stringify(existingAction) }`
113
+ );
114
+ }
115
+ else
116
+ {
117
+ this._log.trace(
118
+ `Action "${ action.name }" already registered with compatible options, skipping registration`
119
+ );
120
+ }
121
+ }
122
+ catch (err)
123
+ {
124
+ this._log.debug(`Failed to register action "${ action.name }": `
125
+ + `${ err instanceof Error ? err.message : String(err) }`);
126
+ }
127
+ }
128
+ }
129
+
130
+ $frameUpdate(dt : number) : void
131
+ {
132
+ this._log.trace(`Updating ${ this.entities.size } entities with dt=${ dt }`);
133
+ for(const entity of this.entities.values())
134
+ {
135
+ entity.$update(dt);
136
+ }
137
+ }
138
+
139
+ //------------------------------------------------------------------------------------------------------------------
140
+ // Public API
141
+ //------------------------------------------------------------------------------------------------------------------
142
+
143
+ /**
144
+ * Registers a new entity definition.
145
+ * @param entityDef - The definition of the entity.
146
+ */
147
+ registerEntityDefinition(entityDef : GameEntityDefinition) : void
148
+ {
149
+ this._log.debug(`Registering entity definition: ${ entityDef.type }`);
150
+
151
+ // Register any actions this entity requires
152
+ this.registerEntityActions(entityDef);
153
+
154
+ // Store the entity definition
155
+ this.entityDefinitions.set(entityDef.type, entityDef);
156
+ }
157
+
158
+ /**
159
+ * Creates a new entity of the given type.
160
+ * @param type - The type of the entity to create.
161
+ * @param initialState - The initial state of the entity.
162
+ * @returns The created entity.
163
+ */
164
+ createEntity<State extends object = object>(type : string, initialState : Partial<State> = {}) : GameEntity<State>
165
+ {
166
+ this._log.debug(`Creating entity of type: ${ type }`);
167
+ const entityDef = this.entityDefinitions.get(type);
168
+ if(!entityDef)
169
+ {
170
+ const errorMsg = `Entity type ${ type } is not registered.`;
171
+ this._log.error(errorMsg);
172
+ throw new Error(errorMsg);
173
+ }
174
+
175
+ this._log.trace(`Using entity definition with ${ entityDef.behaviors?.length || 0 } behaviors`);
176
+ const mergedState = { ...entityDef.defaultState, ...initialState } as State;
177
+
178
+ const entity = new GameEntity<State>(
179
+ entityDef.type,
180
+ this.eventBus,
181
+ mergedState,
182
+ entityDef.behaviors
183
+ );
184
+
185
+ this.entities.set(entity.id, entity);
186
+ this._log.debug(`Entity created with ID: ${ entity.id }`);
187
+ return entity;
188
+ }
189
+
190
+ /**
191
+ * Destroys the entity with the given ID.
192
+ * @param entityID - The ID of the entity to destroy.
193
+ */
194
+ destroyEntity(entityID : string) : void
195
+ {
196
+ this._log.debug(`Destroying entity: ${ entityID }`);
197
+ const entity = this.entities.get(entityID);
198
+ if(entity)
199
+ {
200
+ entity.$destroy();
201
+ this.entities.delete(entityID);
202
+ this._log.debug(`Entity ${ entityID } destroyed`);
203
+ }
204
+ else
205
+ {
206
+ this._log.warn(`Attempted to destroy non-existent entity: ${ entityID }`);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Gets the entity with the given ID.
212
+ * @param entityID - The ID of the entity to get.
213
+ * @returns The entity with the given ID, or null if it does not exist.
214
+ */
215
+ getEntity(entityID : string) : GameEntity | null
216
+ {
217
+ this._log.trace(`Getting entity: ${ entityID }`);
218
+ return this.entities.get(entityID) ?? null;
219
+ }
220
+
221
+ /**
222
+ * Adds an existing entity to the entity manager.
223
+ * @param entity - The entity to add.
224
+ */
225
+ addEntity(entity : GameEntity) : void
226
+ {
227
+ this._log.debug(`Adding existing entity: ${ entity.id } (type: ${ entity.type })`);
228
+ this.entities.set(entity.id, entity);
229
+ }
230
+
231
+ /**
232
+ * Removes the entity with the given ID, without destroying it.
233
+ * @param entityID - The ID of the entity to remove.
234
+ */
235
+ removeEntity(entityID : string) : void
236
+ {
237
+ this._log.debug(`Removing entity: ${ entityID }`);
238
+ const entity = this.entities.get(entityID);
239
+ if(entity)
240
+ {
241
+ this.entities.delete(entityID);
242
+ this._log.debug(`Entity ${ entityID } removed`);
243
+ }
244
+ else
245
+ {
246
+ this._log.warn(`Attempted to remove non-existent entity: ${ entityID }`);
247
+ }
248
+ }
249
+ }
250
+
251
+ //----------------------------------------------------------------------------------------------------------------------
252
+
@@ -0,0 +1,111 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Game Manager
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ import { AbstractEngine, Scene } from '@babylonjs/core';
6
+
7
+ // Interfaces
8
+ import { GameCanvas } from '../interfaces/game.ts';
9
+ import { LoggerInterface } from '../interfaces/logger.ts';
10
+
11
+ // Managers
12
+ import { GameEntityManager } from './entity.ts';
13
+ import { UserInputManager } from './input.ts';
14
+
15
+ // Engines
16
+ import { SceneEngine } from '../engines/scene.ts';
17
+
18
+ // Utils
19
+ import { isBrowser } from '../utils/capabilities.ts';
20
+ import { LoggingUtility, SAGELogger } from '../utils/logger.ts';
21
+
22
+ //----------------------------------------------------------------------------------------------------------------------
23
+
24
+ export class GameManager
25
+ {
26
+ private _engine : AbstractEngine;
27
+ private _entityManager : GameEntityManager;
28
+ private _inputManager : UserInputManager;
29
+ private _sceneEngine : SceneEngine;
30
+ private _currentScene : Scene | null = null;
31
+ private _log : LoggerInterface;
32
+
33
+ public started = false;
34
+
35
+ //------------------------------------------------------------------------------------------------------------------
36
+
37
+ constructor(
38
+ engine : AbstractEngine,
39
+ sceneEngine : SceneEngine,
40
+ entityManager : GameEntityManager,
41
+ inputManager : UserInputManager,
42
+ logger ?: LoggingUtility
43
+ )
44
+ {
45
+ this._engine = engine;
46
+ this._sceneEngine = sceneEngine;
47
+ this._entityManager = entityManager;
48
+ this._inputManager = inputManager;
49
+ this._log = logger?.getLogger('GameManager') || new SAGELogger('GameManager');
50
+
51
+ if(isBrowser())
52
+ {
53
+ // Resize the engine on window resize
54
+ window.addEventListener('resize', this._resizeHandler.bind(this));
55
+ }
56
+ }
57
+
58
+ private _renderLoop() : void
59
+ {
60
+ // Update entities and entity manager
61
+ const deltaTime = this._engine.getDeltaTime();
62
+ this._entityManager.$frameUpdate(deltaTime);
63
+
64
+ // Update input devices
65
+ if(this._inputManager)
66
+ {
67
+ this._inputManager.pollGamepads();
68
+ }
69
+
70
+ // Render the current scene
71
+ if(this._currentScene)
72
+ {
73
+ this._currentScene.render();
74
+ }
75
+ }
76
+
77
+ private _resizeHandler() : void
78
+ {
79
+ if(this.started)
80
+ {
81
+ this._engine.resize();
82
+ }
83
+ }
84
+
85
+ //------------------------------------------------------------------------------------------------------------------
86
+ // Public API
87
+ //------------------------------------------------------------------------------------------------------------------
88
+
89
+ async start(canvas : GameCanvas) : Promise<void>
90
+ {
91
+ // TODO: There needs to be some sort of initial scene we load
92
+ this._currentScene = await this._sceneEngine.loadScene(canvas);
93
+
94
+ // Start the render loop
95
+ this._engine.runRenderLoop(this._renderLoop.bind(this));
96
+
97
+ this.started = true;
98
+
99
+ this._log.info('SkewedAspect Game Engine started successfully');
100
+ }
101
+
102
+ async stop() : Promise<void>
103
+ {
104
+ this.started = false;
105
+ this._engine.stopRenderLoop();
106
+
107
+ this._log.info('SkewedAspect Game Engine stopped successfully');
108
+ }
109
+ }
110
+
111
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,233 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // User Input Manager
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ // Interfaces
6
+ import type { GamepadInputState, InputDevice, KeyboardInputState, MouseInputState } from '../interfaces/input.ts';
7
+ import type { LoggerInterface } from '../interfaces/logger.ts';
8
+
9
+ // Classes
10
+ import { type GameEvent, GameEventBus } from '../classes/eventBus.ts';
11
+
12
+ // Utils
13
+ import { LoggingUtility, SAGELogger } from '../utils/logger.ts';
14
+
15
+ // Resource Access
16
+ import { KeyboardInputPlugin } from '../classes/input/keyboard.ts';
17
+ import { MouseInputPlugin } from '../classes/input/mouse.ts';
18
+ import { GamepadInputPlugin } from '../classes/input/gamepad.ts';
19
+
20
+ //----------------------------------------------------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Manager for handling user input from various devices (keyboard, mouse, gamepad)
24
+ */
25
+ export class UserInputManager
26
+ {
27
+ private _eventBus : GameEventBus;
28
+
29
+ private _keyboardRA : KeyboardInputPlugin;
30
+ private _mouseRA : MouseInputPlugin;
31
+ private _gamepadRA : GamepadInputPlugin;
32
+
33
+ /** Logger instance */
34
+ private _log : LoggerInterface;
35
+
36
+ //------------------------------------------------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Create a new UserInputManager
40
+ *
41
+ * @param eventBus - The game event bus to publish events to
42
+ * @param canvas - The DOM element to attach input listeners to
43
+ * @param logger - The logging utility to use
44
+ */
45
+ constructor(
46
+ eventBus : GameEventBus,
47
+ canvas : HTMLElement,
48
+ logger ?: LoggingUtility
49
+ )
50
+ {
51
+ this._eventBus = eventBus;
52
+
53
+ // Initialize logger
54
+ this._log = logger?.getLogger('UserInputManager') || new SAGELogger('UserInputManager');
55
+
56
+ this._log.info('Initializing UserInputManager');
57
+
58
+ // Initialize resource access classes
59
+ this._log.debug('Initializing input resource access classes');
60
+ this._keyboardRA = new KeyboardInputPlugin();
61
+ this._mouseRA = new MouseInputPlugin(canvas);
62
+ this._gamepadRA = new GamepadInputPlugin();
63
+
64
+ // Register callbacks
65
+ this._log.debug('Registering input event callbacks');
66
+ this._keyboardRA.onDeviceConnected(this._publishDeviceConnected.bind(this));
67
+ this._keyboardRA.onInputChanged(this._publishInputChanged.bind(this));
68
+ this._mouseRA.onDeviceConnected(this._publishDeviceConnected.bind(this));
69
+ this._mouseRA.onInputChanged(this._publishInputChanged.bind(this));
70
+ this._gamepadRA.onDeviceConnected(this._publishDeviceConnected.bind(this));
71
+ this._gamepadRA.onDeviceDisconnected(this._publishDeviceDisconnected.bind(this));
72
+ this._gamepadRA.onInputChanged(this._publishInputChanged.bind(this));
73
+
74
+ this._log.info('UserInputManager initialized successfully');
75
+ }
76
+
77
+ //------------------------------------------------------------------------------------------------------------------
78
+ // Private methods
79
+ //------------------------------------------------------------------------------------------------------------------
80
+
81
+ /**
82
+ * Publish device connected event to the event bus
83
+ */
84
+ private _publishDeviceConnected(device : InputDevice) : void
85
+ {
86
+ this._log.debug(`Device connected: ${ device.id } (${ device.name })`);
87
+
88
+ const gameEvent : GameEvent = {
89
+ type: 'input:device:connected',
90
+ payload: { device },
91
+ };
92
+
93
+ this._eventBus.publish(gameEvent);
94
+ }
95
+
96
+ /**
97
+ * Publish device disconnected event to the event bus
98
+ */
99
+ private _publishDeviceDisconnected(device : InputDevice) : void
100
+ {
101
+ this._log.debug(`Device disconnected: ${ device.id } (${ device.name })`);
102
+
103
+ const gameEvent : GameEvent = {
104
+ type: 'input:device:disconnected',
105
+ payload: { device },
106
+ };
107
+
108
+ this._eventBus.publish(gameEvent);
109
+ }
110
+
111
+ /**
112
+ * Publish input changed event to the event bus, used by all device types
113
+ */
114
+ private _publishInputChanged(
115
+ device : InputDevice,
116
+ state : KeyboardInputState | MouseInputState | GamepadInputState
117
+ ) : void
118
+ {
119
+ this._log.trace(`Input changed: ${ device.id }`, state);
120
+
121
+ const gameEvent : GameEvent = {
122
+ type: 'input:changed',
123
+ payload: {
124
+ deviceId: device.id,
125
+ device,
126
+ state,
127
+ },
128
+ };
129
+
130
+ // Publish the event to the event bus
131
+ this._eventBus.publish(gameEvent);
132
+ }
133
+
134
+ //------------------------------------------------------------------------------------------------------------------
135
+ // Internal methods
136
+ //------------------------------------------------------------------------------------------------------------------
137
+
138
+ /**
139
+ * Destroy the input manager and clean up event listeners
140
+ */
141
+ public $destroy() : void
142
+ {
143
+ this._log.info('Destroying UserInputManager');
144
+
145
+ // Cleanup all resource access instances
146
+ this._log.debug('Cleaning up input resource access instances');
147
+ this._keyboardRA.destroy();
148
+ this._mouseRA.destroy();
149
+ this._gamepadRA.destroy();
150
+
151
+ this._log.info('UserInputManager destroyed');
152
+ }
153
+
154
+ //------------------------------------------------------------------------------------------------------------------
155
+ // Public methods
156
+ //------------------------------------------------------------------------------------------------------------------
157
+
158
+ /**
159
+ * Get all input devices
160
+ *
161
+ * @return An array of input devices
162
+ */
163
+ public listDevices() : InputDevice[]
164
+ {
165
+ this._log.debug('Getting all connected input devices');
166
+
167
+ const devices : InputDevice[] = [];
168
+
169
+ // Add keyboard and mouse
170
+ devices.push(this._keyboardRA.getDevice());
171
+ devices.push(this._mouseRA.getDevice());
172
+
173
+ // Add all gamepads
174
+ devices.push(...this._gamepadRA.getDevices());
175
+
176
+ this._log.debug(`Found ${ devices.length } connected devices`);
177
+ return devices;
178
+ }
179
+
180
+ /**
181
+ * Get a specific input device by ID
182
+ *
183
+ * @param deviceId - The ID of the device to get
184
+ *
185
+ * @return The input device, or null if not found
186
+ */
187
+ public getDevice(deviceId : string) : InputDevice | null
188
+ {
189
+ this._log.debug(`Getting device: ${ deviceId }`);
190
+
191
+ // Shortcut for keyboard and mouse
192
+ if(deviceId.startsWith('keyboard-'))
193
+ {
194
+ return this._keyboardRA.getDevice();
195
+ }
196
+ else if(deviceId.startsWith('mouse-'))
197
+ {
198
+ return this._mouseRA.getDevice();
199
+ }
200
+ else if(deviceId.startsWith('gamepad-'))
201
+ {
202
+ const index = parseInt(deviceId.split('-')[1], 10);
203
+ return this._gamepadRA.getDevice(index);
204
+ }
205
+ else
206
+ {
207
+ // This is the slow path, and should never be hit, but it is here for completeness
208
+ const devices = this.listDevices();
209
+
210
+ for(const device of devices)
211
+ {
212
+ if(device.id === deviceId)
213
+ {
214
+ return device;
215
+ }
216
+ }
217
+ }
218
+
219
+ this._log.warn(`Device not found: ${ deviceId }`);
220
+ return null;
221
+ }
222
+
223
+ /**
224
+ * Poll for gamepad state updates - call this in your game loop
225
+ */
226
+ public pollGamepads() : void
227
+ {
228
+ this._gamepadRA.pollGamepads();
229
+ }
230
+ }
231
+
232
+ //----------------------------------------------------------------------------------------------------------------------
233
+