|
|
class Input { constructor() { this.left = false; this.right = false; this.up = false; this.down = false; this.a = false; this.b = false; this.x = false; this.y = false; this.l = false; this.r = false; this.select = false; this.start = false;
window.addEventListener('gamepadconnected', this.gamepadConnected); window.addEventListener('gamepaddisconnected', this.gamepadDisconnected); }
update() { // TODO: have a config screen instead of hard-coding the 8Bitdo SNES30 pad.
// TODO: handle connects / disconnects more correctly.
const gamepad = navigator.getGamepads()[0]; if (gamepad == null || !gamepad.connected || gamepad.axes.length < 2 || gamepad.buttons.length < 12) { return; }
this.left = gamepad.axes[0] < 0; this.right = gamepad.axes[0] > 0; this.up = gamepad.axes[1] < 0; this.down = gamepad.axes[1] > 0; this.a = gamepad.buttons[0].pressed; this.b = gamepad.buttons[1].pressed; this.x = gamepad.buttons[3].pressed; this.y = gamepad.buttons[4].pressed; this.l = gamepad.buttons[6].pressed; this.r = gamepad.buttons[7].pressed; this.select = gamepad.buttons[10].pressed; this.start = gamepad.buttons[11].pressed; debug(this.toString()); }
gamepadConnected(e) { debug('gamepad connected! :)'); console.log('gamepad connected @ index %d: %d buttons, %d axes\n[%s]', e.gamepad.index, e.gamepad.buttons.length, e.gamepad.axes.length, e.gamepad.id); }
gamepadDisconnected(e) { debug('gamepad disconnected :('); console.log('gamepad disconnected @ index %d:\n[%s]', e.gamepad.index, e.gamepad.id); }
toString() { let result = '';
if (this.up) { result += '^'; } else if (this.down) { result += 'v'; } else { result += '-'; }
if (this.left) { result += '<'; } else if (this.right) { result += '>'; } else { result += '-'; }
result += ' ';
if (this.a) { result += 'A'; } if (this.b) { result += 'B'; } if (this.x) { result += 'X'; } if (this.y) { result += 'Y'; } if (this.l) { result += 'L'; } if (this.r) { result += 'R'; } if (this.select) { result += 's'; } if (this.start) { result += 'S'; } return result; } }
const input = new Input();
class Graphics { constructor(canvas) { this.canvas_ = canvas; this.ctx_ = canvas.getContext('2d'); }
get width() { return this.canvas_.width; }
get height() { return this.canvas_.height; }
fill(color) { this.ctx_.fillStyle = color; this.ctx_.beginPath(); this.ctx_.rect(0, 0, this.canvas_.width, this.canvas_.height); this.ctx_.fill(); this.ctx_.closePath(); }
circle(x, y, radius, color) { this.ctx_.fillStyle = color; this.ctx_.beginPath(); this.ctx_.arc(x, y, radius, 0, 2 * Math.PI) this.ctx_.fill(); this.ctx_.closePath(); }
// TODO: replace with custom sprite-based text rendering.
text(string, x, y, size, color) { this.ctx_.imageSmoothingEnabled = false; this.ctx_.fillStyle = color; this.ctx_.font = '' + size + 'px monospace'; this.ctx_.fillText(string, x, y); } }
class FpsCounter { constructor() { this.fps = 0; this.frameTimes_ = new Array(60); this.idx_ = 0; }
update(timestampMs) { if (this.frameTimes_[this.idx_]) { const timeElapsed = (timestampMs - this.frameTimes_[this.idx_]) / 1000; this.fps = this.frameTimes_.length / timeElapsed; } this.frameTimes_[this.idx_] = timestampMs; this.idx_++; if (this.idx_ == this.frameTimes_.length) { this.idx_ = 0; } }
draw(gfx) { gfx.text('FPS: ' + Math.round(this.fps), 8, 16, 16, 'yellow'); } }
class World { constructor() { this.state_ = null; this.fpsCounter_ = new FpsCounter(); this.input_ = new Input(); this.gamepadRenderer_ = new GamepadRenderer(); }
update(timestampMs) { this.fpsCounter_.update(timestampMs); this.input_.update(); }
draw(gfx) { this.gamepadRenderer_.draw(gfx, this.input_); this.fpsCounter_.draw(gfx); } }
class GamepadRenderer { draw(gfx, input) { const centerX = gfx.width / 2; const centerY = gfx.height / 2;
gfx.fill('black');
// Select & Start
gfx.circle(centerX + 12, centerY, 8, input.start ? 'cyan' : 'grey'); gfx.circle(centerX - 12, centerY, 8, input.select ? 'cyan' : 'grey');
// Y X B A
gfx.circle(centerX + 48, centerY, 8, input.y ? 'cyan' : 'grey'); gfx.circle(centerX + 64, centerY - 16, 8, input.x ? 'cyan' : 'grey'); gfx.circle(centerX + 64, centerY + 16, 8, input.b ? 'cyan' : 'grey'); gfx.circle(centerX + 80, centerY, 8, input.a ? 'cyan' : 'grey');
// dpad
gfx.circle(centerX - 48, centerY, 8, input.right ? 'cyan' : 'grey'); gfx.circle(centerX - 64, centerY - 16, 8, input.up ? 'cyan' : 'grey'); gfx.circle(centerX - 64, centerY + 16, 8, input.down ? 'cyan' : 'grey'); gfx.circle(centerX - 80, centerY, 8, input.left ? 'cyan' : 'grey');
// L & R
gfx.circle(centerX + 30, centerY - 32, 8, input.r ? 'cyan' : 'grey'); gfx.circle(centerX - 30, centerY - 32, 8, input.l ? 'cyan' : 'grey'); } }
function debug(message) { const debugDiv = document.getElementById('debug'); debugDiv.innerText = message; }
function loop(world, gfx) { return timestampMs => { world.update(timestampMs); world.draw(gfx); window.requestAnimationFrame(loop(world, gfx)); }; }
function setCanvasScale(scale) { const snesWidth = 256; const snesHeight = 224; const canvas = document.getElementById('canvas'); canvas.style.width = '' + snesWidth * scale + 'px'; canvas.style.height = '' + snesHeight * scale + 'px'; }
function init() { const world = new World(); const gfx = new Graphics(document.getElementById('canvas'));
document.getElementById('1x').onclick = () => setCanvasScale(1); document.getElementById('2x').onclick = () => setCanvasScale(2); document.getElementById('3x').onclick = () => setCanvasScale(3); document.getElementById('4x').onclick = () => setCanvasScale(4); document.getElementById('5x').onclick = () => setCanvasScale(5); setCanvasScale(4); window.requestAnimationFrame(loop(world, gfx)); debug('initialized!'); }
init();
|