coherent-gameface-interaction-manager
Version:
Library for the most common UI interactions
479 lines (473 loc) • 17.9 kB
JavaScript
var gamepad = (() => {
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
// interaction-manager/src/utils/gamepad-mappings.js
var gamepad_mappings_default;
var init_gamepad_mappings = __esm({
"interaction-manager/src/utils/gamepad-mappings.js"() {
gamepad_mappings_default = {
FACE_BUTTON_DOWN: 0,
FACE_BUTTON_RIGHT: 1,
FACE_BUTTON_LEFT: 2,
FACE_BUTTON_TOP: 3,
LEFT_SHOULDER: 4,
RIGHT_SHOULDER: 5,
LEFT_SHOULDER_BOTTOM: 6,
RIGHT_SHOULDER_BOTTOM: 7,
SELECT: 8,
START: 9,
LEFT_ANALOGUE_STICK: 10,
RIGHT_ANALOGUE_STICK: 11,
PAD_UP: 12,
PAD_DOWN: 13,
PAD_LEFT: 14,
PAD_RIGHT: 15,
CENTER_BUTTON: 16,
aliases: {
"face-button-down": "FACE_BUTTON_DOWN",
"face-button-left": "FACE_BUTTON_LEFT",
"face-button-right": "FACE_BUTTON_RIGHT",
"face-button-top": "FACE_BUTTON_TOP",
"left-sholder": "LEFT_SHOULDER",
"right-sholder": "RIGHT_SHOULDER",
"left-sholder-bottom": "LEFT_SHOULDER_BOTTOM",
"right-sholder-bottom": "RIGHT_SHOULDER_BOTTOM",
select: "SELECT",
start: "START",
"left-analogue-stick": "LEFT_ANALOGUE_STICK",
"right-analogue-stick": "RIGHT_ANALOGUE_STICK",
"pad-up": "PAD_UP",
"pad-down": "PAD_DOWN",
"pad-left": "PAD_LEFT",
"pad-right": "PAD_RIGHT",
"center-button": "CENTER_BUTTON",
"playstation.x": "FACE_BUTTON_DOWN",
"playstation.square": "FACE_BUTTON_LEFT",
"playstation.circle": "FACE_BUTTON_RIGHT",
"playstation.triangle": "FACE_BUTTON_TOP",
"playstation.l1": "LEFT_SHOULDER",
"playstation.r1": "RIGHT_SHOULDER",
"playstation.l2": "LEFT_SHOULDER_BOTTOM",
"playstation.r2": "RIGHT_SHOULDER_BOTTOM",
"playstation.share": "SELECT",
"playstation.options": "START",
"playstation.l3": "LEFT_ANALOGUE_STICK",
"playstation.r3": "RIGHT_ANALOGUE_STICK",
"playstation.d-pad-up": "PAD_UP",
"playstation.d-pad-down": "PAD_DOWN",
"playstation.d-pad-left": "PAD_LEFT",
"playstation.d-pad-right": "PAD_RIGHT",
"playstation.center": "CENTER_BUTTON",
"xbox.a": "FACE_BUTTON_DOWN",
"xbox.x": "FACE_BUTTON_LEFT",
"xbox.b": "FACE_BUTTON_RIGHT",
"xbox.y": "FACE_BUTTON_TOP",
"xbox.lb": "LEFT_SHOULDER",
"xbox.rb": "RIGHT_SHOULDER",
"xbox.lt": "LEFT_SHOULDER_BOTTOM",
"xbox.rt": "RIGHT_SHOULDER_BOTTOM",
"xbox.view": "SELECT",
"xbox.menu": "START",
"xbox.left-thumbstick": "LEFT_ANALOGUE_STICK",
"xbox.right-thumbstick": "RIGHT_ANALOGUE_STICK",
"xbox.d-pad-up": "PAD_UP",
"xbox.d-pad-down": "PAD_DOWN",
"xbox.d-pad-left": "PAD_LEFT",
"xbox.d-pad-right": "PAD_RIGHT",
"xbox.center": "CENTER_BUTTON"
},
axisAliases: [
"right.joystick",
"left.joystick",
"left.joystick.down",
"left.joystick.up",
"left.joystick.left",
"left.joystick.right",
"right.joystick.down",
"right.joystick.up",
"right.joystick.left",
"right.joystick.right"
]
};
}
});
// interaction-manager/src/utils/global-object.js
var IM, global_object_default;
var init_global_object = __esm({
"interaction-manager/src/utils/global-object.js"() {
IM = class _IM2 {
// eslint-disable-next-line require-jsdoc
constructor() {
this.actions = [];
this.keyboardFunctions = [];
this.gamepadFunctions = [];
}
/**
* Initialize global object
*/
init() {
if (!window._IM)
window._IM = new _IM2();
}
/**
*
* @param {string[]} keys Array of key combinations
* @returns {string[]} Key combination from the _IM global object
*/
getKeys(keys) {
return _IM.keyboardFunctions.filter((keyFunction) => keyFunction.keys.every((key) => keys.includes(key)));
}
/**
*
* @param {string[]} keys Array of key combinations
* @returns {number} Index of key combination in _IM
*/
getKeysIndex(keys) {
return _IM.keyboardFunctions.findIndex((keyFunction) => keyFunction.keys.every((key) => keys.includes(key)));
}
/**
*
* @param {Array} actions Array of actions
* @param {string} type Type of action
* @returns {Object} Action from the _IM global object
*/
getGamepadAction({ actions, type }) {
return _IM.gamepadFunctions.find((gpFunc) => {
return gpFunc.actions.every((action) => actions.includes(action)) && gpFunc.type === type && gpFunc.actions.length === actions.length;
});
}
/**
*
* @param {Array} actions Array of actions
* @param {string} type Type of action
* @returns {Object} Action from the _IM global object
*/
getGamepadActions(actions) {
return _IM.gamepadFunctions.filter(
(gpFunc) => gpFunc.actions.every((action) => actions.includes(action)) && gpFunc.actions.length === actions.length
);
}
/**
*
* @param {Array} actions Array of actions
* @returns {number} Index of an action from the _IM global object
*/
getGamepadActionIndex(actions) {
return _IM.gamepadFunctions.findIndex((gpFunc) => gpFunc.actions.every((action) => actions.includes(action)));
}
/**
*
* @param {string} action Action to search for
* @returns {Object}
*/
getAction(action) {
return _IM.actions.find((actionObj) => actionObj.name === action);
}
/**
*
* @param {string} action Action to search for
* @returns {number}
*/
getActionIndex(action) {
return _IM.actions.findIndex((actionObj) => actionObj.name === action);
}
};
global_object_default = new IM();
}
});
// interaction-manager/src/lib_components/actions.js
var Actions, actions_default;
var init_actions = __esm({
"interaction-manager/src/lib_components/actions.js"() {
init_global_object();
Actions = class {
/**
* Register an action
* @param {string} action - Function alias that allows you to save functions and reuse them
* @param {function} callback
* @returns {void}
*/
register(action, callback) {
if (global_object_default.getAction(action))
return console.error(`The following action "${action}" is already registered!`);
_IM.actions.push({ name: action, callback });
}
/**
* Remove a registered action
* @param {string} action - Name of action you want to remove
* @returns {void}
*/
remove(action) {
const actionIndex = global_object_default.getActionIndex(action);
if (actionIndex === -1)
return console.error(`${action} is not a registered action!`);
_IM.actions.splice(actionIndex, 1);
}
/**
* Trigger an action
* @param {string} action - Name of action you want to execute
* @param {any} value - Value that is passed to the callback
* @returns {void}
*/
execute(action, value) {
const actionObject = global_object_default.getAction(action);
if (!actionObject)
return console.error(`${action} is not a registered action!`);
actionObject.callback(value);
}
};
actions_default = new Actions();
}
});
// interaction-manager/src/lib_components/gamepad.js
var AXIS_THRESHOLD, ACTION_TYPES, Gamepad, gamepad_default;
var init_gamepad = __esm({
"interaction-manager/src/lib_components/gamepad.js"() {
init_gamepad_mappings();
init_global_object();
init_actions();
AXIS_THRESHOLD = 0.9;
ACTION_TYPES = ["press", "hold"];
Gamepad = class {
// eslint-disable-next-line require-jsdoc
constructor() {
this.mappings = gamepad_mappings_default;
this.pollingStarted = false;
this.gamepadEnabled = false;
this.onGamepadConnected = this.onGamepadConnected.bind(this);
this.sanitizeAction = this.sanitizeAction.bind(this);
this.pollingInterval = 200;
this._pressedAction = null;
}
/**
* Allow gamepads to be connected
* @param {boolean} isEnabled
*/
set enabled(isEnabled) {
this.gamepadEnabled = isEnabled;
this.gamepadEnabled ? this.init() : this.deinit();
}
/**
* Attaches the event listeners for the gamepads
* @private
*/
init() {
window.addEventListener("gamepadconnected", this.onGamepadConnected);
}
/**
* Removes any attached event listeners for gamepads
* @private
*/
deinit() {
window.removeEventListener("gamepadconnected", this.onGamepadConnected);
}
/**
* Starts polling on the first connected
* @returns {void}
* @private
*/
onGamepadConnected() {
if (this.pollingStarted)
return;
this.pollingStarted = true;
this.startPolling();
}
/**
*
* @param {Object} options
* @param {string[] | number[]} options.actions - Action to trigger the callback. Can be name of button or joystick
* @param {'press' | 'hold'} options.type - The type of action to trigger the callback. The available options are hold and press.
* @param {function} options.callback - Callback to trigger on the set action
* @param {number} options.gamepadNumber - The number of the gamepad that you want to trigger the callback on. Use -1 for all gamepads
* @returns {void}
*/
on(options) {
options.actions = options.actions.map(this.sanitizeAction);
const isAxisAlias = this.mappings.axisAliases.some((alias) => options.actions.includes(alias));
if (!options.type || !ACTION_TYPES.includes(options.type))
options.type = "hold";
if (options.type === "press" && isAxisAlias) {
return console.error(`You can't use an axis action with a 'press' type!`);
}
if (options.actions.length > 1 && isAxisAlias) {
return console.error(`You can't use an axis action in a combination with a button action`);
}
if (global_object_default.getGamepadAction(options)) {
return console.error(
"You have already registered a callback for this action. If you want to overwrite it, remove it first with .off([actions])"
);
}
_IM.gamepadFunctions.push(options);
}
/**
* Removes registered actions
* @param {Array} actions - Array containing the action you want to remove
* @returns {void}
*/
off(actions) {
const actionsIndex = global_object_default.getGamepadActionIndex(actions.map(this.sanitizeAction));
if (actionsIndex === -1)
return console.error("You are trying to remove a non-existent action!");
_IM.gamepadFunctions.splice(actionsIndex, 1);
}
/**
* Loop that handles button presses and axis movement
* @returns {void}
* @private
*/
startPolling() {
const gamepads = navigator.getGamepads();
if (gamepads.length === 0) {
this.pollingStarted = false;
return;
}
gamepads.forEach((gamepad, index) => {
if (!gamepad)
return;
this.handleButtons(gamepad.buttons, index);
this.handleJoysticks(gamepad.axes);
});
setTimeout(this.startPolling.bind(this), this.pollingInterval);
}
/**
*
* @param {Object[]} buttons
* @private
*/
handleButtons(buttons) {
const pressedButtons = buttons.reduce(
(acc, el, index) => {
if (el.pressed) {
acc.buttonIndexes.push(index);
acc.buttons.push(el);
}
return acc;
},
{ buttonIndexes: [], buttons: [] }
);
const gamepadActions = global_object_default.getGamepadActions(pressedButtons.buttonIndexes);
if (!gamepadActions.length === 0)
return;
if (this._pressedAction) {
if (!gamepadActions.includes(this._pressedAction)) {
this.executeCallback(this._pressedAction, this._pressedAction.actions);
}
this._pressedAction = null;
}
gamepadActions.forEach((gamepadAction) => {
if (gamepadAction.type === "press") {
this._pressedAction = gamepadAction;
return;
}
this.executeCallback(gamepadAction, pressedButtons.buttons);
});
}
/* eslint-disable max-lines-per-function */
/**
*
* @param {number[]} axes
* @private
*/
handleJoysticks(axes) {
const joystickActions = this.getJoystickActions();
joystickActions.forEach((jAction) => {
switch (jAction.actions[0]) {
case "left.joystick":
return this.executeCallback(jAction, [axes[0], axes[1]]);
case "right.joystick":
return this.executeCallback(jAction, [axes[2], axes[3]]);
case "left.joystick.down":
if (axes[1] > AXIS_THRESHOLD)
return this.executeCallback(jAction, [axes[0], axes[1]]);
break;
case "left.joystick.up":
if (axes[1] < -AXIS_THRESHOLD)
return this.executeCallback(jAction, [axes[0], axes[1]]);
break;
case "left.joystick.left":
if (axes[0] < -AXIS_THRESHOLD)
return this.executeCallback(jAction, [axes[0], axes[1]]);
break;
case "left.joystick.right":
if (axes[0] > AXIS_THRESHOLD)
return this.executeCallback(jAction, [axes[0], axes[1]]);
break;
case "right.joystick.down":
if (axes[3] > AXIS_THRESHOLD)
return this.executeCallback(jAction, [axes[2], axes[3]]);
break;
case "right.joystick.up":
if (axes[3] < -AXIS_THRESHOLD)
return this.executeCallback(jAction, [axes[2], axes[3]]);
break;
case "right.joystick.left":
if (axes[2] < -AXIS_THRESHOLD)
return this.executeCallback(jAction, [axes[2], axes[3]]);
break;
case "right.joystick.right":
if (axes[2] > AXIS_THRESHOLD)
return this.executeCallback(jAction, [axes[2], axes[3]]);
break;
}
});
}
/* eslint-enable max-lines-per-function */
/**
* Convert button aliases to indexes or keep joystick aliases
* @param {string | number} action - Actions to convert
* @returns {string | number} - Converted action strings
* @private
*/
sanitizeAction(action) {
if (typeof action === "number")
return action;
if (this.mappings.axisAliases.includes(action.toLowerCase()))
return action.toLowerCase();
if (typeof action === "string") {
const key = this.mappings.aliases[action.toLowerCase()];
if (!key)
return console.error(`You have entered a non-supported button alias ${action}`);
return this.mappings[key];
}
return action;
}
/**
* Gets all registered Joystick actions
* @returns {Object[]} - Joystick actions
* @private
*/
getJoystickActions() {
return _IM.gamepadFunctions.filter((gpFunc) => this.mappings.axisAliases.includes(gpFunc.actions[0]));
}
/**
* Executes the callback from the registered action
* @param {Object} action
* @param {any} value
* @returns {void}
* @private
*/
executeCallback(action, value) {
if (typeof action.callback === "string")
return actions_default.execute(action.callback, value);
action.callback(value);
}
};
gamepad_default = new Gamepad();
}
});
// interaction-manager/src/gamepad.js
var require_gamepad = __commonJS({
"interaction-manager/src/gamepad.js"(exports, module) {
init_gamepad();
init_global_object();
global_object_default.init();
module.exports = gamepad_default;
}
});
return require_gamepad();
})();