UNPKG

@epicgames-ps/lib-pixelstreamingfrontend-ue5.4

Version:
315 lines (288 loc) 12.2 kB
// Copyright Epic Games, Inc. All Rights Reserved. import { Logger } from '../Logger/Logger'; import { StreamMessageController } from '../UeInstanceMessage/StreamMessageController'; import { EventListenerTracker } from '../Util/EventListenerTracker'; import { Controller } from './GamepadTypes'; /** * The class that handles the functionality of gamepads and controllers */ export class GamePadController { controllers: Array<Controller>; requestAnimationFrame: (callback: FrameRequestCallback) => number; toStreamerMessagesProvider: StreamMessageController; // Utility for keeping track of event handlers and unregistering them private gamePadEventListenerTracker = new EventListenerTracker(); /** * @param toStreamerMessagesProvider - Stream message instance */ constructor(toStreamerMessagesProvider: StreamMessageController) { this.toStreamerMessagesProvider = toStreamerMessagesProvider; this.requestAnimationFrame = ( window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.requestAnimationFrame ).bind(window); const browserWindow = window as Window; const onBeforeUnload = (ev: Event) => this.onBeforeUnload(ev); window.addEventListener('beforeunload', onBeforeUnload); if ('GamepadEvent' in browserWindow) { const onGamePadConnected = (ev: GamepadEvent) => this.gamePadConnectHandler(ev); const onGamePadDisconnected = (ev: GamepadEvent) => this.gamePadDisconnectHandler(ev); window.addEventListener('gamepadconnected', onGamePadConnected); window.addEventListener('gamepaddisconnected', onGamePadDisconnected); this.gamePadEventListenerTracker.addUnregisterCallback( () => window.removeEventListener('gamepadconnected', onGamePadConnected) ); this.gamePadEventListenerTracker.addUnregisterCallback( () => window.removeEventListener('gamepaddisconnected', onGamePadDisconnected) ); } else if ('WebKitGamepadEvent' in browserWindow) { const onWebkitGamePadConnected = (ev: GamepadEvent) => this.gamePadConnectHandler(ev); const onWebkitGamePadDisconnected = (ev: GamepadEvent) => this.gamePadDisconnectHandler(ev); window.addEventListener('webkitgamepadconnected', onWebkitGamePadConnected); window.addEventListener('webkitgamepaddisconnected', onWebkitGamePadDisconnected); this.gamePadEventListenerTracker.addUnregisterCallback( () => window.removeEventListener('webkitgamepadconnected', onWebkitGamePadConnected) ); this.gamePadEventListenerTracker.addUnregisterCallback( () => window.removeEventListener('webkitgamepaddisconnected', onWebkitGamePadDisconnected) ); } this.controllers = []; if (navigator.getGamepads) { for (const gamepad of navigator.getGamepads()) { if (gamepad) { this.gamePadConnectHandler(new GamepadEvent('gamepadconnected', { gamepad })); } } } } /** * Unregisters all event handlers */ unregisterGamePadEvents() { this.gamePadEventListenerTracker.unregisterAll(); for(const controller of this.controllers) { if(controller.id !== undefined) { this.onGamepadDisconnected(controller.id); } } this.controllers = []; this.onGamepadConnected = () => { /* */ }; this.onGamepadDisconnected = () => { /* */ }; } /** * Connects the gamepad handler * @param gamePadEvent - the activating gamepad event */ gamePadConnectHandler(gamePadEvent: GamepadEvent) { Logger.Log(Logger.GetStackTrace(), 'Gamepad connect handler', 6); const gamepad = gamePadEvent.gamepad; const temp: Controller = { currentState: gamepad, prevState: gamepad, id: undefined }; this.controllers.push(temp); this.controllers[gamepad.index].currentState = gamepad; this.controllers[gamepad.index].prevState = gamepad; Logger.Log( Logger.GetStackTrace(), 'gamepad: ' + gamepad.id + ' connected', 6 ); window.requestAnimationFrame(() => this.updateStatus()); this.onGamepadConnected(); } /** * Disconnects the gamepad handler * @param gamePadEvent - the activating gamepad event */ gamePadDisconnectHandler(gamePadEvent: GamepadEvent) { Logger.Log(Logger.GetStackTrace(), 'Gamepad disconnect handler', 6); Logger.Log( Logger.GetStackTrace(), 'gamepad: ' + gamePadEvent.gamepad.id + ' disconnected', 6 ); const deletedController = this.controllers[gamePadEvent.gamepad.index]; delete this.controllers[gamePadEvent.gamepad.index]; this.controllers = this.controllers.filter( (controller) => controller !== undefined ); this.onGamepadDisconnected(deletedController.id); } /** * Scan for connected gamepads */ scanGamePads() { const gamepads = navigator.getGamepads ? navigator.getGamepads() : navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []; for (let i = 0; i < gamepads.length; i++) { if (gamepads[i] && gamepads[i].index in this.controllers) { this.controllers[gamepads[i].index].currentState = gamepads[i]; } } } /** * Updates the status of the gamepad and sends the inputs */ updateStatus() { this.scanGamePads(); const toStreamerHandlers = this.toStreamerMessagesProvider.toStreamerHandlers; // Iterate over multiple controllers in the case the multiple gamepads are connected for (const controller of this.controllers) { // If we haven't received an id (possible if using an older version of UE), return to original functionality const controllerIndex = (controller.id === undefined) ? this.controllers.indexOf(controller) : controller.id; const currentState = controller.currentState; for (let i = 0; i < controller.currentState.buttons.length; i++) { const currentButton = controller.currentState.buttons[i]; const previousButton = controller.prevState.buttons[i]; if (currentButton.pressed) { // press if (i == gamepadLayout.LeftTrigger) { // UEs left analog has a button index of 5 toStreamerHandlers.get('GamepadAnalog')([ controllerIndex, 5, currentButton.value ]); } else if (i == gamepadLayout.RightTrigger) { // UEs right analog has a button index of 6 toStreamerHandlers.get('GamepadAnalog')([ controllerIndex, 6, currentButton.value ]); } else { toStreamerHandlers.get('GamepadButtonPressed')([ controllerIndex, i, previousButton.pressed ? 1 : 0 ]); } } else if (!currentButton.pressed && previousButton.pressed) { // release if (i == gamepadLayout.LeftTrigger) { // UEs left analog has a button index of 5 toStreamerHandlers.get('GamepadAnalog')([ controllerIndex, 5, 0 ]); } else if (i == gamepadLayout.RightTrigger) { // UEs right analog has a button index of 6 toStreamerHandlers.get('GamepadAnalog')([ controllerIndex, 6, 0 ]); } else { toStreamerHandlers.get('GamepadButtonReleased')([ controllerIndex, i, 0 ]); } } } // Iterate over gamepad axes (we will increment in lots of 2 as there is 2 axes per stick) for (let i = 0; i < currentState.axes.length; i += 2) { // Horizontal axes are even numbered const x = parseFloat(currentState.axes[i].toFixed(4)); // Vertical axes are odd numbered // https://w3c.github.io/gamepad/#remapping Gamepad browser side standard mapping has positive down, negative up. This is downright disgusting. So we fix it. const y = -parseFloat(currentState.axes[i + 1].toFixed(4)); // UE's analog axes follow the same order as the browsers, but start at index 1 so we will offset as such toStreamerHandlers.get('GamepadAnalog')([ controllerIndex, i + 1, x ]); // Horizontal axes, only offset by 1 toStreamerHandlers.get('GamepadAnalog')([ controllerIndex, i + 2, y ]); // Vertical axes, offset by two (1 to match UEs axes convention and then another 1 for the vertical axes) } this.controllers[controllerIndex].prevState = currentState; } if (this.controllers.length > 0) { this.requestAnimationFrame(() => this.updateStatus()); } } onGamepadResponseReceived(gamepadId: number) { for(const controller of this.controllers) { if(controller.id === undefined) { controller.id = gamepadId; break; } } } /** * Event to send the gamepadconnected message to the application */ onGamepadConnected() { // Default Functionality: Do Nothing } /** * Event to send the gamepaddisconnected message to the application */ // eslint-disable-next-line @typescript-eslint/no-unused-vars onGamepadDisconnected(controllerIdx: number) { // Default Functionality: Do Nothing } onBeforeUnload(ev: Event) { // When a user navigates away from the page, we need to inform UE of all the disconnecting // controllers for(const controller of this.controllers) { this.onGamepadDisconnected(controller.id); } } } /** * Additional types for Window and Navigator */ declare global { interface Window { mozRequestAnimationFrame(callback: FrameRequestCallback): number; webkitRequestAnimationFrame(callback: FrameRequestCallback): number; } interface Navigator { webkitGetGamepads(): Gamepad[]; } } /** * Gamepad layout codes enum */ export enum gamepadLayout { RightClusterBottomButton = 0, RightClusterRightButton = 1, RightClusterLeftButton = 2, RightClusterTopButton = 3, LeftShoulder = 4, RightShoulder = 5, LeftTrigger = 6, RightTrigger = 7, SelectOrBack = 8, StartOrForward = 9, LeftAnalogPress = 10, RightAnalogPress = 11, LeftClusterTopButton = 12, LeftClusterBottomButton = 13, LeftClusterLeftButton = 14, LeftClusterRightButton = 15, CentreButton = 16, // Axes LeftStickHorizontal = 0, LeftStickVertical = 1, RightStickHorizontal = 2, RightStickVertical = 3 }