// touch.js — on-screen controls for phones/tablets (no physical keyboard). // // A left thumb-joystick drives the same `keys` object the player already reads, // so movement reuses player.update untouched. Right-side ACTION and GIFT // buttons fire the same handlers as SPACE and G. Created only on touch devices; // CSS hides them whenever a menu/dialogue/comic owns the screen. Tap-to-move // (bindStageClick) keeps working too. import { keys, emit } from "./input.js"; const DEAD = 0.3; // joystick deadzone as a fraction of its radius export function isTouch() { return "ontouchstart" in window || navigator.maxTouchPoints > 0; } export function setupTouch() { if (!isTouch()) return; document.body.classList.add("touch"); buildJoystick(); buildButtons(); } function clearKeys() { keys.up = keys.down = keys.left = keys.right = false; } function buildJoystick() { const base = document.createElement("div"); base.id = "tc-stick"; base.innerHTML = `
`; document.getElementById("viewport").appendChild(base); const thumb = base.querySelector(".tc-thumb"); let id = null, cx = 0, cy = 0, r = 1; function place(t) { let dx = (t.clientX - cx) / r, dy = (t.clientY - cy) / r; const mag = Math.hypot(dx, dy); if (mag > 1) { dx /= mag; dy /= mag; } thumb.style.transform = `translate(${dx * r * 0.55}px, ${dy * r * 0.55}px)`; keys.left = dx < -DEAD; keys.right = dx > DEAD; keys.up = dy < -DEAD; keys.down = dy > DEAD; } base.addEventListener("touchstart", (e) => { const rect = base.getBoundingClientRect(); r = rect.width / 2; cx = rect.left + r; cy = rect.top + r; id = e.changedTouches[0].identifier; place(e.changedTouches[0]); e.preventDefault(); }, { passive: false }); base.addEventListener("touchmove", (e) => { for (const t of e.changedTouches) if (t.identifier === id) { place(t); e.preventDefault(); } }, { passive: false }); const end = (e) => { for (const t of e.changedTouches) if (t.identifier === id) { id = null; clearKeys(); thumb.style.transform = "translate(0,0)"; } }; base.addEventListener("touchend", end); base.addEventListener("touchcancel", end); } function buildButtons() { const wrap = document.createElement("div"); wrap.id = "tc-buttons"; wrap.innerHTML = ` `; document.getElementById("viewport").appendChild(wrap); const bind = (sel, name, arg) => { const b = wrap.querySelector(sel); // touchstart for zero-delay response; preventDefault stops the synth click b.addEventListener("touchstart", (e) => { e.preventDefault(); b.classList.add("down"); emit(name, arg); }, { passive: false }); const up = () => b.classList.remove("down"); b.addEventListener("touchend", up); b.addEventListener("touchcancel", up); }; bind(".tc-action", "interact"); bind(".tc-gift", "gift"); }