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

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;