gamepad.js
Version:
A simple HTML5 Gamepad handler that provides keyboard-like events for Gamepad axes and button
166 lines (138 loc) • 4.1 kB
JavaScript
import EventEmitter from 'tom32i-event-emitter.js';
import OptionResolver from 'option-resolver.js';
/**
* Gamepad Handler
*
* @param {Gamepad} gamepad
* @param {Object} config
*/
export default class GamepadHandler extends EventEmitter
{
static optionResolver = new OptionResolver()
.setDefaults({
analog: true,
deadZone: 0,
precision: 0,
})
.setTypes({
analog: 'boolean',
deadZone: 'number',
precision: 'number',
})
.setValidators({
deadZone: value => Math.max(Math.min(value, 1), 0),
precision: value => value > 0 ? Math.pow(10, value) : 0,
});
constructor(index, gamepad, config = {}) {
super();
this.index = index;
this.gamepad = gamepad;
this.options = this.constructor.resolveOptions(config);
this.axes = new Array(gamepad.axes.length).fill(null);
this.buttons = new Array(gamepad.buttons.length).fill(null);
this.initAxes();
this.initButtons();
}
/**
* Resolve options
*
* @param {Object} config
*
* @return {Object}
*/
static resolveOptions(config) {
const { axis, button } = config;
return {
axis: this.optionResolver.resolve(axis ?? button ?? config ?? {}),
button: this.optionResolver.resolve(button ?? axis ?? config ?? {}),
};
}
initAxes() {
const { length } = this.axes;
for (let index = 0; index < length; index++) {
this.axes[index] = this.resolveAxisValue(index);
}
}
initButtons() {
const { length } = this.buttons;
for (let index = 0; index < length; index++) {
this.buttons[index] = this.resolveButtonValue(index);
}
}
update(gamepad) {
this.gamepad = gamepad;
this.updateAxis();
this.updateButtons();
}
updateAxis() {
const { length } = this.axes;
for (let index = 0; index < length; index++) {
this.setAxisValue(index, this.resolveAxisValue(index));
}
}
updateButtons() {
const { length } = this.buttons;
for (let index = 0; index < length; index++) {
this.setButtonValue(index, this.resolveButtonValue(index));
}
}
setAxisValue(index, value) {
if (this.axes[index] !== value) {
this.axes[index] = value;
this.emit('axis', {
gamepad: this.gamepad,
index: this.index,
axis: index,
value,
});
}
}
setButtonValue(index, value) {
if (this.buttons[index] !== value) {
this.buttons[index] = value;
this.emit('button', {
gamepad: this.gamepad,
index: this.index,
button: index,
pressed: this.gamepad.buttons[index].pressed,
value,
});
}
}
/**
* @param {Number} index
*/
resolveAxisValue(index) {
const { deadZone, analog, precision } = this.options.axis;
const value = this.gamepad.axes[index];
if (deadZone && value < deadZone && value > -deadZone) {
return 0;
}
if (!analog) {
return value > 0 ? 1 : value < 0 ? -1 : 0;
}
if (precision) {
return Math.round(value * precision) / precision;
}
return value;
}
/**
* @param {Number} index
*
* @return {Number}
*/
resolveButtonValue(index) {
const { deadZone, analog, precision } = this.options.button;
const { value } = this.gamepad.buttons[index];
if (deadZone > 0 && value < deadZone && value > -deadZone) {
return 0;
}
if (!analog) {
return value === 0 ? 0 : 1;
}
if (precision) {
return Math.round(value * precision) / precision;
}
return value;
}
}