Add mobile touch controls

main
darcy 4 weeks ago
parent f9496cd628
commit 8516754065

@ -55,6 +55,15 @@
<button id="pauseAction" class="secondary-action" type="button">暂停</button> <button id="pauseAction" class="secondary-action" type="button">暂停</button>
<button id="stopAction" class="danger-action" type="button">结束</button> <button id="stopAction" class="danger-action" type="button">结束</button>
</div> </div>
<div class="touch-controls" aria-label="触摸操作">
<div class="dpad" aria-label="方向控制">
<button class="touch-button dpad-up" type="button" data-control="up" aria-label="向上"></button>
<button class="touch-button dpad-left" type="button" data-control="left" aria-label="向左"></button>
<button class="touch-button dpad-down" type="button" data-control="down" aria-label="向下"></button>
<button class="touch-button dpad-right" type="button" data-control="right" aria-label="向右"></button>
</div>
<button id="fireAction" class="fire-action" type="button" aria-label="开火">开火</button>
</div>
<div class="key-hints" aria-label="键盘快捷键"> <div class="key-hints" aria-label="键盘快捷键">
<div class="key-hint"><kbd>WASD</kbd><kbd>↑↓←→</kbd><span>Move</span></div> <div class="key-hint"><kbd>WASD</kbd><kbd>↑↓←→</kbd><span>Move</span></div>
<div class="key-hint"><kbd>Space</kbd><span>Fire</span></div> <div class="key-hint"><kbd>Space</kbd><span>Fire</span></div>

@ -68,6 +68,8 @@
const pauseAction = document.querySelector("#pauseAction"); const pauseAction = document.querySelector("#pauseAction");
const stopAction = document.querySelector("#stopAction"); const stopAction = document.querySelector("#stopAction");
const endAction = document.querySelector("#endAction"); const endAction = document.querySelector("#endAction");
const fireAction = document.querySelector("#fireAction");
const touchButtons = [...document.querySelectorAll("[data-control]")];
const WIDTH = canvas.width; const WIDTH = canvas.width;
const HEIGHT = canvas.height; const HEIGHT = canvas.height;
@ -703,11 +705,52 @@
keys.delete(event.key.toLowerCase()); 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); primaryAction.addEventListener("click", startOrResume);
if (startAction) startAction.addEventListener("click", startOrResume); if (startAction) startAction.addEventListener("click", startOrResume);
if (pauseAction) pauseAction.addEventListener("click", togglePause); if (pauseAction) pauseAction.addEventListener("click", togglePause);
if (stopAction) stopAction.addEventListener("click", endGame); if (stopAction) stopAction.addEventListener("click", endGame);
if (endAction) endAction.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(); regenerateWalls();
player = tank(384, 536, "#77b255", "up", "player"); player = tank(384, 536, "#77b255", "up", "player");

@ -32,6 +32,7 @@ body {
font-family: font-family:
"Microsoft YaHei", "PingFang SC", "Segoe UI", Arial, sans-serif; "Microsoft YaHei", "PingFang SC", "Segoe UI", Arial, sans-serif;
letter-spacing: 0; letter-spacing: 0;
overscroll-behavior: none;
} }
button, button,
@ -216,6 +217,7 @@ canvas {
width: 100%; width: 100%;
height: 100%; height: 100%;
image-rendering: pixelated; image-rendering: pixelated;
touch-action: none;
} }
.overlay { .overlay {
@ -275,6 +277,67 @@ canvas {
gap: 10px; 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 { .key-hints {
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: var(--radius); border-radius: var(--radius);
@ -377,6 +440,25 @@ kbd {
grid-template-columns: repeat(3, minmax(0, 1fr)); 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 { .key-hints {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));

Loading…
Cancel
Save