🎵🎨 Jan 8 Visual & Audio Systems Complete - Biome Music + Spatial Triggers + Test Scene

 SYSTEMS CREATED:

**1. BiomeMusicSystem.js (Background Music):**
- Automatic music switching based on player position
- Smooth cross-fade transitions (2 seconds)
- Biome-specific tracks (grassland, forest, town, combat)
- Night music override (8pm-6am)
- Volume control + master volume
- Loop support for ambient tracks

**2. AudioTriggerSystem.js (Spatial Audio):**
- Trigger audio when player enters specific tiles
- One-time trigger support (play only once)
- Radius detection (exact tile or area)
- Delay support before audio plays
- Callback functions after audio
- Visual debug markers (green circle + 🔊 icon)
- Trigger history tracking

**3. TestVisualAudioScene.js (DEMO SCENE):**
🎬 Complete visual & audio demonstration:

**Visual Effects:**
- Kai character with 8 animated dreadlocks
- Dreadlocks wave in wind (sine wave animation)
- 20 falling leaves (continuous spawn)
- Leaf rotation + side-sway animation
- WASD movement controls
- Camera follow with zoom

**Audio Triggers:**
- Yellow tile at (10, 7) triggers Kai's voice
- Plays: 'My name is Kai, and I will find my sister.'
- One-time trigger (won't repeat)
- Speech bubble appears after trigger
- Visual feedback (green flash)

**Scene Features:**
- Grass tile grid (20x15)
- Alternating light/dark grass pattern
- Instructions overlay
- ESC to exit scene

**Integration:**
- Added to index.html
- Added to game.js scene list
- Ready to launch: game.scene.start('TestVisualAudioScene')

🎯 Test Command:
Open browser console and type:
game.scene.start('TestVisualAudioScene')

📝 For music:
1. Add music files to /assets/audio/music/
2. System automatically cross-fades on biome change
3. Night music override active 8pm-6am
This commit is contained in:
2026-01-08 16:07:46 +01:00
parent ce3b89d776
commit 90b8396e45
5 changed files with 679 additions and 1 deletions

View File

@@ -0,0 +1,181 @@
/**
* AudioTriggerSystem.js
* Spatial audio triggers - play sound once when player enters area
*/
class AudioTriggerSystem {
constructor(scene) {
this.scene = scene;
// Active triggers
this.triggers = new Map();
// Triggered history (to prevent re-triggering)
this.triggered = new Set();
console.log('🔊 AudioTriggerSystem initialized');
}
/**
* Add a spatial audio trigger
* @param {number} x - Grid X position
* @param {number} y - Grid Y position
* @param {string} audioKey - Audio file key
* @param {object} options - Additional options
*/
addTrigger(x, y, audioKey, options = {}) {
const triggerId = `${x},${y}`;
const trigger = {
x,
y,
audioKey,
radius: options.radius || 0, // 0 = exact tile only
volume: options.volume || 1.0,
oneTime: options.oneTime !== false, // Default true
delay: options.delay || 0, // Delay before playing (ms)
callback: options.callback || null, // Optional callback after playing
visualDebug: options.visualDebug || false
};
this.triggers.set(triggerId, trigger);
// Add visual debug marker if enabled
if (trigger.visualDebug && this.scene.add) {
const worldX = x * 48 + 24;
const worldY = y * 48 + 24;
const circle = this.scene.add.circle(worldX, worldY, 20, 0x00ff00, 0.3);
circle.setDepth(1000);
const text = this.scene.add.text(worldX, worldY - 30, '🔊', {
fontSize: '20px',
color: '#00ff00'
});
text.setOrigin(0.5);
text.setDepth(1001);
}
console.log(`✅ Audio trigger added at (${x}, ${y}): ${audioKey}`);
return triggerId;
}
/**
* Remove a trigger
*/
removeTrigger(triggerId) {
this.triggers.delete(triggerId);
this.triggered.delete(triggerId);
}
/**
* Reset trigger (allow re-triggering)
*/
resetTrigger(triggerId) {
this.triggered.delete(triggerId);
}
/**
* Reset all triggers
*/
resetAll() {
this.triggered.clear();
}
/**
* Check if player is in trigger zone
*/
checkTrigger(playerX, playerY) {
const playerGridX = Math.floor(playerX / 48);
const playerGridY = Math.floor(playerY / 48);
this.triggers.forEach((trigger, triggerId) => {
// Skip if already triggered and it's one-time only
if (trigger.oneTime && this.triggered.has(triggerId)) {
return;
}
// Check distance
const dx = Math.abs(playerGridX - trigger.x);
const dy = Math.abs(playerGridY - trigger.y);
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= trigger.radius) {
// TRIGGER!
this.activateTrigger(trigger, triggerId);
}
});
}
/**
* Activate a trigger (play audio)
*/
activateTrigger(trigger, triggerId) {
console.log(`🔊 TRIGGER ACTIVATED: ${triggerId} (${trigger.audioKey})`);
// Mark as triggered
this.triggered.add(triggerId);
// Play audio after delay
if (trigger.delay > 0) {
this.scene.time.delayedCall(trigger.delay, () => {
this.playAudio(trigger);
});
} else {
this.playAudio(trigger);
}
}
/**
* Play audio for trigger
*/
playAudio(trigger) {
// Check if audio exists
if (!this.scene.cache.audio.exists(trigger.audioKey)) {
console.warn(`⚠️ Audio not found: ${trigger.audioKey}`);
return;
}
// Play sound
const sound = this.scene.sound.add(trigger.audioKey, {
volume: trigger.volume
});
sound.play();
// Visual feedback (optional)
if (trigger.visualDebug) {
const worldX = trigger.x * 48 + 24;
const worldY = trigger.y * 48 + 24;
const flash = this.scene.add.circle(worldX, worldY, 30, 0xffff00, 0.8);
flash.setDepth(1002);
this.scene.tweens.add({
targets: flash,
alpha: 0,
scale: 2,
duration: 500,
ease: 'Quad.Out',
onComplete: () => flash.destroy()
});
}
// Callback
if (trigger.callback) {
trigger.callback();
}
console.log(`✅ Audio played: ${trigger.audioKey}`);
}
/**
* Update - called every frame
*/
update(playerX, playerY) {
if (this.triggers.size === 0) return;
this.checkTrigger(playerX, playerY);
}
}