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:
1446
assets/audio/footstep_grass.wav
Normal file
1446
assets/audio/footstep_grass.wav
Normal file
File diff suppressed because one or more lines are too long
1446
assets/audio/forest_ambient.mp3
Normal file
1446
assets/audio/forest_ambient.mp3
Normal file
File diff suppressed because one or more lines are too long
1446
assets/audio/wood_chop.wav
Normal file
1446
assets/audio/wood_chop.wav
Normal file
File diff suppressed because one or more lines are too long
@@ -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', {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user