I haven't been able to find any libraries for interacting with the HTML5 Gamepad API that I really like. This is an attempt to sort out some of my ideas for what I would want out of such a library and ultimately implement it.
- Abstract away complexity
- Manage gamepad support (detection, vender impl, polling, etc.)
- Make it dead simple for developers to implement
- Allow devs to focus on making games, not fiddling with controller config
- Multi player support
- Handle input from multiple gamepads
- Don't sacrifice simplicity for single gamepad usage (most common use case)
- Independent controls per player
- Allow player 1 to have different control settings from player 2
- Player 1 prefers button A to jump
- Player 2 prefers right bumper to jump
- Manage key codes
- Track sequence of inputs and handle successful sequence
- Easter egg (Konami Code)
- Character special moves (Mortal Kombat)
Define constants for all the indices of raw gamepad state, as well as all events.
Gamepad.Events = {
CONNECT: 'connect',
DISCONNECT: 'disconnect',
TICK: 'tick',
ERROR: 'error',
BUTTON_A: 'button-a',
BUTTON_B: 'button-b',
BUTTON_X: 'button-x',
BUTTON_Y: 'button-y',
BUTTON_LEFT_BUMPER: 'button-left-bumper',
BUTTON_RIGHT_BUMPER: 'button-right-bumper',
BUTTON_LEFT_TRIGGER: 'button-left-trigger',
BUTTON_RIGHT_TRIGGER: 'button-right-trigger',
BUTTON_BACK: 'button-back',
BUTTON_START: 'button-start',
BUTTON_LEFT_ANALOGUE: 'button-left-analogue',
BUTTON_RIGHT_ANALOGUE: 'button-right-analogue',
BUTTON_UP: 'button-up',
BUTTON_DOWN: 'button-down',
BUTTON_LEFT: 'button-left',
BUTTON_RIGHT: 'button-right',
LEFT_ANALOGUE_FORWARD: 'left-analogue-forward',
LEFT_ANALOGUE_BACKWARD: 'left-analogue-backward',
LEFT_ANALOGUE_LEFT: 'left-analogue-left',
LEFT_ANALOGUE_RIGHT: 'left-analogue-right',
RIGHT_ANALOGUE_FORWARD: 'right-analogue-forward',
RIGHT_ANALOGUE_BACKWARD: 'right-analogue-backward',
RIGHT_ANALOGUE_LEFT: 'right-analogue-left',
RIGHT_ANALOGUE_RIGHT: 'right-analogue-right'
};
Gamepad.Buttons = {
A: 0,
B: 1,
X: 2,
Y: 3,
LEFT_BUMPER: 4,
RIGHT_BUMPER: 5,
LEFT_TRIGGER: 6,
RIGHT_TRIGGER: 7,
BACK: 8,
START: 9,
LEFT_ANALOGUE: 10,
RIGHT_ANALOGUE: 11,
UP: 12,
DOWN: 13,
LEFT: 14,
RIGHT: 15
};
Gamepad.Axes = {
LEFT_ANALOGUE_LEFT_TO_RIGHT: 0,
LEFT_ANALOGUE_FRONT_TO_BACK: 1,
RIGHT_ANALOGUE_LEFT_TO_RIGHT: 2,
RIGHT_ANALOGUE_FRONT_TO_BACK: 3
};
Provide an API for processing raw gamepad state. This would be ideal when used in conjunction with the tick
event.
Gamepad.getButtonAValue = function (state) { /*...*/ };
Gamepad.getButtonBValue = function (state) { /*...*/ };
Gamepad.getButtonXValue = function (state) { /*...*/ };
Gamepad.getButtonYValue = function (state) { /*...*/ };
/*...*/
Gamepad.getLeftAnalogueForwardValue = function (state) { /*...*/ };
Gamepad.getLeftAnalogueBackwardValue = function (state) { /*...*/ };
Gamepad.getLeftAnalogueLeftValue = function (state) { /*...*/ };
Gamepad.getLeftAnalogueRightValue = function (state) { /*...*/ };
Gamepad.getRightAnalogueForwardValue = function (state) { /*...*/ };
Gamepad.getRightAnalogueBackwardValue = function (state) { /*...*/ };
Gamepad.getRightAnalogueLeftValue = function (state) { /*...*/ };
Gamepad.getRightAnalogueRightValue = function (state) { /*...*/ };
Gamepad.player(1)
.on('connect', function () {
console.log('Player 1 is connected');
})
.on('tick', function (state, delta) {
console.log(state);
// {buttons: Array[16], axes: Array[4], mapping: "standard", connected: true...}
})
.on('buttonpress', function (which, value) {
if (which === Gamepad.Buttons.A) {
console.log('Player 1 pressed A');
}
})
.on('axischange', function (which, value) {
if (which === Gamepad.Axes.LEFT_ANALOGUE_FRONT_TO_BACK && value < 0) {
console.log('Player 1 forward motion left analogue');
}
});
Pros
- Familiar EventEmmiter pattern
Cons
- Verbose
- Conditional testing for which button/axis triggered event
Gamepad.player(1).listen({
Gamepad.Events.CONNECT: function () {
console.log('Player 1 is connected');
},
Gamepad.Events.TICK: function (state, delta) {
console.log(state);
// {buttons: Array[16], axes: Array[4], mapping: "standard", connected: true...}
},
Gamepad.Events.BUTTON_A: function (value) {
console.log('Player 1 pressed A');
},
Gamepad.Events.LEFT_ANALOGUE_FORWARD: function (value) {
console.log('Player 1 forward motion left analogue');
}
});
Pros
- No need for conditional testing for
which
- Easier to support custom mapping for each user
Cons
- Only a single handler per event
For sake of example, let's assume the event driven approach.
// Reference players by index
Gamepad.player(1).on('connect', function () { /*...*/ });
Gamepad.player(2).on('connect', function () { /*...*/ });
// Terse reference to player 1 for single gamepad usage
Gamepad.on('connect', function () { /*...*/ });
I would go with the EventEmitter pattern, since you'll probably expose that API anyway to change listeners at runtime (and to add multiple listeners, unless you support each key in the mapping having an array of listeners).
A thought: my use case would probably be polling the controller from a game loop, so you might want to expose current state in a map or something, instead of just through events.