Session 4: Sound effects system with Web Audio API
This commit is contained in:
@@ -72,4 +72,30 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Session 4 - 2026-08-20 (Morning)
|
||||||
|
**Status:** ✅ Sound Effects System Added
|
||||||
|
|
||||||
|
#### What was done:
|
||||||
|
- Created complete audio system using Web Audio API (no external assets needed)
|
||||||
|
- Added click sound effect (rising C-major arpeggio on each harvest)
|
||||||
|
- Added purchase sound effect (descending chime when buying buildings)
|
||||||
|
- Added achievement fanfare (ascending chord progression on unlock)
|
||||||
|
- Integrated audio into game.js, achievements.js, and index.html
|
||||||
|
- Audio context auto-initializes on game start; browsers handle suspension automatically
|
||||||
|
|
||||||
|
#### Files modified/created:
|
||||||
|
- `js/audio.js` - New file with Audio system (click, purchase, achievement sounds via Web Audio API)
|
||||||
|
- `js/game.js` - Added Audio.init() in init(), Audio.playClick() in harvest(), Audio.playPurchase() in buyBuilding()
|
||||||
|
- `js/achievements.js` - Added Audio.playAchievement() on achievement unlock
|
||||||
|
- `index.html` - Added audio.js script tag
|
||||||
|
|
||||||
|
#### Next goals:
|
||||||
|
- Mobile touch optimizations (tap feedback, responsive layout)
|
||||||
|
- Prestige/reset system with bonuses
|
||||||
|
- Consider adding more sound variations or music
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
*Journal auto-updated by Hermes Agent cron job (every 8 hours)*
|
*Journal auto-updated by Hermes Agent cron job (every 8 hours)*
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
<script src="js/ui.js"></script>
|
<script src="js/ui.js"></script>
|
||||||
<script src="js/stats.js"></script>
|
<script src="js/stats.js"></script>
|
||||||
<script src="js/particles.js"></script>
|
<script src="js/particles.js"></script>
|
||||||
|
<script src="js/audio.js"></script>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -165,6 +165,9 @@ const Achievements = {
|
|||||||
Game.state.achievements.push(id);
|
Game.state.achievements.push(id);
|
||||||
UI.showNotification(`Achievement: ${achievement.name}!`, 'achievement');
|
UI.showNotification(`Achievement: ${achievement.name}!`, 'achievement');
|
||||||
|
|
||||||
|
// Audio feedback
|
||||||
|
Audio.playAchievement();
|
||||||
|
|
||||||
// Particle effect for achievement unlock
|
// Particle effect for achievement unlock
|
||||||
const countEl = document.querySelector('#achievements-tab h3');
|
const countEl = document.querySelector('#achievements-tab h3');
|
||||||
if (countEl) {
|
if (countEl) {
|
||||||
|
|||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - Audio System
|
||||||
|
* Web Audio API-based sound generation (no external assets needed)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Audio = {
|
||||||
|
ctx: null,
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize audio context (must be called after user interaction)
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
try {
|
||||||
|
window.AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||||
|
this.ctx = new AudioContext();
|
||||||
|
console.log('🔊 Audio system initialized');
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Web Audio API not supported');
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume audio context (for browsers that suspend it)
|
||||||
|
*/
|
||||||
|
resume() {
|
||||||
|
if (this.ctx && this.ctx.state === 'suspended') {
|
||||||
|
this.ctx.resume();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a simple tone
|
||||||
|
*/
|
||||||
|
playTone(frequency, duration, type = 'sine', volume = 0.1) {
|
||||||
|
if (!this.enabled || !this.ctx) return;
|
||||||
|
|
||||||
|
const osc = this.ctx.createOscillator();
|
||||||
|
const gain = this.ctx.createGain();
|
||||||
|
|
||||||
|
osc.type = type;
|
||||||
|
osc.frequency.setValueAtTime(frequency, this.ctx.currentTime);
|
||||||
|
|
||||||
|
gain.gain.setValueAtTime(volume, this.ctx.currentTime);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
|
||||||
|
|
||||||
|
osc.connect(gain);
|
||||||
|
gain.connect(this.ctx.destination);
|
||||||
|
|
||||||
|
osc.start(this.ctx.currentTime);
|
||||||
|
osc.stop(this.ctx.currentTime + duration);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a rising arpeggio (for clicks)
|
||||||
|
*/
|
||||||
|
playClick() {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
this.resume();
|
||||||
|
|
||||||
|
const now = this.ctx.currentTime;
|
||||||
|
const notes = [523.25, 659.25, 783.99]; // C major chord
|
||||||
|
|
||||||
|
notes.forEach((freq, i) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.playTone(freq, 0.1, 'sine', 0.08);
|
||||||
|
}, i * 30);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a purchase sound (descending chime)
|
||||||
|
*/
|
||||||
|
playPurchase() {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
this.resume();
|
||||||
|
|
||||||
|
const notes = [783.99, 659.25, 523.25]; // G - E - C
|
||||||
|
|
||||||
|
notes.forEach((freq, i) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.playTone(freq, 0.15, 'triangle', 0.1);
|
||||||
|
}, i * 50);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play an achievement unlock fanfare
|
||||||
|
*/
|
||||||
|
playAchievement() {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
this.resume();
|
||||||
|
|
||||||
|
const notes = [523.25, 659.25, 783.99, 1046.50]; // C - E - G - C (high)
|
||||||
|
|
||||||
|
notes.forEach((freq, i) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.playTone(freq, 0.2, 'square', 0.06);
|
||||||
|
}, i * 80);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a notification sound
|
||||||
|
*/
|
||||||
|
playNotification() {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
this.resume();
|
||||||
|
|
||||||
|
this.playTone(880, 0.1, 'sine', 0.08);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.playTone(1100, 0.15, 'sine', 0.08);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make globally accessible
|
||||||
|
window.Audio = Audio;
|
||||||
@@ -32,6 +32,7 @@ const Game = {
|
|||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.startGameLoop();
|
this.startGameLoop();
|
||||||
this.startAutoSave();
|
this.startAutoSave();
|
||||||
|
Audio.init();
|
||||||
UI.init();
|
UI.init();
|
||||||
Stats.init();
|
Stats.init();
|
||||||
console.log('🌌 Cosmic Garden initialized!');
|
console.log('🌌 Cosmic Garden initialized!');
|
||||||
@@ -99,6 +100,9 @@ const Game = {
|
|||||||
this.addStardust(amount);
|
this.addStardust(amount);
|
||||||
this.state.totalClicks++;
|
this.state.totalClicks++;
|
||||||
|
|
||||||
|
// Audio feedback
|
||||||
|
Audio.playClick();
|
||||||
|
|
||||||
// Visual feedback
|
// Visual feedback
|
||||||
UI.showFloatingText(event.clientX, event.clientY, `+${this.formatNumber(amount)}`);
|
UI.showFloatingText(event.clientX, event.clientY, `+${this.formatNumber(amount)}`);
|
||||||
Particles.createClickParticles(event.clientX, event.clientY, 8);
|
Particles.createClickParticles(event.clientX, event.clientY, 8);
|
||||||
@@ -182,6 +186,9 @@ const Game = {
|
|||||||
|
|
||||||
UI.showNotification(`Bought ${BUILDINGS[buildingId].name}!`, 'success');
|
UI.showNotification(`Bought ${BUILDINGS[buildingId].name}!`, 'success');
|
||||||
|
|
||||||
|
// Audio feedback
|
||||||
|
Audio.playPurchase();
|
||||||
|
|
||||||
// Particle effect on building purchase
|
// Particle effect on building purchase
|
||||||
const card = document.getElementById(`building-${buildingId}`);
|
const card = document.getElementById(`building-${buildingId}`);
|
||||||
if (card) {
|
if (card) {
|
||||||
|
|||||||
Reference in New Issue
Block a user