Initial commit: 3D Hommie RPG game
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
209
js/game/SoundManager.js
Normal file
209
js/game/SoundManager.js
Normal file
@@ -0,0 +1,209 @@
|
||||
export class SoundManager {
|
||||
constructor(game) {
|
||||
this.game = game;
|
||||
this.ctx = null;
|
||||
this.masterGain = null;
|
||||
this.enabled = true;
|
||||
this.initialized = false;
|
||||
this.ambientSource = null;
|
||||
this.currentAmbient = null;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.initialized) return;
|
||||
try {
|
||||
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
this.masterGain = this.ctx.createGain();
|
||||
this.masterGain.gain.value = 0.3;
|
||||
this.masterGain.connect(this.ctx.destination);
|
||||
this.initialized = true;
|
||||
} catch (e) {
|
||||
this.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
resume() {
|
||||
if (this.ctx && this.ctx.state === 'suspended') {
|
||||
this.ctx.resume();
|
||||
}
|
||||
}
|
||||
|
||||
// Шаги
|
||||
playStep() {
|
||||
if (!this.enabled || !this.ctx) return;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'triangle';
|
||||
osc.frequency.value = 80 + Math.random() * 40;
|
||||
gain.gain.setValueAtTime(0.05, this.ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.1);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
osc.start();
|
||||
osc.stop(this.ctx.currentTime + 0.1);
|
||||
}
|
||||
|
||||
// Подбор предмета
|
||||
playPickup() {
|
||||
if (!this.enabled || !this.ctx) return;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sine';
|
||||
osc.frequency.setValueAtTime(400, this.ctx.currentTime);
|
||||
osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.15);
|
||||
gain.gain.setValueAtTime(0.1, this.ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
osc.start();
|
||||
osc.stop(this.ctx.currentTime + 0.2);
|
||||
}
|
||||
|
||||
// Квест выполнен
|
||||
playQuestComplete() {
|
||||
if (!this.enabled || !this.ctx) return;
|
||||
const notes = [523, 659, 784]; // C5, E5, G5
|
||||
notes.forEach((freq, i) => {
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sine';
|
||||
osc.frequency.value = freq;
|
||||
gain.gain.setValueAtTime(0.08, this.ctx.currentTime + i * 0.12);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + i * 0.12 + 0.4);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
osc.start(this.ctx.currentTime + i * 0.12);
|
||||
osc.stop(this.ctx.currentTime + i * 0.12 + 0.4);
|
||||
});
|
||||
}
|
||||
|
||||
// Урон
|
||||
playHurt() {
|
||||
if (!this.enabled || !this.ctx) return;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(200, this.ctx.currentTime);
|
||||
osc.frequency.exponentialRampToValueAtTime(80, this.ctx.currentTime + 0.3);
|
||||
gain.gain.setValueAtTime(0.1, this.ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.3);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
osc.start();
|
||||
osc.stop(this.ctx.currentTime + 0.3);
|
||||
}
|
||||
|
||||
// Монеты
|
||||
playCoin() {
|
||||
if (!this.enabled || !this.ctx) return;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'square';
|
||||
osc.frequency.setValueAtTime(1200, this.ctx.currentTime);
|
||||
osc.frequency.exponentialRampToValueAtTime(1800, this.ctx.currentTime + 0.08);
|
||||
gain.gain.setValueAtTime(0.04, this.ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.15);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
osc.start();
|
||||
osc.stop(this.ctx.currentTime + 0.15);
|
||||
}
|
||||
|
||||
// Еда
|
||||
playEat() {
|
||||
if (!this.enabled || !this.ctx) return;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'triangle';
|
||||
osc.frequency.value = 300 + Math.random() * 100;
|
||||
const t = this.ctx.currentTime + i * 0.1;
|
||||
gain.gain.setValueAtTime(0.06, t);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.08);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
osc.start(t);
|
||||
osc.stop(t + 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
// Диалог
|
||||
playDialogOpen() {
|
||||
if (!this.enabled || !this.ctx) return;
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
osc.type = 'sine';
|
||||
osc.frequency.value = 600;
|
||||
gain.gain.setValueAtTime(0.06, this.ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2);
|
||||
osc.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
osc.start();
|
||||
osc.stop(this.ctx.currentTime + 0.2);
|
||||
}
|
||||
|
||||
// Амбиент ночной — тихий гул
|
||||
playAmbient(type) {
|
||||
if (!this.enabled || !this.ctx) return;
|
||||
if (this.currentAmbient === type) return;
|
||||
this.stopAmbient();
|
||||
this.currentAmbient = type;
|
||||
|
||||
if (type === 'night') {
|
||||
const osc = this.ctx.createOscillator();
|
||||
const gain = this.ctx.createGain();
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.value = 60;
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.value = 100;
|
||||
gain.gain.value = 0.02;
|
||||
osc.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
osc.start();
|
||||
this.ambientSource = { osc, gain };
|
||||
} else if (type === 'rain') {
|
||||
// Белый шум через буфер
|
||||
const bufferSize = this.ctx.sampleRate * 2;
|
||||
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
|
||||
const data = buffer.getChannelData(0);
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
data[i] = (Math.random() * 2 - 1) * 0.5;
|
||||
}
|
||||
const src = this.ctx.createBufferSource();
|
||||
src.buffer = buffer;
|
||||
src.loop = true;
|
||||
const gain = this.ctx.createGain();
|
||||
const filter = this.ctx.createBiquadFilter();
|
||||
filter.type = 'lowpass';
|
||||
filter.frequency.value = 800;
|
||||
gain.gain.value = 0.04;
|
||||
src.connect(filter);
|
||||
filter.connect(gain);
|
||||
gain.connect(this.masterGain);
|
||||
src.start();
|
||||
this.ambientSource = { osc: src, gain };
|
||||
}
|
||||
}
|
||||
|
||||
stopAmbient() {
|
||||
if (this.ambientSource) {
|
||||
try { this.ambientSource.osc.stop(); } catch (e) {}
|
||||
this.ambientSource = null;
|
||||
this.currentAmbient = null;
|
||||
}
|
||||
}
|
||||
|
||||
setVolume(v) {
|
||||
if (this.masterGain) {
|
||||
this.masterGain.gain.value = Math.max(0, Math.min(1, v));
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.enabled = !this.enabled;
|
||||
if (!this.enabled) this.stopAmbient();
|
||||
return this.enabled;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user