UNPKG

virtual-gamepad-lib

Version:

Emulate and display virtual and real gamepads on the web

192 lines (191 loc) 17 kB
import { gamepadButtonType, gamepadDirection, gamepadEmulationState, gpadButtonMapType } from './enums.js'; /** Extends the browser Gamepad interface to include an emulation property that exposes how this gamepad is being emulated (or not) * This Gamepad API interface defines an individual gamepad or other controller, allowing access to information such as button presses, axis positions, and id. Normally Available only in secure webpage contexts. */ export interface EGamepad extends Omit<Gamepad, "vibrationActuator"> { readonly emulation: gamepadEmulationState | undefined; readonly displayId: string; hapticActuators?: GamepadHapticActuator[]; vibrationActuator?: GamepadHapticActuator; } export interface EGamepadPrivateData { /** true if this e-gamepad was created in overlay mode */ overlayMode: boolean; /** stores a refrence to a function to cleanup all event listeners created for controlling buttons on this egamepad */ removeButtonListenersFunc?: (() => void); /** stores a refrence to a function to cleanup all event listeners created for controlling axes on this egamepad */ removeJoystickListenersFunc?: (() => void); } /** Extends the browser Gamepad Event interface to use an {@link EGamepad} type instead of a Gamepad */ export interface EGamepadEvent extends Omit<GamepadEvent, "gamepad"> { gamepad: EGamepad; } export interface OnOffButtonTouchConfig { type: gamepadButtonType.onOff; /** Taps/clicks/hovers on this element will trigger events for this button on the emulated gamepad. */ tapTarget: (HTMLElement | SVGElement); /** The index of the gamepad button this tap target should control * corresponds to the button index as found in the {@link EGamepad.buttons} array * Can also pass a value from the {@link standardGpadButtonMap}, {@link xboxButtonMap}, etc... */ buttonIndex: number | gpadButtonMapType; /** Allows this tap target to control multiple gamepad buttons at once * useful for e.g. allowing diagonal directions of a dpad. * Overrides buttonIndex - Each number in the array is the index corresponding to a button in the {@link EGamepad.buttons} array that this tap target will control */ buttonIndexes?: (number | gpadButtonMapType)[]; /** Should this button lock the cursor once it is preseed (mouse or touch), such that NO pointer/mouse/touch events are fired with that pointer on any other elements on the page unil the finger leaves the screen or mouse lets go. * This option also prevents this button from reacting when a press starts on another button or page element and then the pointer/touch moves over the tap target of this button while being held down. */ lockTargetWhilePressed?: boolean; } export interface VariableButtonTouchConfig { type: gamepadButtonType.variable; /** The element where a tap or mouse click must start to control this variable button. * The pointer does not need to remain within this element while dragging to continue controlling the variable button as long as the mouse / touch / pointer is held down and lockTargetWhilePressed is true */ tapTarget: (HTMLElement | SVGElement); /** The index of the gamepad button this tap target should control * corresponds to the button index as found in the {@link EGamepad.buttons} array * Can also pass a value from the {@link standardGpadButtonMap}, {@link xboxButtonMap}, etc... */ buttonIndex: number | gpadButtonMapType; /** Allows this tap target to control multiple gamepad buttons at once * useful for e.g. allowing diagonal directions of a dpad. * Overrides buttonIndex - Each number in the array is the index corresponding to a button in the {@link EGamepad.buttons} array that this tap target will control */ buttonIndexes?: (number | gpadButtonMapType)[]; /** The distance drag gesture must go in pixels to appear as a fully pressed button: value = 1 */ dragDistance: number; /** Should this variable button lock the cursor once a drag gesture has started, such that NO pointer/mouse/touch events are fired with that pointer on any other elements on the page unil the gesture is finished (finger leaves the screen or mouse lets go) * This option also prevents this button from reacting when a press starts on another button or page element and then the pointer/touch moves over the tap target of this button while being held down. */ lockTargetWhilePressed?: boolean; /** What drag/movement gesture directions should result in the button value of this varaible button increasing * Typically only one direction will be set to true, but you can set multiple to true if you want to. See: {@link gamepadDirection} */ directions: { [gamepadDirection.up]?: boolean; [gamepadDirection.down]?: boolean; [gamepadDirection.left]?: boolean; [gamepadDirection.right]?: boolean; }; } export type ButtonTouchConfig = OnOffButtonTouchConfig | VariableButtonTouchConfig; export interface JoystickTouchConfig { /** The element where a tap or mouse click must start to control this joystick * The pointer does not need to remain within this element while dragging to continue controlling the joystick as long as the mouse / touch / pointer is held down */ tapTarget: HTMLElement | SVGElement; /** The distance a drag gesture must go in pixels to register as a full 1 or -1 on the x or y axis (Alternatively, the distance from the touch start posisiton that the joystick can be dragged) */ dragDistance: number; /** What emulated gamepad axis (the index in {@link EGamepad.axes}) to drive When the virtual joystick is dragged left (-) and right (+) */ xAxisIndex?: number; /** What emulated gamepad axis (the index in {@link EGamepad.axes}) to drive When the virtual joystick is dragged up (-) and down (+) */ yAxisIndex?: number; /** Should the joystick lock the cursor once a drag gesture has started, such that NO pointer/mouse/touch events are fired with that pointer on any other elements on the page unil the gesture is finished (finger leaves the screen or mouse lets go) */ lockTargetWhilePressed?: boolean; /** What drag/movement directions does this joystick support. See: {@link gamepadDirection} */ directions: { [gamepadDirection.up]?: boolean; [gamepadDirection.down]?: boolean; [gamepadDirection.left]?: boolean; [gamepadDirection.right]?: boolean; }; } /** A number of typical buttons recognized by Gamepad API and mapped to * standard controls. Any extraneous buttons will have larger indexes. */ export declare const DEFAULT_GPAD_BUTTON_COUNT: number; /** A number of typical axes recognized by Gamepad API and mapped to * standard controls. Any extraneous axes will have larger indexes.*/ export declare const DEFAULT_GPAD_AXIS_COUNT: number; /** Class to handle emulated gamepads and injecting them into the browser getGamepads() and event listener APIs. * > You **MUST** call `new GamepadEmulator()` before any other libraries or functions use or store the browser gamepad api for it to function! */ export declare class GamepadEmulator { /** a static class variable to tell if any other instances of the GamepadEmulator class are active, and throw an error if a new one is created */ protected static instanceRunning: boolean; /** stores a reference to the real, unpatched navigator.getGamepads() function (if available) **/ getNativeGamepads?: () => (Gamepad | null)[]; /** the threshold above which a variable button is considered a "pressed" button */ protected buttonPressThreshold: number; /** A list of the indecies of all the real gamepads that have ever been conected durring this browser session, where the array index is the "gamepadIndex" returned by the native gamepad api, and the value is the index that gamepad should be exposed at in the emulated getGamepads() array */ protected realGpadToPatchedIndexMap: number[]; /** the reverse mapping array of {@link GamepadEmulator.realGpadToPatchedIndexMap} */ protected patchedGpadToRealIndexMap: number[]; /** A list of all the emulated gamepads, where the index is the "gamepadIndex" passed when AddEmulatedGamepad() was called (Ie: there may be holes in the list), * when an emulated gamepad is "connected" ie: call AddEmulatedGamepad(), it is added to this list at the provided index (or returns false if there is already an emulated gamepad at that index). * when an emulated gamepad is "disconnected" ie: call removeEmulatedGamepad(), it is removed from this list provided index (or returns false if there is already an emulated gamepad at that index). */ protected emulatedGamepads: (EGamepad | null)[]; /** A list that mirrors the structure of {@link GamepadEmulator.emulatedGamepads}, but contains data internal to this class for keeping track of their state */ protected emulatedGamepadsMetadata: (EGamepadPrivateData | null)[]; /** stores the function returned by monkeyPatchGamepadEvents() to undo the gamepad event monkey patch **/ protected undoEventPatch: () => void; /** Creates a new GamepadEmulator object and monkey patches the browser getGamepads() API and gamepad events to report emulated gamepads * - **MUST** be called before any other libraries or functions use or store the browser gamepad api! * @param buttonPressThreshold - the threshold above which a variable button is considered a "pressed" button */ constructor(buttonPressThreshold: number); /** @returns true if the gamepad api is supported natively by the browser context */ gamepadApiNativelySupported(): boolean; /** creates a new emmulated gamepad at the given index as would be read in navigator.getGamepads * @param {number} gpadIndex - the index of the gamepad to create, pass null to create a new gamepad at the next available index * @param {string} overlayMode - if a real gamepad is connected at the same index as this emulated one and overlayMode is true, the emulated gamepad values will get merged or overlayed on the real gamepad button and axis values, otherwise the emulated gamepad will be shifted to the next available index (appear as a separate gamepad from the real gamepad) * @param {number} buttonCount - normally 18, the number of buttons on the gamepad * @param {number} axisCount - normally 4, the number of axes on the gamepad */ AddEmulatedGamepad(gpadIndex: number, overlayMode: boolean, buttonCount?: number, axisCount?: number): EGamepad | false; /** removes the emmulated gamepad at the passed index as would be read from the list in navigator.getGamepads * @param {number} gpadIndex - the index of the gamepad to remove */ RemoveEmulatedGamepad(gpadIndex: number): void; /** emulates pressing a button on an emulated gamepad at the given gamepad button index * @param {number} gpadIndex - the index of the emulated gamepad (as returned by navigator.getGamepads()) to press the button on * @param {number} buttonIndex - the index of the button to press on the gamepad - pass an array of indexes to control multiple buttons at once * @param {number} value - the value to set the button to between 0 and 1 (0 = not pressed, 1 = fully pressed, 0.5 = half pressed) if this value is greater than the pressedThreshold from the constructor, the button will be considered pressed * @param {boolean} touched - whether the button is considered "touched" or not, a "pressed" button is always considered "touched" */ PressButton(gpadIndex: number, buttonIndex: number | number[], value: number, touched?: boolean): void; /** emulates moving an axis on the gamepad at the given axis index * @param gpadIndex - the index of the emulated gamepad to move the axis on * @param axisIndex - the index of the axis to move * @param value - the value to set the axis to between -1 and 1 (0 = center, -1 = left/up, 1 = right/down) */ MoveAxis(gpadIndex: number, axisIndex: number, value: number): void; /** add event listeners to the html/svg button elements of an onscreen gamepad to emulate gamepad input when touched, clicked or dragged * @param gpadIndex - the index of the emulated gamepad to register events for * @param buttonConfigs - an array of config objects that set how each of the buttons on the onscreen gamepad should behave, and how they map to the emulated gamepad buttons. */ AddButtonTouchEventListeners(gpadIndex: number, buttonConfigs: (ButtonTouchConfig)[]): void; /** add event listeners to the html/svg joystick elements of an onscreen gamepad to emulate gamepad input when dragged with a mouse, touch or pen. * @param gpadIndex - the index of the emulated gamepad to register events for * @param joystickConfigs - an array of config objects that set how each of the joysticks on the onscreen gamepad should behave, and how they map to the emulated gamepad axes. */ AddJoystickTouchEventListeners(gpadIndex: number, joystickConfigs: JoystickTouchConfig[]): void; /** removes event listeners added with AddButtonTouchEventListeners() * @param gpadIndex - the index of the emulated gamepad to un-register events for */ ClearButtonTouchEventListeners(gpadIndex: number): void; /** removes event listeners added with AddJoystickTouchEventListeners() * @param gpadIndex - the index of the emulated gamepad to un-register events for */ ClearJoystickTouchEventListeners(gpadIndex: number): void; protected AddDragControlListener(config: JoystickTouchConfig, callback: (touched: boolean, xValue: number, yValue: number) => void): () => void; /** returns copy of the passed Gamepad object * The axies and buttons arrays are deep copied. * Every other property is a shallow copy * @param original - the gamepad object to copy */ protected cloneGamepad(original: EGamepad | Gamepad | null): EGamepad | null; /** Searches for the next available index a new emulated gamepad could go and returns that index * this means no emulated gamepad is at that index and either the no real gamepad is at that index, or a real gamepad is at that index, but the @param overlayMode is true. */ protected nextEmptyEGpadIndex(overlayMode: boolean): number; /** Searches for the next available index a freshly connected real gamepad could go and returns that index * this means no real gamepad is mapped to that index and either no emulated gamepad is at that index, or the emulated gamepad is in overlay mode * @param startingIndex the index to start searching from */ protected nextEmptyRealGpadIndex(startingIndex: number): number; /** Intercepts gamepadconnected & gamepaddisconnected events and re-sends them with the correct gamepad indecies */ protected monkeyPatchGamepadEvents(): () => void; /** overwrite the browser gamepad api getGamepads() to return the emulated gamepad data for gamepad indexes corresponding to emulated gamepads * if a real gamepad is found with the same index value as an emulated gamepad, the the navigator.getGamepads() list will either shift the emulated gamepad's index up to make room for the real gamepad when (emulatedGamepad.overlayMode = false), * or it will return the emulated gamepad "overlayed" on the real one where buttons pressed or axes moved on both the real gamepad and the emulated one will show up on that gamepad. */ protected monkeyPatchGetGamepads(): void; /** (destructor) - Cleans up any event listeners made by this class and restores the normal navigator.getGamepad() function and gamepad events */ cleanup(): void; /** @deprecated AddDisplayButtonEventListeners is now called AddButtonTouchEventListeners */ AddDisplayButtonEventListeners: (gpadIndex: number, buttonConfigs: (ButtonTouchConfig)[]) => void; /** @deprecated AddDisplayJoystickEventListeners is now called AddJoystickTouchEventListeners */ AddDisplayJoystickEventListeners: (gpadIndex: number, joystickConfigs: JoystickTouchConfig[]) => void; /** @deprecated ClearDisplayButtonEventListeners is now called ClearButtonTouchEventListeners */ ClearDisplayButtonEventListeners: (gpadIndex: number) => void; /** @deprecated ClearDisplayJoystickEventListeners is now called ClearJoystickTouchEventListeners */ ClearDisplayJoystickEventListeners: (gpadIndex: number) => void; } /** @deprecated ButtonConfig is now called ButtonTouchConfig */ export type ButtonConfig = ButtonTouchConfig; /** @deprecated JoystickConfig is now called JoystickTouchConfig */ export type JoystickConfig = JoystickTouchConfig; /** @deprecated OnOffButtonConfig is now called OnOffButtonTouchConfig */ export type OnOffButtonConfig = OnOffButtonTouchConfig; /** @deprecated VariableButtonConfig is now called VariableButtonTouchConfig */ export type VariableButtonConfig = VariableButtonTouchConfig;