Impact

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