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));