215 lines
7.7 KiB
JavaScript
Executable File
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;
|