diff --git a/DEVLOG.md b/DEVLOG.md index 7abc466..aa4f7b1 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -19,6 +19,11 @@ - **Corrección de "Diagonal Leaking"**: Solucionado el problema donde los disparos atravesaban esquinas diagonales entre muros (se verifican ambos vecinos en cruces de vértice). - **Detección de Muros por Conectividad**: Reemplazada la comprobación simple de vacío por `canMoveBetween`, asegurando que los muros entre habitaciones/pasillos contiguos bloquen la visión correctamente si no hay puerta, incluso si ambas celdas tienen suelo. +3. **Sistema de Audio**: + - Implementado `SoundManager` para gestión centralizada de audio. + - **Música Ambiental**: Reproducción de `Abandoned_Ruins.mp3` con loop y manejo de políticas de autoplay del navegador. + - **Efectos de Sonido (SFX)**: Gatillo de sonido `opendoor.mp3` sincronizado con la apertura visual de puertas. + ### Estado Actual El juego cuenta con una visualización táctica profesional y un sistema de línea de visión robusto y justo, eliminando los fallos de detección en esquinas y muros. diff --git a/public/assets/music/ingame/Abandoned_Ruins.mp3 b/public/assets/music/ingame/Abandoned_Ruins.mp3 new file mode 100644 index 0000000..feac651 Binary files /dev/null and b/public/assets/music/ingame/Abandoned_Ruins.mp3 differ diff --git a/public/assets/sfx/opendoor.mp3 b/public/assets/sfx/opendoor.mp3 new file mode 100644 index 0000000..29dbe66 Binary files /dev/null and b/public/assets/sfx/opendoor.mp3 differ diff --git a/src/main.js b/src/main.js index 72ebc9e..85c5673 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,7 @@ import { GameEngine } from './engine/game/GameEngine.js'; import { GameRenderer } from './view/GameRenderer.js'; import { CameraManager } from './view/CameraManager.js'; import { UIManager } from './view/UIManager.js'; +import { SoundManager } from './view/SoundManager.js'; import { MissionConfig, MISSION_TYPES } from './engine/dungeon/MissionConfig.js'; @@ -19,10 +20,15 @@ const renderer = new GameRenderer('app'); const cameraManager = new CameraManager(renderer); const game = new GameEngine(); const ui = new UIManager(cameraManager, game); +const soundManager = new SoundManager(); + +// Start Music (Autoplay handling included in manager) +soundManager.playMusic('exploration'); // Global Access window.GAME = game; window.RENDERER = renderer; +window.SOUND_MANAGER = soundManager; // 3. Connect Dungeon Generator to Renderer const generator = game.dungeon; @@ -222,6 +228,7 @@ const handleClick = (x, y, doorMesh) => { // Open door visually renderer.openDoor(doorMesh); + if (window.SOUND_MANAGER) window.SOUND_MANAGER.playSound('door_open'); // Get proper exit data with direction const exitData = doorMesh.userData.exitData; diff --git a/src/view/SoundManager.js b/src/view/SoundManager.js new file mode 100644 index 0000000..4775a50 --- /dev/null +++ b/src/view/SoundManager.js @@ -0,0 +1,115 @@ + +export class SoundManager { + constructor() { + this.musicVolume = 0.3; // Default volume (not too loud) + this.sfxVolume = 0.5; + this.currentMusic = null; + this.isMuted = false; + + // Asset Library + this.assets = { + music: { + 'exploration': '/assets/music/ingame/Abandoned_Ruins.mp3' + }, + sfx: { + 'door_open': '/assets/sfx/opendoor.mp3' + } + }; + + this.initialized = false; + } + + /** + * Initializes the audio context if needed (browser restriction handling) + * Can be called on the first user interaction (click) + */ + init() { + if (this.initialized) return; + this.initialized = true; + console.log("[SoundManager] Audio System Initialized"); + } + + playMusic(key) { + if (this.isMuted) return; + + const url = this.assets.music[key]; + if (!url) { + console.warn(`[SoundManager] Music track not found: ${key}`); + return; + } + + // If same track is playing, do nothing + if (this.currentMusic && this.currentMusic.src.includes(url) && !this.currentMusic.paused) { + return; + } + + // Stop current + this.stopMusic(); + + // Start new + this.currentMusic = new Audio(url); + this.currentMusic.loop = true; + this.currentMusic.volume = this.musicVolume; + + // Handle autoplay promises + const playPromise = this.currentMusic.play(); + if (playPromise !== undefined) { + playPromise.catch(error => { + console.log("[SoundManager] Autoplay prevented. Waiting for user interaction."); + // We can add a one-time click listener to window to resume if needed + const resume = () => { + this.currentMusic.play(); + window.removeEventListener('click', resume); + window.removeEventListener('keydown', resume); + }; + window.addEventListener('click', resume); + window.addEventListener('keydown', resume); + }); + } + + console.log(`[SoundManager] Playing music: ${key}`); + } + + stopMusic() { + if (this.currentMusic) { + this.currentMusic.pause(); + this.currentMusic.currentTime = 0; + this.currentMusic = null; + } + } + + setMusicVolume(vol) { + this.musicVolume = Math.max(0, Math.min(1, vol)); + if (this.currentMusic) { + this.currentMusic.volume = this.musicVolume; + } + } + + toggleMute() { + this.isMuted = !this.isMuted; + if (this.isMuted) { + if (this.currentMusic) this.currentMusic.pause(); + } else { + if (this.currentMusic) this.currentMusic.play(); + } + return this.isMuted; + } + + playSound(key) { + if (this.isMuted) return; + + const url = this.assets.sfx[key]; + if (!url) { + console.warn(`[SoundManager] SFX not found: ${key}`); + return; + } + + const audio = new Audio(url); + audio.volume = this.sfxVolume; + // Fire and forget, but catch errors + audio.play().catch(e => { + // Check if error is NotAllowedError (autoplay) - silently ignore usually for SFX + // or log if needed + }); + } +}