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);