Implemented 3 advanced game systems: CinematicVoiceSystem (emotional depth, reverb, typewriter sync, Zombie Scout audio), DynamicEnvironmentAudio (material doors, adaptive rain, puddles, footsteps), ElectionSystem (chaos phase, voting, inauguration, city improvements). All systems integrated with ADHD-friendly features and smooth transitions.
This commit is contained in:
391
src/systems/DynamicEnvironmentAudio.js
Normal file
391
src/systems/DynamicEnvironmentAudio.js
Normal file
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* 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);
|
||||
* }
|
||||
*/
|
||||
Reference in New Issue
Block a user