deviceInputSystem.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import { Observable } from "../Misc/observable";
  2. import { Engine } from '../Engines/engine';
  3. import { IDisposable } from '../scene';
  4. import { Nullable } from '../types';
  5. /**
  6. * This class will take all inputs from Keyboard, Pointer, and
  7. * any Gamepads and provide a polling system that all devices
  8. * will use. This class assumes that there will only be one
  9. * pointer device and one keyboard.
  10. */
  11. export class DeviceInputSystem implements IDisposable {
  12. // Static
  13. /** POINTER_DEVICE */
  14. public static readonly POINTER_DEVICE: string = "Pointer";
  15. /** KEYBOARD_DEVICE */
  16. public static readonly KEYBOARD_DEVICE: string = "Keyboard";
  17. /**
  18. * Observable to be triggered when a device is connected
  19. */
  20. public onDeviceConnectedObservable = new Observable<string>();
  21. /**
  22. * Observable to be triggered when a device is disconnected
  23. */
  24. public onDeviceDisconnectedObservable = new Observable<string>();
  25. // Private Members
  26. private _inputs: { [key: string]: Array<Nullable<number>> } = {};
  27. private _gamepads: Array<string>;
  28. private _keyboardActive: boolean = false;
  29. private _pointerActive: boolean = false;
  30. private _elementToAttachTo: HTMLElement;
  31. private _keyboardDownEvent = (evt: any) => { };
  32. private _keyboardUpEvent = (evt: any) => { };
  33. private _pointerMoveEvent = (evt: any) => { };
  34. private _pointerDownEvent = (evt: any) => { };
  35. private _pointerUpEvent = (evt: any) => { };
  36. private _gamepadConnectedEvent = (evt: any) => { };
  37. private _gamepadDisconnectedEvent = (evt: any) => { };
  38. private static _MAX_KEYCODES: number = 222;
  39. private static _MAX_POINTER_INPUTS: number = 7;
  40. /**
  41. * Default Constructor
  42. * @param engine - engine to pull input element from
  43. */
  44. constructor(engine: Engine) {
  45. const inputElement = engine.getInputElement();
  46. if (inputElement) {
  47. this._elementToAttachTo = inputElement;
  48. this._handleKeyActions();
  49. this._handlePointerActions();
  50. this._handleGamepadActions();
  51. }
  52. }
  53. // Public functions
  54. /**
  55. * Checks for current device input value, given an id and input index
  56. * @param deviceName Id of connected device
  57. * @param inputIndex Index of device input
  58. * @returns Current value of input
  59. */
  60. public pollInput(deviceName: string, inputIndex: number): Nullable<number> {
  61. const device = this._inputs[deviceName];
  62. if (!device) {
  63. throw `Unable to find device ${deviceName}`;
  64. }
  65. this._updateDevice(deviceName, inputIndex);
  66. if (device[inputIndex] === undefined) {
  67. throw `Unable to find input ${inputIndex} on device ${deviceName}`;
  68. }
  69. return device[inputIndex];
  70. }
  71. /**
  72. * Dispose of all the eventlisteners and clears the observables
  73. */
  74. public dispose() {
  75. this.onDeviceConnectedObservable.clear();
  76. this.onDeviceDisconnectedObservable.clear();
  77. // Keyboard Events
  78. if (this._keyboardActive) {
  79. window.removeEventListener("keydown", this._keyboardDownEvent);
  80. window.removeEventListener("keyup", this._keyboardUpEvent);
  81. }
  82. // Pointer Events
  83. if (this._pointerActive) {
  84. this._elementToAttachTo.removeEventListener("pointermove", this._pointerMoveEvent);
  85. this._elementToAttachTo.removeEventListener("pointerdown", this._pointerDownEvent);
  86. this._elementToAttachTo.removeEventListener("pointerup", this._pointerUpEvent);
  87. }
  88. // Gamepad Events
  89. window.removeEventListener("gamepadconnected", this._gamepadConnectedEvent);
  90. window.removeEventListener("gamepaddisconnected", this._gamepadDisconnectedEvent);
  91. }
  92. // Private functions
  93. /**
  94. * Add device and inputs to device map
  95. * @param deviceName Assigned name of device (may be SN)
  96. * @param numberOfInputs Number of input entries to create for given device
  97. */
  98. private _registerDevice(deviceName: string, numberOfInputs: number) {
  99. if (!this._inputs[deviceName]) {
  100. const device = new Array<Nullable<number>>(numberOfInputs);
  101. for (let i = 0; i < numberOfInputs; i++) {
  102. device[i] = null;
  103. }
  104. this._inputs[deviceName] = device;
  105. this.onDeviceConnectedObservable.notifyObservers(deviceName);
  106. }
  107. }
  108. /**
  109. * Given a specific device name, remove that device from the device map
  110. * @param deviceName Name of device to be removed
  111. */
  112. private _unregisterDevice(deviceName: string) {
  113. if (this._inputs[deviceName]) {
  114. delete this._inputs[deviceName];
  115. this.onDeviceDisconnectedObservable.notifyObservers(deviceName);
  116. }
  117. }
  118. /**
  119. * Handle all actions that come from keyboard interaction
  120. */
  121. private _handleKeyActions() {
  122. this._keyboardDownEvent = ((evt) => {
  123. if (!this._keyboardActive) {
  124. this._keyboardActive = true;
  125. this._registerDevice(DeviceInputSystem.KEYBOARD_DEVICE, DeviceInputSystem._MAX_KEYCODES);
  126. }
  127. const kbKey = this._inputs[DeviceInputSystem.KEYBOARD_DEVICE];
  128. if (kbKey) {
  129. kbKey[evt.keyCode] = 1;
  130. }
  131. });
  132. this._keyboardUpEvent = ((evt) => {
  133. const kbKey = this._inputs[DeviceInputSystem.KEYBOARD_DEVICE];
  134. if (kbKey) {
  135. kbKey[evt.keyCode] = 0;
  136. }
  137. });
  138. window.addEventListener("keydown", this._keyboardDownEvent);
  139. window.addEventListener("keyup", this._keyboardUpEvent);
  140. }
  141. /**
  142. * Handle all actions that come from pointer interaction
  143. */
  144. private _handlePointerActions() {
  145. this._pointerMoveEvent = ((evt) => {
  146. const deviceName = `${DeviceInputSystem.POINTER_DEVICE}-${evt.pointerId}`;
  147. if (!this._pointerActive) {
  148. this._pointerActive = true;
  149. this._registerDevice(deviceName, DeviceInputSystem._MAX_POINTER_INPUTS);
  150. }
  151. const pointer = this._inputs[deviceName];
  152. if (pointer) {
  153. pointer[0] = evt.clientX;
  154. pointer[1] = evt.clientY;
  155. }
  156. });
  157. this._pointerDownEvent = ((evt) => {
  158. const deviceName = `${DeviceInputSystem.POINTER_DEVICE}-${evt.pointerId}`;
  159. if (!this._pointerActive) {
  160. this._pointerActive = true;
  161. this._registerDevice(deviceName, DeviceInputSystem._MAX_POINTER_INPUTS);
  162. }
  163. const pointer = this._inputs[deviceName];
  164. if (pointer) {
  165. pointer[0] = evt.clientX;
  166. pointer[1] = evt.clientY;
  167. pointer[evt.button + 2] = 1;
  168. }
  169. });
  170. this._pointerUpEvent = ((evt) => {
  171. const deviceName = `${DeviceInputSystem.POINTER_DEVICE}-${evt.pointerId}`;
  172. const pointer = this._inputs[deviceName];
  173. if (pointer) {
  174. pointer[evt.button + 2] = 0;
  175. }
  176. if (evt.pointerId != 1) // Don't unregister the mouse
  177. {
  178. this._unregisterDevice(deviceName);
  179. }
  180. });
  181. this._elementToAttachTo.addEventListener("pointermove", this._pointerMoveEvent);
  182. this._elementToAttachTo.addEventListener("pointerdown", this._pointerDownEvent);
  183. this._elementToAttachTo.addEventListener("pointerup", this._pointerUpEvent);
  184. }
  185. /**
  186. * Handle all actions that come from gamepad interaction
  187. */
  188. private _handleGamepadActions() {
  189. this._gamepadConnectedEvent = ((evt: any) => {
  190. const deviceName = `${evt.gamepad.id}-${evt.gamepad.index}`;
  191. this._registerDevice(deviceName, evt.gamepad.buttons.length + evt.gamepad.axes.length);
  192. this._gamepads = this._gamepads || new Array<string>(evt.gamepad.index + 1);
  193. this._gamepads[evt.gamepad.index] = deviceName;
  194. });
  195. this._gamepadDisconnectedEvent = ((evt: any) => {
  196. const deviceName = this._gamepads[evt.gamepad.index];
  197. this._unregisterDevice(deviceName);
  198. delete this._gamepads[evt.gamepad.index];
  199. });
  200. window.addEventListener("gamepadconnected", this._gamepadConnectedEvent);
  201. window.addEventListener("gamepaddisconnected", this._gamepadDisconnectedEvent);
  202. }
  203. /**
  204. * Update all non-event based devices with each frame
  205. */
  206. private _updateDevice(deviceName: string, inputIndex: number) {
  207. // Gamepads
  208. const gamepads = navigator.getGamepads();
  209. // Look for current gamepad and get updated values
  210. for (const gp of gamepads) {
  211. if (gp && deviceName == this._gamepads[gp.index]) {
  212. const device = this._inputs[deviceName];
  213. if (inputIndex >= gp.buttons.length) {
  214. device[inputIndex] = gp.axes[inputIndex - gp.buttons.length].valueOf();
  215. }
  216. else {
  217. device[inputIndex] = gp.buttons[inputIndex].value;
  218. }
  219. }
  220. }
  221. }
  222. }