-
diff --git a/src/game.js b/src/game.js
index fe33bfe..42731eb 100644
--- a/src/game.js
+++ b/src/game.js
@@ -69,7 +69,8 @@
const stopAction = document.querySelector("#stopAction");
const endAction = document.querySelector("#endAction");
const fireAction = document.querySelector("#fireAction");
- const touchButtons = [...document.querySelectorAll("[data-control]")];
+ const moveStick = document.querySelector("#moveStick");
+ const moveKnob = document.querySelector("#moveKnob");
const WIDTH = canvas.width;
const HEIGHT = canvas.height;
@@ -619,21 +620,21 @@
if (isPlayer) {
if (health > 66) {
- glowColor = "rgba(132, 238, 212, 0.75)";
- glowBlur = 12;
+ glowColor = "rgba(132, 238, 212, 0.55)";
+ glowBlur = 7;
outerColor = "#e8fff7";
innerColor = "#102822";
coreColor = entity.color;
} else if (health > 33) {
glowColor = "rgba(240, 182, 70, 0.45)";
- glowBlur = 8;
+ glowBlur = 6;
outerColor = "#c8c0a0";
innerColor = "#1e2818";
coreColor = "#9a9850";
} else {
const pulse = 0.5 + 0.5 * Math.sin(Date.now() / 180);
- glowColor = `rgba(212, 93, 69, ${0.5 + 0.4 * pulse})`;
- glowBlur = 10 + 6 * pulse;
+ glowColor = `rgba(212, 93, 69, ${0.42 + 0.32 * pulse})`;
+ glowBlur = 7 + 4 * pulse;
outerColor = "#3a2020";
innerColor = "#1a0c0c";
coreColor = "#6b2828";
@@ -766,38 +767,63 @@
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 stickKeys = ["arrowup", "arrowdown", "arrowleft", "arrowright"];
+ let activeStickKey = null;
+
+ function clearStickDirection() {
+ if (activeStickKey) keys.delete(activeStickKey);
+ activeStickKey = null;
+ if (moveKnob) moveKnob.style.transform = "translate(-50%, -50%)";
+ }
+
+ function setStickDirection(key) {
+ if (activeStickKey === key) return;
+ if (activeStickKey) keys.delete(activeStickKey);
+ activeStickKey = key;
+ if (key) keys.add(key);
+ }
+
+ function updateStick(event) {
+ if (!moveStick) return;
+ event.preventDefault();
+ const rect = moveStick.getBoundingClientRect();
+ const centerX = rect.left + rect.width / 2;
+ const centerY = rect.top + rect.height / 2;
+ const dx = event.clientX - centerX;
+ const dy = event.clientY - centerY;
+ const distance = Math.hypot(dx, dy);
+ const deadZone = rect.width * 0.12;
+ if (distance < deadZone) {
+ clearStickDirection();
+ return;
+ }
+ const key = Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? "arrowright" : "arrowleft") : dy > 0 ? "arrowdown" : "arrowup";
+ setStickDirection(key);
+ if (moveKnob) {
+ const max = rect.width * 0.24;
+ const scale = Math.min(max, distance) / distance;
+ moveKnob.style.transform = `translate(calc(-50% + ${dx * scale}px), calc(-50% + ${dy * scale}px))`;
+ }
+ canvas.focus({ preventScroll: true });
}
- 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);
+ if (moveStick) {
+ moveStick.addEventListener("pointerdown", (event) => {
+ moveStick.setPointerCapture?.(event.pointerId);
+ updateStick(event);
+ });
+ moveStick.addEventListener("pointermove", updateStick);
+ for (const type of ["pointerup", "pointercancel", "pointerleave"]) {
+ moveStick.addEventListener(type, (event) => {
+ event.preventDefault();
+ clearStickDirection();
+ try {
+ moveStick.releasePointerCapture?.(event.pointerId);
+ } catch {
+ // Some browsers release capture automatically.
+ }
+ });
+ }
}
primaryAction.addEventListener("click", startOrResume);
diff --git a/src/styles.css b/src/styles.css
index 6f7b5e2..aa3e300 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -299,50 +299,62 @@ canvas {
align-items: center;
}
-.dpad {
+.move-stick {
+ position: relative;
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;
+ border: 1px solid var(--line-bright);
+ border-radius: 50%;
+ background:
+ radial-gradient(circle at 50% 50%, rgba(240, 182, 70, 0.1) 0 24%, transparent 25%),
+ radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.07), transparent 58%),
+ var(--panel);
+ box-shadow: inset 0 0 0 8px rgba(16, 21, 13, 0.72), 0 12px 28px var(--shadow);
touch-action: none;
user-select: none;
- -webkit-user-select: none;
}
-.touch-button {
- font-size: 1.25rem;
+.move-stick::before,
+.move-stick::after {
+ content: "";
+ position: absolute;
+ inset: 50% auto auto 50%;
+ background: rgba(198, 216, 123, 0.45);
+ transform: translate(-50%, -50%);
+ pointer-events: none;
}
-.dpad-up {
- grid-column: 2;
- grid-row: 1;
+.move-stick::before {
+ width: 70%;
+ height: 2px;
}
-.dpad-left {
- grid-column: 1;
- grid-row: 2;
+.move-stick::after {
+ width: 2px;
+ height: 70%;
}
-.dpad-down {
- grid-column: 2;
- grid-row: 3;
+.move-knob {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 42%;
+ aspect-ratio: 1;
+ border: 1px solid #d8e58c;
+ border-radius: 50%;
+ background: linear-gradient(180deg, #adc764, #718536);
+ box-shadow: 0 8px 18px var(--shadow);
+ transform: translate(-50%, -50%);
+ pointer-events: none;
}
-.dpad-right {
- grid-column: 3;
- grid-row: 2;
+.fire-action {
+ min-height: 54px;
+ padding: 0;
+ display: grid;
+ place-items: center;
+ touch-action: none;
+ -webkit-user-select: none;
}
.fire-action {
@@ -450,12 +462,10 @@ kbd {
row-gap: 10px;
}
- .dpad {
+ .move-stick {
width: 100%;
- gap: 6px;
}
- .touch-button,
.fire-action {
min-height: 50px;
}