joystickInput.x = 0; joystickInput.y = 0; joystickStick.style.transform = 'translate(-50%, -50%)'; }); function updateJoystick(touch) { const rect = joystickZone.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; let dx = touch.clientX - centerX; let dy = touch.clientY - centerY; const maxDist = 40; const dist = Math.sqrt(dx * dx + dy * dy); if (dist > maxDist) { dx = (dx / dist) * maxDist; dy = (dy / dist) * maxDist; } joystickInput.x = dx / maxDist; joystickInput.y = -dy / maxDist; joystickStick.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`; } // Jump button jumpBtn.addEventListener('touchstart', (e) => { e.preventDefault(); jumpPressed = true; }); // Look controls lookZone.addEventListener('touchstart', (e) => { e.preventDefault(); if (lookTouch === null) { lookTouch = e.changedTouches[0].identifier; lastLookPos.x = e.changedTouches[0].clientX; lastLookPos.y = e.changedTouches[0].clientY; } // Hide instructions on first touch instructions.style.opacity = '0'; }); lookZone.addEventListener('touchmove', (e) => { e.preventDefault(); for (let touch of e.changedTouches) { if (touch.identifier === lookTouch) { const dx = touch.clientX - lastLookPos.x; const dy = touch.clientY - lastLookPos.y; yaw -= dx * LOOK_SENSITIVITY * 0.01; pitch -= dy * LOOK_SENSITIVITY * 0.01; // Clamp pitch pitch = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, pitch)); lastLookPos.x = touch.clientX; lastLookPos.y = touch.clientY; } } }); lookZone.addEventListener('touchend', (e) => { for (let touch of e.changedTouches) { if (touch.identifier === lookTouch) { lookTouch = null; } } }); lookZone.addEventListener('touchcancel', (e) => { lookTouch = null; }); // --- KEYBOARD CONTROLS (for testing on desktop) --- const keys = {}; document.addEventListener('keydown', (e) => { keys[e.code] = true; if (e.code === 'Space') { jumpPressed = true; } }); document.addEventListener('keyup', (e) => { keys[e.code] = false; }); function updateKeyboardInput() { if (keys['KeyW'] || keys['ArrowUp']) joystickInput.y = 1; else if (keys['KeyS'] || keys['ArrowDown']) joystickInput.y = -1; else if (joystickTouch === null) joystickInput.y = 0; if (keys['KeyD'] || keys['ArrowRight']) joystickInput.x = 1; else if (keys['KeyA'] || keys['ArrowLeft']) joystickInput.x = -1; else if (joystickTouch === null) joystickInput.x = 0; } // Mouse look (for desktop testing) let mouseDown = false; renderer.domElement.addEventListener('mousedown', (e) => { mouseDown = true; instructions.style.opacity = '0'; }); renderer.domElement.addEventListener('mouseup', () => { mouseDown = false; }); renderer.domElement.addEventListener('mousemove', (e) => { if (mouseDown) { yaw -= e.movementX * 0.003; pitch -= e.movementY * 0.003; pitch = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, pitch)); } }); // --- RENDER LOOP --- let lastTime = performance.now(); function update() { const now = performance.now(); const deltaTime = (now - lastTime) / 1000; lastTime = now; updateKeyboardInput(); updatePlayer(deltaTime); renderer.render(scene, camera); requestAnimationFrame(update); } window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Prevent default touch behavior document.addEventListener('touchmove', (e) => { e.preventDefault(); }, { passive: false }); update(); >