feat: Automated Audio System & Royalty-Free Asset Integration (Phase 8 & 9)

- Added footstep_grass.wav, wood_chop.wav, forest_ambient.mp3
- Synchronized rhythm footsteps with walk animation frames
- Implemented proximity-based pond music modulation
- Updated SoundManager to prioritize high-quality assets
This commit is contained in:
2025-12-28 00:52:33 +01:00
parent 822c586843
commit c8743986ad
7 changed files with 4540 additions and 21 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1446
assets/audio/wood_chop.wav Normal file

File diff suppressed because one or more lines are too long

View File

@@ -450,6 +450,23 @@ class Player {
if (this.sprite.anims.currentAnim) {
this.sprite.anims.currentAnim.frameRate = frameRate;
}
// 🔉 PHASE 8: Footstep triggers (frames 1 and 3 are usually down-steps)
const currentFrame = this.sprite.anims.currentFrame ? this.sprite.anims.currentFrame.index : 0;
if ((currentFrame === 1 || currentFrame === 3) && this.lastFootstepFrame !== currentFrame) {
if (this.scene.soundManager) {
// Determine surface
let surface = 'grass';
if (this.scene.terrainSystem && this.scene.terrainSystem.getTile) {
const tile = this.scene.terrainSystem.getTile(this.gridX, this.gridY);
if (tile && tile.type === 'dirt') surface = 'dirt';
}
this.scene.soundManager.playFootstep(surface);
}
this.lastFootstepFrame = currentFrame;
} else if (currentFrame !== 1 && currentFrame !== 3) {
this.lastFootstepFrame = -1;
}
}
}
@@ -773,6 +790,8 @@ class Player {
const used = this.scene.statusEffectSystem.applyEffect('high');
if (used) {
invSys.removeItem(slot.type, 1);
// Audio feedback
if (this.scene.soundManager) this.scene.soundManager.playSmokeSound();
// Visual feedback
this.createSmokeParticles(this.gridX, this.gridY);
this.scene.events.emit('show-floating-text', {

View File

@@ -809,6 +809,7 @@ class GameScene extends Phaser.Scene {
console.log('🎵 Initializing Sound Manager...');
this.soundManager = new SoundManager(this);
this.soundManager.startMusic();
this.soundManager.playForestAmbient(); // 🌲 PHASE 9: Start background ambience
// Initialize Parallax System
console.log('🌄 Initializing Parallax System...');
@@ -1987,7 +1988,26 @@ class GameScene extends Phaser.Scene {
}
}
// Parallax Logic
// 🎵 PHASE 8: Pond Proximity Audio Modulation
if (this.player && this.lakeSystem && this.soundManager) {
const playerPos = this.player.getPosition();
let minPondDist = 1000;
// Find nearest pond
this.lakeSystem.lakes.forEach(lake => {
if (lake.type === 'pond' || lake.type === 'lake') {
const d = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, lake.x, lake.y);
if (d < minPondDist) minPondDist = d;
}
});
// Map distance to modulation factor (e.g., 0 within 5 tiles, 1 far away)
// Or 1 at pond center, 0 far away? User said "malo spremeni, ko sem blizu"
// Let's go with 1.0 (max change) at dist=0, and 0.0 (no change) beyond 15 tiles
const modFactor = Phaser.Math.Clamp(1 - (minPondDist / 15), 0, 1);
this.soundManager.setAmbientModulation(modFactor);
}
if (this.parallaxSystem && this.player) {
const playerPos = this.player.getPosition();
// 🎨 FLAT 2D (NEW!) - Direct position, no conversion

View File

@@ -386,10 +386,12 @@ class PreloadScene extends Phaser.Scene {
console.log('🎮 Krvava Žetev: 122+ sprite sheets loaded!');
*/
// ═══════════════════════════════════════════════════════════════
// End of Krvava Žetev Asset Library (DISABLED)
// ═══════════════════════════════════════════════════════════════
// 🔉 AUDIO ASSETS
this.load.audio('footstep_grass', 'assets/audio/footstep_grass.wav');
this.load.audio('wood_chop', 'assets/audio/wood_chop.wav');
this.load.audio('forest_ambient', 'assets/audio/forest_ambient.mp3');
console.log('🔉 Audio assets queued for loading');
}
createAnimations() {

View File

@@ -14,6 +14,9 @@ class SoundManager {
if (this.scene.sound.get(key)) {
this.scene.sound.play(key, { volume: this.sfxVolume });
} else if (key === 'chop' && this.scene.sound.get('wood_chop')) {
// Priority for real wood chop sound
this.scene.sound.play('wood_chop', { volume: this.sfxVolume });
} else {
// Enhanced placeholder beeps
if (key === 'chop') {
@@ -32,6 +35,24 @@ class SoundManager {
}
}
playMusic(key, loop = true) {
if (this.isMuted) return;
// Stop current music
if (this.currentMusic) {
this.currentMusic.stop();
}
if (this.scene.sound.get(key)) {
this.currentMusic = this.scene.sound.add(key, { volume: this.musicVolume, loop: loop });
this.currentMusic.play();
}
}
playForestAmbient() {
this.playMusic('forest_ambient', true);
}
beepChop() {
if (!this.scene.sound.context) return;
const ctx = this.scene.sound.context;
@@ -151,18 +172,29 @@ class SoundManager {
}
beepFootstep() {
if (!this.scene.sound.context) return;
const ctx = this.scene.sound.context;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.value = 120 + Math.random() * 20;
osc.type = 'sine';
gain.gain.setValueAtTime(0.05, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.03);
osc.start();
osc.stop(ctx.currentTime + 0.03);
this.playFootstep('grass');
}
playFootstep(surface = 'grass') {
if (this.isMuted) return;
const key = `footstep_${surface}`;
if (this.scene.sound.get(key)) {
this.scene.sound.play(key, { volume: this.sfxVolume * 0.4 });
} else {
// Fallback beep
if (!this.scene.sound.context) return;
const ctx = this.scene.sound.context;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.value = 120 + Math.random() * 20;
osc.type = 'sine';
gain.gain.setValueAtTime(0.05, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.03);
osc.start();
osc.stop(ctx.currentTime + 0.03);
}
}
beepDeath() {
@@ -257,6 +289,38 @@ class SoundManager {
}, 2000);
}
setAmbientModulation(factor) {
// factor 0 to 1 (proximity to pond)
this.ambientModFactor = factor;
// Optionally adjust existing music pitch or filter
if (this.currentMusic && this.currentMusic.isPlaying) {
this.currentMusic.setRate(1.0 - (factor * 0.1)); // Slightly slow down
}
}
startHighMusic() {
if (!this.scene.sound.context || this.highInterval) return;
console.log('🌈 Starting High Ambient Music...');
// Warped/Low scales for high effect
const scale = [65.41, 77.78, 87.31, 98.00, 116.54, 130.81];
this.highInterval = setInterval(() => {
if (this.isMuted) return;
if (Math.random() > 0.4) {
const freq = scale[Math.floor(Math.random() * scale.length)];
this.playProceduralNote(freq, 4.0, 0.1, 'sawtooth'); // More buzzy/long
}
}, 3000);
}
stopHighMusic() {
if (this.highInterval) {
clearInterval(this.highInterval);
this.highInterval = null;
}
}
stopMusic() {
if (this.musicInterval) {
clearInterval(this.musicInterval);
@@ -264,7 +328,7 @@ class SoundManager {
}
}
playProceduralNote(freq) {
playProceduralNote(freq, duration = 2.0, volumeMod = 0.05, type = 'sine') {
if (!this.scene.sound.context) return;
const ctx = this.scene.sound.context;
@@ -275,13 +339,12 @@ class SoundManager {
gain.connect(ctx.destination);
osc.frequency.value = freq;
osc.type = 'sine'; // Soft tone
osc.type = type;
const now = ctx.currentTime;
const duration = 2.0; // Long decay like reverb/pad
gain.gain.setValueAtTime(0, now);
gain.gain.linearRampToValueAtTime(0.05 * this.musicVolume, now + 0.5); // Slow attack
gain.gain.linearRampToValueAtTime(volumeMod * this.musicVolume, now + 0.5); // Slow attack
gain.gain.exponentialRampToValueAtTime(0.001, now + duration); // Long release
osc.start(now);
@@ -294,7 +357,13 @@ class SoundManager {
console.log(this.isMuted ? '🔇 Muted' : '🔊 Unmuted');
}
playChop() { this.playSFX('chop'); }
playChop() {
if (this.scene.sound.get('wood_chop')) {
this.scene.sound.play('wood_chop', { volume: this.sfxVolume });
} else {
this.playSFX('chop');
}
}
playPlant() { this.playSFX('plant'); }
playHarvest() { this.playSFX('harvest'); }
playBuild() { this.playSFX('build'); }
@@ -307,6 +376,77 @@ class SoundManager {
playDeath() { this.beepDeath(); }
playRainSound() { this.startRainNoise(); }
stopRainSound() { this.stopRainNoise(); }
playHighAmbient() { this.startHighMusic(); }
stopHighAmbient() { this.stopHighMusic(); }
playCraftSound() {
if (!this.scene.sound.context) return;
const ctx = this.scene.sound.context;
// Hammering rhythm
for (let i = 0; i < 3; i++) {
const time = ctx.currentTime + (i * 0.3);
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.setValueAtTime(200 - (i * 20), time);
osc.type = 'square';
gain.gain.setValueAtTime(0.1, time);
gain.gain.exponentialRampToValueAtTime(0.01, time + 0.1);
osc.start(time);
osc.stop(time + 0.1);
}
}
playSuccessSound() {
if (!this.scene.sound.context) return;
const ctx = this.scene.sound.context;
const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6
notes.forEach((freq, i) => {
const time = ctx.currentTime + (i * 0.1);
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.value = freq;
osc.type = 'sine';
gain.gain.setValueAtTime(0, time);
gain.gain.linearRampToValueAtTime(0.1, time + 0.05);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.3);
osc.start(time);
osc.stop(time + 0.3);
});
}
playSmokeSound() {
if (!this.scene.sound.context) return;
const ctx = this.scene.sound.context;
// Inhale (White noise sweep)
const bufferSize = ctx.sampleRate * 0.5;
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
const source = ctx.createBufferSource();
source.buffer = buffer;
const filter = ctx.createBiquadFilter();
const gain = ctx.createGain();
source.connect(filter);
filter.connect(gain);
gain.connect(ctx.destination);
filter.type = 'lowpass';
filter.frequency.setValueAtTime(100, ctx.currentTime);
filter.frequency.exponentialRampToValueAtTime(2000, ctx.currentTime + 0.5);
gain.gain.setValueAtTime(0, ctx.currentTime);
gain.gain.linearRampToValueAtTime(0.1, ctx.currentTime + 0.1);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.5);
source.start();
}
beepUIClick() {
if (!this.scene.sound.context) return;