Ejecta supports Gamepads on iOS and tvOS. It closely follows the W3C Gamepad API Spec - including the "standard" mapping.
On tvOS, the TV Remote is mapped to the appropriate buttons, the touchpad is mapped to an analog stick and(!) a D-Pad. The one addition to the W3C spec is the `gamepad.exitOnMenuPress` flag. See here for the fascinating details about why this flag exists.
Example, as shown in this Demo Video:
// Setup canvas with retina res
var canvas = document.getElementById('canvas');
canvas.width = window.innerWidth * window.devicePixelRatio;
canvas.height = window.innerHeight * window.devicePixelRatio;
canvas.style.width = window.innerWidth + "px";
canvas.style.height = window.innerHeight + "px";
var ctx = canvas.getContext("2d");
// The gamepad we draw below is about 1280px wide. We just
// scale the ctx so that it fills the actual screen width
var scale = canvas.width / 1280;
ctx.scale(scale, scale);
ctx.font = '32px Helvetica';
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
var drawButton = function(button, x, y) {
ctx.fillRect(x - 24, y + 24 - 48 * button.value, 48, 48 * button.value );
ctx.strokeStyle = button.pressed ? '#0f0' : '#ccc';
ctx.strokeRect(x - 24, y - 24, 48, 48);
ctx.strokeStyle = '#fff';
};
var drawAnalogStick = function( axisX, axisY, x, y ) {
ctx.beginPath();
ctx.arc(x, y, 64, 0, 2*Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(x + axisX*48, y + axisY*48, 16, 0, 2*Math.PI);
ctx.fill();
};
setInterval(function(){
ctx.clearRect(0, 0, canvas.width/scale, canvas.height/scale);
// Select a gamepad for the visualization. Here we try to find an
// "extendedGamepad" or "gamepad", instead of the Apple Remote
// ("microGamepad"). We still use the microGamepad if no other is present.
var gamepads = navigator.getGamepads();
var gamepad = null;
for( var i = 0; i < gamepads.length; i++ ) {
if( gamepads[i] ) {
gamepad = gamepads[i];
if( gamepad.profile !== 'microGamepad' ) {
// found a real gamepad; stop search
break;
}
}
}
if( !gamepad ) {
ctx.fillText('No Gamepads connected', 32, 32);
return;
}
ctx.fillText('Using Gamepad: #' + gamepad.index + ' ('+gamepad.id+')', 32, 32);
// Button Mappings according to http://www.w3.org/TR/gamepad/#remapping
drawButton(gamepad.buttons[0], 928, 354); // A
drawButton(gamepad.buttons[1], 994, 288); // B
drawButton(gamepad.buttons[2], 862, 288); // X
drawButton(gamepad.buttons[3], 928, 224); // Y
drawButton(gamepad.buttons[4], 412, 96); // L1
drawButton(gamepad.buttons[5], 868, 96); // R1
drawButton(gamepad.buttons[6], 288, 96); // L2
drawButton(gamepad.buttons[7], 994, 96); // R2
drawButton(gamepad.buttons[12], 352, 224); // Up
drawButton(gamepad.buttons[13], 352, 354); // Down
drawButton(gamepad.buttons[14], 288, 288); // Left
drawButton(gamepad.buttons[15], 416, 288); // Right
drawButton(gamepad.buttons[16], 640, 196); // Menu
drawAnalogStick(gamepad.axes[0], gamepad.axes[1], 540, 416); // left stick
drawAnalogStick(gamepad.axes[2], gamepad.axes[3], 736, 416); // right stick
// You can control whether the MENU button exits your game. Apple will reject your
// App if it does not. For Gamepads, the B button can also act as a MENU button.
// However, MENU should only exit the App the in certain cases.
// The expected behavior is this:
// > Pauses/resumes gameplay. Returns to previous screen, exits to main game menu,
// > and/or exits to Apple TV Home screen.
// ~ https://developer.apple.com/tvos/human-interface-guidelines/remote-and-interaction/
// In any case, judging from other games, you're probably safe to set 'exitOnMenuPress'
// to false during gameplay and only set it to true when you're on the Title Screen of
// your game.
gamepad.exitOnMenuPress = true;
}, 16);