From 7db3ec3de939aa809fd7fe23090a0f00a011a8f1 Mon Sep 17 00:00:00 2001 From: TwoSquirrels Date: Wed, 10 Jul 2024 12:45:28 +0900 Subject: [PATCH] Support keyboard --- src/main.js | 125 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 93 insertions(+), 32 deletions(-) diff --git a/src/main.js b/src/main.js index a1b4f29..27df083 100644 --- a/src/main.js +++ b/src/main.js @@ -3,6 +3,7 @@ import "sanitize.css"; import "./style.css"; const elms = { + body: document.querySelector("body"), main: { joystickZone: document.querySelector("#joystick-zone"), buttonsZone: document.querySelector("#buttons-zone"), @@ -17,19 +18,56 @@ const elms = { let host = "http://localhost"; let interval = 125; let showButtons = true; -let intervalId = null; - -async function postJoystick(angle = Math.PI / 2, force = 1) { - const query = [ - Math.round((angle * 128) / Math.PI + (force >= 0 ? 256 : 384)) % 256, - Math.min(Math.abs(Math.floor(force * 255)), 255), - ] - .map((num) => num.toString(16).toUpperCase().padStart(2, "0")) - .join(""); - console.log("POST joystick", { angle, force, query }); - const res = await fetch(`${host}/joystick?q=${query}`, { method: "POST" }); - console.log("Responded joystick", await res.arrayBuffer(), await res.text()); -} + +const joystickPoster = { + _angle: Math.PI / 2, + _force: 1, + get angle() { + return this._angle; + }, + get force() { + return this._force; + }, + get query() { + return [ + Math.round((this.angle * 128) / Math.PI + (this.force >= 0 ? 256 : 384)) % 256, + Math.min(Math.abs(Math.floor(this.force * 255)), 255), + ] + .map((num) => num.toString(16).toUpperCase().padStart(2, "0")) + .join(""); + }, + + async post() { + console.log("POST joystick", { angle: this.angle, force: this.force, query: this.query }); + try { + const res = await fetch(`${host}/joystick?q=${this.query}`, { method: "POST" }); + console.log("Responded joystick", await res.arrayBuffer(), await res.text()); + return res; + } catch (error) { + console.error(error); + return null; + } + }, + + intervalId: null, + + update({ angle, force } = {}) { + this._angle = angle ?? Math.PI / 2; + this._force = force ?? 1; + if (this.intervalId) return false; + this.intervalId = setInterval(() => this.post(), interval); + return true; + }, + stop() { + if (!this.intervalId) return false; + clearInterval(this.intervalId); + this.intervalId = null; + this.post(); + return true; + }, +}; + +/// settings elms.settings.host.oninput = ({ target: input }) => { console.log(`Modify host ${host} -> ${input.value}`); @@ -48,6 +86,14 @@ elms.settings.buttons.oninput = ({ target: checkbox }) => { else elms.main.buttonsZone.classList.add("erase"); }; +document.addEventListener("DOMContentLoaded", ({}) => { + for (const input of Object.values(elms.settings)) input.oninput?.({ target: input }); +}); + +/// gamepad + +// joystick + const joystickManager = nipplejs.create({ zone: elms.main.joystickZone, color: "#55CCFF", @@ -55,21 +101,11 @@ const joystickManager = nipplejs.create({ mode: "dynamic", }); -let angle, force; - joystickManager - .on("start,move", (event, data) => { - angle = data.angle?.radian ?? Math.PI / 2; - force = data.force ?? 0; - }) - .on("start", (_event, _data) => { - clearInterval(intervalId); - intervalId = setInterval(() => postJoystick(angle, force), interval); - }) - .on("end", (_event, _data) => { - clearInterval(intervalId); - postJoystick(angle, force); - }); + .on("move", (_event, data) => joystickPoster.update({ angle: data.angle?.radian, force: data.force })) + .on("end", (_event, _data) => joystickPoster.stop()); + +// buttons for (const button of elms.main.buttonsZone.childNodes) { button.onclick = async ({ target: button }) => { @@ -80,8 +116,33 @@ for (const button of elms.main.buttonsZone.childNodes) { }; } -document.addEventListener("DOMContentLoaded", ({}) => { - for (const input of Object.values(elms.settings)) { - input.oninput?.({ target: input }); - } -}); +// keyboard + +const pressing = Object.fromEntries( + ["arrowright", "arrowup", "arrowleft", "arrowdown", "d", "w", "a", "s", "shift"].map((key) => [key, false]), +); + +function keyUpdate() { + let x = 0; + let y = 0; + if (pressing.arrowright || pressing.d) x += 1; + if (pressing.arrowup || pressing.w) y += 1; + if (pressing.arrowleft || pressing.a) x -= 1; + if (pressing.arrowdown || pressing.s) y -= 1; + if (x === 0 && y === 0) joystickPoster.stop(); + else joystickPoster.update({ angle: Math.atan2(y, x), force: pressing.shift ? 0.5 : 1 }); +} + +elms.body.onkeydown = ({ target, key }) => { + key = key.toLowerCase(); + if (target.nodeName === "input" || typeof pressing[key] !== "boolean") return; + pressing[key] = true; + keyUpdate(); +}; + +elms.body.onkeyup = ({ key }) => { + key = key.toLowerCase(); + if (typeof pressing[key] !== "boolean") return; + pressing[key] = false; + keyUpdate(); +};