deviceInputSystem.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import { Engine } from '../Engines/engine';
  2. import { IDisposable } from '../scene';
  3. import { Nullable } from '../types';
  4. import { DeviceType } from './InputDevices/deviceEnums';
  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. /**
  13. * Callback to be triggered when a device is connected
  14. */
  15. public onDeviceConnected: (deviceType: DeviceType, deviceSlot: number) => void = () => { };
  16. /**
  17. * Callback to be triggered when a device is disconnected
  18. */
  19. public onDeviceDisconnected: (deviceType: DeviceType, deviceSlot: number) => void = () => { };
  20. /**
  21. * Callback to be triggered when event driven input is updated
  22. */
  23. public onInputChanged: (deviceType: DeviceType, deviceSlot: number, inputIndex: number, previousState: Nullable<number>, currentState: Nullable<number>) => void;
  24. // Private Members
  25. private _inputs: Array<Array<Array<Nullable<number>>>> = [];
  26. private _gamepads: Array<DeviceType>;
  27. private _keyboardActive: boolean = false;
  28. private _pointerActive: boolean = false;
  29. private _elementToAttachTo: HTMLElement;
  30. private _keyboardDownEvent = (evt: any) => { };
  31. private _keyboardUpEvent = (evt: any) => { };
  32. private _pointerMoveEvent = (evt: any) => { };
  33. private _pointerDownEvent = (evt: any) => { };
  34. private _pointerUpEvent = (evt: any) => { };
  35. private _gamepadConnectedEvent = (evt: any) => { };
  36. private _gamepadDisconnectedEvent = (evt: any) => { };
  37. private static _MAX_KEYCODES: number = 255;
  38. private static _MAX_POINTER_INPUTS: number = 7;
  39. /**
  40. * Default Constructor
  41. * @param engine - engine to pull input element from
  42. */
  43. constructor(engine: Engine) {
  44. const inputElement = engine.getInputElement();
  45. if (inputElement) {
  46. this._elementToAttachTo = inputElement;
  47. this._handleKeyActions();
  48. this._handlePointerActions();
  49. this._handleGamepadActions();
  50. }
  51. }
  52. // Public functions
  53. /**
  54. * Checks for current device input value, given an id and input index
  55. * @param deviceName Id of connected device
  56. * @param inputIndex Index of device input
  57. * @returns Current value of input
  58. */
  59. /**
  60. * Checks for current device input value, given an id and input index
  61. * @param deviceType Enum specifiying device type
  62. * @param deviceSlot "Slot" or index that device is referenced in
  63. * @param inputIndex Id of input to be checked
  64. * @returns Current value of input
  65. */
  66. public pollInput(deviceType: DeviceType, deviceSlot: number, inputIndex: number): Nullable<number> {
  67. const device = this._inputs[deviceType][deviceSlot];
  68. if (!device) {
  69. throw `Unable to find device ${DeviceType[deviceType]}`;
  70. }
  71. this._updateDevice(deviceType, deviceSlot, inputIndex);
  72. if (device[inputIndex] === undefined) {
  73. throw `Unable to find input ${inputIndex} for device ${DeviceType[deviceType]} in slot ${deviceSlot}`;
  74. }
  75. return device[inputIndex];
  76. }
  77. /**
  78. * Dispose of all the eventlisteners
  79. */
  80. public dispose() {
  81. // Keyboard Events
  82. if (this._keyboardActive) {
  83. window.removeEventListener("keydown", this._keyboardDownEvent);
  84. window.removeEventListener("keyup", this._keyboardUpEvent);
  85. }
  86. // Pointer Events
  87. if (this._pointerActive) {
  88. this._elementToAttachTo.removeEventListener("pointermove", this._pointerMoveEvent);
  89. this._elementToAttachTo.removeEventListener("pointerdown", this._pointerDownEvent);
  90. this._elementToAttachTo.removeEventListener("pointerup", this._pointerUpEvent);
  91. }
  92. // Gamepad Events
  93. window.removeEventListener("gamepadconnected", this._gamepadConnectedEvent);
  94. window.removeEventListener("gamepaddisconnected", this._gamepadDisconnectedEvent);
  95. }
  96. // Private functions
  97. /**
  98. * Add device and inputs to device array
  99. * @param deviceType Enum specifiying device type
  100. * @param deviceSlot "Slot" or index that device is referenced in
  101. * @param numberOfInputs Number of input entries to create for given device
  102. */
  103. private _registerDevice(deviceType: DeviceType, deviceSlot: number, numberOfInputs: number) {
  104. if (!this._inputs[deviceType]) {
  105. this._inputs[deviceType] = [];
  106. }
  107. if (!this._inputs[deviceType][deviceSlot]) {
  108. const device = new Array<Nullable<number>>(numberOfInputs);
  109. for (let i = 0; i < numberOfInputs; i++) {
  110. device[i] = null;
  111. }
  112. this._inputs[deviceType][deviceSlot] = device;
  113. this.onDeviceConnected(deviceType, deviceSlot);
  114. }
  115. }
  116. /**
  117. * Given a specific device name, remove that device from the device map
  118. * @param deviceType Enum specifiying device type
  119. * @param deviceSlot "Slot" or index that device is referenced in
  120. */
  121. private _unregisterDevice(deviceType: DeviceType, deviceSlot: number) {
  122. if (this._inputs[deviceType][deviceSlot]) {
  123. delete this._inputs[deviceType][deviceSlot];
  124. this.onDeviceDisconnected(deviceType, deviceSlot);
  125. }
  126. }
  127. /**
  128. * Handle all actions that come from keyboard interaction
  129. */
  130. private _handleKeyActions() {
  131. this._keyboardDownEvent = ((evt) => {
  132. if (!this._keyboardActive) {
  133. this._keyboardActive = true;
  134. this._registerDevice(DeviceType.Keyboard, 0, DeviceInputSystem._MAX_KEYCODES);
  135. }
  136. const kbKey = this._inputs[DeviceType.Keyboard][0];
  137. if (kbKey) {
  138. if (this.onInputChanged) {
  139. this.onInputChanged(DeviceType.Keyboard, 0, evt.keyCode, kbKey[evt.keyCode], 1);
  140. }
  141. kbKey[evt.keyCode] = 1;
  142. }
  143. });
  144. this._keyboardUpEvent = ((evt) => {
  145. const kbKey = this._inputs[DeviceType.Keyboard][0];
  146. if (kbKey) {
  147. if (this.onInputChanged) {
  148. this.onInputChanged(DeviceType.Keyboard, 0, evt.keyCode, kbKey[evt.keyCode], 0);
  149. }
  150. kbKey[evt.keyCode] = 0;
  151. }
  152. });
  153. window.addEventListener("keydown", this._keyboardDownEvent);
  154. window.addEventListener("keyup", this._keyboardUpEvent);
  155. }
  156. /**
  157. * Handle all actions that come from pointer interaction
  158. */
  159. private _handlePointerActions() {
  160. this._pointerMoveEvent = ((evt) => {
  161. const deviceType = (evt.pointerType == "mouse") ? DeviceType.Mouse : DeviceType.Touch;
  162. const deviceSlot = (evt.pointerType == "mouse") ? 0 : evt.pointerId;
  163. if (!this._inputs[deviceType]) {
  164. this._inputs[deviceType] = [];
  165. }
  166. if (!this._inputs[deviceType][deviceSlot]) {
  167. this._pointerActive = true;
  168. this._registerDevice(deviceType, deviceSlot, DeviceInputSystem._MAX_POINTER_INPUTS);
  169. }
  170. const pointer = this._inputs[deviceType][deviceSlot];
  171. if (pointer) {
  172. if (this.onInputChanged) {
  173. this.onInputChanged(deviceType, deviceSlot, 0, pointer[0], evt.clientX);
  174. this.onInputChanged(deviceType, deviceSlot, 1, pointer[1], evt.clientY);
  175. }
  176. pointer[0] = evt.clientX;
  177. pointer[1] = evt.clientY;
  178. }
  179. });
  180. this._pointerDownEvent = ((evt) => {
  181. const deviceType = (evt.pointerType == "mouse") ? DeviceType.Mouse : DeviceType.Touch;
  182. const deviceSlot = (evt.pointerType == "mouse") ? 0 : evt.pointerId;
  183. if (!this._inputs[deviceType]) {
  184. this._inputs[deviceType] = [];
  185. }
  186. if (!this._inputs[deviceType][deviceSlot]) {
  187. this._pointerActive = true;
  188. this._registerDevice(deviceType, deviceSlot, DeviceInputSystem._MAX_POINTER_INPUTS);
  189. }
  190. const pointer = this._inputs[deviceType][deviceSlot];
  191. if (pointer) {
  192. if (this.onInputChanged) {
  193. this.onInputChanged(deviceType, deviceSlot, 0, pointer[0], evt.clientX);
  194. this.onInputChanged(deviceType, deviceSlot, 1, pointer[1], evt.clientY);
  195. this.onInputChanged(deviceType, deviceSlot, evt.button + 2, pointer[evt.button + 2], 1);
  196. }
  197. pointer[0] = evt.clientX;
  198. pointer[1] = evt.clientY;
  199. pointer[evt.button + 2] = 1;
  200. }
  201. });
  202. this._pointerUpEvent = ((evt) => {
  203. const deviceType = (evt.pointerType == "mouse") ? DeviceType.Mouse : DeviceType.Touch;
  204. const deviceSlot = (evt.pointerType == "mouse") ? 0 : evt.pointerId;
  205. const pointer = this._inputs[deviceType][deviceSlot];
  206. if (pointer) {
  207. if (this.onInputChanged) {
  208. this.onInputChanged(deviceType, deviceSlot, evt.button + 2, pointer[evt.button + 2], 0);
  209. }
  210. pointer[evt.button + 2] = 0;
  211. }
  212. // We don't want to unregister the mouse because we may miss input data when a mouse is moving after a click
  213. if (evt.pointerType != "mouse") {
  214. this._unregisterDevice(deviceType, deviceSlot);
  215. }
  216. });
  217. this._elementToAttachTo.addEventListener("pointermove", this._pointerMoveEvent);
  218. this._elementToAttachTo.addEventListener("pointerdown", this._pointerDownEvent);
  219. this._elementToAttachTo.addEventListener("pointerup", this._pointerUpEvent);
  220. }
  221. /**
  222. * Handle all actions that come from gamepad interaction
  223. */
  224. private _handleGamepadActions() {
  225. this._gamepadConnectedEvent = ((evt: any) => {
  226. const deviceType = this._getGamepadDeviceType(evt.gamepad.id);
  227. const deviceSlot = evt.gamepad.index;
  228. this._registerDevice(deviceType, deviceSlot, evt.gamepad.buttons.length + evt.gamepad.axes.length);
  229. this._gamepads = this._gamepads || new Array<string>(evt.gamepad.index + 1);
  230. this._gamepads[deviceSlot] = deviceType;
  231. });
  232. this._gamepadDisconnectedEvent = ((evt: any) => {
  233. if (this._gamepads) {
  234. const deviceType = this._getGamepadDeviceType(evt.gamepad.id);
  235. const deviceSlot = evt.gamepad.index;
  236. this._unregisterDevice(deviceType, deviceSlot);
  237. delete this._gamepads[deviceSlot];
  238. }
  239. });
  240. window.addEventListener("gamepadconnected", this._gamepadConnectedEvent);
  241. window.addEventListener("gamepaddisconnected", this._gamepadDisconnectedEvent);
  242. }
  243. /**
  244. * Update all non-event based devices with each frame
  245. * @param deviceType Enum specifiying device type
  246. * @param deviceSlot "Slot" or index that device is referenced in
  247. * @param inputIndex Id of input to be checked
  248. */
  249. private _updateDevice(deviceType: DeviceType, deviceSlot: number, inputIndex: number) {
  250. // Gamepads
  251. const gp = navigator.getGamepads()[deviceSlot];
  252. if (gp && deviceType == this._gamepads[deviceSlot]) {
  253. const device = this._inputs[deviceType][deviceSlot];
  254. if (inputIndex >= gp.buttons.length) {
  255. device[inputIndex] = gp.axes[inputIndex - gp.buttons.length].valueOf();
  256. }
  257. else {
  258. device[inputIndex] = gp.buttons[inputIndex].value;
  259. }
  260. }
  261. }
  262. /**
  263. * Gets DeviceType from the device name
  264. * @param deviceName Name of Device from DeviceInputSystem
  265. * @returns DeviceType enum value
  266. */
  267. private _getGamepadDeviceType(deviceName: string): DeviceType {
  268. if (deviceName.indexOf("054c") !== -1) { // DualShock 4 Gamepad
  269. return DeviceType.DualShock;
  270. }
  271. else if (deviceName.indexOf("Xbox One") !== -1 || deviceName.search("Xbox 360") !== -1 || deviceName.search("xinput") !== -1) { // Xbox Gamepad
  272. return DeviceType.Xbox;
  273. }
  274. else if (deviceName.indexOf("057e") !== -1) { // Switch Gamepad
  275. return DeviceType.Switch;
  276. }
  277. return DeviceType.Generic;
  278. }
  279. }