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 () { /*...*/ });
Can you give me an example of changing listeners at runtime?
When would you want multiple handlers for a single input? Take a Mario style game for example. Pressing the left D-Pad moves Mario left, right D-Pad moves right, etc. Is there any secondary action that ever happens with a single input? I am coming at this as more of a gamer, than a game developer. Maybe there's something that I'm not considering.
The native HTML5 Gamepad API already provides the state that you would want for polling. I am accounting for this by providing the
tick
event. This is called on every tick ofrequestAnimationFrame
, which depending on frame rate would likely by 60 ticks/second. I've updated the proposed code to illustrate.