/** * DYNAMIC ENVIRONMENT AUDIO SYSTEM * Mrtva Dolina - Fluidni okolju prilagojeni zvoki * * Features: * - Material-based door sounds (metal ruins vs wood farm) * - Adaptive weather audio (rain outside vs inside) * - Puddle system with splash footsteps * - Dynamic footstep sounds based on surface * - Smooth audio transitions (no AI jumps) */ export class DynamicEnvironmentAudio { constructor(scene) { this.scene = scene; // Door sounds by material this.doorSounds = { metal_ruins: { open: 'door_metal_creak', close: 'door_metal_slam', volume: 0.7 }, wood_farm: { open: 'door_wood_open', close: 'door_wood_close', volume: 0.5 }, tech_workshop: { open: 'door_tech_hiss', close: 'door_tech_lock', volume: 0.6 }, default: { open: 'door_generic_open', close: 'door_generic_close', volume: 0.5 } }; // Footstep sounds by surface this.footstepSounds = { grass: ['footstep_grass_1', 'footstep_grass_2', 'footstep_grass_3'], dirt: ['footstep_dirt_1', 'footstep_dirt_2', 'footstep_dirt_3'], stone: ['footstep_stone_1', 'footstep_stone_2', 'footstep_stone_3'], wood: ['footstep_wood_1', 'footstep_wood_2', 'footstep_wood_3'], puddle: ['splash_puddle_1', 'splash_puddle_2', 'splash_puddle_3'], metal: ['footstep_metal_1', 'footstep_metal_2'] }; // Weather system this.weatherActive = false; this.isIndoors = false; this.rainSound = null; this.rainSoundIndoors = null; // Puddle tracking this.puddles = []; this.puddlesLayer = null; this.init(); } init() { // Create weather audio objects (will be loaded separately) this.rainSound = { key: 'rain_outside', volume: 0.6, loop: true, audio: null }; this.rainSoundIndoors = { key: 'rain_inside_muffled', volume: 0.3, loop: true, audio: null }; console.log('✅ DynamicEnvironmentAudio initialized'); } /** * DOOR SYSTEM - Material-based sounds */ playDoorSound(doorType, action = 'open') { const door = this.doorSounds[doorType] || this.doorSounds.default; const soundKey = door[action]; if (this.scene.sound && this.scene.sound.get(soundKey)) { const sound = this.scene.sound.add(soundKey, { volume: door.volume }); sound.play(); // Add subtle environmental reverb based on location if (doorType === 'metal_ruins') { // More echo in ruins this.addReverb(sound, 0.4); } } else { console.warn(`🔇 Door sound not found: ${soundKey}`); } } /** * WEATHER SYSTEM - Adaptive rain audio */ startRain(intensity = 1.0) { this.weatherActive = true; // Play appropriate rain sound based on indoor/outdoor status this.updateRainAudio(); // Start creating puddles on the ground this.startPuddleGeneration(); console.log('🌧️ Rain started'); } stopRain() { this.weatherActive = false; // Fade out rain sounds this.fadeOutRain(); // Stop puddle generation (existing puddles remain) this.stopPuddleGeneration(); console.log('☀️ Rain stopped'); } updateRainAudio() { if (!this.weatherActive) return; if (this.isIndoors) { // Fade to muffled indoor rain this.crossFadeRain(this.rainSoundIndoors, this.rainSound); } else { // Fade to outdoor rain this.crossFadeRain(this.rainSound, this.rainSoundIndoors); } } crossFadeRain(soundIn, soundOut) { // Fade out old sound if (soundOut.audio) { this.scene.tweens.add({ targets: soundOut.audio, volume: 0, duration: 800, ease: 'Sine.easeInOut', onComplete: () => { soundOut.audio.pause(); } }); } // Fade in new sound if (!soundIn.audio) { soundIn.audio = this.scene.sound.add(soundIn.key, { volume: 0, loop: soundIn.loop }); } soundIn.audio.play(); this.scene.tweens.add({ targets: soundIn.audio, volume: soundIn.volume, duration: 800, ease: 'Sine.easeInOut' }); } fadeOutRain() { const sounds = [this.rainSound, this.rainSoundIndoors]; sounds.forEach(sound => { if (sound.audio) { this.scene.tweens.add({ targets: sound.audio, volume: 0, duration: 1500, ease: 'Sine.easeOut', onComplete: () => { sound.audio.stop(); sound.audio = null; } }); } }); } /** * Set player indoor/outdoor status */ setIndoors(isIndoors) { if (this.isIndoors === isIndoors) return; this.isIndoors = isIndoors; // Update rain audio if weather is active if (this.weatherActive) { this.updateRainAudio(); } console.log(`🏠 Player is now ${isIndoors ? 'indoors' : 'outdoors'}`); } /** * PUDDLE SYSTEM */ startPuddleGeneration() { // Create puddles over time this.puddleInterval = setInterval(() => { this.createPuddle(); }, 3000); // New puddle every 3 seconds } stopPuddleGeneration() { if (this.puddleInterval) { clearInterval(this.puddleInterval); this.puddleInterval = null; } } createPuddle() { // Create random puddle on ground const x = Phaser.Math.Between(0, this.scene.cameras.main.width); const y = Phaser.Math.Between(0, this.scene.cameras.main.height); const puddle = this.scene.add.graphics(); puddle.fillStyle(0x4a6fa5, 0.4); // Blue-ish, semi-transparent // Random puddle shape const radius = Phaser.Math.Between(20, 50); puddle.fillEllipse(x, y, radius, radius * 0.7); // Add ripple animation this.addPuddleRipples(x, y, radius); // Store puddle for collision detection this.puddles.push({ x: x, y: y, radius: radius, graphics: puddle }); // Puddles slowly evaporate after rain stops if (!this.weatherActive) { this.scene.tweens.add({ targets: puddle, alpha: 0, duration: 10000, delay: 5000, onComplete: () => { puddle.destroy(); this.puddles = this.puddles.filter(p => p.graphics !== puddle); } }); } } addPuddleRipples(x, y, radius) { // Periodic ripple circles from raindrops const rippleInterval = setInterval(() => { if (!this.weatherActive) { clearInterval(rippleInterval); return; } const ripple = this.scene.add.graphics(); ripple.lineStyle(2, 0xffffff, 0.6); ripple.strokeCircle(x, y, 5); this.scene.tweens.add({ targets: ripple, alpha: 0, scaleX: radius / 5, scaleY: radius / 5, duration: 1000, ease: 'Sine.easeOut', onComplete: () => ripple.destroy() }); }, Phaser.Math.Between(500, 2000)); } checkPuddleCollision(x, y) { // Check if player is stepping in a puddle for (const puddle of this.puddles) { const distance = Phaser.Math.Distance.Between(x, y, puddle.x, puddle.y); if (distance < puddle.radius) { return true; } } return false; } /** * FOOTSTEP SYSTEM */ playFootstep(x, y, surface = 'grass') { // Check if stepping in puddle if (this.checkPuddleCollision(x, y)) { surface = 'puddle'; } const soundArray = this.footstepSounds[surface] || this.footstepSounds.grass; const randomSound = Phaser.Utils.Array.GetRandom(soundArray); if (this.scene.sound && this.scene.sound.get(randomSound)) { const volume = surface === 'puddle' ? 0.5 : 0.3; this.scene.sound.play(randomSound, { volume: volume }); } else { console.warn(`🔇 Footstep sound not found: ${randomSound}`); } } /** * Play character-specific footstep (for Zombie Scout with gear) */ playCharacterFootstep(character, x, y, surface = 'grass') { // Play base footstep this.playFootstep(x, y, surface); // Add character-specific sounds if (character === 'zombie_scout') { // Add gear rattle and backpack sounds setTimeout(() => { if (this.scene.sound && this.scene.sound.get('gear_rattle')) { this.scene.sound.play('gear_rattle', { volume: 0.2 }); } }, 50); } } /** * Simple reverb effect (placeholder - real reverb needs Web Audio API) */ addReverb(sound, amount = 0.3) { // This is a simplified approach // Real implementation would use ConvolverNode in Web Audio API console.log(`🎙️ Adding ${amount * 100}% reverb to sound`); } /** * Update loop - check player position for puddles */ update() { // Called every frame by main scene if (this.scene.player) { const player = this.scene.player; // Detect when player steps in puddle if (player.isMoving && this.checkPuddleCollision(player.x, player.y)) { // Trigger splash VFX this.scene.events.emit('player:stepped_in_puddle', player.x, player.y); } } } /** * Cleanup */ destroy() { this.stopRain(); this.stopPuddleGeneration(); // Clean up puddles this.puddles.forEach(puddle => { if (puddle.graphics) { puddle.graphics.destroy(); } }); this.puddles = []; } } /** * INTEGRATION EXAMPLE: * * // In MainScene.js create() * this.envAudio = new DynamicEnvironmentAudio(this); * * // When player opens door * this.envAudio.playDoorSound('metal_ruins', 'open'); * * // When weather changes * this.envAudio.startRain(1.0); * * // When player enters building * this.envAudio.setIndoors(true); * * // In update loop * if (this.player.isMoving && this.player.stepCount % 10 === 0) { * const surface = this.getCurrentSurface(this.player.x, this.player.y); * this.envAudio.playFootstep(this.player.x, this.player.y, surface); * } * * // For Zombie Scout companion * if (this.zombieScout.isMoving) { * this.envAudio.playCharacterFootstep('zombie_scout', * this.zombieScout.x, this.zombieScout.y, surface); * } */