Joystick for mobile
This commit is contained in:
1 parent
e8c3f9862a
commit
54c6327ac7
4 files changed
+120
-3
No files matched your search
@@ -9,6 +9,30 @@ html, body {
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: #000;
|
background: #000;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
.joystick-base {
|
||||||
|
position: fixed;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.25);
|
||||||
|
pointer-events: none;
|
||||||
|
display: none;
|
||||||
|
z-index: 5;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
.joystick-thumb {
|
||||||
|
position: fixed;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.45);
|
||||||
|
pointer-events: none;
|
||||||
|
display: none;
|
||||||
|
z-index: 6;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
#fullscreen {
|
#fullscreen {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ export class Input {
|
|||||||
this.mouse = new Vector2D(viewport.width / 2, viewport.height / 2);
|
this.mouse = new Vector2D(viewport.width / 2, viewport.height / 2);
|
||||||
this.mouseDown = false;
|
this.mouseDown = false;
|
||||||
this.mouseClicked = false;
|
this.mouseClicked = false;
|
||||||
|
this.viewport = viewport;
|
||||||
|
this.isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||||
|
this.joystickActive = false;
|
||||||
|
this.joystickTouchId = null;
|
||||||
|
this.joystickBasePos = { x: 0, y: 0 };
|
||||||
|
this.joystickRadius = 60;
|
||||||
|
|
||||||
window.addEventListener('keydown', (event) => {
|
window.addEventListener('keydown', (event) => {
|
||||||
this.keys.add(event.key.toLowerCase());
|
this.keys.add(event.key.toLowerCase());
|
||||||
@@ -47,6 +53,92 @@ export class Input {
|
|||||||
window.addEventListener('mouseup', () => {
|
window.addEventListener('mouseup', () => {
|
||||||
this.mouseDown = false;
|
this.mouseDown = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.isMobile) {
|
||||||
|
this._initTouchJoystick(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_initTouchJoystick(canvas) {
|
||||||
|
this.joystickBaseEl = document.createElement('div');
|
||||||
|
this.joystickBaseEl.className = 'joystick-base';
|
||||||
|
this.joystickBaseEl.style.display = 'none';
|
||||||
|
|
||||||
|
this.joystickThumbEl = document.createElement('div');
|
||||||
|
this.joystickThumbEl.className = 'joystick-thumb';
|
||||||
|
this.joystickThumbEl.style.display = 'none';
|
||||||
|
|
||||||
|
document.body.appendChild(this.joystickBaseEl);
|
||||||
|
document.body.appendChild(this.joystickThumbEl);
|
||||||
|
|
||||||
|
canvas.addEventListener('touchstart', (event) => this._handleTouchStart(event), { passive: false });
|
||||||
|
canvas.addEventListener('touchmove', (event) => this._handleTouchMove(event), { passive: false });
|
||||||
|
canvas.addEventListener('touchend', (event) => this._handleTouchEnd(event), { passive: false });
|
||||||
|
canvas.addEventListener('touchcancel', (event) => this._handleTouchEnd(event), { passive: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleTouchStart(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
for (const touch of event.changedTouches) {
|
||||||
|
const x = touch.clientX;
|
||||||
|
if (x < this.viewport.width / 2 && !this.joystickActive) {
|
||||||
|
this.joystickActive = true;
|
||||||
|
this.joystickTouchId = touch.identifier;
|
||||||
|
this.joystickBasePos.x = x;
|
||||||
|
this.joystickBasePos.y = touch.clientY;
|
||||||
|
|
||||||
|
this.joystickBaseEl.style.left = `${x}px`;
|
||||||
|
this.joystickBaseEl.style.top = `${this.joystickBasePos.y}px`;
|
||||||
|
this.joystickBaseEl.style.display = 'block';
|
||||||
|
this.joystickThumbEl.style.left = `${x}px`;
|
||||||
|
this.joystickThumbEl.style.top = `${this.joystickBasePos.y}px`;
|
||||||
|
this.joystickThumbEl.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleTouchMove(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
for (const touch of event.changedTouches) {
|
||||||
|
if (touch.identifier !== this.joystickTouchId) continue;
|
||||||
|
const dx = touch.clientX - this.joystickBasePos.x;
|
||||||
|
const dy = touch.clientY - this.joystickBasePos.y;
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
let clampedDx = dx;
|
||||||
|
let clampedDy = dy;
|
||||||
|
if (dist > this.joystickRadius) {
|
||||||
|
clampedDx = (dx / dist) * this.joystickRadius;
|
||||||
|
clampedDy = (dy / dist) * this.joystickRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.joystickThumbEl.style.left = `${this.joystickBasePos.x + clampedDx}px`;
|
||||||
|
this.joystickThumbEl.style.top = `${this.joystickBasePos.y + clampedDy}px`;
|
||||||
|
|
||||||
|
const deadZone = 12;
|
||||||
|
this.keys.delete('w');
|
||||||
|
this.keys.delete('a');
|
||||||
|
this.keys.delete('s');
|
||||||
|
this.keys.delete('d');
|
||||||
|
if (dy < -deadZone) this.keys.add('w');
|
||||||
|
if (dy > deadZone) this.keys.add('s');
|
||||||
|
if (dx < -deadZone) this.keys.add('a');
|
||||||
|
if (dx > deadZone) this.keys.add('d');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleTouchEnd(event) {
|
||||||
|
for (const touch of event.changedTouches) {
|
||||||
|
if (touch.identifier === this.joystickTouchId) {
|
||||||
|
this.joystickActive = false;
|
||||||
|
this.joystickTouchId = null;
|
||||||
|
this.keys.delete('w');
|
||||||
|
this.keys.delete('a');
|
||||||
|
this.keys.delete('s');
|
||||||
|
this.keys.delete('d');
|
||||||
|
this.joystickBaseEl.style.display = 'none';
|
||||||
|
this.joystickThumbEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPressed() {
|
clearPressed() {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ window.addEventListener('orientationchange', resizeToWindow);
|
|||||||
resizeToWindow();
|
resizeToWindow();
|
||||||
|
|
||||||
const input = new Input(canvas, viewport);
|
const input = new Input(canvas, viewport);
|
||||||
|
const isMobile = input.isMobile;
|
||||||
|
|
||||||
const fullscreenButton = document.getElementById('fullscreen');
|
const fullscreenButton = document.getElementById('fullscreen');
|
||||||
const updateFullscreenLabel = () => {
|
const updateFullscreenLabel = () => {
|
||||||
@@ -86,7 +87,7 @@ const updateCamera = (cx, cy) => {
|
|||||||
camera.y = Math.min(Math.max(0, targetY), Math.max(0, WORLD_H - viewport.height));
|
camera.y = Math.min(Math.max(0, targetY), Math.max(0, WORLD_H - viewport.height));
|
||||||
};
|
};
|
||||||
|
|
||||||
let autoAim = false;
|
let autoAim = isMobile;
|
||||||
let muted = false;
|
let muted = false;
|
||||||
let autoAimNotice = 0;
|
let autoAimNotice = 0;
|
||||||
let muteNotice = 0;
|
let muteNotice = 0;
|
||||||
@@ -977,7 +978,7 @@ const gameLoop = (now) => {
|
|||||||
muted = sounds.toggleMuted();
|
muted = sounds.toggleMuted();
|
||||||
muteNotice = 1.2;
|
muteNotice = 1.2;
|
||||||
}
|
}
|
||||||
if (input.pressed.has('q')) {
|
if (!isMobile && input.pressed.has('q')) {
|
||||||
input.clearPressed();
|
input.clearPressed();
|
||||||
autoAim = !autoAim;
|
autoAim = !autoAim;
|
||||||
autoAimNotice = 1.2;
|
autoAimNotice = 1.2;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||||
<title>Zombies</title>
|
<title>Zombies</title>
|
||||||
@vite(['resources/css/game.css', 'resources/js/main.js'])
|
@vite(['resources/css/game.css', 'resources/js/main.js'])
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in new issue
Block a user