From 8516754065248d1429194373197638e3c99cc1a1 Mon Sep 17 00:00:00 2001 From: darcy Date: Sat, 20 Jun 2026 04:07:30 +0800 Subject: [PATCH] Add mobile touch controls --- index.html | 9 ++++++ src/game.js | 43 ++++++++++++++++++++++++++ src/styles.css | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/index.html b/index.html index 36a4d1f..06e020a 100644 --- a/index.html +++ b/index.html @@ -55,6 +55,15 @@ +
+
+ + + + +
+ +
WASD↑↓←→Move
SpaceFire
diff --git a/src/game.js b/src/game.js index 3d604c6..8bb7124 100644 --- a/src/game.js +++ b/src/game.js @@ -68,6 +68,8 @@ const pauseAction = document.querySelector("#pauseAction"); const stopAction = document.querySelector("#stopAction"); const endAction = document.querySelector("#endAction"); + const fireAction = document.querySelector("#fireAction"); + const touchButtons = [...document.querySelectorAll("[data-control]")]; const WIDTH = canvas.width; const HEIGHT = canvas.height; @@ -703,11 +705,52 @@ keys.delete(event.key.toLowerCase()); }); + function bindHoldButton(button, key) { + const press = (event) => { + event.preventDefault(); + button.setPointerCapture?.(event.pointerId); + keys.add(key); + canvas.focus({ preventScroll: true }); + }; + const release = (event) => { + event.preventDefault(); + keys.delete(key); + try { + button.releasePointerCapture?.(event.pointerId); + } catch { + // Some browsers release capture automatically on cancel/leave. + } + }; + button.addEventListener("pointerdown", press); + button.addEventListener("pointerup", release); + button.addEventListener("pointercancel", release); + button.addEventListener("pointerleave", release); + } + + const touchKeyMap = { + up: "arrowup", + down: "arrowdown", + left: "arrowleft", + right: "arrowright", + }; + + for (const button of touchButtons) { + const key = touchKeyMap[button.dataset.control]; + if (key) bindHoldButton(button, key); + } + primaryAction.addEventListener("click", startOrResume); if (startAction) startAction.addEventListener("click", startOrResume); if (pauseAction) pauseAction.addEventListener("click", togglePause); if (stopAction) stopAction.addEventListener("click", endGame); if (endAction) endAction.addEventListener("click", endGame); + if (fireAction) { + fireAction.addEventListener("pointerdown", (event) => { + event.preventDefault(); + canvas.focus({ preventScroll: true }); + if (state === "playing") fire(player); + }); + } regenerateWalls(); player = tank(384, 536, "#77b255", "up", "player"); diff --git a/src/styles.css b/src/styles.css index a2c1b78..5e6cf54 100644 --- a/src/styles.css +++ b/src/styles.css @@ -32,6 +32,7 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Segoe UI", Arial, sans-serif; letter-spacing: 0; + overscroll-behavior: none; } button, @@ -216,6 +217,7 @@ canvas { width: 100%; height: 100%; image-rendering: pixelated; + touch-action: none; } .overlay { @@ -275,6 +277,67 @@ canvas { gap: 10px; } +.touch-controls { + display: grid; + grid-template-columns: minmax(150px, 190px) minmax(112px, 1fr); + gap: 12px; + align-items: center; +} + +.dpad { + width: min(190px, 48vw); + aspect-ratio: 1; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-rows: repeat(3, minmax(0, 1fr)); + gap: 7px; + touch-action: none; + user-select: none; +} + +.touch-button, +.fire-action { + min-height: 54px; + padding: 0; + display: grid; + place-items: center; + touch-action: none; + user-select: none; + -webkit-user-select: none; +} + +.touch-button { + font-size: 1.25rem; +} + +.dpad-up { + grid-column: 2; + grid-row: 1; +} + +.dpad-left { + grid-column: 1; + grid-row: 2; +} + +.dpad-down { + grid-column: 2; + grid-row: 3; +} + +.dpad-right { + grid-column: 3; + grid-row: 2; +} + +.fire-action { + min-height: 86px; + border-color: #ffd36a; + background: linear-gradient(180deg, #f1c55d, #b97127); + color: #151007; + font-size: 1.1rem; +} + .key-hints { border: 1px solid var(--line); border-radius: var(--radius); @@ -377,6 +440,25 @@ kbd { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .touch-controls { + grid-template-columns: minmax(144px, 42vw) minmax(104px, 1fr); + gap: 10px; + } + + .dpad { + width: min(176px, 44vw); + gap: 6px; + } + + .touch-button, + .fire-action { + min-height: 50px; + } + + .fire-action { + min-height: 82px; + } + .key-hints { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));