Files

215 lines
7.7 KiB
JavaScript
Executable File

/**
* DOOM 3D - Mobile Touch Controls
* Virtual thumbstick for movement, swipe aiming, and action buttons.
*/
class DoomMobileControls {
constructor(player) {
this.player = player;
this.container = document.getElementById('mobile-controls');
this.joystickBase = document.getElementById('joystick-base');
this.joystickKnob = document.getElementById('joystick-knob');
this.lookZone = document.getElementById('mobile-look-zone');
this.touchMoveId = null;
this.touchLookId = null;
this.joystickCenter = { x: 0, y: 0 };
this.maxRadius = 50;
this.lastLookX = 0;
this.lastLookY = 0;
this.init();
}
init() {
const isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
if (isTouch && this.container) {
this.container.classList.remove('hidden');
}
this.setupJoystick();
this.setupLookZone();
this.setupActionButtons();
}
setupJoystick() {
if (!this.joystickBase || !this.joystickKnob) return;
const onTouchStart = (e) => {
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i];
if (this.touchMoveId === null) {
this.touchMoveId = touch.identifier;
const rect = this.joystickBase.getBoundingClientRect();
this.joystickCenter.x = rect.left + rect.width / 2;
this.joystickCenter.y = rect.top + rect.height / 2;
this.handleJoystickMove(touch.clientX, touch.clientY);
}
}
};
const onTouchMove = (e) => {
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i];
if (touch.identifier === this.touchMoveId) {
this.handleJoystickMove(touch.clientX, touch.clientY);
}
}
};
const onTouchEnd = (e) => {
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i];
if (touch.identifier === this.touchMoveId) {
this.touchMoveId = null;
this.joystickKnob.style.transform = 'translate(-50%, -50%)';
this.player.keys.forward = false;
this.player.keys.backward = false;
this.player.keys.left = false;
this.player.keys.right = false;
}
}
};
this.joystickBase.addEventListener('touchstart', onTouchStart, { passive: false });
window.addEventListener('touchmove', onTouchMove, { passive: false });
window.addEventListener('touchend', onTouchEnd, { passive: false });
window.addEventListener('touchcancel', onTouchEnd, { passive: false });
}
handleJoystickMove(clientX, clientY) {
const dx = clientX - this.joystickCenter.x;
const dy = clientY - this.joystickCenter.y;
const dist = Math.hypot(dx, dy);
const clampedDist = Math.min(dist, this.maxRadius);
const angle = Math.atan2(dy, dx);
const knobX = Math.cos(angle) * clampedDist;
const knobY = Math.sin(angle) * clampedDist;
this.joystickKnob.style.transform = `translate(calc(-50% + ${knobX}px), calc(-50% + ${knobY}px))`;
// Normalize inputs (-1 to 1)
const normX = knobX / this.maxRadius;
const normY = knobY / this.maxRadius;
const threshold = 0.25;
this.player.keys.forward = normY < -threshold;
this.player.keys.backward = normY > threshold;
this.player.keys.left = normX < -threshold;
this.player.keys.right = normX > threshold;
}
setupLookZone() {
if (!this.lookZone) return;
this.lookZone.addEventListener('touchstart', (e) => {
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i];
if (this.touchLookId === null) {
this.touchLookId = touch.identifier;
this.lastLookX = touch.clientX;
this.lastLookY = touch.clientY;
}
}
}, { passive: false });
window.addEventListener('touchmove', (e) => {
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i];
if (touch.identifier === this.touchLookId) {
const dx = touch.clientX - this.lastLookX;
const dy = touch.clientY - this.lastLookY;
this.lastLookX = touch.clientX;
this.lastLookY = touch.clientY;
// Apply rotation to player camera
const sensitivity = this.player.mouseSensitivity * 1.5;
this.player.euler.y -= dx * sensitivity;
this.player.euler.x -= dy * sensitivity;
const maxPitch = Math.PI / 2 - 0.02;
this.player.euler.x = Math.max(-maxPitch, Math.min(maxPitch, this.player.euler.x));
this.player.camera.quaternion.setFromEuler(this.player.euler);
}
}
}, { passive: false });
const endLook = (e) => {
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i];
if (touch.identifier === this.touchLookId) {
this.touchLookId = null;
}
}
};
window.addEventListener('touchend', endLook, { passive: false });
window.addEventListener('touchcancel', endLook, { passive: false });
}
setupActionButtons() {
const btnFire = document.getElementById('btn-mobile-fire');
const btnJump = document.getElementById('btn-mobile-jump');
const btnUse = document.getElementById('btn-mobile-use');
const btnNext = document.getElementById('btn-mobile-next');
const btnPrev = document.getElementById('btn-mobile-prev');
if (btnFire) {
btnFire.addEventListener('touchstart', (e) => {
e.preventDefault();
this.player.keys.shoot = true;
if (window.game && window.game.weapons) {
window.game.weapons.triggerAttack();
}
});
btnFire.addEventListener('touchend', (e) => {
e.preventDefault();
this.player.keys.shoot = false;
});
}
if (btnJump) {
btnJump.addEventListener('touchstart', (e) => {
e.preventDefault();
this.player.keys.jump = true;
});
btnJump.addEventListener('touchend', (e) => {
e.preventDefault();
this.player.keys.jump = false;
});
}
if (btnUse) {
btnUse.addEventListener('touchstart', (e) => {
e.preventDefault();
this.player.checkInteractions();
});
}
if (btnNext) {
btnNext.addEventListener('touchstart', (e) => {
e.preventDefault();
if (window.game && window.game.weapons) {
window.game.weapons.nextWeapon();
}
});
}
if (btnPrev) {
btnPrev.addEventListener('touchstart', (e) => {
e.preventDefault();
if (window.game && window.game.weapons) {
window.game.weapons.prevWeapon();
}
});
}
}
}
window.DoomMobileControls = DoomMobileControls;