add main.js & main.html
This commit is contained in:
parent
89cfccf433
commit
3cd1b66965
33
main.html
Normal file
33
main.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>snej</title>
|
||||||
|
<style>
|
||||||
|
* { padding: 0; margin: 0; }
|
||||||
|
body { background: black; color: white; font-family: monospace; }
|
||||||
|
div { text-align: center; }
|
||||||
|
canvas {
|
||||||
|
background: magenta;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 1px;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- SNES resolution: 256x224. 4x scaled: 1024 x 896. -->
|
||||||
|
<canvas id="canvas" width="256" height="224"></canvas>
|
||||||
|
<div id="debug"></div>
|
||||||
|
<button id="1x">1x</button>
|
||||||
|
<button id="2x">2x</button>
|
||||||
|
<button id="3x">3x</button>
|
||||||
|
<button id="4x">4x</button>
|
||||||
|
<button id="5x">5x</button>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
254
main.js
Normal file
254
main.js
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
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();
|
Loading…
Reference in New Issue
Block a user